Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/sdkPublic

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
Copilot wants to merge14 commits intorelease/dnup
base:release/dnup
Choose a base branch
Loading
fromcopilot/modify-admin-path-windows

Conversation

Copy link
Contributor

CopilotAI commentedDec 16, 2025
edited
Loading

Implementation Plan for Windows PATH Management in dotnetup

  • CreateWindowsPathHelper utility class to handle Windows-specific PATH operations
    • Read/write admin PATH from registry (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment)
    • Read/write user PATH from registry preserving unexpanded variables
    • Support reading with/without environment variable expansion
    • Use Environment.IsPrivilegedProcess for elevation checking
    • Remove/Add Program Files dotnet path to admin PATH
    • Generic AddPathEntry and RemovePathEntry helpers
    • Preserve unexpanded environment variables when modifying PATH
    • Implement IDisposable with log file lifecycle management
    • Progressive logging throughout operations - all output to log, errors to Console.Error
    • Proper exception handling - complete exception info with ToString()
    • Broadcast WM_SETTINGCHANGE message after changes
    • Extract testable methods for PATH manipulation logic
    • Refactor orchestrating methods to encapsulate full logic
    • Clear method naming - GetAdminPathWithProgramFilesDotnetRemoved makes it clear it doesn't modify registry
    • AdminPathContainsProgramFilesDotnet overload to return found paths
  • CreateElevatedAdminPathCommand and parser fordotnetup elevatedadminpath subcommand
    • Implementremovedotnet subcommand
    • Implementadddotnet subcommand
    • Hide command from help output (internal command)
    • Simplified to use WindowsPathHelper orchestrating methods
  • CreateSetInstallRootCommand and parser fordotnetup setinstallroot subcommand
    • Implementuser subcommand that:
      • Checks if admin PATH needs to be changed
      • Displays dotnet paths found before removal
      • Shells out todotnetup elevatedadminpath removedotnet if necessary
      • Uses WindowsPathHelper for user PATH operations preserving unexpanded variables
      • Adds user dotnet path to user PATH
    • Implementadmin subcommand that:
      • Adds Program Files dotnet path back to admin PATH
      • Removes user dotnet path from user PATH preserving unexpanded variables
      • Unsets user DOTNET_ROOT environment variable
    • Uses same WindowsPathHelper logic as ElevatedAdminPathCommand
    • Proper handling of elevation cancellation and failures
    • Add constants for install types and use AcceptOnlyFromAmong for validation
  • Add tests for new commands
    • Parser tests
    • WindowsPathHelper tests (18 tests total)
    • Unit tests for testable PATH manipulation methods
  • Update Parser.cs to register new commands
  • Build successfully
  • Test the changes manually
  • Run code review and address feedback
  • Run CodeQL security checker
  • Format code

Summary

This PR implements Windows PATH management commands for dotnetup as specified in issue#52121. The implementation includes:

New Commands:

  1. dotnetup elevatedadminpath removedotnet - Removes Program Files dotnet path from machine-wide admin PATH (requires elevation, hidden from help)
  2. dotnetup elevatedadminpath adddotnet - Adds Program Files dotnet path to machine-wide admin PATH (requires elevation, hidden from help)
  3. dotnetup setinstallroot user - Sets up user installation root, handling admin PATH modification via elevation if needed
  4. dotnetup setinstallroot admin - Restores admin installation root by adding Program Files dotnet back to admin PATH and removing user settings

