542

If I modify or add an environment variable I have to restart the command prompt. Is there a command I could execute that would do this without restarting CMD?

Andy McRae
  • 163
  • 9
Eric Schoonover
  • 44,080
  • 43
  • 148
  • 200
  • 45
    Actually, every program that needs to see them has to be restarted. The environment is copied into the process' memory on startup and therefore has no connection whatsoever to the system-defined envvars anymore. – Joey Sep 02 '09 at 05:58
  • 19
    after reading these, I realized that *there is no spoon* ;) in the real world, you just restart cmd. – n611x007 Jul 14 '13 at 09:39
  • Not a command, so not quite an answer, but there is support for it using Win32 API if I read the following correctly: https://support.microsoft.com/en-us/help/104011/how-to-propagate-environment-variables-to-the-system Shoud be able to compile that line into a simple C program and run it following environment variable updates. – Charles Grunwald Feb 26 '18 at 05:53
  • 1
    WM_SETTINGCHANGE (the win32 api mentioned by @CharlesGrunwald) doesn't work for cmd.exe windows according to this thread: https://github.com/chocolatey/choco/issues/1589 -- it's the reason they wrote the refreshenv command – davr Oct 11 '19 at 22:43

24 Answers24

159

On Windows 7/8/10, you can install Chocolatey, which has a script for this built-in.

After installing Chocolatey, just type refreshenv.

MarredCheese
  • 9,495
  • 5
  • 59
  • 63
jolly
  • 1,981
  • 1
  • 12
  • 15
  • 1
    this is valid answer, would be nice to hear from guy who'd downvoted it – the_joric Aug 01 '17 at 12:58
  • 2
    What am I doing wrong? $>refreshenv 'refreshenv' is not recognized as an internal or external command, operable program or batch file. – aclowkay Aug 06 '17 at 07:29
  • @aclokay Not sure. Please provide more detail about ur system conf to debug. Meanwhile you can refer to a similar open issue here. https://github.com/chocolatey/choco/issues/250 – jolly Aug 10 '17 at 15:59
  • It doesn't work for me neither. I'm on W7 professional, maybe it only works in more complete versions. – alseether Oct 19 '17 at 07:49
  • It doesn't work out of the box. You have to have Chocolatey already installed, which installs the refreshenv script. Saying "In windows 7/8/10" is misleading – Michael Nov 07 '17 at 21:38
  • 3
    If refreshenv prematurely exists your script (as it did for me), you can use "call RefreshEnv.cmd" instead. (see https://github.com/chocolatey/choco/issues/1461) – sfiss May 25 '18 at 12:13
144

You can capture the system environment variables with a vbs script, but you need a bat script to actually change the current environment variables, so this is a combined solution.

Create a file named resetvars.vbs containing this code, and save it on the path:

Set oShell = WScript.CreateObject("WScript.Shell")
filename = oShell.ExpandEnvironmentStrings("%TEMP%\resetvars.bat")
Set objFileSystem = CreateObject("Scripting.fileSystemObject")
Set oFile = objFileSystem.CreateTextFile(filename, TRUE)

set oEnv=oShell.Environment("System")
for each sitem in oEnv 
    oFile.WriteLine("SET " & sitem)
next
path = oEnv("PATH")

set oEnv=oShell.Environment("User")
for each sitem in oEnv 
    oFile.WriteLine("SET " & sitem)
next

path = path & ";" & oEnv("PATH")
oFile.WriteLine("SET PATH=" & path)
oFile.Close

create another file name resetvars.bat containing this code, same location:

@echo off
%~dp0resetvars.vbs
call "%TEMP%\resetvars.bat"

When you want to refresh the environment variables, just run resetvars.bat


Apologetics:

The two main problems I had coming up with this solution were

a. I couldn't find a straightforward way to export environment variables from a vbs script back to the command prompt, and

b. the PATH environment variable is a concatenation of the user and the system PATH variables.

I'm not sure what the general rule is for conflicting variables between user and system, so I elected to make user override system, except in the PATH variable which is handled specifically.

I use the weird vbs+bat+temporary bat mechanism to work around the problem of exporting variables from vbs.

Note: this script does not delete variables.

This can probably be improved.

ADDED

If you need to export the environment from one cmd window to another, use this script (let's call it exportvars.vbs):

Set oShell = WScript.CreateObject("WScript.Shell")
filename = oShell.ExpandEnvironmentStrings("%TEMP%\resetvars.bat")
Set objFileSystem = CreateObject("Scripting.fileSystemObject")
Set oFile = objFileSystem.CreateTextFile(filename, TRUE)

set oEnv=oShell.Environment("Process")
for each sitem in oEnv 
    oFile.WriteLine("SET " & sitem)
next
oFile.Close

Run exportvars.vbs in the window you want to export from, then switch to the window you want to export to, and type:

"%TEMP%\resetvars.bat"
Vishrant
  • 10,695
  • 8
  • 48
  • 87
itsadok
  • 27,343
  • 27
  • 120
  • 167
  • 2
    Perhaps you can avoid the temporary file using the FOR /F "tokens=1,*" %%c IN ('resetvars.vbs') DO construct – tzot Oct 05 '08 at 09:51
  • 2
    As I did say in my answer "or, manually add using SET in the existing command prompt." which is what this is effectively doing. Good answer though. – Kev Oct 05 '08 at 22:18
  • 2
    @itsadok - given that this is now the accepted answer, you should add a brief explaination at the start to put the script in context. i.e point out that it isn't possible to propagate an env var change to an open cmd.exe without manually updating as above or by restarting cmd.exe. – Kev Oct 05 '08 at 22:24
  • The script handles the use case of changing environment variables globally in "My Computer...Environment Variables", but if an environment variable is changed in one cmd.exe the script will not propagate it to another running cmd.exe which I think is probably a common scenario. – Kev Oct 05 '08 at 22:48
  • This one saved my butt with tryoing to use env vars for CI server cross-job string sharing. Hate to use files to store one-liner paramtric info :) – HX_unbanned Sep 26 '12 at 14:37
  • Has this been implemented in any software presently available for download as a binary that does the task of performing the refresh and little or nothing else? – Keyslinger Nov 30 '13 at 21:10
  • 1
    @Keyslinger: That's not actually possible. Any program spawned can update its own environment, but not that of the running cmd.exe instance. A batch file CAN update the cmd.exe environment, because it runs within the same instance of cmd.exe. – Ben Voigt Oct 14 '14 at 16:13
  • tldr; Why not use setx? :) – sotn Feb 15 '17 at 19:04
  • 1
    Be aware that some programs also cache the environment variables too. In my case I ran this script and then had to restart Intellij. – Soapy Mar 14 '17 at 10:53
  • 1
    I also replaced the `resetvars.vbs` line in the .bat with `%~dp0resetvars.vbs`. This way you can also run the .bat by its absolute path from a console which you had open before you came to this page :) And add `@echo off` before that line if you don't want all that noise in the console. – lapis May 24 '17 at 10:32
  • While I prefer the Chocolatey code-wise for being pure batch code, overall I decided to use this one, since it's faster. (~0.3 seconds instead of ~1 second -- which is nice, since I use it frequently in my Explorer "start cmd here" entry) – Venryx Jun 06 '17 at 09:44
  • should this work from `Powershell` too? It only seems to work from `cmd.exe` for me. – craq Jan 29 '19 at 01:44
  • 1
    For extra jazz - create a hybrid batch file containing the vbscript - https://ss64.com/vb/syntax-hybrid.html – JJS Jun 23 '20 at 23:10
138

Here is what Chocolatey uses.

https://github.com/chocolatey/choco/blob/master/src/chocolatey.resources/redirects/RefreshEnv.cmd

@echo off
::
:: RefreshEnv.cmd
::
:: Batch file to read environment variables from registry and
:: set session variables to these values.
::
:: With this batch file, there should be no need to reload command
:: environment every time you want environment changes to propagate

::echo "RefreshEnv.cmd only works from cmd.exe, please install the Chocolatey Profile to take advantage of refreshenv from PowerShell"
echo | set /p dummy="Refreshing environment variables from registry for cmd.exe. Please wait..."

goto main

:: Set one environment variable from registry key
:SetFromReg
    "%WinDir%\System32\Reg" QUERY "%~1" /v "%~2" > "%TEMP%\_envset.tmp" 2>NUL
    for /f "usebackq skip=2 tokens=2,*" %%A IN ("%TEMP%\_envset.tmp") do (
        echo/set "%~3=%%B"
    )
    goto :EOF

:: Get a list of environment variables from registry
:GetRegEnv
    "%WinDir%\System32\Reg" QUERY "%~1" > "%TEMP%\_envget.tmp"
    for /f "usebackq skip=2" %%A IN ("%TEMP%\_envget.tmp") do (
        if /I not "%%~A"=="Path" (
            call :SetFromReg "%~1" "%%~A" "%%~A"
        )
    )
    goto :EOF

:main
    echo/@echo off >"%TEMP%\_env.cmd"

    :: Slowly generating final file
    call :GetRegEnv "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" >> "%TEMP%\_env.cmd"
    call :GetRegEnv "HKCU\Environment">>"%TEMP%\_env.cmd" >> "%TEMP%\_env.cmd"

    :: Special handling for PATH - mix both User and System
    call :SetFromReg "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" Path Path_HKLM >> "%TEMP%\_env.cmd"
    call :SetFromReg "HKCU\Environment" Path Path_HKCU >> "%TEMP%\_env.cmd"

    :: Caution: do not insert space-chars before >> redirection sign
    echo/set "Path=%%Path_HKLM%%;%%Path_HKCU%%" >> "%TEMP%\_env.cmd"

    :: Cleanup
    del /f /q "%TEMP%\_envset.tmp" 2>nul
    del /f /q "%TEMP%\_envget.tmp" 2>nul

    :: capture user / architecture
    SET "OriginalUserName=%USERNAME%"
    SET "OriginalArchitecture=%PROCESSOR_ARCHITECTURE%"

    :: Set these variables
    call "%TEMP%\_env.cmd"

    :: Cleanup
    del /f /q "%TEMP%\_env.cmd" 2>nul

    :: reset user / architecture
    SET "USERNAME=%OriginalUserName%"
    SET "PROCESSOR_ARCHITECTURE=%OriginalArchitecture%"

    echo | set /p dummy="Finished."
    echo .
NateS
  • 5,747
  • 4
  • 46
  • 53
