简体   繁体   中英

Set nested expandable environment variable with PowerShell

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM