diff --git a/build/02-test.ps1 b/build/02-test.ps1 index dce2422c..2847dd77 100644 --- a/build/02-test.ps1 +++ b/build/02-test.ps1 @@ -10,13 +10,19 @@ $failure=$false pushd $PSScriptRoot\.. -dotnet test .\src\gsudo.sln --logger "trx;LogFileName=$((gi .).FullName)\TestResults.trx" --logger:"console;verbosity=normal" -v quiet -p:WarningLevel=0 -if (! $?) { $failure = $true } -if ($failure) { exit 1 } # fail fast +dotnet build .\src\gsudo.sln || $(exit $LASTEXITCODE) -$env:path=(Get-Item .\src\gsudo\bin\net7.0\).FullName+";"+$env:path +$originalPath = $env:path -gsudo -k > $null +$env:path=(Get-Item .\src\gsudo.Tests\bin\Debug\net7.0\).FullName+";" + [String]::Join(";", (($ENV:Path).Split(";") -notlike "*gsudo*" | % {$_ -replace "\\$" })) + +gsudo -k +gsudo --debug cache on -p 0 -d 1 +$env:nokill=1 +gsudo -d --debug --integrity medium dotnet test .\src\gsudo.sln --no-build --logger "trx;LogFileName=$((gi .).FullName)\TestResults.trx" --logger:"console;verbosity=normal" -v quiet -p:WarningLevel=0 + +if (! $?) { $failure = $true } +if ($failure) { exit 1 } # fail fast $script = { $ProgressPreference = "SilentlyContinue"; @@ -34,15 +40,16 @@ $script = { Invoke-Pester -Configuration $configuration } - +gsudo --debug cache on -p 0 -d 1 Write-Verbose -verbose "Running PowerShell Tests on Windows PowerShell (v5.x)" -powershell $script -outputformat text +gsudo --integrity medium powershell -noprofile $script -outputformat text if (! $?) { $failure = $true } +gsudo cache on -p 0 -d 1 Write-Verbose -verbose "Running PowerShell Tests on Pwsh Core (v7.x)" -pwsh $script +gsudo --integrity medium pwsh -noprofile $script if (! $?) { $failure = $true } -.\src\gsudo\bin\net7.0\gsudo.exe -k +gsudo.exe -k if ($failure) { exit 1 } diff --git a/src/gsudo.Tests/CmdTests.cs b/src/gsudo.Tests/CmdTests.cs index 0c804e45..e1f85167 100644 --- a/src/gsudo.Tests/CmdTests.cs +++ b/src/gsudo.Tests/CmdTests.cs @@ -180,7 +180,7 @@ public static void StartCacheSession() new ProcessStartInfo() { FileName = "cmd", - Arguments = $" /c start \"gsudo Service\" \"{gsudoPath}\" --debug cache on --pid 0 --duration 0:1:0 ", + Arguments = $" /c start \"gsudo Service\" \"{gsudoPath}\" \"{gsudoPath}\" --debug cache on --pid 0 --duration 0:1:0 ", Verb = "RunAs" } )?.WaitForExit(); diff --git a/src/gsudo.Tests/PowershellTests.cs b/src/gsudo.Tests/PowershellTests.cs index 8861d2bc..0bd82c6b 100644 --- a/src/gsudo.Tests/PowershellTests.cs +++ b/src/gsudo.Tests/PowershellTests.cs @@ -105,5 +105,14 @@ public virtual void PS_EchoDoubleQuotesTest() ; Assert.AreEqual(0, p.ExitCode); } + + + [TestMethod] + public void PS_WriteProgress() + { + var p = new TestProcess($"{PS_FILENAME} {PS_ARGS}\r\n./gsudo Write-Progress -Activity \"Test\"; exit\r\n"); + p.WaitForExit(); + Assert.IsFalse(p.GetStdOut().Contains("internal error", StringComparison.OrdinalIgnoreCase)); + } } } \ No newline at end of file diff --git a/src/gsudo/Commands/RunCommand.cs b/src/gsudo/Commands/RunCommand.cs index 7ec892cf..f71dcfa3 100644 --- a/src/gsudo/Commands/RunCommand.cs +++ b/src/gsudo/Commands/RunCommand.cs @@ -18,7 +18,7 @@ namespace gsudo.Commands { public class RunCommand : ICommand { - public IEnumerable CommandToRun { get; set; } + public IList CommandToRun { get; set; } private string GetArguments() => GetArgumentsString(CommandToRun, 1); public async Task Execute() @@ -33,7 +33,9 @@ public async Task Execute() CommandToRun = CommandToRunGenerator.AugmentCommand(CommandToRun.ToArray()); bool isWindowsApp = ProcessFactory.IsWindowsApp(CommandToRun.FirstOrDefault()); - var elevationMode = GetElevationMode(isWindowsApp); + var elevationMode = GetElevationMode(isWindowsApp); + + CommandToRun = CommandToRunGenerator.FixCommandExceptions(CommandToRun); if (!isRunningAsDesiredUser) CommandToRun = CommandToRunGenerator.AddCopyEnvironment(CommandToRun, elevationMode); diff --git a/src/gsudo/Helpers/ArgumentsHelper.cs b/src/gsudo/Helpers/ArgumentsHelper.cs index 8e91c2f7..2df37e26 100644 --- a/src/gsudo/Helpers/ArgumentsHelper.cs +++ b/src/gsudo/Helpers/ArgumentsHelper.cs @@ -1,19 +1,14 @@ -using gsudo; -using gsudo.Commands; -using gsudo.Native; +using gsudo.Native; using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; using System.Runtime.InteropServices; namespace gsudo.Helpers { public static class ArgumentsHelper { - public static IEnumerable SplitArgs(string args) - { + public static IList SplitArgs(string args) + { args = args.Trim(); var results = new List(); int pushed = 0; diff --git a/src/gsudo/Helpers/CommandLineParser.cs b/src/gsudo/Helpers/CommandLineParser.cs index 9a5086ba..2f461744 100644 --- a/src/gsudo/Helpers/CommandLineParser.cs +++ b/src/gsudo/Helpers/CommandLineParser.cs @@ -219,14 +219,14 @@ private ICommand ParseVerb() } if (arg.In("run")) - return new RunCommand() { CommandToRun = args }; + return new RunCommand() { CommandToRun = args.ToArray() }; args.AddFirst(arg); if (arg == "!!" || arg.StartsWith("!", StringComparison.InvariantCulture)) return new BangBangCommand() { Pattern = string.Join(" ", args) }; - return new RunCommand() { CommandToRun = args }; + return new RunCommand() { CommandToRun = args.ToArray() }; } #region Posix option matching functions diff --git a/src/gsudo/Helpers/CommandToRunGenerator.cs b/src/gsudo/Helpers/CommandToRunGenerator.cs index 79b1e610..8ddca4ff 100644 --- a/src/gsudo/Helpers/CommandToRunGenerator.cs +++ b/src/gsudo/Helpers/CommandToRunGenerator.cs @@ -1,7 +1,9 @@ using gsudo.Native; +using Microsoft.VisualBasic; using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Security.AccessControl; @@ -119,7 +121,7 @@ Running ./gsudo {command} should elevate the powershell command. } } - return DoFixIfIsMicrosoftStoreApp(currentShellExeName, newArgs.ToArray()); + return newArgs.ToArray(); } else if (currentShell == Shell.Yori) { @@ -185,8 +187,7 @@ Running ./gsudo {command} should elevate the powershell command. if (exename != null && CreateProcessSupportedExtensions.Contains(Path.GetExtension(exename))) { args[0] = $"\"{exename}\""; - var newArgs = DoFixIfIsMicrosoftStoreApp(exename, args); - return newArgs.ToArray(); + return args; } else { @@ -200,25 +201,50 @@ Running ./gsudo {command} should elevate the powershell command. } } - private static string[] DoFixIfIsMicrosoftStoreApp(string targetExe, string[] args) + internal static IList FixCommandExceptions(IList args) { - // -- Workaround for https://github.com/gerardog/gsudo/issues/65 - - // ISSUE: Apps installed via Microsoft Store, need a special attribute in it's security token to work (WIN://SYSAPPID), + string targetFullPath = args.First().UnQuote(); + string targetFileName = Path.GetFileName(targetFullPath); + + var ExceptionDict = Settings.ExceptionList.Value + .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Split(new string[] { ":=" }, StringSplitOptions.None)) + .ToDictionary(x => x.First(), x => x.Skip(1).FirstOrDefault(), StringComparer.OrdinalIgnoreCase); + + if ( + // ISSUE 1: + // -- https://github.com/gerardog/gsudo/issues/65 + // Apps installed via Microsoft Store, need a special attribute in it's security token to work (WIN://SYSAPPID), // That attrib is inserted by CreateProcess() Api, but gsudo replaces the special token with regular but elevated one // which doesnt have the attribute. So the app fails to load. - // WORKAROUND: The CreateProcess(pwsh.exe) call must be already elevated so that Api can manipulate the final token, // and the easiest way I found is delegate the final CreateProcess to an elevated CMD instance: To elevate "cmd /c pwsh.exe" instead. - - if (targetExe.IndexOf("\\WindowsApps\\", StringComparison.OrdinalIgnoreCase) >= 0) // Terrible but cheap Microsoft Store App detection. + targetFullPath.IndexOf("\\WindowsApps\\", StringComparison.OrdinalIgnoreCase) >= 0) // Terrible but cheap Microsoft Store App detection. { Logger.Instance.Log("Applying workaround for target app installed via MSStore.", LogLevel.Debug); + return new string[] { Environment.GetEnvironmentVariable("COMSPEC"), "/s /c" , $"\"{string.Join(" ", args)}\""}; } + else if (ExceptionDict.ContainsKey(targetFileName)) + { + // ISSUE 2: https://github.com/gerardog/gsudo/issues/131 + // notepad won't open notepad on CMD / Win11: + // It appears that notepad opens a Microsoft Store version of Notepad.exe. It fails to load using sudo. + // ISSUE 3: https://github.com/gerardog/gsudo/issues/180 + // Strange console "Access Denied" error while + + string action = ExceptionDict[targetFileName]; + + if (string.IsNullOrEmpty(action)) + action = $"\"{Environment.GetEnvironmentVariable("COMSPEC")}\" /s /c \"{{0}}\""; + + Logger.Instance.Log($"Found {targetFileName} in Exception List with Action=\"{action}\".", LogLevel.Debug); + + return ArgumentsHelper.SplitArgs(String.Format(CultureInfo.InvariantCulture, action, string.Join(" ", args))).ToList(); + } else return args; // -- End of workaround. @@ -230,7 +256,7 @@ private static string[] DoFixIfIsMicrosoftStoreApp(string targetExe, string[] ar /// /// CopyNetworkShares is *the best I could do*. Too much verbose, asks for passwords, etc. Far from ideal. /// a modified args list - internal static IEnumerable AddCopyEnvironment(IEnumerable args, ElevationRequest.ConsoleMode mode) + internal static IList AddCopyEnvironment(IList args, ElevationRequest.ConsoleMode mode) { if (Settings.CopyEnvironmentVariables || Settings.CopyNetworkShares) { diff --git a/src/gsudo/Settings.cs b/src/gsudo/Settings.cs index 5e4275ad..c5a54f56 100644 --- a/src/gsudo/Settings.cs +++ b/src/gsudo/Settings.cs @@ -76,6 +76,12 @@ class Settings new RegistrySetting(nameof(SecurityEnforceUacIsolation), defaultValue: false, deserializer: bool.Parse, + scope: RegistrySettingScope.GlobalOnly); + + public static RegistrySetting ExceptionList { get; } = + new RegistrySetting(nameof(ExceptionList), + defaultValue: "notepad.exe;powershell.exe;", + deserializer: (string s)=>s, scope: RegistrySettingScope.GlobalOnly); public static IDictionary AllKeys => @@ -92,7 +98,8 @@ class Settings CopyEnvironmentVariables, CopyNetworkShares, PowerShellLoadProfile, - SecurityEnforceUacIsolation); + SecurityEnforceUacIsolation, + ExceptionList); internal static TimeSpan TimeSpanParseWithInfinite(string value) { diff --git a/src/gsudo/gsudo.csproj b/src/gsudo/gsudo.csproj index b0ad67c5..feb8993b 100644 --- a/src/gsudo/gsudo.csproj +++ b/src/gsudo/gsudo.csproj @@ -59,7 +59,7 @@ - +