Key Features:

  • Windows-specific implementation with proper OS guards
  • Uses Environment.IsPrivilegedProcess for elevation checking (built-in .NET API instead of custom implementation)
  • Preserves unexpanded environment variables (e.g.,%SystemRoot%,%USERPROFILE%) when modifying both admin AND user PATH
  • All PATH operations (admin and user) now use expanded PATH for detection but modify unexpanded PATH to preserve environment variable references
  • Uses expanded PATH for detection, unexpanded PATH for modification to avoid breaking existing environment variable references
  • Automatic elevation handling when needed
  • Progressive logging with disposable pattern - All operations logged to file, only errors to Console.Error
  • Proper exception handling - Complete exception info logged using ToString() for inner exceptions and stack traces
  • Log file lifecycle management - Opens on creation, auto-flushes for immediate writes
  • Elevation process improvements - Returns bool for success/cancellation, captures STDOUT/STDERR, throws exception with details on failure
  • Environment change notification broadcasting (logged, not console output)
  • User feedback - Displays the list of dotnet paths found in admin PATH before removing them
  • Comprehensive tests for new functionality (18 tests)
  • Testable PATH manipulation methods that don't require registry access for reliable unit testing
  • Clear method naming -GetAdminPathWithProgramFilesDotnetRemoved() makes it explicit that it doesn't modify the registry
  • Refactored architecture - GenericAddPathEntry() andRemovePathEntry() methods for reusable PATH manipulation
  • User PATH registry operations -ReadUserPath() andWriteUserPath() methods preserve unexpanded environment variables
  • Code reuse -PathContainsDotnet() method used consistently across the codebase
  • Argument validation - Uses AcceptOnlyFromAmong with constants (UserInstallType, AdminInstallType) for install type validation
  • Bidirectional migration - Supports both user→admin and admin→user installation transitions
  • No code duplication - Commands simplified to just create helper and call methods
  • Graceful handling of non-Windows platforms
  • elevatedadminpath command is hidden from help as it's an internal command
Original prompt

This section details on the original issue you should resolve

<issue_title>dotnetup: Modify admin PATH on windows</issue_title>
<issue_description>As part of#52121, on Windows we want to modify the machine-wide admin PATH to remove the Program Files dotnet path.

Rough plan to do so:

  • Read the admin PATH from the registry (HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment) to check if it needs to be updated
  • If it does:
    • Check if current process is elevated
    • If not, then launch an elevated dotnetup process:dotnetup elevatedadminpath removedotnet to do the following:
      • Remove the Program Files dotnet path from the admin PATH
      • Write to a log (event log and/or a log in the %temp% folder) the previous and new values of the PATH
      • Broadcast a WM_SETTINGCHANGE message when done
    • If the process was already elevated, then it should do the same steps in-process</issue_description>

<agent_instructions>Start by adding adotnetup elevatedadminpath removedotnet command as described. Also add adotnetup elevatedadminpath adddotnet which will add the program files dotnet path back to the system PATH.

Then add adotnetup setinstallroot user command which will check if the admin path needs to be changed, and if so modify it (via shelling out todotnetup elevatedadminpath removedotnet if necessary). Then add the user dotnet path to the user PATH.</agent_instructions>

Comments on the Issue (you are@copilot in this section)

@joeloffWe should make sure that modifying the path is logged. It's a system change that can have impact, but otherwise this looks fine.@dsplaisted@joeloff Would the log need to be in a system-wide admin path or could it be a user log? If it needs to be system-wide then we'd probably have to launch our own process as elevated instead of relying on `setx`.

✨ Let Copilot coding agentset things up for you — coding agent works faster and does higher quality work when set up for your repo.

CopilotAIand others added4 commitsDecember 16, 2025 14:28
- Created WindowsPathHelper utility class for Windows-specific PATH operations- Added ElevatedAdminPathCommand for 'dotnetup elevatedadminpath removedotnet/adddotnet'- Added SetInstallRootCommand for 'dotnetup setinstallroot user'- Registered new commands in Parser.csCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
- Added parser tests for elevatedadminpath and setinstallroot commands- Added unit tests for WindowsPathHelper utility methods- All tests pass successfullyCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
- Extract PATH splitting logic to SplitPath helper method- Remove extra blank lines in WindowsPathHelper.cs- All tests still passCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
Co-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
CopilotAI changed the title[WIP] Modify admin PATH on Windows to remove dotnet pathAdd Windows admin PATH management commands to dotnetupDec 16, 2025
@dsplaisted
Copy link
Member

