如何在Windows批处理文件中仅设置一次PATH环境变量?

4

我有一个批处理文件,用于设置用户路径,并作为Visual Studio IDE构建步骤的一部分运行。

@ECHO OFF
@ECHO %PATH%
set COMSPEC = "%VCINSTALLDIR%\vcvarsall.bat" amd64
setx PATH "..\..\lib\libsndfile;..\..\lib\simulink" 
@ECHO %PATH%

当我构建项目时,关闭 VS,并重新打开并重新构建后,我发现附加的路径作为 PATH 变量的一部分添加了进来。然而,在 Windows 环境变量设置中,PATH 变量是在用户环境变量下创建的。
..\..\lib\libsndfile;..\..\lib\simulink

问题1:

为什么这条路径同时作为系统环境变量的一部分,也会出现在附加路径中?

通过Visual Studio控制台执行 echo %PATH%命令(当我第二次运行项目时),会打印系统变量路径和我创建的新路径附加在其后。

问题2:

我想修改我的批处理文件,以便它在第一次运行Visual Studio构建时只设置一次用户设置中的 PATH 环境变量。如果用户变量 PATH 已经存在于后续运行中,则不应再次执行 set 命令,以避免在系统变量中再次附加新路径。

有什么好的想法可以实现这个目标吗?

2个回答

9
编辑:经过一些测试,看起来我的原始答案并不完全适用于OP的问题。为了更直接地回答OP:
1. %PATH%HKLM\System\CurrentControlSet\Control\Session Manager\Environment\Path中的值与HKCU\Environment\Path中的值结合起来。当你使用setx "dir;dir"时,你设置的是当前用户的HKEY_CURRENT_USER Path值。计算机范围内的HKEY_LOCAL_MACHINE Path值保持不变。这就是为什么你看到的值是附加的,而不是替换的。你必须使用setx /m来替换HKLM Path值。但是请不要这样做,除非你想在操作系统安装中创建严重的问题。
2. 如果你想测试%PATH%中是否存在目录,你可以cdpushd至你想检查的目录和%PATH%中每个目录,以统一每个目录,确保所有相对路径、环境变量等都被展开。对于每个目录,使用set "var=%CD%"。然后使用if /I "!dir1!"=="!dir2!" 来判断目录是否已经存在于%PATH%中。这在我的原始答案中有例子。
我的原始答案并不完全适用,因为setx本身并没有我曾经想象的那么破坏性。危险在于,通常情况下,当用户想将一个目录添加到他们的路径中时,他们会使用setx /m PATH "%PATH%;new dir";而这是破坏性的。因为在setx写入值之前,%PATH%被展开,所有路径中的目录都被过早地展开了。
以下方法更安全:
set "env=HKLM\System\CurrentControlSet\Control\Session Manager\Environment"

for /f "tokens=2*" %%I in (
    'reg query "%env%" /v Path ^| findstr /i "\<Path\>"'
) do setx /m PATH "%%J;new directory"

但那并不是OP所问的,我对我那个莽撞的回答道歉。


原始回答:setx 是破坏性的,不应该这样使用。当你执行 setx PATH 时,你将注册表值数据类型从 REG_EXPAND_SZ 转换为 REG_SZ。一旦这样做,存储在你的 %PATH% 中的所有动态环境变量都会被转换为平面、绝对路径。使用 path 命令将目录暂时附加到你的 %PATH% 中,使用 reg add 永久地这样做。(顺便说一句,还有一个 dpath,它可以将一个目录暂时添加到你的路径中,但只能被 type 命令使用。在这个页面的2/3位置滚动查看更多关于 dpath 的信息。)

这是我写的一个实用脚本,以一种不太具有破坏性的方式向我的 %PATH% 添加目录。它还将避免将相同的目录不管以何种方式格式化 (如尾随反斜杠、相对路径、环境变量或任何其他排列组合) 多次添加到 %PATH% 中。

@echo off
setlocal enabledelayedexpansion

if not exist "%~1" goto usage

