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
Expand Up @@ -6,6 +6,7 @@ public struct OverridenInstallationOptions
public bool? RunAsAdministrator;
public bool PowerShell_DoNotSetScopeParameter = false;
public bool PowerShell_AllowClobber = false;
public bool PowerShell_UpdateThroughInstall = false;
public bool? WinGet_SpecifyVersion = null;
public bool Pip_BreakSystemPackages = false;
public bool WinGet_DropArchAndScope = false;
Expand All @@ -21,6 +22,6 @@ public OverridenInstallationOptions(string? scope = null, bool? runAsAdministrat

public override string ToString()
{
return $"<Scope={Scope};RunAsAdministrator={RunAsAdministrator};WG_SpecifyVersion={WinGet_SpecifyVersion};PS_NoScope={PowerShell_DoNotSetScopeParameter};PS_AllowClobber={PowerShell_AllowClobber};Pip_BreakSystemPackages={Pip_BreakSystemPackages};WG_DropArchAndScope={WinGet_DropArchAndScope};WG_UseLocalId={WinGet_UseLocalIdentifier};Cargo_DoNotUseBinstall={Cargo_DoNotUseBinstall};Cargo_CustomInstallPath={Cargo_CustomInstallPathRequested}>";
return $"<Scope={Scope};RunAsAdministrator={RunAsAdministrator};WG_SpecifyVersion={WinGet_SpecifyVersion};PS_NoScope={PowerShell_DoNotSetScopeParameter};PS_AllowClobber={PowerShell_AllowClobber};PS_UpdateThroughInstall={PowerShell_UpdateThroughInstall};Pip_BreakSystemPackages={Pip_BreakSystemPackages};WG_DropArchAndScope={WinGet_DropArchAndScope};WG_UseLocalId={WinGet_UseLocalIdentifier};Cargo_DoNotUseBinstall={Cargo_DoNotUseBinstall};Cargo_CustomInstallPath={Cargo_CustomInstallPathRequested}>";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,43 +25,65 @@ protected override IReadOnlyList<string> _getOperationParameters(
bool standalone
)
{
bool updatesThroughInstall =
operation is OperationType.Update && options.SkipHashCheck;
bool usesInstallVerb = operation is OperationType.Install || updatesThroughInstall;

List<string> parameters =
[
operation switch
{
OperationType.Install => Manager.Properties.InstallVerb,
OperationType.Update => Manager.Properties.UpdateVerb,
OperationType.Uninstall => Manager.Properties.UninstallVerb,
_ => throw new InvalidDataException("Invalid package operation"),
},
usesInstallVerb
? Manager.Properties.InstallVerb
Comment thread
GabrielDuf marked this conversation as resolved.
Resolved
: operation switch
{
OperationType.Update => Manager.Properties.UpdateVerb,
OperationType.Uninstall => Manager.Properties.UninstallVerb,
_ => throw new InvalidDataException("Invalid package operation"),
},
];
parameters.AddRange(["-Name", package.Id, "-Confirm:$false", "-Force"]);

if (!standalone)
package.OverridenOptions.PowerShell_UpdateThroughInstall = updatesThroughInstall;

bool shellInterpreted = standalone || Manager.Status.OperationCallArgs.Count is 0;

if (updatesThroughInstall && package.Source.Name.Length > 0)
parameters.AddRange(
[
"-Repository",
shellInterpreted ? QuoteForPowerShell(package.Source.Name) : package.Source.Name,
]
);

if (operation is not OperationType.Uninstall)
{
if (options.PreRelease)
parameters.Add("-AllowPrerelease");

// Update-Module (PowerShellGet) has no -Scope parameter; only Install-Module accepts it
if (operation is OperationType.Install && !package.OverridenOptions.PowerShell_DoNotSetScopeParameter)
if (usesInstallVerb && !package.OverridenOptions.PowerShell_DoNotSetScopeParameter)
{
// The scope chosen in the options dialog wins; fall back to the auto-detected install scope
string scope = options.InstallationScope.Length > 0
? options.InstallationScope
: package.OverridenOptions.Scope ?? "";
parameters.AddRange(["-Scope", scope == PackageScope.Global ? "AllUsers" : "CurrentUser"]);

if (operation is OperationType.Install || scope.Length > 0)
parameters.AddRange(
["-Scope", scope == PackageScope.Global ? "AllUsers" : "CurrentUser"]
);
}
}

if (operation is OperationType.Install)
if (usesInstallVerb)
{
if (package.OverridenOptions.PowerShell_AllowClobber)
parameters.Add("-AllowClobber");

if (options.SkipHashCheck)
parameters.Add("-SkipPublisherCheck");

if (options.Version != "")
if (operation is OperationType.Install && options.Version != "")
parameters.AddRange(["-RequiredVersion", options.Version]);
}

Expand Down Expand Up @@ -100,6 +122,9 @@ bool standalone
return parameters;
}

private static string QuoteForPowerShell(string value) =>
$"'{value.Replace("'", "''")}'";

protected override OperationVeredict _getOperationResult(
IPackage package,
OperationType operation,
Expand All @@ -109,6 +134,9 @@ int returnCode
{
string output_string = string.Join("\n", processOutput);

bool routedThroughInstall = package.OverridenOptions.PowerShell_UpdateThroughInstall;
package.OverridenOptions.PowerShell_UpdateThroughInstall = false;

if (
package.OverridenOptions.RunAsAdministrator is not true
&& (
Expand All @@ -132,7 +160,7 @@ package.OverridenOptions.RunAsAdministrator is not true
}

if (
operation is OperationType.Install
(operation is OperationType.Install || routedThroughInstall)
&& output_string.Contains("CommandAlreadyAvailable")
&& !package.OverridenOptions.PowerShell_AllowClobber
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Classes.Manager;
using UniGetUI.PackageEngine.Classes.Manager.BaseProviders;
using UniGetUI.PackageEngine.Enums;
using UniGetUI.PackageEngine.Interfaces;
Expand Down Expand Up @@ -243,20 +244,22 @@ or ElevationRequirement.ElevatesSelf
)
{
if (CoreTools.IsAdministrator())
throw new UnauthorizedAccessException(
throw new OperationPreconditionException(
CoreTools.Translate(
"This package cannot be installed from an elevated context."
)
+ " "
+ CoreTools.Translate(
"Please run UniGetUI as a regular user and try again."
)
);

if (options.RunAsAdministrator)
throw new UnauthorizedAccessException(
throw new OperationPreconditionException(
CoreTools.Translate(
"This package cannot be installed from an elevated context."
)
+ " "
+ CoreTools.Translate(
"Please check the installation options for this package and try again"
)
Expand All @@ -276,7 +279,7 @@ or ElevationRequirement.ElevatesSelf
}
catch (Exception ex)
{
if (ex is UnauthorizedAccessException)
if (ex is OperationPreconditionException)
throw;

Logger.Error("Recovered from fatal WinGet exception:");
Expand Down
25 changes: 20 additions & 5 deletions src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Classes.Manager;
using UniGetUI.PackageEngine.Enums;

namespace UniGetUI.PackageOperations;
Expand Down Expand Up @@ -395,10 +396,17 @@ private async Task MainThreadCore(CancellationTokenSource runCancellation)
}
catch (Exception ex)
{
Line("An internal error occurred:", LineType.Error);
foreach (var line in ex.ToString().Split("\n"))
if (ex is OperationPreconditionException)
{
Line(line, LineType.Error);
Line(ex.Message, LineType.Error);
}
else
{
Line("An internal error occurred:", LineType.Error);
foreach (var line in ex.ToString().Split("\n"))
{
Line(line, LineType.Error);
}
}

RemoveFromQueue(this);
Expand Down Expand Up @@ -547,9 +555,16 @@ private async Task<OperationVeredict> _runOperation()
? OperationVeredict.Canceled
: OperationVeredict.Failure;
Logger.Error(e);
foreach (string l in e.ToString().Split("\n"))
if (e is OperationPreconditionException)
{
Line(e.Message, LineType.Error);
}
else
{
Line(l, LineType.Error);
foreach (string l in e.ToString().Split("\n"))
{
Line(l, LineType.Error);
}
}
}
} while (result is OperationVeredict.AutoRetry);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace UniGetUI.PackageEngine.Classes.Manager;

public sealed class OperationPreconditionException : Exception
{
public OperationPreconditionException(string message)
: base(message) { }
}
56 changes: 56 additions & 0 deletions src/UniGetUI.PackageEngine.Tests/PackageOperationsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.Interface.Enums;
using UniGetUI.PackageEngine.Classes.Manager;
using UniGetUI.PackageEngine.Enums;
using UniGetUI.PackageEngine.Interfaces;
using UniGetUI.PackageEngine.Managers.NpmManager;
Expand Down Expand Up @@ -1871,6 +1872,61 @@ protected override Task<OperationVeredict> PerformOperation()
}
}

[Fact]
public async Task UnmetPreconditionIsReportedWithoutTheInternalErrorStackTrace()
{
using var operation = new ThrowingStubOperation(
new OperationPreconditionException("Run UniGetUI as a regular user and try again.")
);

await operation.MainThread();

Assert.Equal(OperationStatus.Failed, operation.Status);
var lines = operation.GetOutput().Select(line => line.Item1).ToList();
Assert.Contains("Run UniGetUI as a regular user and try again.", lines);
Assert.DoesNotContain("An internal error occurred:", lines);
Assert.DoesNotContain(lines, line => line.Contains("OperationPreconditionException"));
}

[Fact]
public async Task AnUnexpectedExceptionStillReportsItsStackTrace()
{
using var operation = new ThrowingStubOperation(
new InvalidOperationException("some unexpected failure")
);

await operation.MainThread();

Assert.Equal(OperationStatus.Failed, operation.Status);
var lines = operation.GetOutput().Select(line => line.Item1).ToList();
Assert.Contains(lines, line => line.Contains("InvalidOperationException"));
Assert.Contains(lines, line => line.TrimStart().StartsWith("at ", StringComparison.Ordinal));
}

private sealed class ThrowingStubOperation : AbstractOperation
{
private readonly Exception _exception;

public ThrowingStubOperation(Exception exception)
: base(queue_enabled: false)
{
_exception = exception;
Metadata.Status = "Throwing stub status";
Metadata.Title = "Throwing stub title";
Metadata.OperationInformation = "Throwing stub info";
Metadata.SuccessTitle = "Throwing stub success";
Metadata.SuccessMessage = "Throwing stub success";
Metadata.FailureTitle = "Throwing stub failure";
Metadata.FailureMessage = "Throwing stub failure";
}

protected override void ApplyRetryAction(string retryMode) { }

protected override Task<OperationVeredict> PerformOperation() => throw _exception;

public override Task<Uri> GetOperationIcon() => Task.FromResult(new Uri("about:blank"));
}

private sealed class CancellationAwareStubOperation : AbstractOperation
{
public TaskCompletionSource<bool> PerformStarted { get; } = new(
Expand Down
Loading
Loading