Czy w systemie Windows istnieje polecenie odświeżenia zmiennych środowiskowych z wiersza polecenia?

480

Jeśli zmodyfikuję lub dodam zmienną środowiskową, muszę ponownie uruchomić wiersz polecenia. Czy istnieje polecenie, które można wykonać, aby zrobić to bez ponownego uruchamiania CMD?

Eric Schoonover
źródło
38
W rzeczywistości każdy program, który musi je zobaczyć, musi zostać zrestartowany. Środowisko jest kopiowane do pamięci procesu podczas uruchamiania i dlatego nie ma już żadnego połączenia ze zdefiniowanymi przez system środowiskami.
Joey
15
po ich przeczytaniu zdałem sobie sprawę, że nie ma łyżki ;) w prawdziwym świecie po prostu restartujesz cmd.
n611x007
Nie jest to polecenie, więc nie do końca odpowiedź, ale istnieje wsparcie dla niego przy użyciu Win32 API, jeśli poprawnie przeczytam następujące informacje: support.microsoft.com/en-us/help/104011/... Powinien być w stanie skompilować tę linię do prosty program C i uruchom go po aktualizacjach zmiennych środowiskowych.
Charles Grunwald
WM_SETTINGCHANGE (interfejs API systemu Windows32 wspomniany przez @CharlesGrunwald) nie działa dla okien cmd.exe według tego wątku: github.com/chocolatey/choco/issues/1589 - to jest powód, dla którego napisali polecenie odświeżania
davr

Odpowiedzi:

137

Możesz przechwycić systemowe zmienne środowiskowe za pomocą skryptu VBS, ale potrzebujesz skryptu nietoperza, aby faktycznie zmienić bieżące zmienne środowiskowe, więc jest to rozwiązanie łączone.

Utwórz plik o nazwie resetvars.vbszawierającej ten kod i zapisz go na ścieżce:

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

utwórz inną nazwę pliku resetvars.bat zawierającą ten kod, tę samą lokalizację:

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

Jeśli chcesz odświeżyć zmienne środowiskowe, po prostu uruchom resetvars.bat


Apologetyka :

Dwa główne problemy, jakie wpadłem na to rozwiązanie, to:

za. Nie mogłem znaleźć prostego sposobu na eksport zmiennych środowiskowych ze skryptu vbs z powrotem do wiersza poleceń i

b. zmienna środowiskowa PATH jest konkatenacją użytkownika i systemowych zmiennych PATH.

Nie jestem pewien, jaka jest ogólna reguła dla sprzecznych zmiennych między użytkownikiem a systemem, dlatego zdecydowałem się na zastąpienie systemu przez użytkownika, z wyjątkiem zmiennej PATH, która jest obsługiwana konkretnie.

Używam dziwnego mechanizmu VBS + nietoperz + tymczasowy nietoperz, aby obejść problem eksportowania zmiennych z VBS.

Uwaga : ten skrypt nie usuwa zmiennych.

Można to prawdopodobnie poprawić.

DODANY