anonymous coward
  • 1,381
  • 1
  • 7
  • 2
  • 81
    +1 If you have Chocolatey installed, you can just run `RefreshEnv` to get updated environment variables into your current session. – Martin Valgur Sep 11 '16 at 10:45
  • 2
    This is an incredibly useful piece of utility software, thanks so much for sharing. – Sabuncu Jan 06 '17 at 09:21
  • 13
    Note: Chocolatey has moved repos and the latest version of this script can be found here (with some bug fixes): https://github.com/chocolatey/choco/blob/master/src/chocolatey.resources/redirects/RefreshEnv.cmd – Michael Burr Nov 11 '17 at 02:03
  • 1
    should this work from `Powershell` too? It only seems to work from `cmd.exe` for me. – craq Jan 29 '19 at 01:44
  • 1
    It works for me in PowerShell, @craq . Running Windows10 x64. – mazunki Mar 15 '19 at 23:08
  • @craq depending on what version of Chocolatey you are using, you may need to add the necessary entry in your PowerShell profile to make it work there. Have a look here: https://chocolatey.org/docs/troubleshooting#why-does-choco-intab-not-work-for-me – Gary Ewan Park Apr 10 '19 at 09:23
  • 1
    this doesn't appear to work for me at all, in cmd/gitbash/PowerShell – abc123 Apr 25 '19 at 16:58
  • 1
    The solution is good but it modifies env variables `TEMP` and `TMP` replacing them with values stored in `HKCU\Environment`. In my case I run the script to update env variables modified by Jenkins job on a slave that's running under SYSTEM account, so TEMP and TMP get substituted by `%USERPROFILE%\AppData\Local\Temp` instead of `C:\Windows\Temp`. This breaks build because linker cannot open system profile's Temp folder. – Gene Mayevsky Sep 26 '19 at 20:51
  • I saved it (the new version from GitHub mentioned in the comment above code) in a .cmd, I ran it **as admin**, it *works without closing all the cmd windows*. – JinSnow Jun 01 '20 at 09:09
61

By design there isn't a built in mechanism for Windows to propagate an environment variable add/change/remove to an already running cmd.exe, either from another cmd.exe or from "My Computer -> Properties ->Advanced Settings -> Environment Variables".

If you modify or add a new environment variable outside of the scope of an existing open command prompt you either need to restart the command prompt, or, manually add using SET in the existing command prompt.

The latest accepted answer shows a partial work-around by manually refreshing all the environment variables in a script. The script handles the use case of changing environment variables globally in "My Computer...Environment Variables", but if an environment variable is changed in one cmd.exe the script will not propagate it to another running cmd.exe.

Community
  • 1
  • 1
Kev
  • 112,868
  • 50
  • 288
  • 373
  • If anyone has a working solution to this problem I will periodically checkin and may change the accepted answer. – Eric Schoonover Oct 05 '08 at 07:39
  • 1
    This should not be the accepted answer simply because it does not answer the question asked. this question should remain without an accepted answer until one is found. – shoosh Oct 05 '08 at 07:48
  • 4
    And annoyingly, extra instances of cmd.exe don't count. They *all* have to be killed before the change is reflected in any new cmd.exe's. –  Oct 05 '08 at 08:08
  • 7
    The negative comments and down marking of this answer shows how broken stack overflow is at times. Kev has given the correct answer. Just because you don't like it is no reason to mark it down. – David Arno Oct 05 '08 at 08:32
  • 1
    Kev definitely does answer the question. The question is there is no built-in solution. – Eric Schoonover Oct 05 '08 at 21:58
  • Regarding @Mike F's comment, this doesn't seem to be the case in Windows 7. – Chris Jan 26 '12 at 17:41
42

I came across this answer before eventually finding an easier solution.

Simply restart explorer.exe in Task Manager.

I didn't test, but you may also need to reopen you command prompt.

Credit to Timo Huovinen here: Node not recognized although successfully installed (if this helped you, please go give this man's comment credit).

Community
  • 1
  • 1
wharding28
  • 1,047
  • 9
  • 13
  • I arrived here because I was trying to add an external tool to visual studio so I could open a command prompt at the root of my solution, as described in this blog: http://neverindoubtnet.blogspot.com/2012/10/add-visual-studio-command-prompt-to.html ... and I had similar issues ... I was trying to get "git" to appear in my path variable. I added the git directories to the PATH variable but then they would not appear in the command prompt that I would open from Visual Studio. The simple solution was to restart Visual Studio. Then the new additions to the PATH variable were visible in cmd. – David Barrows Feb 28 '15 at 09:23
  • 4
    That solution helps me in Windows 10 – ganchito55 May 01 '16 at 23:25
  • 13
    The question was: "Is there a command I could execute that would do this *without restarting CMD*?" – Florian F Jun 17 '16 at 09:29
  • 1
    Ok from the task manager i was not able to restart explorer.exe, only finish him. I did it but my task bar has been broken. To start explorer;exe it's really simple. Let's "Ctrl + shift + escape" --> file --> "execute new task" -> "explorer.exe" did the work for me. And yes, after all env var has been used in the new cmd window. Thanks for all – Oscar Oct 11 '16 at 09:27
  • 1
    Good solution, thank you! To expand and address @Oscar 's comment: Start a `cmd` window as Administrator. Use the command `taskkill /f /im explorer.exe && explorer.exe`. This will kill the explorer.exe process and restart it. – S3DEV Jan 12 '18 at 11:10
  • Thanks a lot! This worked for me on Windows 7. I didn't have time to analyze/run some vb script and I needed a quick and obvious way to force refresh the env vars. Thanks again a ton! – Mladen B. Mar 19 '18 at 22:37
30

This works on windows 7: SET PATH=%PATH%;C:\CmdShortcuts

tested by typing echo %PATH% and it worked, fine. also set if you open a new cmd, no need for those pesky reboots any more :)

