Skip to content
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

Windows: Modify PATH without requiring a system restart? #1614

Open
DavisVaughan opened this issue Dec 9, 2024 · 14 comments · May be fixed by #1657 or #1658
Open

Windows: Modify PATH without requiring a system restart? #1614

DavisVaughan opened this issue Dec 9, 2024 · 14 comments · May be fixed by #1657 or #1658

Comments

@DavisVaughan
Copy link
Contributor

Hi there, first off, great tool! Really appreciate the excellent work here!

I noticed that on Windows when you install ruff using the cargo-dist installer script, as of ruff 0.8.0 it often requires a restart of your whole machine the first time around to get the updates to the PATH in the registry to propagate. The reason this happens in 0.8.0 is due to the changes in ruff to use ~/.local/bin as the install location (the right choice, I think), which often doesn't exist yet on Windows and has to be created and added to the PATH by cargo-dist. Note that previously ruff used the default of cargo's bin/ directory, which often does already exist on the PATH from cargo itself if the user has installed Rust before, so this didn't show up as often before. astral-sh/ruff#14457

PS D:\Users\davis-vaughan> powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"                                 
Downloading ruff 0.8.0 (x86_64-pc-windows-msvc)                                                                         
Installing to D:\Users\davis-vaughan\.local\bin
  ruff.exe
everything's installed!

To add D:\Users\davis-vaughan\.local\bin to your PATH, either restart your system or run:

    set Path=D:\Users\davis-vaughan\.local\bin;%Path%   (cmd)
    $env:Path = "D:\Users\davis-vaughan\.local\bin;$env:Path"   (powershell)

From what I understand, modifying the PATH in the registry typically requires that you kill any processes and restart them to get them to have an updated copy of the PATH. It isn't enough to just kill cmd.exe though (i.e. a standard shell restart), as explorer.exe is the parent process of each Command Prompt so explorer.exe itself must be restarted. This is possible without rebooting the system but not elegantly, and it is easier to just tell people to reboot Windows.


However! I am fairly confident there is a way to "notify" all processes that they should refresh the environment. For example, the cargo installer does not require a system restart for you to get \.cargo\bin; on your PATH. The way they do this is by emitting a WB_SETTINGCHANGE event as described in this article:
https://web.archive.org/web/20091124062536/http://support.microsoft.com/kb/104011

You can even see the usage of it here:
https://github.com/search?q=org%3Arust-lang+WM_SETTINGCHANGE&type=code

I also noted that Inno Setup does this too:
https://github.com/jrsoftware/issrc/blob/72dd250bb1ca19bd1e6d3a48998722a4df059944/Projects/Src/Setup.InstFunc.pas#L1021

Which is why apps like rig (created with inno setup) don't require a system restart to get the path to rig to show up on the user's PATH, in inno setup you just set this https://github.com/r-lib/rig/blob/532d6e5d330a4b900cc26ff99be5559815b5a211/rig.iss#L32


So with all that in mind, I wonder if it is possible to fire this after a PATH update to get explorer.exe to refresh the environment, causing all subsequent cmd.exe to also have refreshed envs without a system restart - that would make for a much nicer first install experience.

  SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
    LPARAM(PChar('Environment')), SMTO_ABORTIFHUNG, 5000, @MsgResult);

CC @zanieb, as the author of that ruff PR I thought you may be interested in this

@zanieb
Copy link
Contributor

zanieb commented Dec 9, 2024

I'd love to avoid a restart if possible and also noticed this when installing the Rust toolchain recently :)

WB_SETTINGCHANGE seems compelling. Thanks for going into so much detail!

@DavisVaughan
Copy link
Contributor Author

Apparently SendMessageTimeout is not exposed directly in powershell, but that has not stopped people in the past. I'm not sure I know enough about this to send a PR for this one, but hopefully someone may find this helpful:

https://www.powershellgallery.com/packages/PSCI/1.0.4/Content/core%5Cutils%5CUpdate-EnvironmentVariables.ps1
https://gist.github.com/alphp/78fffb6d69e5bb863c76bbfc767effda?permalink_comment_id=4097043

@duckinator
Copy link
Contributor

Is there a per-user install location for Windows that wouldn't require updating the path?

If there is, we could try adding it to install-path like we did with the XDG stuff. As an example, if %LocalAppData%\Programs\ was such a directory, we could try this:

