Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions docs/docs/gsudo-vs-sudo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 |
Expand Down Expand Up @@ -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.
71 changes: 55 additions & 16 deletions src/gsudo/AppSettings/PathPrecedenceSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,46 +20,85 @@ public PathPrecedenceSetting():

}

/// <summary>
///
/// </summary>
/// <param name="newValue"></param>
/// <param name="global"></param>
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<string> 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);
Expand Down
2 changes: 1 addition & 1 deletion src/gsudo/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions src/gsudo/Helpers/ProcessFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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)
Expand Down