diff --git a/src/UniGetUI.PackageEngine.Managers.PowerShell/Helpers/PowerShellPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.PowerShell/Helpers/PowerShellPkgOperationHelper.cs index 29d0aa85ac..bebfed9cab 100644 --- a/src/UniGetUI.PackageEngine.Managers.PowerShell/Helpers/PowerShellPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.PowerShell/Helpers/PowerShellPkgOperationHelper.cs @@ -7,6 +7,8 @@ namespace UniGetUI.PackageEngine.Managers.PowerShellManager; internal sealed class PowerShellPkgOperationHelper : BasePkgOperationHelper { + internal const string ErrorVariableName = "UniGetUIOperationError"; + public PowerShellPkgOperationHelper(PowerShell manager) : base(manager) { } @@ -53,19 +55,30 @@ OperationType operation parameters.AddRange(["-RequiredVersion", options.Version]); } - parameters.AddRange( - operation switch - { - OperationType.Update => options.CustomParameters_Update, - OperationType.Uninstall => options.CustomParameters_Uninstall, - _ => options.CustomParameters_Install, - } + IReadOnlyList customParameters = operation switch + { + OperationType.Update => options.CustomParameters_Update, + OperationType.Uninstall => options.CustomParameters_Uninstall, + _ => options.CustomParameters_Install, + }; + + bool bindsOwnErrorVariable = customParameters.Any(parameter => + parameter.StartsWith("-ev", StringComparison.OrdinalIgnoreCase) + || parameter.StartsWith("-errorv", StringComparison.OrdinalIgnoreCase) ); + if (!bindsOwnErrorVariable) + parameters.AddRange(["-ErrorVariable", ErrorVariableName]); + + parameters.AddRange(customParameters); + // Windows PowerShell 5.x defaults to TLS 1.0/1.1, which the PowerShell Gallery rejects; force TLS 1.2 so gallery operations can connect under -NoProfile if (operation is not OperationType.Uninstall) parameters.Insert(0, "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;"); + if (!bindsOwnErrorVariable) + parameters.Add($";if(${ErrorVariableName}){{exit(1)}}"); + return parameters; } diff --git a/src/UniGetUI.PackageEngine.Tests/PowerShellManagerTests.cs b/src/UniGetUI.PackageEngine.Tests/PowerShellManagerTests.cs index f3dd0799dc..4c4a904a9c 100644 --- a/src/UniGetUI.PackageEngine.Tests/PowerShellManagerTests.cs +++ b/src/UniGetUI.PackageEngine.Tests/PowerShellManagerTests.cs @@ -95,6 +95,58 @@ public void GetParameters_UpdateOmitsScope() Assert.DoesNotContain("-Scope", parameters); } + [Theory] + [InlineData(OperationType.Install)] + [InlineData(OperationType.Update)] + [InlineData(OperationType.Uninstall)] + public void GetParameters_PropagatesNonTerminatingErrorsToTheExitCode(OperationType operation) + { + var manager = new PowerShell(); + var package = BuildInstalledPackage(manager); + + var parameters = manager.OperationHelper.GetParameters(package, new InstallOptions(), operation); + + var errorVariableIndex = parameters.ToList().IndexOf("-ErrorVariable"); + Assert.True(errorVariableIndex >= 0); + Assert.Equal(PowerShellPkgOperationHelper.ErrorVariableName, parameters[errorVariableIndex + 1]); + Assert.Equal( + $";if(${PowerShellPkgOperationHelper.ErrorVariableName}){{exit(1)}}", + parameters[^1] + ); + } + + [Theory] + [InlineData("-ErrorVariable")] + [InlineData("-ev")] + [InlineData("-errorvariable:mine")] + public void GetParameters_YieldsToACustomErrorVariable(string customParameter) + { + var manager = new PowerShell(); + var package = BuildInstalledPackage(manager); + + var options = new InstallOptions { CustomParameters_Update = [customParameter, "mine"] }; + var parameters = manager.OperationHelper.GetParameters(package, options, OperationType.Update); + + Assert.DoesNotContain(PowerShellPkgOperationHelper.ErrorVariableName, parameters); + Assert.DoesNotContain( + $";if(${PowerShellPkgOperationHelper.ErrorVariableName}){{exit(1)}}", + parameters + ); + } + + [Fact] + public void GetParameters_KeepsCustomParametersBoundToTheCmdlet() + { + var manager = new PowerShell(); + var package = BuildInstalledPackage(manager); + + var options = new InstallOptions { CustomParameters_Update = ["-Proxy", "http://proxy"] }; + var parameters = manager.OperationHelper.GetParameters(package, options, OperationType.Update); + + Assert.Equal("-Proxy", parameters[^3]); + Assert.Equal("http://proxy", parameters[^2]); + } + [Fact] public void Capabilities_ScopeAppliesToInstallOnly() {