Jeśli chcesz wyeksportować środowisko z jednego okna cmd do drugiego, użyj tego skryptu (nazwijmy go 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

Uruchom exportvars.vbsw oknie chcesz wyeksportować z , a następnie przejść do okna, które chcesz wyeksportować do i wpisz:

"%TEMP%\resetvars.bat"
itsadok
źródło
2
Być może możesz uniknąć pliku tymczasowego, używając tokenów FOR / F = 1, * "%% c IN ('resetvars.vbs') DO skonstruuj
tzot
2
Tak jak powiedziałem w mojej odpowiedzi „lub dodaj ręcznie za pomocą SET w istniejącym wierszu polecenia”. co to skutecznie robi. Dobra odpowiedź.
Kev
2
@itsadok - biorąc pod uwagę, że jest to obecnie akceptowana odpowiedź, należy dodać krótkie wyjaśnienie na początku, aby umieścić skrypt w kontekście. tzn. zwróć uwagę, że nie jest możliwe propagowanie zmiany env var do otwartego cmd.exe bez ręcznej aktualizacji jak wyżej lub przez ponowne uruchomienie cmd.exe.
Kev
Skrypt obsługuje przypadek użycia globalnej zmiany zmiennych środowiskowych w „Mój komputer ... Zmienne środowiskowe”, ale jeśli zmienna środowiskowa zostanie zmieniona w jednym cmd.exe, skrypt nie propaguje jej do innego działającego cmd.exe, który moim zdaniem jest prawdopodobnie powszechny scenariusz.
Kev
1
@Keyslinger: W rzeczywistości nie jest to możliwe. Każdy spawnowany program może aktualizować swoje własne środowisko, ale nie działającego wystąpienia cmd.exe. Plik wsadowy MOŻE zaktualizować środowisko cmd.exe, ponieważ działa w tej samej instancji cmd.exe.
Ben Voigt
112

Oto, czego używa Chocolatey.

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 | set /p dummy="Reading environment variables from registry. 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

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

    echo | set /p dummy="Done"
    echo .
anonimowy tchórz
źródło
65
+1 Jeśli masz zainstalowany program Chocolatey, możesz po prostu uruchomić, RefreshEnvaby zaktualizować zmienne środowiskowe do bieżącej sesji.
Martin Valgur,
2
To niezwykle użyteczne narzędzie, dzięki za udostępnienie.
Sabuncu
10
Uwaga: Chocolatey przeniosła repozytorium, a najnowszą wersję tego skryptu można znaleźć tutaj (z kilkoma poprawkami błędów): github.com/chocolatey/choco/blob/master/src/…
Michael Burr
1
czy to też powinno działać Powershell? Wydaje się, że działa tylko cmd.exedla mnie.
craq
1
Działa dla mnie w PowerShell, @craq. Z systemem Windows 10 x64.
mazunki
100

W systemie Windows 7/8/10 możesz zainstalować Chocolatey, który ma skrypt dla tego wbudowanego.

Po zainstalowaniu Chocolatey po prostu wpisz refreshenv.

wesoły
źródło
to jest prawidłowa odpowiedź, miło byłoby usłyszeć od faceta, który głosował na nią
the_joric
2
Co ja robię źle? $> refreshenv „refreshenv” nie jest rozpoznawany jako wewnętrzna lub zewnętrzna komenda, program operacyjny lub plik wsadowy.
aclokay
@aclokay Nie jestem pewien. Podaj więcej szczegółów na temat deb systemu conf. W międzyczasie możesz odnieść się do podobnego otwartego problemu tutaj. github.com/chocolatey/choco/issues/250
Jolly
Dla mnie to też nie działa. Jestem na W7 Professional, może działa tylko w pełniejszych wersjach.
Alseether
1
Jeśli skrypt odświeżania przedwcześnie istnieje (tak jak dla mnie), możesz zamiast tego użyć polecenia „call RefreshEnv.cmd”. (patrz github.com/chocolatey/choco/issues/1461 )
sfiss
59

Z założenia nie ma wbudowanego mechanizmu dla systemu Windows do propagowania dodawania / zmiany / usuwania zmiennej środowiskowej do już uruchomionego cmd.exe, z innego cmd.exe lub z „Mój komputer -> Właściwości -> Ustawienia zaawansowane -> Zmienne środowiska".

Jeśli zmodyfikujesz lub dodasz nową zmienną środowiskową poza zakresem istniejącego otwartego wiersza polecenia, musisz albo zrestartować wiersz polecenia, albo ręcznie dodać za pomocą SET w istniejącym wierszu polecenia.

Te ostatnie Zaakceptowanych odpowiedź przedstawia częściowy obejść poprzez ręczne odświeżanie wszystkie zmienne środowiskowe w skrypcie. Skrypt obsługuje przypadek użycia globalnej zmiany zmiennych środowiskowych w „Mój komputer ... Zmienne środowiskowe”, ale jeśli zmienna środowiskowa zostanie zmieniona w jednym cmd.exe, skrypt nie propaguje jej do innego działającego cmd.exe.

Kev
źródło
Jeśli ktoś ma działające rozwiązanie tego problemu, będę okresowo sprawdzać i mogę zmienić przyjętą odpowiedź.
Eric Schoonover,
1
To nie powinna być zaakceptowana odpowiedź po prostu dlatego, że nie odpowiada na zadane pytanie. pytanie to powinno pozostać bez zaakceptowanej odpowiedzi, dopóki nie zostanie znalezione.
shoosh,
4
I irytujące, dodatkowe wystąpienia cmd.exe nie liczą się. Oni wszyscy mają być zabity zanim zmiana zostanie odzwierciedlona w jakikolwiek nowy cmd.exe jest.
6
Negatywne komentarze i oznaczenia w dół tej odpowiedzi pokazują, jak czasami zdarza się przepełnienie uszkodzonego stosu. Kev podał prawidłową odpowiedź. Tylko dlatego, że ci się nie podoba, nie ma powodu, aby to zaznaczać.
David Arno,
Kev zdecydowanie odpowiada na pytanie. Pytanie brzmi: nie ma wbudowanego rozwiązania.
Eric Schoonover,
40

Natknąłem się na tę odpowiedź, zanim w końcu znalazłem łatwiejsze rozwiązanie.

Po prostu uruchom ponownie explorer.exew Menedżerze zadań.

Nie testowałem, ale może być konieczne ponowne otwarcie wiersza polecenia.

Podziękowania dla Timo Huovinena tutaj: Węzeł nie został rozpoznany, chociaż został pomyślnie zainstalowany (jeśli to pomogło, proszę podać informację o komentarzu tego człowieka).

wharding28
źródło
Przybyłem tutaj, ponieważ próbowałem dodać zewnętrzne narzędzie do programu Visual Studio, aby móc otworzyć wiersz polecenia w katalogu głównym mojego rozwiązania, jak opisano w tym blogu: neverindoubtnet.blogspot.com/2012/10/ ... ... i Miałem podobne problemy ... Próbowałem zmusić git do pojawienia się w mojej zmiennej ścieżki. Dodałem katalogi git do zmiennej PATH, ale wtedy nie pojawią się one w wierszu poleceń, który otworzę z Visual Studio. Prostym rozwiązaniem było ponowne uruchomienie programu Visual Studio. Następnie nowe dodatki do zmiennej PATH były widoczne w cmd.
David Barrows
4
To rozwiązanie pomaga mi w systemie Windows 10
ganchito55
7
Pytanie brzmiało: „Czy istnieje polecenie, które można wykonać, aby zrobić to bez ponownego uruchamiania CMD ?”
Florian F
Ok z menedżera zadań nie udało mi się ponownie uruchomić explorer.exe, tylko go wykończyć. Zrobiłem to, ale pasek zadań został uszkodzony. Na początek odkrywca; exe to naprawdę proste. „Ctrl + shift + escape” -> plik -> „wykonaj nowe zadanie” -> „explorer.exe” wykonał dla mnie pracę. I tak, po tym jak wszystkie env var zostały użyte w nowym oknie cmd. Dzięki za wszystko
Oscar
Dobre rozwiązanie, dziękuję! Aby rozwinąć i zaadresować komentarz @Oscar: Uruchom cmdokno jako Administrator. Użyj polecenia taskkill /f /im explorer.exe && explorer.exe. To zabije proces explorer.exe i uruchom go ponownie.
S3DEV,
32

Działa to w systemie Windows 7: SET PATH=%PATH%;C:\CmdShortcuts

przetestowane przez wpisanie echa% PATH% i zadziałało, w porządku. ustaw także, jeśli otworzysz nowe cmd, nie ma już potrzeby tych nieznośnych restartów :)