shanethehat
  • 15,105
  • 10
  • 54
  • 84
  • 1
    Not working for "new cmd" for me (Win7 x64). See [screenvideo](http://www.youtube.com/watch?v=pf3U7q_Su4E) – Igor Oct 26 '13 at 10:51
  • 29
    This does not solve the question being asked, nor should it. The original question is how to refresh an environment variable to a value that has been set outisde of that terminal. – csauve Jun 15 '15 at 15:10
  • Although this doesn't answer the question, it provides half of the best working solution. I use this - for whatever variable I am setting - then I open up the control panel and add the environmental variable globally. I don't like using `setx` because it inherits the current environment which may have variables that have been modified and not what I want permanently. Doing it this way allows me to avoid restarting the console in order to use the variables, while avoiding the problem of not having them available globally in the future. – dgo May 13 '16 at 13:16
24

Use "setx" and restart cmd prompt

There is a command line tool named "setx" for this job. It's for reading and writing env variables. The variables persist after the command window has been closed.

It "Creates or modifies environment variables in the user or system environment, without requiring programming or scripting. The setx command also retrieves the values of registry keys and writes them to text files."

Note: variables created or modified by this tool will be available in future command windows but not in the current CMD.exe command window. So, you have to restart.

If setx is missing:


Or modify the registry

MSDN says:

To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, then broadcast a WM_SETTINGCHANGE message with lParam set to the string "Environment".

This allows applications, such as the shell, to pick up your updates.

Jens A. Koch
  • 34,456
  • 12
  • 100
  • 117
  • 1
    Could you expand on how to use setx to read an environment variable? I've been over the various documentation and I'm just not seeing it. :-/ – Mark Ribau Sep 15 '12 at 09:17
  • 2
    setx VARIABLE -k "HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\CurrentVersion" echo %VARIABLE% – Jens A. Koch Nov 03 '12 at 14:14
  • 3
    current system environment: `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\VARIABLE` current user environment: `HKEY_CURRENT_USER\Environment\VARIABLE` – Mark Ribau Nov 05 '12 at 22:54
  • 1
    The read var code from JAK didn't make any sense to me so I came up with this: `for /f "tokens=3* skip=2" %%i in ('reg query "HKCU\Environment" /v MyVarName') do (set MyVarName=%%i %%j)` which goes for User vars. See above for the System path. – Pecos Bill May 24 '13 at 00:11
  • **on XP** I did **not have** this in built-in `windows`, but in an installable [Windows Resource Kits\Tools](http://technet.microsoft.com/en-us/library/bb491050.aspx). I forgot how did I install it, maybe [here](https://www.microsoft.com/en-us/download/details.aspx?id=20494) (not tested) – n611x007 Jul 14 '13 at 09:16
  • On XP, this didn't actually update my cmd.exe's env variables. It correctly gets the values but do nothing to the environment. I was able to overwrite the registry settings with it, however. o_o – n611x007 Jul 14 '13 at 09:30
  • 6
    setx /? says in a note: "On a local system, variables created or modified by this tool will be available in future command windows but **not in the current** CMD.exe command window." OP wanted to update the current cmd. – Superole Oct 07 '14 at 10:53
  • For me, when I attempt this after having this problem, I get the following: `'setx' is not recognized as an internal or external command, operable program or batch file.`. – Dan Atkinson Jun 11 '15 at 21:40
  • That's odd. I've added the download link to my answer. – Jens A. Koch Jun 11 '15 at 21:54
  • 5
    Buyer beware! If you have a particularly long `%PATH%` then `setx` may truncate this to 1024 bytes! _And just like that, his evening vanished_ – FaCE Aug 15 '16 at 19:47
  • @FaCE lol thanks for the warning. By the way, use `set` and `setx` side by side if you want your variable to be available in current and future command windows.. – sotn Feb 15 '17 at 19:02
  • Doesn't answer the OP needs. The answer is supposed to be a command and it's not supposed to close the CMD, none of which exists without 3rd party tools. SetX implicitly requires to close the CMD to be able to use the new data. The way you're using invites more garbage in the registry than it's worth it. By the same logic one can place data in a temp file and read it from there for future needs or parallel scripts, and without limitations or littering. – JasonXA May 26 '17 at 17:58
15

Calling this function has worked for me:

VOID Win32ForceSettingsChange()
{
    DWORD dwReturnValue;
    ::SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) "Environment", SMTO_ABORTIFHUNG, 5000, &dwReturnValue);
}
Sridhar Ratnakumar
  • 68,948
  • 61
  • 139
  • 172
