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)