kristofer månsson
źródło
1
Nie działa dla mnie „nowy cmd” (Win7 x64). Zobacz screenvideo
Igor,
26
To nie rozwiązuje zadanego pytania, ani też nie powinno. Pierwotne pytanie brzmi: jak odświeżyć zmienną środowiskową do wartości ustawionej poza tym terminalem.
csauve
Chociaż to nie odpowiada na pytanie, zapewnia połowę najlepszego działającego rozwiązania. Używam tego - dla dowolnej zmiennej, którą ustawiam - następnie otwieram panel sterowania i globalnie dodam zmienną środowiskową. Nie lubię używać, setxponieważ dziedziczy bieżące środowisko, które może mieć zmienione zmienne, a nie to, czego chcę na stałe. Wykonanie tego w ten sposób pozwala mi uniknąć ponownego uruchamiania konsoli w celu użycia zmiennych, unikając jednocześnie problemu braku ich globalnej dostępności w przyszłości.
dgo
25

Użyj „setx” i uruchom ponownie polecenie cmd

Dla tego zadania istnieje narzędzie wiersza polecenia o nazwie „ setx ”. Służy do odczytu i zapisu zmiennych env. Zmienne są zachowywane po zamknięciu okna poleceń.

„Tworzy lub modyfikuje zmienne środowiskowe w środowisku użytkownika lub systemu, bez konieczności programowania lub pisania skryptów. Komenda setx pobiera również wartości kluczy rejestru i zapisuje je w plikach tekstowych”.

