3

I have read many posts on this topic and tried many things but cannot seem to get this to work. I want to set an environment variable and then nest that variable in the Path environment variable. I switched from Batch files to Powershell because I could not get late expansion working to prevent expanding nested variables already in the Path, etc.

Here is script to demonstrate the issue. Given that you have Maven unzipped to e:\Apps\maven\apache-maven-3.2.1 location, the test script will run, create the MAVEN_HOME variable, nest that variable unexpanded in the Path, and execute the mvn --help.

This all works fine, except that upon opening a fresh command prompt and typing ECHO %PATH% it is clear that the change has not been applied.

I have heard that alphabetical order of the environment variables can matter, but in this case "MAVEN_HOME" comes before "PATH" so that shouldn't matter.

The Path variable is being created in the registry as a REG_EXPAND_SZ type.

I am running the Powershell script from a batch file to avoid signing:

Call Powershell.exe -executionpolicy bypass -File .\test.ps1

Here is the Powershell script:

#Environment Variable
$HOME_VAR = "MAVEN_HOME"
$HOME_PATH = "e:\Apps\maven\apache-maven-3.2.1"
$APP_CMD = "mvn"
$APP_ARGS = "--help"

#String to be added to the Path
$BIN_PATH = "%$HOME_VAR%\bin"

#Registry location of Machine Environment variables
$SYSVAR_REG_PATH = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"

#Get the correct hive
$HKLM = [Microsoft.Win32.Registry]::LocalMachine
#Get the registry key with true to indicate that it is for editing
$sysvar_regkey = $HKLM.OpenSubKey($SYSVAR_REG_PATH, $TRUE)

#Set the value in the registry
$sysvar_regkey.SetValue($HOME_VAR, $HOME_PATH)

#Read the value back out
$HOME_PATH = $sysvar_regkey.GetValue($HOME_VAR)

#Set the value within the current process
[Environment]::SetEnvironmentVariable($HOME_VAR, $HOME_PATH, [EnvironmentVariableTarget]::Process)

#Must use RegistryKey to get value because it allows the "DoNotExpandEnvironmentNames" option
#This ensures that nested environment variables are not expanded when read
$envpath = $sysvar_regkey.GetValue("Path", "C:\Windows", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
$segments = $envpath.split(";")

Write-Host "BEFORE"
Write-Host $env:path

#See if bin path is already in the Path
If (($segments -contains $BIN_PATH) -eq $FALSE) {
    #Add the bin path to the path
    $segments += $BIN_PATH
    $envpath = $segments -join ";"

    #RegistryValueKind.ExpandString ensures that variables in the path will expand when the Path is read
    $sysvar_regkey.SetValue("Path", $envpath, [Microsoft.Win32.RegistryValueKind]::ExpandString)
}   

#Read the path value as expanded
#All nested variables in the Path are expanded
$envpath = $sysvar_regkey.GetValue("Path")

#Update the Path for the current process
#Must do this every time to expand the Path
[Environment]::SetEnvironmentVariable("Path", $envpath, [EnvironmentVariableTarget]::Process)

Write-Host "AFTER"
Write-Host $env:path

#Run the command line
& $APP_CMD $APP_ARGS | Write-Host
jedatu
  • 4,053
  • 6
  • 48
  • 60

1 Answers1

0

A new cmd session uses the path inherited from the parent process's environment (generally either Windows Explorer, or a cmd or PowerShell session that spawned it). Changing the value of an environment variable in the registry doesn't automatically change Explorer's environment, so it doesn't change the value used by new cmd sessions.

If you set an environment variable through the System Properties control panel, the value is reflected in new cmd sessions not because it's stored in the registry, but because that also changes the value in the environment of the main Explorer process. (Note that simply opening the Environment Variables dialog box from System Properties and clicking OK updates all of Explorer's environment variables from the registry values).

To achieve the same effect from PowerShell — simultaneously changing the value in the registry and in Explorer's environment which is passed on to new cmd sessions — you can do this:

[Environment]::SetEnvironmentVariable("Path", $envpath, 'Machine')

Note that this does not replace

[Environment]::SetEnvironmentVariable("Path", $envpath, 'Process')

because if the target is Machine, the value in the current PowerShell session is not changed. (You can use the strings 'Process' and 'Machine' instead of [EnvironmentVariableTarget]::Process and [EnvironmentVariableTarget]::Machine).


Note, BTW, that new PowerShell sessions always use the Path value from the registry rather than the one inherited from the parent. This behavior applies exclusively to Path; all other environment variables are inherited from the parent process. See this answer for more information.
Community
  • 1
  • 1
Adi Inbar
  • 12,097
  • 13
  • 56
  • 69
  • 1
    I should have mentioned this in the question. In any event, the problem with this is that [Environment]::SetEnvironmentVariable changes the registry entry type to REG_SZ and as a result the nested variables do not expand and the Path is then broken. – jedatu May 22 '14 at 21:28
  • It is likely that `SetEnvironmentVariable(x,y,'Machine')` works in the usual way, i.e., by making the change in the registry and then broadcasting a message which tells interested applications to reread the registry key. This means that if you make your change directly to the registry, and then using `SetEnvironmentVariable` to make a *different* change, both changes should become live. – Harry Johnston May 23 '14 at 06:40
  • Yeah I am doing this with the process because I don't care if the already expanded Path is set to the process. However, setting the machine through that API is a destructive set to the registry. I have tried just using the SetEnvironmentVariable alone and it still changes the RegistryKey type to REG_SZ. – jedatu May 23 '14 at 15:42
  • It just occurred to me that I could use the SetEnvironmentVariable API to update Explorer with the expanded path, then use the RegistryKey to correct the Registry. – jedatu May 23 '14 at 15:51