Brian Weed
  • 151
  • 1
  • 2
  • 8
    and not all programs listen to this message (in fact most of them probably don't) – Rehan Nov 18 '11 at 23:32
  • No, it also works for non-GUI programs. As for listening programs...the issue if ensuring that a restarted program will receive the updated environment, and this gives you that. – user2023370 Feb 14 '15 at 16:35
11

The best method I came up with was to just do a Registry query. Here is my example.

In my example I did an install using a Batch file that added new environment variables. I needed to do things with this as soon as the install was complete, but was unable to spawn a new process with those new variables. I tested spawning another explorer window and called back to cmd.exe and this worked but on Vista and Windows 7, Explorer only runs as a single instance and normally as the person logged in. This would fail with automation since I need my admin creds to do things regardless of running from local system or as an administrator on the box. The limitation to this is that it does not handle things like path, this only worked on simple enviroment variables. This allowed me to use a batch to get over to a directory (with spaces) and copy in files run .exes and etc. This was written today from may resources on stackoverflow.com

Orginal Batch calls to new Batch:

testenvget.cmd SDROOT (or whatever the variable)

@ECHO OFF
setlocal ENABLEEXTENSIONS
set keyname=HKLM\System\CurrentControlSet\Control\Session Manager\Environment
set value=%1
SET ERRKEY=0

REG QUERY "%KEYNAME%" /v "%VALUE%" 2>NUL| FIND /I "%VALUE%"
IF %ERRORLEVEL% EQU 0 (
ECHO The Registry Key Exists 
) ELSE (
SET ERRKEY=1
Echo The Registry Key Does not Exist
)

Echo %ERRKEY%
IF %ERRKEY% EQU 1 GOTO :ERROR

FOR /F "tokens=1-7" %%A IN ('REG QUERY "%KEYNAME%" /v "%VALUE%" 2^>NUL^| FIND /I "%VALUE%"') DO (
ECHO %%A
ECHO %%B
ECHO %%C
ECHO %%D
ECHO %%E
ECHO %%F
ECHO %%G
SET ValueName=%%A
SET ValueType=%%B
SET C1=%%C
SET C2=%%D
SET C3=%%E
SET C4=%%F
SET C5=%%G
)

SET VALUE1=%C1% %C2% %C3% %C4% %C5%
echo The Value of %VALUE% is %C1% %C2% %C3% %C4% %C5%
cd /d "%VALUE1%"
pause
REM **RUN Extra Commands here**
GOTO :EOF

:ERROR
Echo The the Enviroment Variable does not exist.
pause
GOTO :EOF

Also there is another method that I came up with from various different ideas. Please see below. This basically will get the newest path variable from the registry however, this will cause a number of issues beacuse the registry query is going to give variables in itself, that means everywhere there is a variable this will not work, so to combat this issue I basically double up the path. Very nasty. The more perfered method would be to do: Set Path=%Path%;C:\Program Files\Software....\

Regardless here is the new batch file, please use caution.

@ECHO OFF
SETLOCAL ENABLEEXTENSIONS
set org=%PATH%
for /f "tokens=2*" %%A in ('REG QUERY "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path ^|FIND /I "Path"') DO (
SET path=%%B
)
SET PATH=%org%;%PATH%
set path
8

The easiest way to add a variable to the path without rebooting for the current session is to open the command prompt and type:

PATH=(VARIABLE);%path%

and press enter.

to check if your variable loaded, type

PATH

and press enter. However, the variable will only be a part of the path until you reboot.

Paul Roub
  • 35,100
  • 27
  • 72
  • 83
7

It is possible to do this by overwriting the Environment Table within a specified process itself.

As a proof of concept I wrote this sample app, which just edited a single (known) environment variable in a cmd.exe process:

typedef DWORD (__stdcall *NtQueryInformationProcessPtr)(HANDLE, DWORD, PVOID, ULONG, PULONG);

int __cdecl main(int argc, char* argv[])
{
    HMODULE hNtDll = GetModuleHandleA("ntdll.dll");
    NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hNtDll, "NtQueryInformationProcess");

    int processId = atoi(argv[1]);
    printf("Target PID: %u\n", processId);

    // open the process with read+write access
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, 0, processId);
    if(hProcess == NULL)
    {
        printf("Error opening process (%u)\n", GetLastError());
        return 0;
    }

    // find the location of the PEB
    PROCESS_BASIC_INFORMATION pbi = {0};
    NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
    if(status != 0)
    {
        printf("Error ProcessBasicInformation (0x%8X)\n", status);
    }
    printf("PEB: %p\n", pbi.PebBaseAddress);

    // find the process parameters
    char *processParamsOffset = (char*)pbi.PebBaseAddress + 0x20; // hard coded offset for x64 apps
    char *processParameters = NULL;
    if(ReadProcessMemory(hProcess, processParamsOffset, &processParameters, sizeof(processParameters), NULL))
    {
        printf("UserProcessParameters: %p\n", processParameters);
    }
    else
    {
        printf("Error ReadProcessMemory (%u)\n", GetLastError());
    }

    // find the address to the environment table
    char *environmentOffset = processParameters + 0x80; // hard coded offset for x64 apps
    char *environment = NULL;
    ReadProcessMemory(hProcess, environmentOffset, &environment, sizeof(environment), NULL);
    printf("environment: %p\n", environment);

    // copy the environment table into our own memory for scanning
    wchar_t *localEnvBlock = new wchar_t[64*1024];
    ReadProcessMemory(hProcess, environment, localEnvBlock, sizeof(wchar_t)*64*1024, NULL);

    // find the variable to edit
    wchar_t *found = NULL;
    wchar_t *varOffset = localEnvBlock;
    while(varOffset < localEnvBlock + 64*1024)
    {
        if(varOffset[0] == '\0')
        {
            // we reached the end
            break;
        }
        if(wcsncmp(varOffset, L"ENVTEST=", 8) == 0)
        {
            found = varOffset;
            break;
        }
        varOffset += wcslen(varOffset)+1;
    }

    // check to see if we found one
    if(found)
    {
        size_t offset = (found - localEnvBlock) * sizeof(wchar_t);
        printf("Offset: %Iu\n", offset);

        // write a new version (if the size of the value changes then we have to rewrite the entire block)
        if(!WriteProcessMemory(hProcess, environment + offset, L"ENVTEST=def", 12*sizeof(wchar_t), NULL))
        {
            printf("Error WriteProcessMemory (%u)\n", GetLastError());
        }
    }

    // cleanup
    delete[] localEnvBlock;
    CloseHandle(hProcess);

    return 0;
}

Sample output:

>set ENVTEST=abc

>cppTest.exe 13796
Target PID: 13796
PEB: 000007FFFFFD3000
UserProcessParameters: 00000000004B2F30
environment: 000000000052E700
Offset: 1528

>set ENVTEST
ENVTEST=def

Notes