Uwaga: zmienne utworzone lub zmodyfikowane przez to narzędzie będą dostępne w przyszłych oknach poleceń, ale nie w bieżącym oknie poleceń CMD.exe. Musisz więc zrestartować.

Jeśli setxbrakuje:


Lub zmodyfikuj rejestr

MSDN mówi:

Aby programowo dodać lub zmodyfikować systemowe zmienne środowiskowe, dodaj je do klucza rejestru HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ Control \ Session Manager \ Environment , a następnie wyślij komunikat WM_SETTINGCHANGE z parametrem lParam ustawionym na ciąg „ Środowisko ”.

Umożliwia to aplikacjom, takim jak powłoka, pobieranie aktualizacji.

Jens A. Koch
źródło
1
Czy mógłbyś rozwinąć sposób używania setx do odczytu zmiennej środowiskowej? Przejrzałem różne dokumenty i po prostu ich nie widzę. : - /
Mark Ribau,
2
setx ZMIENNA -k "HKEY_LOCAL_MACHINE \ Software \ Microsoft \ WindowsNT \ CurrentVersion \ CurrentVersion" echo% ZMIENNE%
Jens A. Koch
3
aktualne środowisko systemowe: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\VARIABLEobecne środowisko użytkownika: HKEY_CURRENT_USER\Environment\VARIABLE
Mark Ribau,
5
setx /? mówi w notatce: „W systemie lokalnym zmienne utworzone lub zmodyfikowane przez to narzędzie będą dostępne w przyszłych oknach poleceń, ale nie w bieżącym oknie poleceń CMD.exe”. OP chciał zaktualizować bieżący cmd.
Superole,
4
Kupujący, strzeż się! Jeśli masz wyjątkowo długi %PATH%czas, setxmożesz go skrócić do 1024 bajtów! I tak po prostu jego wieczór zniknął
2016 FaCE
15

Wywołanie tej funkcji zadziałało dla mnie:

VOID Win32ForceSettingsChange()
{
    DWORD dwReturnValue;
    ::SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) "Environment", SMTO_ABORTIFHUNG, 5000, &dwReturnValue);
}
Brian Weed
źródło
8
i nie wszystkie programy słuchają tej wiadomości (w rzeczywistości większość z nich prawdopodobnie tego nie robi)
Rehan Khwaja
Nie, działa również w przypadku programów innych niż GUI. Jeśli chodzi o programy nasłuchujące ... problem dotyczy zapewnienia, że ​​zrestartowany program otrzyma zaktualizowane środowisko, a to daje ci tyle.
user2023370
11

Najlepszą metodą, jaką wymyśliłem, było wykonanie zapytania do rejestru. Oto mój przykład.

W moim przykładzie wykonałem instalację przy użyciu pliku Batch, który dodał nowe zmienne środowiskowe. Musiałem to zrobić, gdy tylko instalacja się zakończy, ale nie byłem w stanie odrodzić nowego procesu z tymi nowymi zmiennymi. Przetestowałem odradzanie innego okna eksploratora i zadzwoniłem z powrotem do cmd.exe i działało to, ale w Vista i Windows 7 Explorer działa tylko jako jedna instancja i normalnie jako osoba zalogowana. Nie udałoby się to z automatyzacją, ponieważ potrzebuję moich uprawnień administratora, aby rób rzeczy niezależnie od uruchamiania z systemu lokalnego lub jako administrator w pudełku. Ograniczeniem jest to, że nie obsługuje rzeczy takich jak ścieżka, działało to tylko na prostych zmiennych środowiskowych. To pozwoliło mi użyć partii do przejścia do katalogu (ze spacjami) i skopiowania plików do uruchomienia .exes itp. To zostało napisane dzisiaj z zasobów maja na stackoverflow.com

Połączenia z oryginalną partią do nowej partii:

testenvget.cmd SDROOT (lub jakakolwiek zmienna)

@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

Jest też inna metoda, którą wymyśliłem z różnych różnych pomysłów. Patrz poniżej. To w zasadzie pobierze najnowszą zmienną ścieżki z rejestru, jednak spowoduje to szereg problemów, ponieważ zapytanie rejestru poda zmienne samo w sobie, co oznacza, że ​​wszędzie tam, gdzie jest zmienna, to nie zadziała, więc aby zwalczyć ten problem I w zasadzie podwajaj ścieżkę. Bardzo paskudny. Bardziej udoskonaloną metodą byłoby: Ustaw ścieżkę =% ścieżki%; C: \ Program Files \ Software .... \

Bez względu na to jest nowy plik wsadowy, należy zachować ostrożność.

@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
Christopher Holmes
źródło
8

Najłatwiejszym sposobem dodania zmiennej do ścieżki bez ponownego uruchamiania dla bieżącej sesji jest otwarcie wiersza polecenia i wpisanie:

PATH=(VARIABLE);%path%

i naciśnij enter .

aby sprawdzić, czy zmienna została załadowana, wpisz

PATH

i naciśnij enter. Jednak zmienna będzie tylko częścią ścieżki, dopóki nie uruchomisz się ponownie.

Richard Woodruff
źródło
nie działało dla mnie, nie mogłem uzyskać dostępu do plików binarnych w zmiennej path
user2305193
7

Można to zrobić, zastępując tabelę środowiska w ramach określonego procesu.

Jako dowód koncepcji napisałem tę przykładową aplikację, która właśnie edytowała jedną (znaną) zmienną środowiskową w procesie cmd.exe:

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;
}

Przykładowe dane wyjściowe:

>set ENVTEST=abc

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

>set ENVTEST
ENVTEST=def

Notatki

Takie podejście byłoby również ograniczone do ograniczeń bezpieczeństwa. Jeśli cel jest uruchamiany na wyższym poziomie lub na wyższym koncie (takim jak SYSTEM), nie mielibyśmy uprawnień do edycji jego pamięci.

Jeśli chcesz to zrobić w aplikacji 32-bitowej, powyższe przesunięcia kodowane na stałe zmieniłyby się odpowiednio na 0x10 i 0x48. Te przesunięcia można znaleźć, usuwając struktury _PEB i _RTL_USER_PROCESS_PARAMETERS w debuggerze (np. W WinDbg dt _PEBidt _RTL_USER_PROCESS_PARAMETERS )

Aby zmienić proof-of-concept na to, czego potrzebuje OP, wystarczy wyliczyć bieżące zmienne systemowe i środowiskowe użytkownika (takie jak udokumentowane odpowiedzią @ tsadok) i zapisać całą tabelę środowiska w pamięci procesu docelowego.