for %%I in ("%~1") do pushd "%%~I" 2>NUL && (set "new=!CD!" && popd) || goto usage
for %%I in ("%PATH:;=";"%") do pushd "%%~I" 2>NUL && (
    rem // delaying expansion of !new! prevents parentheses from breaking things
    if /i "!new!"=="!CD!" (
        echo !new! already exists in %%PATH%%
        goto :EOF
    )
    popd
)

call :append_path "%new%"

goto :EOF

:usage
echo Usage: %~nx0 "dir"
goto :EOF

:append_path <val>
set "env=HKLM\System\CurrentControlSet\Control\Session Manager\Environment"
for /f "tokens=2*" %%I in ('reg query "%env%" /v Path ^| findstr /i "\<Path\>"') do (

    rem // make addition persistent through reboots
    reg add "%env%" /f /v Path /t REG_EXPAND_SZ /d "%%J;%~1"

    rem // apply change to the current process
    for %%a in ("%%J;%~1") do path %%~a
)

rem // use setx to set a temporary throwaway value to trigger a WM_SETTINGCHANGE
rem // applies change to new console windows without requiring a reboot
(setx /m foo bar & reg delete "%env%" /f /v foo) >NUL 2>NUL

color 4E
echo Warning: %%PATH%% has changed.  Reopen the console to inherit the changes.

goto :EOF

@user915783 我突然想到,这个脚本可能需要以提升的权限运行。你可以尝试以管理员身份运行它,看看是否能够更顺利地运行? - rojo
1
@rojo哪个版本的__setx__会将注册表值的类型从REG_EXPAND_SZ更改为REG_SZ?阅读了您的答案后,我想要为此任务调整我的批处理解决方案。但是,在使用文件版本6.1.7600.16385的__setx__在Windows 7 SP1 x64上执行批处理代码之前和之后,先查看注册表值Path的类型,但每次修改后,注册表值的类型都没有更改为REG_EXPAND_SZ。我可以在Process Monitor中看到__setx__首先查询Path,然后以正确的类型设置它。 - Mofi
1
@Mofi 同版本。前往 HKLM\System\CurrentControlSet\Control\Session Manager\Environment 并创建一个新的 REG_EXPAND_SZ 值,将其命名为 "foo" 并将其数据设置为 "bar"。保持该窗口打开,但打开一个控制台窗口并执行 setx /m foo bar。现在返回注册表编辑器窗口并刷新。foo 现在是类型 REG_SZ。但我发现如果我执行 setx /m foo %bar%(其中 %bar% 未定义),setx 将数据类型设置回 REG_EXPAND_SZ。我忘记在哪里读到了 setx 的破坏性质,但直到现在我才真正进行了实验。 - rojo
我想,所有你需要做的就是确保在使用 setx 添加目录时,%PATH% 中的所有变量都保持折叠。 - rojo
@rojo,分析得很好,解释得也很清楚。注册表值的类型取决于变量的值。如果__setx__的值包含%...%,则使用REG_EXPAND_SZ,否则类型为REG_SZ - Mofi
显示剩余2条评论

1
  1. Setx具有这样的功能,参见Setx /?。它会将其永久添加到用户环境变量中(除非使用了/m选项)。但是%PATH%是从系统和用户环境的PATH构建的(还有32位Windows上的autoexec.bat以及通过ShellExecute启动时也会用到App Paths注册表键)。

  2. 不用担心。如果路径已经存在于%PATH%中,Setx不会将其添加到%PATH%中。

为什么要重新定义系统变量%COMSPEC%。


感谢两个答案。我正在重新定义%PATH%,以便后续的路径添加可以在Visual Studio内成功进行。虽然您的第二个答案解决了我的问题,但出于好奇,有没有办法在下次运行批处理文件时从% PATH%中删除追加路径的部分? - user915783
你可以使用 reg 命令和 wmic 来读取、编辑并写回你想要的内容。问题在于 Windows(也就是资源管理器)只有在注销/登录后才会知道它已经更改了。但你可以使用 reg 删除并使用 setx 重新设置。Setx 会向监视它的程序发送系统更改事件,而资源管理器会重建路径,因此它启动的任何程序(如 CMD)都将具有新的路径。 - bill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接