diff --git a/docs/docs/gsudo-vs-sudo.md b/docs/docs/gsudo-vs-sudo.md index a16f17c7..ceec28d7 100644 --- a/docs/docs/gsudo-vs-sudo.md +++ b/docs/docs/gsudo-vs-sudo.md @@ -23,8 +23,9 @@ Surprisingly, Microsoft's sudo does not leverage new OS features to enhance secu | Supports output redirection (`sudo dir > file.txt`) | Yes | Yes | | Supports input redirection (`echo md SomeFolder \| sudo cmd`) | Yes | Yes | | Returns the command exit code | Yes | Yes | -| Preserves the current directory | Yes | Yes, except in new-window mode! ⚠️ [Learn More](https://github.com/microsoft/sudo/issues/63) | +| Preserves the current directory | Yes | Not in new-window mode! ⚠️ Be carefull! [Learn More](https://github.com/microsoft/sudo/issues/63) | | Source code available | [Yes](https://github.com/gerardog/gsudo) | [Yes](https://github.com/microsoft/sudo) | +| Works with future Win11 [`Administration Protection`](https://techcommunity.microsoft.com/blog/microsoft-security-blog/evolving-the-windows-user-model-%E2%80%93-introducing-administrator-protection/4370453) | Yes | No | ### Security Impersonation Features @@ -39,7 +40,7 @@ Surprisingly, Microsoft's sudo does not leverage new OS features to enhance secu | Feature | `gsudo` | Sudo for Windows | | ------- | ------- | ------------------ | -| Easy to install and update | Yes (winget, choco, scoop) | No (Windows Insider build required) | +| Easy to install and update | Yes (winget, choco, scoop) | No (c required) | | See less UAC Pop-ups | Yes ([Credentials Cache](credentials-cache.md)) | No | | Elevate current shell | Yes | No | | Elevate commands using current shell | Yes | No | @@ -68,8 +69,9 @@ Surprisingly, Microsoft's sudo does not leverage new OS features to enhance secu If you have both Microsoft Sudo and `gsudo` installed, they both should work independently. -The `sudo` keyword will run Microsoft's sudo instead of `gsudo` because the typical install of `Sudo for Windows` puts it in `c:\Windows\System32\sudo.exe`. This folder appears first in the `PATH` environment variable, therefore when running `sudo`, the Microsoft `sudo.exe` will take precedence over gsudo's `sudo` alias. +**The `sudo` keyword will run Microsoft's sudo instead of `gsudo`** because the typical install of `Sudo for Windows` puts it in `c:\Windows\System32\sudo.exe`. This folder appears first in the `PATH` environment variable, therefore when running `sudo`, the Microsoft `sudo.exe` will take precedence over gsudo's `sudo` alias. -With the release of `gsudo` v2.5.0, a new configuration setting called `PathPrecedence` has been added. When set to true, it ensures gsudo appears first in the `PATH` variable, making the `sudo` keyword start `gsudo` instead of Microsoft's sudo. To activate, call `gsudo config PathPrecedence true` and restart all consoles to apply the change. Setting it back to `false` will revert to the normal behavior. +**To change this behavior and make `sudo` point to `gsudo`** you can +call `gsudo config PathPrecedence true` and restart all your consoles to apply the change. Setting it back to `false` will revert to the normal behavior. -Additionally, gsudo now supports Microsoft sudo styled arguments such as --inline, --disable-input, --preserve-env, --new-window, and -D / --chdir {directory}, ensuring a smoother transition for users familiar with Microsoft sudo. +Additionally, gsudo now supports Microsoft sudo styled arguments such as `--inline`, `--disable-input`, `--preserve-env`, `--new-window`, and `-D / --chdir {directory}`, ensuring a smoother transition for users familiar with Microsoft sudo. diff --git a/src/gsudo/AppSettings/PathPrecedenceSetting.cs b/src/gsudo/AppSettings/PathPrecedenceSetting.cs index d14901c2..6c4a11c1 100644 --- a/src/gsudo/AppSettings/PathPrecedenceSetting.cs +++ b/src/gsudo/AppSettings/PathPrecedenceSetting.cs @@ -20,46 +20,85 @@ public PathPrecedenceSetting(): } + /// + /// + /// + /// + /// public override void Save(string newValue, bool global) { - bool bNewValue = bool.Parse(newValue); - var ourPath = Path.GetDirectoryName(ProcessFactory.FindExecutableInPath("gsudo.exe")) // shim + bool shouldPrioritizeGsudo = bool.Parse(newValue); // true = Prioritize gsudo, false = xde-prioritize gsudo in favor of system32 + + var ourPath = Path.GetDirectoryName(ProcessFactory.FindExecutableInPath("gsudo.exe")) ?? Path.GetDirectoryName(ProcessHelper.GetOwnExeName()); + AdjustPathOrder(shouldPrioritizeGsudo, ourPath); + + CreateSudoSymLinkIfNeeded(shouldPrioritizeGsudo, ourPath); + + // Save the value to the registry + base.Save(newValue, global); + + // Notify the system of the change + string environment = "Environment"; + IntPtr lParam = Marshal.StringToHGlobalUni(environment); + SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, UIntPtr.Zero, lParam); + Marshal.FreeHGlobal(lParam); + } + + private static void CreateSudoSymLinkIfNeeded(bool shouldPrioritizeGsudo, string ourPath) + { + // Create a Symbolic link to sudo.exe, if none in the path exists + if (shouldPrioritizeGsudo) + { + // Detect if other sudo.exe still first in the path: +#if NET9_0_OR_GREATER + string estimatedNewPath = $"{Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine)};{Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.User)}"; + var actualPath = Path.GetDirectoryName(ProcessFactory.FindExecutableInPath("sudo.exe", estimatedNewPath)); + if (actualPath != ourPath) + { + // Create the symbolic link + Logger.Instance.Log($"A symbolic link \"sudo.exe\" will be created at \"{ourPath}\" to point to gsudo.exe.", LogLevel.Warning); + File.CreateSymbolicLink(Path.Combine(ourPath, "sudo.exe"), Path.Combine(ourPath, "gsudo.exe")); + } +#endif + } + Logger.Instance.Log("Please restart all your consoles to ensure the change makes effect.", LogLevel.Warning); + } + + private static void AdjustPathOrder(bool shouldPrioritizeGsudo, string ourPath) + { var system32Path = Environment.GetFolderPath(Environment.SpecialFolder.System); + // Calculate the new PATH var allPaths = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - // I could also do .Distinct(StringComparer.OrdinalIgnoreCase); - // ...and it works well on local, but may be out of our responsibility to fix that. - + IEnumerable newPath; - if (bNewValue) + if (shouldPrioritizeGsudo) newPath = new[] { ourPath }.Concat(allPaths.Where(p => !p.Equals(ourPath, StringComparison.OrdinalIgnoreCase))); else newPath = allPaths.Where(p => !p.Equals(ourPath, StringComparison.OrdinalIgnoreCase)).Concat(new[] { ourPath }); var finalStringPath = string.Join(";", newPath); + // Update the PATH Logger.Instance.Log($"Updating PATH environment variable to: {finalStringPath}", LogLevel.Debug); Environment.SetEnvironmentVariable("Path", finalStringPath, EnvironmentVariableTarget.Machine); - base.Save(newValue, global); - if (bNewValue) + if (shouldPrioritizeGsudo) + { Logger.Instance.Log($"\"{ourPath}\" path is now prioritized in the PATH environment variable.", LogLevel.Info); + } else + { Logger.Instance.Log($"\"{system32Path}\" path is now prioritized in the PATH environment variable.", LogLevel.Info); - - Logger.Instance.Log("Please restart all your consoles to ensure the change makes effect.", LogLevel.Warning); - - // Notify the system of the change - SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, UIntPtr.Zero, "Environment"); + } } - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern bool SendNotifyMessage( - IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam); + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + public static extern bool SendNotifyMessage(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam); private const uint WM_SETTINGCHANGE = 0x001A; private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xFFFF); diff --git a/src/gsudo/Commands/HelpCommand.cs b/src/gsudo/Commands/HelpCommand.cs index cddb7950..87431d52 100644 --- a/src/gsudo/Commands/HelpCommand.cs +++ b/src/gsudo/Commands/HelpCommand.cs @@ -20,7 +20,7 @@ internal static void ShowVersion(bool verbose = true) Console.WriteLine($"{assembly.GetName().Name} v{GitVersionInformation.FullSemVer} ({GitVersionInformation.FullBuildMetaData})"); Console.ResetColor(); - if (verbose) Console.WriteLine("Copyright(c) 2019-2022 Gerardo Grignoli and GitHub contributors"); + if (verbose) Console.WriteLine("Copyright(c) 2019-2025 Gerardo Grignoli and GitHub contributors"); } internal static void ShowHelp() diff --git a/src/gsudo/Helpers/ProcessFactory.cs b/src/gsudo/Helpers/ProcessFactory.cs index 28923cbd..993ff7b6 100644 --- a/src/gsudo/Helpers/ProcessFactory.cs +++ b/src/gsudo/Helpers/ProcessFactory.cs @@ -157,7 +157,7 @@ public static bool IsWindowsApp(string exe) return retval; } - public static string FindExecutableInPath(string input) + public static string FindExecutableInPath(string input, string searchPath = null) { input = Environment.ExpandEnvironmentVariables(input); @@ -192,7 +192,7 @@ public static string FindExecutableInPath(string input) else { pathsToSearch.Add(Environment.CurrentDirectory); - pathsToSearch.AddRange((Environment.GetEnvironmentVariable("PATH") ?? "").Split(';')); + pathsToSearch.AddRange((searchPath ?? Environment.GetEnvironmentVariable("PATH") ?? "").Split(';')); } foreach (string pathCandidate in pathsToSearch)