@copilot I forgot to tell you that the elevatedadminpath command should be hidden so it doesn't show up on the command line help, since it's meant to be an internal command not invoked by users. Can you fix that?

The elevatedadminpath command is now hidden from the main help output as it's an internal command not meant to be invoked directly by users. The command is still functional when invoked explicitly.Co-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
Copy link
ContributorAuthor

@copilot I forgot to tell you that the elevatedadminpath command should be hidden so it doesn't show up on the command line help, since it's meant to be an internal command not invoked by users. Can ...

Done in commit3d163b5. Theelevatedadminpath command is now hidden from the help output by settingcommand.Hidden = true. The command remains functional when invoked explicitly.

Before:

Commands:  sdk                            Manage sdk installations  install <CHANNEL>              Installs the .NET SDK  update                         Updates the .NET SDK  elevatedadminpath <OPERATION>  Modifies the machine-wide admin PATH (requires elevated privileges)  setinstallroot <INSTALL_TYPE>  Sets the dotnet installation root

After:

Commands:  sdk                            Manage sdk installations  install <CHANNEL>              Installs the .NET SDK  update                         Updates the .NET SDK  setinstallroot <INSTALL_TYPE>  Sets the dotnet installation root

The command still works when called directly:dotnetup elevatedadminpath removedotnet

@dsplaisted
Copy link
Member

@copilot Next, let's change the logic for setting the admin PATH so that we don't expand variables embedded in it.

Change the WindowsPathHelper.ReadAdminPath method to take an argument controlling whether it should expand the registry value. We should use the expanded value when checking to see if the PATH needs to be changed. When modifying the path, we should find the indices of the paths for dotnet in the expanded value, but then get the unexpanded value, delete those indices, and set the admin PATH to the new unexpanded value.

We should also factor the manipulation logic into methods that don't require actually reading or writing the registry so we can write unit tests for that logic.

- Modified ReadAdminPath to accept expand parameter controlling variable expansion- Created testable methods (FindDotnetPathIndices, RemovePathEntriesByIndices, PathContainsDotnet)- Added RemoveProgramFilesDotnetFromAdminPath that uses expanded PATH for detection but modifies unexpanded PATH- Updated AdminPathContainsProgramFilesDotnet to use expanded PATH for accurate detection- Updated commands to use new methods- Added 7 new unit tests for the testable methods- All 18 tests passCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
Copy link
ContributorAuthor

@copilot Next, let's change the logic for setting the admin PATH so that we don't expand variables embedded in it.

Change the WindowsPathHelper.ReadAdminPath method to take an argument controlling ...

Done in commit 5585351. I've refactored the PATH manipulation logic as requested:

Changes made:

  1. ReadAdminPath now takes anexpand parameter - Controls whether environment variables are expanded viaRegistryValueOptions

  2. New testable methods without registry access:

    • FindDotnetPathIndices(pathEntries, dotnetPaths) - Finds indices of dotnet paths
    • RemovePathEntriesByIndices(path, indices) - Removes entries at specified indices
    • PathContainsDotnet(pathEntries, dotnetPaths) - Checks if dotnet exists
  3. New RemoveProgramFilesDotnetFromAdminPath method:

    • Reads both expanded and unexpanded PATH from registry
    • Finds indices using the expanded PATH
    • Removes those indices from the unexpanded PATH
    • Preserves environment variables like%SystemRoot%,%USERPROFILE%, etc.
  4. Updated detection to use expanded PATH:

    • AdminPathContainsProgramFilesDotnet() now usesReadAdminPath(expand: true)
  5. Added 7 new unit tests including:

    • RemovePathEntriesByIndices_PreservesUnexpandedVariables() - Verifies%SystemRoot% and%USERPROFILE% are preserved

All 18 tests pass successfully.

@dsplaisted
Copy link
Member

