From 54dc4bc6e4615c7833c672e797a1f1322f6f2a65 Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Wed, 28 Sep 2022 00:25:09 -0300 Subject: [PATCH 1/7] fix(PowerShellReadConsoleOutput): Add test to reproduce #180 --- src/gsudo.Tests/PowershellTests.cs | 9 +++++++++ 1 file changed, 9 insertions(+) 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 From 35f06ac3f044f1e3e7cb225d5a83f0234218b709 Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Wed, 28 Sep 2022 16:45:44 -0300 Subject: [PATCH 2/7] fix(PowerShellReadConsoleOutput): Test shall fail first. --- build/02-test.ps1 | 25 ++++++++++++++++--------- src/gsudo.Tests/CmdTests.cs | 2 +- src/gsudo/gsudo.csproj | 2 +- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/build/02-test.ps1 b/build/02-test.ps1 index dce2422c..5165abb1 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) +$originalPath = $env:path -$env:path=(Get-Item .\src\gsudo\bin\net7.0\).FullName+";"+$env:path +$env:path=(Get-Item .\src\gsudo.Tests\bin\Debug\net7.0\).FullName+";" + [String]::Join(";", (($ENV:Path).Split(";") -notlike "*gsudo*" | % {$_ -replace "\\$" })) -gsudo -k > $null +gsudo -k +gsudo --debug cache on -p 0 -d 1 +$env:nokill=1 +gsudo -d --debug --integrity medium -n -w cmd /s /c "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 $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/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 @@ - + From ae57d3cf9b48cdafb6306eb26d77e926a461c233 Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Wed, 28 Sep 2022 17:05:06 -0300 Subject: [PATCH 3/7] fix(PowerShellReadConsoleOutput): Test shall fail first, for the right reasons. --- build/02-test.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/02-test.ps1 b/build/02-test.ps1 index 5165abb1..208de038 100644 --- a/build/02-test.ps1 +++ b/build/02-test.ps1 @@ -18,7 +18,7 @@ $env:path=(Get-Item .\src\gsudo.Tests\bin\Debug\net7.0\).FullName+";" + [String] gsudo -k gsudo --debug cache on -p 0 -d 1 $env:nokill=1 -gsudo -d --debug --integrity medium -n -w cmd /s /c "dotnet test .\src\gsudo.sln --logger `"trx;LogFileName=$((gi .).FullName)\TestResults.trx`" --logger:`"console;verbosity=normal`" -v quiet -p:WarningLevel=0" +gsudo --debug --integrity medium -n -w { dotnet test .\src\gsudo.sln --logger "trx;LogFileName=$((gi .).FullName)\TestResults.trx" --logger:"console;verbosity=normal" -v quiet -p:WarningLevel=0 } if (! $?) { $failure = $true } From c7f53c7a7a9805e9688250b8c4676738f4549a8b Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Wed, 28 Sep 2022 17:36:53 -0300 Subject: [PATCH 4/7] fix(PowerShellReadConsoleOutput): Test shall fail first, for the right reasons. --- build/02-test.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build/02-test.ps1 b/build/02-test.ps1 index 208de038..716a011c 100644 --- a/build/02-test.ps1 +++ b/build/02-test.ps1 @@ -18,8 +18,7 @@ $env:path=(Get-Item .\src\gsudo.Tests\bin\Debug\net7.0\).FullName+";" + [String] gsudo -k gsudo --debug cache on -p 0 -d 1 $env:nokill=1 -gsudo --debug --integrity medium -n -w { dotnet test .\src\gsudo.sln --logger "trx;LogFileName=$((gi .).FullName)\TestResults.trx" --logger:"console;verbosity=normal" -v quiet -p:WarningLevel=0 } - +gsudo -d --debug --integrity medium 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 From 9282ebe2d358e0a0831a787300600a3486664529 Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Wed, 28 Sep 2022 17:54:35 -0300 Subject: [PATCH 5/7] fix(PowerShellReadConsoleOutput): Test shall fail first, for the right reasons. --- build/02-test.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/02-test.ps1 b/build/02-test.ps1 index 716a011c..2847dd77 100644 --- a/build/02-test.ps1 +++ b/build/02-test.ps1 @@ -11,6 +11,7 @@ $failure=$false pushd $PSScriptRoot\.. dotnet build .\src\gsudo.sln || $(exit $LASTEXITCODE) + $originalPath = $env:path $env:path=(Get-Item .\src\gsudo.Tests\bin\Debug\net7.0\).FullName+";" + [String]::Join(";", (($ENV:Path).Split(";") -notlike "*gsudo*" | % {$_ -replace "\\$" })) @@ -18,7 +19,7 @@ $env:path=(Get-Item .\src\gsudo.Tests\bin\Debug\net7.0\).FullName+";" + [String] gsudo -k gsudo --debug cache on -p 0 -d 1 $env:nokill=1 -gsudo -d --debug --integrity medium dotnet test .\src\gsudo.sln --logger "trx;LogFileName=$((gi .).FullName)\TestResults.trx" --logger:"console;verbosity=normal" -v quiet -p:WarningLevel=0 +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 From 1a9fa026bd63bbec1c821f9e371ac82febd03e75 Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Sat, 1 Oct 2022 11:24:01 -0300 Subject: [PATCH 6/7] fix(PowerShellReadConsoleOutput): Added Settings.ExceptionList, a list of processes that should have special treatment. For example: - Notepad.exe in Win11 behaves as an execution alias. Fixes #131 - Or Windows PowerShell v5 (not Core) issue with ReadConsoleOutput --- src/gsudo/Commands/RunCommand.cs | 6 ++- src/gsudo/Helpers/ArgumentsHelper.cs | 11 ++--- src/gsudo/Helpers/CommandLineParser.cs | 4 +- src/gsudo/Helpers/CommandToRunGenerator.cs | 48 +++++++++++++++++----- src/gsudo/Settings.cs | 9 +++- 5 files changed, 54 insertions(+), 24 deletions(-) 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..79803097 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(';') + .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) { From 59af3cc70c3c5bd18024f0e96cd5f42aff5da0a2 Mon Sep 17 00:00:00 2001 From: Gerardo Grignoli Date: Sat, 1 Oct 2022 11:40:20 -0300 Subject: [PATCH 7/7] Fix(ReadConsoleOutput): fixed action literal --- src/gsudo/Helpers/CommandToRunGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gsudo/Helpers/CommandToRunGenerator.cs b/src/gsudo/Helpers/CommandToRunGenerator.cs index 79803097..8ddca4ff 100644 --- a/src/gsudo/Helpers/CommandToRunGenerator.cs +++ b/src/gsudo/Helpers/CommandToRunGenerator.cs @@ -207,7 +207,7 @@ internal static IList FixCommandExceptions(IList args) string targetFileName = Path.GetFileName(targetFullPath); var ExceptionDict = Settings.ExceptionList.Value - .Split(';') + .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Split(new string[] { ":=" }, StringSplitOptions.None)) .ToDictionary(x => x.First(), x => x.Skip(1).FirstOrDefault(), StringComparer.OrdinalIgnoreCase); @@ -239,7 +239,7 @@ internal static IList FixCommandExceptions(IList args) string action = ExceptionDict[targetFileName]; if (string.IsNullOrEmpty(action)) - action = $"\"{Environment.GetEnvironmentVariable("COMSPEC")}\" /s /c \"{0}\""; + action = $"\"{Environment.GetEnvironmentVariable("COMSPEC")}\" /s /c \"{{0}}\""; Logger.Instance.Log($"Found {targetFileName} in Exception List with Action=\"{action}\".", LogLevel.Debug);