This approach would also be limited to security restrictions. If the target is run at higher elevation or a higher account (such as SYSTEM) then we wouldn't have permission to edit its memory.

If you wanted to do this to a 32-bit app, the hard coded offsets above would change to 0x10 and 0x48 respectively. These offsets can be found by dumping out the _PEB and _RTL_USER_PROCESS_PARAMETERS structs in a debugger (e.g. in WinDbg dt _PEB and dt _RTL_USER_PROCESS_PARAMETERS)

To change the proof-of-concept into a what the OP needs, it would just enumerate the current system and user environment variables (such as documented by @tsadok's answer) and write the entire environment table into the target process' memory.

Edit: The size of the environment block is also stored in the _RTL_USER_PROCESS_PARAMETERS struct, but the memory is allocated on the process' heap. So from an external process we wouldn't have the ability to resize it and make it larger. I played around with using VirtualAllocEx to allocate additional memory in the target process for the environment storage, and was able to set and read an entirely new table. Unfortunately any attempt to modify the environment from normal means will crash and burn as the address no longer points to the heap (it will crash in RtlSizeHeap).

josh poley
  • 6,546
  • 1
  • 20
  • 24
6

Try opening a new command prompt as an administrator. This worked for me on Windows 10. (I know this is an old answer, but I had to share this because having to write a VBS script just for this is absurd).

estebro
  • 852
  • 9
  • 19
6

Environment variables are kept in HKEY_LOCAL_MACHINE\SYSTEM\ControlSet\Control\Session Manager\Environment.

Many of the useful env vars, such as Path, are stored as REG_SZ. There are several ways to access the registry including REGEDIT:

REGEDIT /E &lt;filename&gt; "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment"

The output starts with magic numbers. So to search it with the find command it needs to be typed and redirected: type <filename> | findstr -c:\"Path\"

So, if you just want to refresh the path variable in your current command session with what's in system properties the following batch script works fine:

RefreshPath.cmd:


    @echo off

    REM This solution requests elevation in order to read from the registry.

    if exist %temp%\env.reg del %temp%\env.reg /q /f

    REGEDIT /E %temp%\env.reg "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment"

    if not exist %temp%\env.reg (
       echo "Unable to write registry to temp location"
       exit 1
       )

    SETLOCAL EnableDelayedExpansion

    for /f "tokens=1,2* delims==" %%i in ('type %temp%\env.reg ^| findstr -c:\"Path\"=') do (
       set upath=%%~j
       echo !upath:\\=\! >%temp%\newpath
       )

     ENDLOCAL

     for /f "tokens=*" %%i in (%temp%\newpath) do set path=%%i
Algonaut
  • 199
  • 3
  • 6
  • 5
    Environment variables are **not** kept in the registry. What is kept in the registry is a _template_, from which programs like Windows Explorer (re-)construct their environment variables [when notified to do so](http://serverfault.com/questions/240570/windows-environment-variables-and-manual-registry-editing-no-wrong-values-issue/241012#241012). Actual environment variables are per-process and are stored in each process' own address space, initially inherited from its parent process and modifiable thereafter at the process' whim. – JdeBP Mar 09 '11 at 00:20
5

Restarting explorer did this for me, but only for new cmd terminals.

The terminal I set the path could see the new Path variable already (in Windows 7).

taskkill /f /im explorer.exe && explorer.exe
Moses Davidowitz
  • 958
  • 11
  • 24
Vince
  • 71
  • 1
  • 1
5

The confusing thing might be that there are a few places to start the cmd from. In my case I ran cmd from windows explorer and the environment variables did not change while when starting cmd from the "run" (windows key + r) the environment variables were changed.

In my case I just had to kill the windows explorer process from the task manager and then restart it again from the task manager.

Once I did this I had access to the new environment variable from a cmd that was spawned from windows explorer.

Daniel Fensterheim
  • 321
  • 1
  • 3
  • 8
3

I use the following code in my batch scripts:

if not defined MY_ENV_VAR (
    setx MY_ENV_VAR "VALUE" > nul
    set MY_ENV_VAR=VALUE
)
echo %MY_ENV_VAR%

By using SET after SETX it is possible to use the "local" variable directly without restarting the command window. And on the next run, the enviroment variable will be used.

Sebastian
  • 196
  • 2
  • 10
  • 1
    While I get what you've done, most likely he wants something for parallel scripts, one script sets globals while another reads them. Otherwise, there's no point in involving setx, set would be sufficient. – JasonXA May 26 '17 at 18:09
3

I liked the approach followed by chocolatey, as posted in anonymous coward's answer, since it is a pure batch approach. However, it leaves a temporary file and some temporary variables lying around. I made a cleaner version for myself.

Make a file refreshEnv.bat somewhere on your PATH. Refresh your console environment by executing refreshEnv.

@ECHO OFF
REM Source found on https://github.com/DieterDePaepe/windows-scripts
REM Please share any improvements made!

REM Code inspired by http://stackoverflow.com/questions/171588/is-there-a-command-to-refresh-environment-variables-from-the-command-prompt-in-w

IF [%1]==[/?] GOTO :help
IF [%1]==[/help] GOTO :help
IF [%1]==[--help] GOTO :help
IF [%1]==[] GOTO :main

ECHO Unknown command: %1
EXIT /b 1 

:help
ECHO Refresh the environment variables in the console.
ECHO.
ECHO   refreshEnv       Refresh all environment variables.
ECHO   refreshEnv /?        Display this help.
GOTO :EOF

:main
REM Because the environment variables may refer to other variables, we need a 2-step approach.
REM One option is to use delayed variable evaluation, but this forces use of SETLOCAL and
REM may pose problems for files with an '!' in the name.
REM The option used here is to create a temporary batch file that will define all the variables.

REM Check to make sure we don't overwrite an actual file.
IF EXIST %TEMP%\__refreshEnvironment.bat (
  ECHO Environment refresh failed!
  ECHO.
  ECHO This script uses a temporary file "%TEMP%\__refreshEnvironment.bat", which already exists. The script was aborted in order to prevent accidental data loss. Delete this file to enable this script.
  EXIT /b 1
)

REM Read the system environment variables from the registry.
FOR /F "usebackq tokens=1,2,* skip=2" %%I IN (`REG QUERY "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"`) DO (
  REM /I -> ignore casing, since PATH may also be called Path
  IF /I NOT [%%I]==[PATH] (
    ECHO SET %%I=%%K>>%TEMP%\__refreshEnvironment.bat
  )
)

REM Read the user environment variables from the registry.
FOR /F "usebackq tokens=1,2,* skip=2" %%I IN (`REG QUERY HKCU\Environment`) DO (
  REM /I -> ignore casing, since PATH may also be called Path
  IF /I NOT [%%I]==[PATH] (
    ECHO SET %%I=%%K>>%TEMP%\__refreshEnvironment.bat
  )
)

REM PATH is a special variable: it is automatically merged based on the values in the
REM system and user variables.
REM Read the PATH variable from the system and user environment variables.
FOR /F "usebackq tokens=1,2,* skip=2" %%I IN (`REG QUERY "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PATH`) DO (
  ECHO SET PATH=%%K>>%TEMP%\__refreshEnvironment.bat
)
FOR /F "usebackq tokens=1,2,* skip=2" %%I IN (`REG QUERY HKCU\Environment /v PATH`) DO (
  ECHO SET PATH=%%PATH%%;%%K>>%TEMP%\__refreshEnvironment.bat
)

REM Load the variable definitions from our temporary file.
CALL %TEMP%\__refreshEnvironment.bat

REM Clean up after ourselves.
DEL /Q %TEMP%\__refreshEnvironment.bat

ECHO Environment successfully refreshed.
DieterDP
  • 3,076
  • 2
  • 22
  • 32
  • is this also for %CLIENTNAME%? - did not work for me - http://stackoverflow.com/questions/37550160/how-to-refresh-environment-variables-using-nodejs – Igor L. May 31 '16 at 16:11
  • %CLIENTNAME% isn't available in my environment, and by reading your question, I'm gonna assume it's something set by an external proces. (When a process starts a childprocess, it's able to adjust the environment for that child.) Since it's not part of the actual environment variables, it won't be updated by this script. – DieterDP Jun 04 '16 at 14:04
  • Hi @DieterDP , your solution works for me! I'm using Windows 10 on a 64-bit machine. I do get an error: "ERROR: The system was unable to find the specified registry key or value.". Nevertheless, the update of the environment variables is successful. Where does the error come from? – K.Mulier Jul 23 '16 at 19:14
  • Hard to say without actually testing it myself, but I'm guessing the registry structure on W10 may slightly differ. If you feel like it, try hunting the error down by executing the commands on the command line. – DieterDP Jul 27 '16 at 14:33
3

The solution I've been using for a few years now:

@echo off
rem Refresh PATH from registry.
setlocal
set USR_PATH=
set SYS_PATH=
for /F "tokens=3* skip=2" %%P in ('%SystemRoot%\system32\reg.exe query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PATH') do @set "SYS_PATH=%%P %%Q"
for /F "tokens=3* skip=2" %%P in ('%SystemRoot%\system32\reg.exe query "HKCU\Environment" /v PATH') do @set "USR_PATH=%%P %%Q"
if "%SYS_PATH:~-1%"==" " set "SYS_PATH=%SYS_PATH:~0,-1%"
if "%USR_PATH:~-1%"==" " set "USR_PATH=%USR_PATH:~0,-1%"
endlocal & call set "PATH=%SYS_PATH%;%USR_PATH%"
goto :EOF

Edit: Woops, here's the updated version.

Charles Grunwald
  • 1,348
  • 16
  • 21
  • I like your answer. Post the same answer to my question here https://stackoverflow.com/q/61473551/1082063 and I will accept it as _the_ answer. Thanks. – David I. McIntosh Apr 28 '20 at 13:56
2

If it concerns just one (or a few) specific vars you want to change, I think the easiest way is a workaround: just set in in your environment AND in your current console session

  • Set will put the var in your current session
  • SetX will put the var in the environment, but NOT in your current session

I have this simple batch script to change my Maven from Java7 to Java8 (which are both env. vars) The batch-folder is in my PATH var so I can always call 'j8' and within my console and in the environment my JAVA_HOME var gets changed:

j8.bat:

@echo off
set JAVA_HOME=%JAVA_HOME_8%
setx JAVA_HOME "%JAVA_HOME_8%"

Till now I find this working best and easiest. You probably want this to be in one command, but it simply isn't there in Windows...

2

Thank you for posting this question which is quite interesting, even in 2019 (Indeed, it is not easy to renew the shell cmd since it is a single instance as mentioned above), because renewing environment variables in windows allows to accomplish many automation tasks without having to manually restart the command line.

For example, we use this to allow software to be deployed and configured on a large number of machines that we reinstall regularly. And I must admit that having to restart the command line during the deployment of our software would be very impractical and would require us to find workarounds that are not necessarily pleasant. Let's get to our problem. We proceed as follows.

1 - We have a batch script that in turn calls a powershell script like this

[file: task.cmd].

cmd > powershell.exe -executionpolicy unrestricted -File C:\path_here\refresh.ps1

2 - After this, the refresh.ps1 script renews the environment variables using registry keys (GetValueNames(), etc.). Then, in the same powershell script, we just have to call the new environment variables available. For example, in a typical case, if we have just installed nodeJS before with cmd using silent commands, after the function has been called, we can directly call npm to install, in the same session, particular packages like follows.

[file: refresh.ps1]

function Update-Environment {
    $locations = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session  Manager\Environment',
                 'HKCU:\Environment'
    $locations | ForEach-Object {
        $k = Get-Item $_
        $k.GetValueNames() | ForEach-Object {
            $name  = $_
            $value = $k.GetValue($_)

            if ($userLocation -and $name -ieq 'PATH') {
                $env:Path += ";$value"
            } else {

                Set-Item -Path Env:\$name -Value $value
            }
        }
        $userLocation = $true
    }
}
Update-Environment
#Here we can use newly added environment variables like for example npm install.. 
npm install -g create-react-app serve

Once the powershell script is over, the cmd script goes on with other tasks. Now, one thing to keep in mind is that after the task is completed, cmd has still no access to the new environment variables, even if the powershell script has updated those in its own session. Thats why we do all the needed tasks in the powershell script which can call the same commands as cmd of course.

Andy McRae
  • 163
  • 9
1

There is no straight way, as Kev said. In most cases, it is simpler to spawn another CMD box. More annoyingly, running programs are not aware of changes either (although IIRC there might be a broadcast message to watch to be notified of such change).

It have been worse: in older versions of Windows, you had to log off then log back to take in account the changes...

PhiLho
  • 38,673
  • 6
  • 89
  • 128
1

I use this Powershell script to add to the PATH variable. With a little adjustment it can work in your case too I believe.

#REQUIRES -Version 3.0

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);
"@
}