install-path = ["$LocalAppData/Programs/", "$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]

@DavisVaughan
Copy link
Contributor Author

DavisVaughan commented Dec 10, 2024

Not that I am aware of (I'm mainly a Mac user though). I have a fairly fresh windows machine, and running PATH on it gives me (after removing a few i think i added)

C:\WINDOWS\system32;
C:\WINDOWS;
C:\WINDOWS\System32\Wbem;
C:\WINDOWS\System32\WindowsPowerShell\v1.0\;

Which doesn't seem to give much room to play with

This is an old post but it seems to suggest there is no accepted answer
https://superuser.com/questions/532460/where-to-install-small-programs-without-installers-on-windows

@zanieb
Copy link
Contributor

zanieb commented Dec 10, 2024

I think I would prefer to signal an update as described anyway. The consistent install location across platform is nice :)

@duckinator
Copy link
Contributor

Not that I am aware of (I'm mainly a Mac user though). I have a fairly fresh windows machine, and running PATH on it gives me (after removing a few i think i added)

This all lines up with my understanding, too. So I think that WB_SETTINGCHANGE event is definitely the right approach. 👍

As a note for whoever implements this, dist uses cargo-wix. I think an upstream change to https://github.com/volks73/cargo-wix/blob/main/src/templates/main.wxs.mustache which adds something like this might be enough to make the PATH changes work without a reboot:

        <!-- Send a WM_SETTINGCHANGE message to tell processes like explorer to update their
             environments so any new command prompts get the updated %PATH% -->
        <CustomActionRef Id="WixBroadcastEnvironmentChange" />

Unfortunately I don't currently have a Windows development environment, so I can't test this myself right now.

@DavisVaughan
Copy link
Contributor Author

Is that only for MSI? My use case is for Powershell

@DavisVaughan
Copy link
Contributor Author

I'd also like to point out that whoever fixes this should update this bit in the book

The most fundamental limitation is that installers fundamentally cannot edit the PATH of the currently running shell (it's a parent process). Powershell does not have an equivalent of `source`, so to the best of our knowledge restarting the shell is the only option (which if using Windows Terminal seems to mean opening a whole new window, tabs aren't good enough). As such, it benefits an installer to try to install to a directory that will already be on PATH (such as [CARGO_HOME][cargo home]). ([rustup also sends a broadcast WM_SETTINGCHANGE message](https://github.com/rust-lang/rustup/blob/bcfac6278c7c2f16a41294f7533aeee2f7f88d07/src/cli/self_update/windows.rs#L397-L409), but we couldn't find any evidence that this does anything useful.)

This made me laugh

(rustup also sends a broadcast WM_SETTINGCHANGE message, but we couldn't find any evidence that this does anything useful

We now know what this does 😆

@duckinator
Copy link
Contributor

Is that only for MSI? My use case is for Powershell

Yes, that's MSI only, sorry about that. 😅

So I suspect we'll need that for the MSI side, and something else for the Powershell side.

It looks like the following (courtesy of someone on StackExchange, found by @mistydemeo) should be the powershell equivalent:

chunk of untested powershell code
    $HWND_BROADCAST = [IntPtr] 0xffff;
    $WM_SETTINGCHANGE = 0x1a;
    $result = [UIntPtr]::Zero

    if (-not ("Win32.NativeMethods" -as [Type]))
    {
        # import sendmessagetimeout from win32
        Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessageTimeout(
        IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
        uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
"@
    }
    # notify all windows of environment block change
    [Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, "Environment", 2, 5000, [ref] $result);

@DavisVaughan
Copy link
Contributor Author

DavisVaughan commented Dec 10, 2024

@duckinator also see my two links here
#1614 (comment)

The powershell gallery one seems nicely self contained and MIT licensed

@fdcastel
Copy link

fdcastel commented Dec 19, 2024

What about

[Environment]::SetEnvironmentVariable("Path", $env:Path + ';.local\bin', 'User')

?

I have tested this on a system that cannot be rebooted at the moment, and it appears to be working as expected. The functionality was verified in both an elevated PowerShell session and a non-elevated session.

@fdcastel
Copy link

fdcastel commented Dec 19, 2024

P.S.: I also tried this. It had no effect.

@fdcastel
Copy link

I'm sorry @DavisVaughan, for any confusion I caused. I didn't had tested your code, specifically.

I arrived at this discussion from another thread (from the uv project).

As it seems, both projects are experiencing a similar issue: the installer is not updating the user PATH variable correctly until after a full system reboot.

That said, I’m curious: why [Environment]::SetEnvironmentVariable() didn’t work for you? It appears to be a clean and well-supported approach.

That was the code I tested, and it worked in every scenario. Without requiring a reboot.

@zanieb
Copy link
Contributor

zanieb commented Dec 19, 2024

(uv uses cargo-dist to power its installer, that's why the issue is being tracked here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants