Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Text.RegularExpressions;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Classes.Manager.BaseProviders;
using UniGetUI.PackageEngine.Enums;
Expand All @@ -6,7 +7,7 @@

namespace UniGetUI.PackageEngine.Managers.PowerShell7Manager;

internal sealed class PowerShell7PkgOperationHelper : BasePkgOperationHelper
internal sealed partial class PowerShell7PkgOperationHelper : BasePkgOperationHelper
{
public PowerShell7PkgOperationHelper(PowerShell7 manager)
: base(manager) { }
Expand Down Expand Up @@ -54,26 +55,40 @@ OperationType operation

if (options.PreRelease)
parameters.Add("-Prerelease");
}

List<string> customParameters = operation switch
{
OperationType.Update => options.CustomParameters_Update,
OperationType.Uninstall => options.CustomParameters_Uninstall,
_ => options.CustomParameters_Install,
};

// The scope chosen in the options dialog wins; fall back to the auto-detected install scope
string scope = options.InstallationScope.Length > 0
// Uninstall targets the scope the copy was detected in; otherwise the scope chosen in the
// options dialog wins, falling back to the auto-detected install scope
string? scope = operation is OperationType.Uninstall
? package.OverridenOptions.Scope
: options.InstallationScope.Length > 0
? options.InstallationScope
: package.OverridenOptions.Scope ?? "";
: package.OverridenOptions.Scope;

if (!customParameters.Any(IsScopeParameter))
parameters.AddRange(["-Scope", scope == PackageScope.Global ? "AllUsers" : "CurrentUser"]);
Comment thread
GabrielDuf marked this conversation as resolved.
Resolved
}

parameters.AddRange(
operation switch
{
OperationType.Update => options.CustomParameters_Update,
OperationType.Uninstall => options.CustomParameters_Uninstall,
_ => options.CustomParameters_Install,
}
);
parameters.AddRange(customParameters);

return parameters;
}

private static bool IsScopeParameter(string argument)
{
if (!argument.StartsWith('-'))
return false;

string name = argument[1..].Split(':', 2)[0];
return name.Length >= 2 && "Scope".StartsWith(name, StringComparison.OrdinalIgnoreCase);
}

protected override OperationVeredict _getOperationResult(
IPackage package,
OperationType operation,
Expand All @@ -83,18 +98,44 @@ int returnCode
{
string output_string = string.Join("\n", processOutput);

if (
package.OverridenOptions.RunAsAdministrator is not true
&& (
output_string.Contains("AdminPrivilegesAreRequired")
|| output_string.Contains("AdminPrivilegeRequired")
)
)
bool needsElevation =
output_string.Contains("AdminPrivilegesAreRequired")
|| output_string.Contains("AdminPrivilegeRequired");

if (operation is OperationType.Uninstall && returnCode != 0)
{
var failures = NotDeletedRegex()
.Matches(output_string)
.Select(match => IsLeftoverOnly(match, package.Id))
.ToArray();

if (failures.Length > 0 && failures.All(leftoverOnly => leftoverOnly))
return OperationVeredict.Success;

needsElevation |=
failures.Length > 0 && package.OverridenOptions.Scope == PackageScope.Global;
}

if (package.OverridenOptions.RunAsAdministrator is not true && needsElevation)
{
package.OverridenOptions.RunAsAdministrator = true;
return OperationVeredict.AutoRetry;
}

return returnCode == 0 ? OperationVeredict.Success : OperationVeredict.Failure;
}

private static bool IsLeftoverOnly(Match match, string packageId) =>
match.Groups["kind"].Value switch
{
"Script metadata file" => true,
"Parent directory" => Path.GetFileName(
match.Groups["path"].Value.TrimEnd('\\', '/')
)
.Equals(packageId, StringComparison.OrdinalIgnoreCase),
_ => false,
};

[GeneratedRegex(@"(?<kind>Parent directory|Script metadata file|Script) '(?<path>[^']+)' could not be deleted")]
private static partial Regex NotDeletedRegex();
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public PowerShell7()
CanRunAsAdmin = true,
SupportsCustomVersions = true,
SupportsCustomScopes = true,
// Uninstall-PSResource is invoked without a scope, so the selector is a no-op there
// Uninstall-PSResource targets the scope the package was detected in, so the selector is a no-op there
SupportsCustomScopesOnUninstall = false,
SupportsCustomSources = true,
SupportsPreRelease = true,
Expand Down
16 changes: 15 additions & 1 deletion src/UniGetUI.PackageEngine.Operations/PackageOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1471,8 +1471,22 @@ static bool IsSupersededBy(IPackage installed, IPackage update) =>

if (role is OperationType.Update && opts.UninstallPreviousVersionsOnUpdate)
{
var capabilities = package.Manager.Capabilities;
string? updatedScope =
capabilities.SupportsCustomScopes
&& capabilities.SupportsCustomScopesOnUpdate
&& opts.InstallationScope.Length > 0
? opts.InstallationScope
: package.OverridenOptions.Scope;

var matches = InstalledPackagesLoader.Instance.Packages.Where(p =>
p.IsEquivalentTo(package) && IsSupersededBy(p, package)
p.IsEquivalentTo(package)
&& (
string.IsNullOrEmpty(p.OverridenOptions.Scope)
|| string.IsNullOrEmpty(updatedScope)
|| p.OverridenOptions.Scope == updatedScope
)
&& IsSupersededBy(p, package)
);
foreach (var match in matches)
{
Expand Down
131 changes: 131 additions & 0 deletions src/UniGetUI.PackageEngine.Tests/PackageOperationsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,137 @@ await InstalledPackagesLoader.Instance.AddForeign(
Assert.Equal("10c8e557", uninstall.Package.VersionString);
}

[Fact]
public async Task UpdateOperationOnlyQueuesPreviousVersionsFromTheSameScope()
{
var manager = CreateManager();
var package = new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("2.0.0")
.WithNewVersion("3.0.0")
.WithOptions(new OverridenInstallationOptions(PackageScope.User))
.Build();
InitializeLoaders();
await InstalledPackagesLoader.Instance.AddForeign(
new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("1.0.0")
.WithOptions(new OverridenInstallationOptions(PackageScope.Machine))
.Build()
);
await InstalledPackagesLoader.Instance.AddForeign(
new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("1.5.0")
.WithOptions(new OverridenInstallationOptions(PackageScope.User))
.Build()
);
await InstalledPackagesLoader.Instance.AddForeign(
new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("1.7.0")
.Build()
);

using var operation = new UpdatePackageOperation(
package,
new InstallOptions { UninstallPreviousVersionsOnUpdate = true }
);

var uninstalled = GetInnerOperations(operation, "PostOperations")
.Select(inner => Assert.IsType<UninstallPackageOperation>(inner.Operation).Package.VersionString)
.Order()
.ToArray();
Assert.Equal(["1.5.0", "1.7.0"], uninstalled);
}

[Fact]
public async Task UpdateOperationQueuesPreviousVersionsFromTheScopeChosenForTheUpdate()
{
var manager = CreateManager();
var package = new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("2.0.0")
.WithNewVersion("3.0.0")
.WithOptions(new OverridenInstallationOptions(PackageScope.User))
.Build();
InitializeLoaders();
await InstalledPackagesLoader.Instance.AddForeign(
new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("1.0.0")
.WithOptions(new OverridenInstallationOptions(PackageScope.Machine))
.Build()
);
await InstalledPackagesLoader.Instance.AddForeign(
new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("1.5.0")
.WithOptions(new OverridenInstallationOptions(PackageScope.User))
.Build()
);

using var operation = new UpdatePackageOperation(
package,
new InstallOptions
{
UninstallPreviousVersionsOnUpdate = true,
InstallationScope = PackageScope.Machine,
}
);

var inner = Assert.Single(GetInnerOperations(operation, "PostOperations"));
var uninstall = Assert.IsType<UninstallPackageOperation>(inner.Operation);
Assert.Equal("1.0.0", uninstall.Package.VersionString);
}

[Fact]
public async Task UpdateOperationWithoutAScopeQueuesPreviousVersionsFromEveryScope()
{
var manager = CreateManager();
var package = new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("2.0.0")
.WithNewVersion("3.0.0")
.Build();
InitializeLoaders();
await InstalledPackagesLoader.Instance.AddForeign(
new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("1.0.0")
.WithOptions(new OverridenInstallationOptions(PackageScope.Machine))
.Build()
);
await InstalledPackagesLoader.Instance.AddForeign(
new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.Tool")
.WithVersion("1.5.0")
.WithOptions(new OverridenInstallationOptions(PackageScope.User))
.Build()
);

using var operation = new UpdatePackageOperation(
package,
new InstallOptions { UninstallPreviousVersionsOnUpdate = true }
);

var uninstalled = GetInnerOperations(operation, "PostOperations")
.Select(inner => Assert.IsType<UninstallPackageOperation>(inner.Operation).Package.VersionString)
.Order()
.ToArray();
Assert.Equal(["1.0.0", "1.5.0"], uninstalled);
}

[Fact]
public void UninstallOperationBuildsPreAndPostCommandsForUninstallPath()
{
Expand Down
Loading
Loading