$HWND_BROADCAST = [intptr]0xffff;
$WM_SETTINGCHANGE = 0x1a;
$result = [uintptr]::zero

function global:ADD-PATH
{
    [Cmdletbinding()]
    param ( 
        [parameter(Mandatory=$True, ValueFromPipeline=$True, Position=0)] 
        [string] $Folder
    )

    # See if a folder variable has been supplied.
    if (!$Folder -or $Folder -eq "" -or $Folder -eq $null) { 
        throw 'No Folder Supplied. $ENV:PATH Unchanged'
    }

    # Get the current search path from the environment keys in the registry.
    $oldPath=$(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path

    # See if the new Folder is already in the path.
    if ($oldPath | Select-String -SimpleMatch $Folder){ 
        return 'Folder already within $ENV:PATH' 
    }

    # Set the New Path and add the ; in front
    $newPath=$oldPath+';'+$Folder
    Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $newPath -ErrorAction Stop

    # Show our results back to the world
    return 'This is the new PATH content: '+$newPath

    # notify all windows of environment block change
    [win32.nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [uintptr]::Zero, "Environment", 2, 5000, [ref]$result)
}

function global:REMOVE-PATH {
    [Cmdletbinding()]
    param ( 
        [parameter(Mandatory=$True, ValueFromPipeline=$True, Position=0)]
        [String] $Folder
    )

    # See if a folder variable has been supplied.
    if (!$Folder -or $Folder -eq "" -or $Folder -eq $NULL) { 
        throw 'No Folder Supplied. $ENV:PATH Unchanged'
    }

    # add a leading ";" if missing
    if ($Folder[0] -ne ";") {
        $Folder = ";" + $Folder;
    }

    # Get the Current Search Path from the environment keys in the registry
    $newPath=$(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path

    # Find the value to remove, replace it with $NULL. If it's not found, nothing will change and you get a message.
    if ($newPath -match [regex]::Escape($Folder)) { 
        $newPath=$newPath -replace [regex]::Escape($Folder),$NULL 
    } else { 
        return "The folder you mentioned does not exist in the PATH environment" 
    }

    # Update the Environment Path
    Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $newPath -ErrorAction Stop

    # Show what we just did
    return 'This is the new PATH content: '+$newPath

    # notify all windows of environment block change
    [win32.nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [uintptr]::Zero, "Environment", 2, 5000, [ref]$result)
}


# Use ADD-PATH or REMOVE-PATH accordingly.

#Anything to Add?

#Anything to Remove?

REMOVE-PATH "%_installpath_bin%"
iulian
  • 1,168
  • 1
  • 15
  • 21
0

Edit: this only works if the environment changes you're doing are as a result of running a batch file.

If a batch file begins with SETLOCAL then it will always unravel back to your original environment on exit even if you forget to call ENDLOCAL before the batch exits, or if it aborts unexpectedly.

Almost every batch file I write begins with SETLOCAL since in most cases I don't want the side-effects of environment changes to remain. In cases where I do want certain environment variable changes to propagate outside the batch file then my last ENDLOCAL looks like this:

ENDLOCAL & (
  SET RESULT1=%RESULT1%
  SET RESULT2=%RESULT2%
)
wardies
  • 1,011
  • 9
  • 13
-1

To solve this I have changed the environment variable using BOTH setx and set, and then restarted all instances of explorer.exe. This way any process subsequently started will have the new environment variable.

My batch script to do this:

setx /M ENVVAR "NEWVALUE"
set ENVVAR="NEWVALUE"

taskkill /f /IM explorer.exe
start explorer.exe >nul
exit

The problem with this approach is that all explorer windows that are currently opened will be closed, which is probably a bad idea - But see the post by Kev to learn why this is necessary