@copilot Refactor the functionality of AddDotnet and RemoveDotnet from the ElevatedAdminPathCommand into WindowsPathHelper, and then use the same logic from SetInstallRootCommand so that the logic isn't duplicated.

Also, in WindowsPathHelper, create the log file when the class is created and close it when the class is disposed. Then write progress to it as it happens instead of all at the end.

- Convert WindowsPathHelper from static to instance class implementing IDisposable- Add constructor that creates and opens log file on creation- Add Dispose method that closes log file and displays path- Add LogMessage method for progressive logging throughout operations- Create RemoveDotnetFromAdminPath() and AddDotnetToAdminPath() orchestrating methods- These methods encapsulate full logic: read, check, modify, write, log, broadcast- Simplify ElevatedAdminPathCommand to just create helper and call methods- Simplify SetInstallRootCommand to use the same logic- Log messages now written progressively during operations- All 18 tests passCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
Copy link
ContributorAuthor

@copilot Refactor the functionality of AddDotnet and RemoveDotnet from the ElevatedAdminPathCommand into WindowsPathHelper, and then use the same logic from SetInstallRootCommand so that the logic isn...

Done in commit1d91179. I've refactored the code as requested:

Changes made:

  1. Converted WindowsPathHelper to disposable instance class:

    • Constructor creates and opens log file on creation
    • Dispose method closes log file and displays the path to user
    • Log file auto-flushes for immediate write
  2. Created orchestrating methods in WindowsPathHelper:

    • RemoveDotnetFromAdminPath() - Full logic: read, check, modify, write, log, broadcast
    • AddDotnetToAdminPath() - Full logic: read, check, modify, write, log, broadcast
  3. Simplified ElevatedAdminPathCommand:

    • RemoveDotnet() now just createsWindowsPathHelper and calls method
    • AddDotnet() now just createsWindowsPathHelper and calls method
    • Reduced from ~70 lines to ~6 lines
  4. Updated SetInstallRootCommand to use same logic:

    • Now uses the sameRemoveDotnetFromAdminPath() method
    • No duplication between commands
  5. Progressive logging implemented:

    • Log messages written throughout operation (start, read, modify, write, broadcast, complete)
    • Errors logged with stack traces
    • Session start/end markers in log

All 18 tests pass successfully.

{
_logWriter?.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}]{message}");
}
catch(Exceptionex)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Don't catch an exception here, let it fail.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitbbd3d5c. Removed the try-catch block from LogMessage method - it now lets exceptions propagate.