Edycja: Rozmiar bloku środowiska jest również przechowywany w strukturze _RTL_USER_PROCESS_PARAMETERS, ale pamięć jest przydzielana na stercie procesu. Zatem z procesu zewnętrznego nie mielibyśmy możliwości zmiany jego rozmiaru i powiększenia. Bawiłem się przy użyciu VirtualAllocEx do przydzielenia dodatkowej pamięci w procesie docelowym do przechowywania środowiska, i byłem w stanie ustawić i odczytać zupełnie nową tabelę. Niestety każda próba modyfikacji środowiska z normalnych środków ulegnie awarii i zostanie spalona, ​​ponieważ adres nie będzie już wskazywał na stos (spowoduje awarię w RtlSizeHeap).

josh poley
źródło
6

Zmienne środowiskowe są przechowywane w HKEY_LOCAL_MACHINE \ SYSTEM \ ControlSet \ Control \ Session Manager \ Environment.

Wiele przydatnych zmiennych środowisk, takich jak Ścieżka, jest przechowywanych jako REG_SZ. Istnieje kilka sposobów dostępu do rejestru, w tym REGEDIT:

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

Wyjście zaczyna się od magicznych liczb. Aby go wyszukać za pomocą polecenia find, należy wpisać i przekierować:type <filename> | findstr -c:\"Path\"

Jeśli więc chcesz odświeżyć zmienną ścieżki w bieżącej sesji poleceń za pomocą właściwości systemowych, następujący skrypt wsadowy działa dobrze:

RefreshPath.cmd:

    @echo wyłączone

    REM To rozwiązanie wymaga podniesienia uprawnień w celu odczytania z rejestru.

    jeśli istnieją% temp% \ env.reg del% temp% \ env.reg / q / f

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

    jeśli nie istnieje% temp% \ env.reg (
       echo „Nie można zapisać rejestru do lokalizacji tymczasowej”
       wyjście 1
       )

    SETLOCAL EnableDelayedExpansion

    dla / f "tokenów = 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) ustaw ścieżkę = %% i
Algonaut
źródło
5
Zmienne środowiskowe nie są przechowywane w rejestrze. W rejestrze przechowywany jest szablon , na podstawie którego programy takie jak Eksplorator Windows (ponownie) konstruują swoje zmienne środowiskowe, gdy zostaną o tym powiadomione . Rzeczywiste zmienne środowiskowe są przypisane do poszczególnych procesów i są przechowywane w „przestrzeni adresowej” każdego procesu, początkowo dziedziczonej po procesie macierzystym, a następnie modyfikowalne według kaprysu procesu.
JdeBP,
5

Spróbuj otworzyć nowy wiersz polecenia jako administrator. Działa to dla mnie w systemie Windows 10. (Wiem, że to stara odpowiedź, ale musiałem się nią podzielić, ponieważ pisanie skryptu VBS tylko w tym celu jest absurdalne).

estebro
źródło
5

Ponowne uruchomienie eksploratora zrobiło to dla mnie, ale tylko dla nowych terminali cmd.

Terminal, dla którego ustawiłem ścieżkę, mógł już zobaczyć nową zmienną Path (w Windows 7).

taskkill /f /im explorer.exe && explorer.exe
Vince
źródło
5

Mylące może być to, że istnieje kilka miejsc, od których można zacząć cmd. W moim przypadku uruchomiłem cmd z Eksploratora Windows i zmienne środowiskowe nie uległy zmianie, a podczas uruchamiania cmd z „uruchom” (klawisz Windows + r) zmienne środowiskowe zostały zmienione .

W moim przypadku musiałem po prostu zabić proces eksploratora Windows z menedżera zadań, a następnie ponownie uruchomić go z menedżera zadań .

Kiedy to zrobiłem, miałem dostęp do nowej zmiennej środowiskowej z cmd, która została spawnowana z Eksploratora Windows.

Daniel Fensterheim
źródło
3

Używam następującego kodu w skryptach wsadowych:

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

