如果修改或添加环境变量,则必须重新启动命令提示符。是否有一个命令,我可以执行,将这样做而不重新启动CMD?


当前回答

我只是想说,那些使用Anaconda的人,当你使用chocoley Refreshenv命令时;所有与conda相关的环境变量都将丢失。 要解决这个问题,最好的方法是重新启动CMD。:(

其他回答

环境变量保存在HKEY_LOCAL_MACHINE\SYSTEM\ControlSet\Control\Session Manager\Environment中。

许多有用的环境变量,如Path,都存储为REG_SZ。有几种方法可以访问注册表,包括REGEDIT:

REGEDIT /E <“HKEY_LOCAL_MACHINE \ SYSTEM \ ControlSet001 \会话管理器\ \控制环境”

输出从神奇的数字开始。因此,要用find命令搜索它,它需要输入并重定向:type <filename> | findstr -c:\"Path\"

所以,如果你只是想用系统属性刷新当前命令会话中的路径变量,下面的批处理脚本可以很好地工作:

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

根据设计,Windows没有内置机制来将环境变量添加/更改/删除传播到已经运行的cmd.exe中,无论是从另一个cmd.exe还是从“我的电脑->属性->高级设置->环境变量”。

如果在现有的开放命令提示符范围之外修改或添加新的环境变量,则需要重新启动命令提示符,或者在现有的命令提示符中使用SET手动添加。

最新接受的答案通过手动刷新脚本中的所有环境变量显示了部分解决方案。该脚本处理在“My Computer…”中全局更改环境变量的用例。但是如果一个环境变量在一个cmd.exe中被更改,脚本不会将其传播到另一个运行cmd.exe的环境变量中。

可以通过在指定进程本身中覆盖环境表来实现这一点。

作为概念的证明,我写了这个示例应用程序,它只是在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;
}

样例输出:

>set ENVTEST=abc

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

>set ENVTEST
ENVTEST=def

笔记

这种方法也仅限于安全限制。如果目标在更高的海拔或更高的帐户(如SYSTEM)上运行,那么我们将没有权限编辑它的内存。

如果你想对一个32位的应用程序这样做,上面的硬编码偏移量将分别更改为0x10和0x48。这些偏移量可以通过在调试器中转储_PEB和_RTL_USER_PROCESS_PARAMETERS结构体来找到(例如在WinDbg dt _PEB和dt _RTL_USER_PROCESS_PARAMETERS中)

要将概念证明更改为OP需要的内容,只需枚举当前系统和用户环境变量(如@tsadok的答案所记录的那样),并将整个环境表写入目标进程的内存中。

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).

您可以使用vbs脚本捕获系统环境变量,但是需要使用bat脚本来实际更改当前环境变量,因此这是一种组合解决方案。

创建一个名为resetvars的文件。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("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

创建另一个包含以下代码的文件名resetvars.bat,相同的位置:

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

当您想要刷新环境变量时,只需运行resetvars.bat


护教学:

我提出这个解决方案时遇到的两个主要问题是

a.我找不到一个直接的方法将环境变量从vbs脚本导出回命令提示符,并且

b. PATH环境变量是用户变量和系统PATH变量的连接。

我不确定用户和系统之间的冲突变量的一般规则是什么,所以我选择让用户覆盖系统,除了在PATH变量中,它是专门处理的。

我使用奇怪的vbs+bat+临时bat机制来解决从vbs导出变量的问题。

注意:此脚本不删除变量。

这可能是可以改进的。

添加

如果你需要将环境从一个cmd窗口导出到另一个窗口,使用这个脚本(让我们称之为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

exportvars运行。VBS,然后切换到要导出的窗口,输入:

"%TEMP%\resetvars.bat"

以下是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 "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 .