4

I have batch file that sets user path and is run as part of Visual Studio IDE build step.

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

When I build the project, close VS, and reopen it, and rebuild, I see appended path as part of PATH variable. However, I see that in Windows setting of environment variable PATH variable is created under user environment variables as

..\..\lib\libsndfile;..\..\lib\simulink

Question 1:

Why does this path also appear as appended path as part of system environment variable?

On executing echo %PATH% through Visual Studio console (when I run the project second times) prints system variable path and the new path I created appended to it.

Question 2:

I want to modify my batch file so that it only sets once PATH environment variable in user settings during first run of Visual Studio build. If the user variable PATH already exists on subsequent runs, it should not execute set command again to avoid appending new path again and again in system variable.

Any ideas how to achieve this?

Mofi
  • 46,139
  • 17
  • 80
  • 143
user915783
  • 689
  • 1
  • 9
  • 27

2 Answers2

9

edit: After some testing, it appears that my original answer isn't entirely applicable to OP's questions. To answer OP more directly:

  1. %PATH% combines the values in HKLM\System\CurrentControlSet\Control\Session Manager\Environment\Path with HKCU\Environment\Path. When you setx "dir;dir", what you're setting is the HKEY_CURRENT_USER Path value. The machine-wide HKEY_LOCAL_MACHINE Path value remains untouched. That's why you see your values as appended, rather than as replacements. You'd have to use setx /m to replace the HKLM Path value. But please don't unless you want to create severe problems with your operating system installation.

  2. If you want to test whether a directory exists in %PATH%, you could cd or pushd both to the directory you want to check and to each directory within %PATH% to unify each, making sure all relative paths, environment variables, etc. are flattened. set "var=%CD%" for each. Then if /I "!dir1!"=="!dir2!" the directory already exists somewhere in %PATH%. There's an example of this in my original answer below.

The reason my original answer isn't entirely applicable is because setx itself isn't as destructive as I once thought. The danger is that often times when users want to append a directory to their path, they'll setx /m PATH "%PATH%;new dir"; and that is destructive. Because %PATH% is expanded before setx writes the value, all the directories in PATH are expanded prematurely.

The following method would be safer:

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"

But that wasn't really what OP asked, and I apologize for the knee-jerk answer.


original answer: setx is destructive and shouldn't be used this way. When you setx PATH you're converting the registry value data type from REG_EXPAND_SZ to REG_SZ. As soon as you do this, all the dynamic environment variables stored in your %PATH% get converted to flat, absolute paths. Use the path command to append directories to your %PATH% temporarily, and reg add to do so permanently. (As a side note, there's also dpath, which temporarily adds a directory to your path, but can only be used by the type command. Scroll 2/3 the way down this page for more info on dpath.)

Here's a utility script I wrote to add directories to my %PATH% in a less destructive manner. It will also avoid adding the same directory to %PATH% more than once, regardless of how it's formatted (e.g. trailing backslash, relative paths, environment variables, or any other permutation).

@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
rojo
  • 24,000
  • 5
  • 55
  • 101
  • I called the above script that you gave with one of the paths. First time, it gave 'Warning: %%PATH%% has changed. Reopen the console to inherit the changes' .Next time, calling it with same argument did not give the message "already exists in path" but rather same path was added twice. This happened both from command prompt and VS pre build event call. – user915783 Aug 11 '15 at 23:19
  • @user915783 I just happened to think, the script might need to be run with elevation. Can you try running it as administrator and see whether you have better luck? – rojo Aug 12 '15 at 11:55
  • Hi rojo. I added your script to [link](http://stackoverflow.com/questions/7044985/how-can-i-auto-elevate-my-batch-file-so-that-it-requests-from-uac-administrator) and sure it does print that path is added.but,if I go into registry and look into added paths, and I can't find those under "PATH" variable anywhere.in fact . I can't find those paths anywhere. So, where are the paths? second, when I ran the script from VS IDE prebuild event as **call setdllpathSafe.bat "..\..\lib\libsndfile" & call setdllpathSafe.bat "..\..\lib\simulink"**, it prints current path thrice before giving mssg? – user915783 Aug 13 '15 at 00:09
  • 1
    @rojo Which version of __setx__ changes type of registry value from `REG_EXPAND_SZ` to `REG_SZ`? After reading your answer, I wanted to adapt [my batch solution](http://stackoverflow.com/a/25919222/3074564) for this task. But I tried first my batch code once more with looking on type of registry value `Path` before and after batch execution on Windows 7 SP1 x64 using __setx__ with file version 6.1.7600.16385. But the registry value type was unchanged `REG_EXPAND_SZ` after every modification. In could see in Process Monitor that __setx__ first queries `Path` and then sets it with right type. – Mofi Sep 22 '15 at 11:08
  • 1
    @Mofi same version. Go to `HKLM\System\CurrentControlSet\Control\Session Manager\Environment` and create a new `REG_EXPAND_SZ` value, call it "foo" and set its data to "bar". Leave that window open, but open a console window and do `setx /m foo bar`. Now go back to the Registry Editor window and refresh. `foo` is now type `REG_SZ`. But I do see that if I do `setx /m foo %bar%` (where `%bar%` is not defined), setx sets the data type back to `REG_EXPAND_SZ`. I forget where I read about the destructive nature of `setx`, but I've never actually experimented with it until now. – rojo Sep 22 '15 at 11:52
  • I suppose all one needs is to make sure all the variables in `%PATH%` stay collapsed when appending directories to it using `setx`. – rojo Sep 22 '15 at 11:54
  • @rojo, well analyzed and explained. Type of registry value depends on value of variable. `REG_EXPAND_SZ` is used by __setx__ with value of variable containing `%...%` otherwise the type is `REG_SZ`. – Mofi Sep 24 '15 at 10:13
1
  1. That what Setx does. See Setx /? where it tells you this. It adds it to the user's environment permanently (unless /m is used). However %PATH% is built from system and user environment's PATH (and also autoexec.bat on 32 bit windows and if started through ShellExecute also App Paths reg key))

  2. Don't worry about. Setx doesn't add paths to %PATH% if they already exist in %PATH%.

Why are you redefining the system variable %COMSPEC%.

bill
  • 215
  • 1
  • 3
  • Thanks for both answers. I was redefining so that subsequent path addition would take place successfully from within Visual Studio. Although Your answer 2 solves my problem, but just out of curiosity, is there a way to remove portion of appended path from %PATH% next time a batch file is run? – user915783 Aug 07 '15 at 22:59
  • You can certainly do it with `reg` command and `wmic` by reading editing and writing back what you want. The problem is windows (which means explorer) won't know it changed till a logoff/on. But you could use `reg` to delete and `setx` to put back. `Setx` sends a system change event to programs that monitor it and explorer rebuilds it's path, so any program it starts (like CMD) will have new path. – bill Aug 07 '15 at 23:47