Używając SET po SETX można bezpośrednio używać zmiennej „lokalnej” bez ponownego uruchamiania okna poleceń. A przy następnym uruchomieniu zostanie użyta zmienna środowiskowa.

Sebastian
źródło
Chociaż rozumiem, co zrobiłeś, najprawdopodobniej chce czegoś do skryptów równoległych, jeden skrypt ustawia globały, a inny je odczytuje. W przeciwnym razie nie ma sensu angażować setx, set byłby wystarczający.
JasonXA
3

Podobało mi się podejście czekoladowe, jak podano w odpowiedzi anonimowego tchórza, ponieważ jest to podejście czysto wsadowe. Pozostawia jednak plik tymczasowy i niektóre zmienne tymczasowe. Zrobiłem dla siebie czystszą wersję.

Zrób plik refreshEnv.batgdzieś na swoim PATH. Odśwież środowisko konsoli, wykonując 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
źródło
czy dotyczy to również% CLIENTNAME%? - nie działało dla mnie - stackoverflow.com/questions/37550160/…
Igor L.
% CLIENTNAME% nie jest dostępny w moim środowisku i czytając twoje pytanie, założę, że jest to coś ustalonego przez zewnętrzny proces. (Gdy proces uruchamia proces potomny, jest w stanie dostosować środowisko dla tego potomka.) Ponieważ nie jest częścią rzeczywistych zmiennych środowiskowych, ten skrypt nie będzie go aktualizował.
DieterDP
Cześć @DieterDP, twoje rozwiązanie działa dla mnie! Używam systemu Windows 10 na komputerze 64-bitowym. Pojawia się błąd: „BŁĄD: System nie mógł znaleźć określonego klucza rejestru lub wartości.”. Niemniej jednak aktualizacja zmiennych środowiskowych zakończyła się powodzeniem. Skąd pochodzi błąd?
K.Mulier
Trudno powiedzieć bez testowania go osobiście, ale domyślam się, że struktura rejestru na W10 może się nieco różnić. Jeśli masz na to ochotę, spróbuj wyśledzić błąd, wykonując polecenia z wiersza poleceń.
DieterDP
2

Jeśli dotyczy tylko jednego (lub kilku) konkretnych zmiennych, które chcesz zmienić, myślę, że najłatwiejszym sposobem jest obejście tego problemu : po prostu ustaw w swoim środowisku ORAZ w bieżącej sesji konsoli

  • Set umieści var w bieżącej sesji
  • SetX umieści var w środowisku, ale NIE w bieżącej sesji

Mam ten prosty skrypt wsadowy, aby zmienić mój Maven z Java7 na Java8 (które są oba w środowisku zmiennym). Folder wsadowy znajduje się w mojej zmiennej PATH, więc zawsze mogę wywołać „ j8 ” oraz w konsoli i w środowisku mój JAVA_HOME var ulega zmianie:

j8.bat:

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

Do tej pory uważam, że to działa najlepiej i najłatwiej. Prawdopodobnie chcesz, aby było to w jednym poleceniu, ale po prostu nie ma go w systemie Windows ...

Jeroen van Dijk-Jun
źródło
2

Rozwiązanie, z którego korzystam od kilku lat:

@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

Edycja: Woops, oto zaktualizowana wersja.

Charles Grunwald
źródło
Podoba mi się twoja odpowiedź. Pisać tę samą odpowiedź na moje pytanie tutaj stackoverflow.com/q/61473551/1082063 a ja przyjmuję to jako na odpowiedź. Dzięki.
David I. McIntosh
1

Nie ma prostej drogi, jak powiedział Kev. W większości przypadków łatwiej jest odrodzić kolejne pole CMD. Co bardziej denerwujące, uruchomione programy również nie są świadome zmian (chociaż IIRC może mieć komunikat do wysłania, aby powiadomić o takiej zmianie).

Gorzej: w starszych wersjach systemu Windows trzeba było się wylogować, a następnie zalogować ponownie, aby uwzględnić zmiany ...