_logWriter?.Dispose();
if(_logFilePath!=null)
{
Console.WriteLine($"PATH changes logged to:{_logFilePath}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Remove console output such as this.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitbbd3d5c. Removed the console output from Dispose method.

/// Removes the Program Files dotnet path from the given PATH string.
/// This is a convenience method that uses the expanded PATH for detection.
/// </summary>
publicstaticstringRemoveProgramFilesDotnetFromPath(stringpath)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This method is currently only called by tests, and it has similar but different logic to the real logic. Change this so that it takes both an unexpanded and expanded path as a parameter, and does the logic currently in RemoveProgramFilesDotnetFromAdminPath. Then that method can call this one.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitbbd3d5c. RefactoredRemoveProgramFilesDotnetFromPath to take bothunexpandedPath andexpandedPath as parameters:

publicstaticstringRemoveProgramFilesDotnetFromPath(stringunexpandedPath,stringexpandedPath)

The method now does the logic that was inRemoveProgramFilesDotnetFromAdminPath, which now just calls this method. Tests updated to pass both parameters.

}

// Check if any Program Files dotnet path is already in PATH
boolalreadyExists=pathEntries.Any(entry=>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Instead of this logic, use the PathContainsDotnet method.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitbbd3d5c. Replaced the custom logic with a call toPathContainsDotnet:

boolalreadyExists=PathContainsDotnet(pathEntries,programFilesDotnetPaths);

- Don't swallow exception in constructor, rethrow with better message preserving inner exception- Remove try-catch from LogMessage method, let it fail if there are issues- Remove console output from Dispose method- Refactor RemoveProgramFilesDotnetFromPath to take both unexpanded and expanded paths as parameters- Update RemoveProgramFilesDotnetFromAdminPath to call the refactored method- Replace custom logic in AddProgramFilesDotnetToPath with PathContainsDotnet method call- Update tests to pass both parameters to RemoveProgramFilesDotnetFromPath- All 18 tests passCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
/// Removes the Program Files dotnet path from the admin PATH while preserving unexpanded environment variables.
/// </summary>
/// <returns>The modified unexpanded PATH string.</returns>
publicstaticstringRemoveProgramFilesDotnetFromAdminPath()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Let's rename this to GetAdminPathWithProgramFilesDotnetRemoved() to make it clearer that it doesn't actually update the saved value of the PATH in the registry.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitf4e9278. Renamed toGetAdminPathWithProgramFilesDotnetRemoved() to make it clear the method doesn't modify the registry, only returns the modified PATH string.

/// This is the main orchestrating method that should be called by commands.
/// </summary>
/// <returns>0 on success, 1 on failure.</returns>
publicintRemoveDotnetFromAdminPath()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Remove the console output from this method and from AddDotnetToAdminPath (except in the case of logging a failure to Console.Error).

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitf4e9278. Removed allConsole.WriteLine statements from bothRemoveDotnetFromAdminPath() andAddDotnetToAdminPath(). OnlyConsole.Error.WriteLine remains for error reporting. All progress is now logged to the log file only.

Comment on lines 321 to 322
LogMessage($"ERROR:{ex.Message}");
LogMessage($"Stack trace:{ex.StackTrace}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

In this method and the next one, rely on the Exception ToString() method. This should include more complete information (such as inner exceptions and their stack traces) in the logs.

Suggested change
LogMessage($"ERROR:{ex.Message}");
LogMessage($"Stack trace:{ex.StackTrace}");
LogMessage($"ERROR:{ex.ToString()}");

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitf4e9278. Changed to useex.ToString() in both methods:

LogMessage($"ERROR:{ex.ToString()}");

This includes complete information with inner exceptions and their stack traces.

/// <summary>
/// Broadcasts a WM_SETTINGCHANGE message to notify other applications that the environment has changed.
/// </summary>
privatevoidBroadcastEnvironmentChange()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Don't write to the Console in this method, write to the log instead. Make sure to use ToString() on the exception when writing it to the log.

In methods that call this method, don't log a message saying that the change was broadcasted, since that will now be logged in this method.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitf4e9278. UpdatedBroadcastEnvironmentChange() to write to log instead of console:

LogMessage("Environment change notification broadcasted");// and on error:LogMessage($"WARNING: Failed to broadcast environment change:{ex.ToString()}");

Removed the "Environment change broadcasted" log messages from the calling methods since it's now logged inBroadcastEnvironmentChange().

/// <summary>
/// Starts an elevated process with the given arguments.
/// </summary>
publicstaticintStartElevatedProcess(stringarguments)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Change this method to return a bool which should be true if the elevation succeeded and the process exit code was zero. It should return false if the elevation was cancelled. If the process returned a non-zero exit code, throw an exception with the process STDOUT and STDERR (which will need to be captured).

If there's some other error starting the process, let the exception propagate (or throw an exception if the process is null, for example).

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitf4e9278. RefactoredStartElevatedProcess() to:

  • Returnbool (true if succeeded, false if cancelled)
  • Capture STDOUT and STDERR withRedirectStandardOutput/Error = true
  • ThrowInvalidOperationException with captured output on non-zero exit code
  • Return false on Win32Exception with error code 1223 (user cancelled UAC)
  • Let other exceptions propagate

UpdatedSetInstallRootCommand to handle the new return type with proper error handling.

- Rename RemoveProgramFilesDotnetFromAdminPath to GetAdminPathWithProgramFilesDotnetRemoved for clarity- Remove all Console.WriteLine statements from RemoveDotnetFromAdminPath and AddDotnetToAdminPath (keep only Console.Error)- Use ex.ToString() for complete exception logging including inner exceptions and stack traces- Update BroadcastEnvironmentChange to log instead of console output- Refactor StartElevatedProcess to return bool (true on success, false if cancelled)- Capture STDOUT and STDERR in StartElevatedProcess and throw exception with details on non-zero exit- Update SetInstallRootCommand to handle new StartElevatedProcess return type- All 18 tests passCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
Copy link
Member

@JeremyKuhneJeremyKuhne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@dsplaisted I don't think you need much if any of the interop.Environment already has this support.

/// <summary>
/// Checks if the current process is running with elevated (administrator) privileges.
/// </summary>
public static bool IsElevated()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I thinkEnvironment.IsPrivilegedProcess will do what you want here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@copilot Delete this method and switch to Environment.IsPrivilegedProcess in places that call it.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commit8aa862f. Deleted theIsElevated() method and replaced all calls withEnvironment.IsPrivilegedProcess. Also removed the unusedSystem.Security.Principal import.

/// Reads the machine-wide PATH environment variable from the registry.
/// </summary>
/// <param name="expand">If true, expands environment variables in the PATH value.</param>
public static string ReadAdminPath(bool expand = false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Environment.Get/SetEnvironmentVariable allows you to target machine-wide.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is there a way with .NET APIs to get the unexpanded value from the registry? My understanding is that the path could have something like%USERPROFILE%\MyApp\ in it, and we don't want to replace that with the expanded value if we're modifying other parts of the PATH. I think we hit this with the installers at one point. /cc@joeloff


internalstaticclassSetInstallRootCommandParser
{
publicstaticreadonlyArgument<string>InstallTypeArgument=new("installtype")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Call the AcceptOnlyFromAmong method here to list the valid argument values. They should be defined as constants in this file. Add "admin" in addition to "user" as a valid value.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commit8aa862f. Added constantsUserInstallType = "user" andAdminInstallType = "admin" toSetInstallRootCommandParser. Updated the argument to useAcceptOnlyFromAmong(UserInstallType, AdminInstallType) for validation. The help now shows:

Arguments:  <INSTALL_TYPE>  The type of installation root to set: 'user' or 'admin'

/// <summary>
/// Checks if the current process is running with elevated (administrator) privileges.
/// </summary>
public static bool IsElevated()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@copilot Delete this method and switch to Environment.IsPrivilegedProcess in places that call it.

/// Helper class for Windows-specific PATH management operations.
/// </summary>
[SupportedOSPlatform("windows")]
internal sealed class WindowsPathHelper : IDisposable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Add implementation for 'dotnet setinstallroot admin` to this class.

This should add the Program Files dotnet path back to the admin PATH, remove the user dotnet path from the user PATH, and unset the user DOTNET_ROOT environment variable.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commit8aa862f. AddedSetAdminInstallRoot() method toSetInstallRootCommand that:

  • Adds the Program Files dotnet path back to the admin PATH (with elevation if needed viaelevatedadminpath adddotnet)
  • Removes the user dotnet path from the user PATH
  • Unsets the user DOTNET_ROOT environment variable

Theadmin install type is now accepted and handled by the command.

… install root- Remove custom IsElevated() method and use Environment.IsPrivilegedProcess instead- Remove unused System.Security.Principal import- Add constants for install types (UserInstallType, AdminInstallType) in SetInstallRootCommandParser- Use AcceptOnlyFromAmong to validate install type arguments- Add SetAdminInstallRoot() method to restore admin installation:  - Adds Program Files dotnet path back to admin PATH (with elevation if needed)  - Removes user dotnet path from user PATH  - Unsets user DOTNET_ROOT environment variable- Update SetInstallRootCommand.Execute() to handle both user and admin install types- All 18 tests passCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
try
{
// Check if admin PATH needs to be changed
if (WindowsPathHelper.AdminPathContainsProgramFilesDotnet())
Copy link
Member

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.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitbd56b89. Added an overloadAdminPathContainsProgramFilesDotnet(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:

Program Files dotnet path(s) found in admin PATH:  - C:\Program Files\dotnet  - C:\Program Files (x86)\dotnetRemoving them...

The original parameterless method now delegates to the new overload with a discard parameter.

- Add AdminPathContainsProgramFilesDotnet(out List<string> foundDotnetPaths) overload- The new overload returns the list of dotnet paths found in the admin PATH- Update HandleWindowsAdminPath to use the overload and display paths before removal- The original parameterless method now delegates to the new overload- All 18 tests passCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
/// <summary>
/// Adds the Program Files dotnet path to the given PATH string if it's not already present.
/// </summary>
publicstaticstringAddProgramFilesDotnetToPath(stringpath)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This process needs to be changed so that if the PATH needs to be modified, it reads the unexpanded path and then adds to that.

Reuse existing methods for similar functionality if possible. Refactor if you need to.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitb182662. RefactoredAddProgramFilesDotnetToPath() to take bothunexpandedPath andexpandedPath parameters. The method now:

  • Uses the expanded PATH for detection (to check if dotnet is already present)
  • Modifies the unexpanded PATH (to preserve environment variables like%ProgramFiles%,%USERPROFILE%, etc.)

UpdatedAddDotnetToAdminPath() to read both expanded and unexpanded admin PATH and pass both to the helper method.

if(!alreadyExists)
{
// Add to the beginning of PATH
pathEntries.Insert(0,userDotnetPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Here we need to read the unexpanded user PATH value from the registry and modify that. Use helper methods from WindowsPathHelper as possible.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitb182662. AddedReadUserPath() andWriteUserPath() methods to WindowsPathHelper for registry-based user PATH access. Created genericAddPathEntry() helper that:

  • Takes both unexpanded and expanded PATH
  • Uses expanded PATH for detection
  • Modifies unexpanded PATH to preserve environment variables

UpdatedSetUserInstallRoot() to use these new methods on Windows, with a fallback to Environment API for non-Windows platforms.


// Remove user dotnet path from user PATH
Console.WriteLine($"Removing{userDotnetPath} from user PATH...");
varuserPath=Environment.GetEnvironmentVariable("PATH",EnvironmentVariableTarget.User)??string.Empty;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Same here, we need to get the unexpanded path and modify that (which is what we do for the admin path in WindowsPathHelper).

Reuse existing helper methods and refactor them as necessary.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed in commitb182662. Created genericRemovePathEntry() helper method that:

  • Takes both unexpanded and expanded PATH
  • Finds indices to remove using the expanded PATH
  • Removes those indices from the unexpanded PATH to preserve environment variables

UpdatedSetAdminInstallRoot() to useReadUserPath(),RemovePathEntry(), andWriteUserPath() for registry-based operations that preserve unexpanded environment variables.

- Add ReadUserPath and WriteUserPath methods for user PATH registry access- Refactor AddProgramFilesDotnetToPath to take both unexpanded and expanded paths- Add new helper methods: AddPathEntry and RemovePathEntry for generic path manipulation- Update AddDotnetToAdminPath to read both expanded and unexpanded paths- Update SetUserInstallRoot to use WindowsPathHelper methods for registry operations on Windows- Update SetAdminInstallRoot to use WindowsPathHelper methods for user PATH removal- Add OS guards to ensure Windows-specific code only runs on Windows- Update tests to pass both parameters to AddProgramFilesDotnetToPath- All 18 tests passCo-authored-by: dsplaisted <145043+dsplaisted@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@JeremyKuhneJeremyKuhneJeremyKuhne requested changes

@dsplaisteddsplaistedAwaiting requested review from dsplaisted

Requested changes must be addressed to merge this pull request.

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

3 participants

@dsplaisted@JeremyKuhne

[8]ページ先頭

©2009-2025 Movatter.jp