-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add Windows admin PATH management commands to dotnetup #52181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/dnup
Are you sure you want to change the base?
Changes from 9 commits
a86586f
542c866
d8fed6a
374d46e
6174dec
3d163b5
5585351
1d91179
bbd3d5c
f4e9278
8aa862f
bd56b89
532dcd0
b182662
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.CommandLine; | ||
| using System.Runtime.InteropServices; | ||
| using System.Runtime.Versioning; | ||
|
|
||
| namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.ElevatedAdminPath; | ||
|
|
||
| internal class ElevatedAdminPathCommand : CommandBase | ||
| { | ||
| private readonly string _operation; | ||
|
|
||
| public ElevatedAdminPathCommand(ParseResult result) : base(result) | ||
| { | ||
| _operation = result.GetValue(ElevatedAdminPathCommandParser.OperationArgument)!; | ||
| } | ||
|
|
||
| public override int Execute() | ||
| { | ||
| // This command only works on Windows | ||
| if (!OperatingSystem.IsWindows()) | ||
| { | ||
| Console.Error.WriteLine("Error: The elevatedadminpath command is only supported on Windows."); | ||
| return 1; | ||
| } | ||
|
|
||
| // Check if running with elevated privileges | ||
| if (!WindowsPathHelper.IsElevated()) | ||
| { | ||
| Console.Error.WriteLine("Error: This operation requires administrator privileges. Please run from an elevated command prompt."); | ||
| return 1; | ||
| } | ||
|
|
||
| return _operation.ToLowerInvariant() switch | ||
| { | ||
| "removedotnet" => RemoveDotnet(), | ||
| "adddotnet" => AddDotnet(), | ||
| _ => throw new InvalidOperationException($"Unknown operation: {_operation}") | ||
| }; | ||
| } | ||
|
|
||
| [SupportedOSPlatform("windows")] | ||
| private int RemoveDotnet() | ||
| { | ||
| using var pathHelper = new WindowsPathHelper(); | ||
| return pathHelper.RemoveDotnetFromAdminPath(); | ||
| } | ||
|
|
||
| [SupportedOSPlatform("windows")] | ||
| private int AddDotnet() | ||
| { | ||
| using var pathHelper = new WindowsPathHelper(); | ||
| return pathHelper.AddDotnetToAdminPath(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.CommandLine; | ||
|
|
||
| namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.ElevatedAdminPath; | ||
|
|
||
| internal static class ElevatedAdminPathCommandParser | ||
| { | ||
| public static readonly Argument<string> OperationArgument = new("operation") | ||
| { | ||
| HelpName = "OPERATION", | ||
| Description = "The operation to perform: 'removedotnet' or 'adddotnet'", | ||
| Arity = ArgumentArity.ExactlyOne, | ||
| }; | ||
|
|
||
| private static readonly Command ElevatedAdminPathCommand = ConstructCommand(); | ||
|
|
||
| public static Command GetCommand() | ||
| { | ||
| return ElevatedAdminPathCommand; | ||
| } | ||
|
|
||
| private static Command ConstructCommand() | ||
| { | ||
| Command command = new("elevatedadminpath", "Modifies the machine-wide admin PATH (requires elevated privileges)"); | ||
| command.Hidden = true; | ||
|
|
||
| command.Arguments.Add(OperationArgument); | ||
|
|
||
| command.SetAction(parseResult => new ElevatedAdminPathCommand(parseResult).Execute()); | ||
|
|
||
| return command; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.CommandLine; | ||
| using System.Runtime.InteropServices; | ||
| using System.Runtime.Versioning; | ||
|
|
||
| namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.SetInstallRoot; | ||
|
|
||
| internal class SetInstallRootCommand : CommandBase | ||
| { | ||
| private readonly string _installType; | ||
| private readonly IDotnetInstallManager _dotnetInstaller; | ||
|
|
||
| public SetInstallRootCommand(ParseResult result, IDotnetInstallManager? dotnetInstaller = null) : base(result) | ||
| { | ||
| _installType = result.GetValue(SetInstallRootCommandParser.InstallTypeArgument)!; | ||
| _dotnetInstaller = dotnetInstaller ?? new DotnetInstallManager(); | ||
| } | ||
|
|
||
| public override int Execute() | ||
| { | ||
| return _installType.ToLowerInvariant() switch | ||
| { | ||
| "user" => SetUserInstallRoot(), | ||
| _ => throw new InvalidOperationException($"Unknown install type: {_installType}") | ||
| }; | ||
| } | ||
|
|
||
| [SupportedOSPlatform("windows")] | ||
| private void HandleWindowsAdminPath() | ||
| { | ||
| try | ||
| { | ||
| // Check if admin PATH needs to be changed | ||
| if (WindowsPathHelper.AdminPathContainsProgramFilesDotnet()) | ||
| { | ||
| Console.WriteLine("Program Files dotnet path found in admin PATH. Removing it..."); | ||
|
|
||
| if (WindowsPathHelper.IsElevated()) | ||
| { | ||
| // We're already elevated, modify the admin PATH directly | ||
| Console.WriteLine("Running with elevated privileges. Modifying admin PATH..."); | ||
| using var pathHelper = new WindowsPathHelper(); | ||
| pathHelper.RemoveDotnetFromAdminPath(); | ||
| } | ||
| else | ||
| { | ||
| // Not elevated, shell out to elevated process | ||
| Console.WriteLine("Launching elevated process to modify admin PATH..."); | ||
| int exitCode = WindowsPathHelper.StartElevatedProcess("elevatedadminpath removedotnet"); | ||
|
|
||
| if (exitCode != 0) | ||
| { | ||
| Console.Error.WriteLine("Warning: Failed to modify admin PATH. You may need to manually remove the Program Files dotnet path from the system PATH."); | ||
| // Continue anyway - we can still set up the user PATH | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine("Admin PATH does not contain Program Files dotnet path. No changes needed."); | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Console.Error.WriteLine($"Warning: Error while checking/modifying admin PATH: {ex.Message}"); | ||
| Console.Error.WriteLine("Continuing with user PATH setup..."); | ||
| } | ||
| } | ||
|
|
||
| private int SetUserInstallRoot() | ||
| { | ||
| // Get the default user dotnet installation path | ||
| string userDotnetPath = _dotnetInstaller.GetDefaultDotnetInstallPath(); | ||
|
|
||
| Console.WriteLine($"Setting up user install root at: {userDotnetPath}"); | ||
|
|
||
| // On Windows, check if we need to modify the admin PATH | ||
| if (OperatingSystem.IsWindows()) | ||
| { | ||
| HandleWindowsAdminPath(); | ||
| } | ||
|
|
||
| // Add the user dotnet path to the user PATH | ||
| try | ||
| { | ||
| Console.WriteLine($"Adding {userDotnetPath} to user PATH..."); | ||
|
|
||
| var userPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? string.Empty; | ||
| var pathEntries = userPath.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries).ToList(); | ||
|
|
||
| // Check if the user dotnet path is already in the user PATH | ||
| bool alreadyExists = pathEntries.Any(entry => | ||
| Path.TrimEndingDirectorySeparator(entry).Equals( | ||
| Path.TrimEndingDirectorySeparator(userDotnetPath), | ||
| StringComparison.OrdinalIgnoreCase)); | ||
|
|
||
| if (!alreadyExists) | ||
| { | ||
| // Add to the beginning of PATH | ||
| pathEntries.Insert(0, userDotnetPath); | ||
|
||
| var newUserPath = string.Join(Path.PathSeparator, pathEntries); | ||
| Environment.SetEnvironmentVariable("PATH", newUserPath, EnvironmentVariableTarget.User); | ||
| Console.WriteLine($"Successfully added {userDotnetPath} to user PATH."); | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine($"User dotnet path is already in user PATH."); | ||
| } | ||
|
|
||
| // Set DOTNET_ROOT for user | ||
| Environment.SetEnvironmentVariable("DOTNET_ROOT", userDotnetPath, EnvironmentVariableTarget.User); | ||
| Console.WriteLine($"Set DOTNET_ROOT to {userDotnetPath}"); | ||
|
|
||
| Console.WriteLine("User install root configured successfully."); | ||
| Console.WriteLine("Note: You may need to restart your terminal or application for the changes to take effect."); | ||
|
|
||
| return 0; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| Console.Error.WriteLine($"Error: Failed to configure user install root: {ex.Message}"); | ||
| return 1; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.CommandLine; | ||
|
|
||
| namespace Microsoft.DotNet.Tools.Bootstrapper.Commands.SetInstallRoot; | ||
|
|
||
| internal static class SetInstallRootCommandParser | ||
| { | ||
| public static readonly Argument<string> InstallTypeArgument = new("installtype") | ||
|
||
| { | ||
| HelpName = "INSTALL_TYPE", | ||
| Description = "The type of installation root to set: 'user'", | ||
| Arity = ArgumentArity.ExactlyOne, | ||
| }; | ||
|
|
||
| private static readonly Command SetInstallRootCommand = ConstructCommand(); | ||
|
|
||
| public static Command GetCommand() | ||
| { | ||
| return SetInstallRootCommand; | ||
| } | ||
|
|
||
| private static Command ConstructCommand() | ||
| { | ||
| Command command = new("setinstallroot", "Sets the dotnet installation root"); | ||
|
|
||
| command.Arguments.Add(InstallTypeArgument); | ||
|
|
||
| command.SetAction(parseResult => new SetInstallRootCommand(parseResult).Execute()); | ||
|
|
||
| return command; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add an overload to this method that returns (via an out parameter) the list of dotnet paths that are in the admin PATH. Then display those to the user before doing the operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit bd56b89. Added an overload
AdminPathContainsProgramFilesDotnet(out List<string> foundDotnetPaths)that returns the list of dotnet paths found in the admin PATH. UpdatedHandleWindowsAdminPath()to use this overload and display the paths to the user before removing them:The original parameterless method now delegates to the new overload with a discard parameter.