PhiLho
źródło
1

Używam tego skryptu Powershell, aby dodać do zmiennej PATH . Uważam, że przy niewielkiej korekcie może również działać w twoim przypadku.

#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 Dita
źródło
1

Dziękujemy za opublikowanie tego pytania, które jest dość interesujące, nawet w 2019 r. (Rzeczywiście, odnowienie powłoki cmd nie jest łatwe, ponieważ jest to pojedyncze wystąpienie, jak wspomniano powyżej), ponieważ odnawianie zmiennych środowiskowych w systemie Windows pozwala na wykonanie wielu zadań automatyzacji bez konieczność ręcznego ponownego uruchomienia wiersza polecenia.

Na przykład używamy tego, aby umożliwić wdrażanie i konfigurowanie oprogramowania na dużej liczbie komputerów, które regularnie instalujemy. I muszę przyznać, że ponowne uruchomienie wiersza poleceń podczas wdrażania naszego oprogramowania byłoby bardzo niepraktyczne i wymagałoby od nas znalezienia obejść, które niekoniecznie są przyjemne. Przejdźmy do naszego problemu. Postępujemy w następujący sposób.

1 - Mamy skrypt wsadowy, który z kolei wywołuje taki skrypt PowerShell

[plik: zadanie.cmd] .

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

2 - Następnie skrypt refresh.ps1 odnawia zmienne środowiskowe za pomocą kluczy rejestru (GetValueNames () itp.). Następnie, w tym samym skrypcie PowerShell, musimy wywołać nowe dostępne zmienne środowiskowe. Na przykład, w typowym przypadku, jeśli przed chwilą zainstalowaliśmy nodeJS z cmd przy użyciu cichych poleceń, po wywołaniu funkcji możemy bezpośrednio wywołać npm w celu zainstalowania, w tej samej sesji, określonych pakietów, takich jak poniżej.

[plik: 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

Po zakończeniu skryptu PowerScript skrypt cmd wykonuje inne zadania. Teraz należy pamiętać, że po zakończeniu zadania cmd nadal nie ma dostępu do nowych zmiennych środowiskowych, nawet jeśli skrypt PowerShell zaktualizował je w swojej sesji. Dlatego wykonujemy wszystkie potrzebne zadania w skrypcie PowerShell, który może wywoływać te same polecenia co cmd.

Andy McRae
źródło
0

Edycja: działa to tylko wtedy, gdy zmiany środowiska, które wprowadzasz, są wynikiem uruchomienia pliku wsadowego.

Jeśli plik wsadowy zaczyna się od, SETLOCALto zawsze wyjdzie z powrotem do oryginalnego środowiska przy wyjściu, nawet jeśli zapomnisz zadzwonićENDLOCAL przed wyjściem z partii lub jeśli niespodziewanie zostanie przerwana.

Prawie każdy plik wsadowy, który piszę, zaczyna się SETLOCALod, ponieważ w większości przypadków nie chcę, aby skutki uboczne zmian środowiska pozostały. W przypadkach, w których chcę, aby pewne zmiany zmiennych środowiskowych były propagowane poza plikiem wsadowym, moje ostatnie ENDLOCALwygląda następująco:

ENDLOCAL & (
  SET RESULT1=%RESULT1%
  SET RESULT2=%RESULT2%
)
oddziały
źródło
-1

Aby rozwiązać ten problem, zmieniłem zmienną środowiskową za pomocą ZARÓWNO setx i set, a następnie zrestartowałem wszystkie instancje explorer.exe. W ten sposób każdy później uruchomiony proces będzie miał nową zmienną środowiskową.

Mój skrypt wsadowy, aby to zrobić:

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

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

Problem z tym podejściem polega na tym, że wszystkie otwarte okna eksploratora zostaną zamknięte, co jest prawdopodobnie złym pomysłem - ale zobacz post Kev, aby dowiedzieć się, dlaczego jest to konieczne

Jens Hykkelbjerg
źródło