使用PowerShell设置嵌套可扩展环境变量

3
我已经阅读了许多关于此主题的文章,并尝试了许多方法,但似乎无法使其正常工作。我想设置一个环境变量,然后将该变量嵌套在Path环境变量中。由于我无法使延迟扩展起作用以防止扩展已存在于Path中的嵌套变量等原因,我从批处理文件切换到Powershell。

以下是演示问题的脚本。假设您将Maven解压缩到e:\Apps\maven\apache-maven-3.2.1位置,测试脚本将运行,创建MAVEN_HOME变量,在Path中未展开地嵌套该变量,并执行mvn --help

这一切都很好,但是在打开全新的命令提示符并键入ECHO %PATH%后,清楚地看到更改没有被应用。

我听说环境变量的字母顺序可能很重要,但在这种情况下,“MAVEN_HOME”位于“PATH”之前,因此这不应该成为问题。

Path变量作为REG_EXPAND_SZ类型在注册表中创建。

我正在从批处理文件中运行Powershell脚本以避免签名:

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

这里是PowerShell脚本:
#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
1个回答

0

新的cmd会话使用从父进程环境继承的路径(通常是Windows资源管理器或生成它的cmd或PowerShell会话)。在注册表中更改环境变量的值不会自动更改资源管理器的环境,因此它不会更改新的cmd会话使用的值。

如果您通过系统属性控制面板设置环境变量,则该值会反映在新的cmd会话中,这不是因为它存储在注册表中,而是因为这也会更改主要资源管理器进程的环境中的值。(请注意,仅打开系统属性中的环境变量对话框并单击确定即可从注册表值更新所有资源管理器的环境变量)。

要从PowerShell实现相同的效果-同时更改注册表中的值和传递给新的cmd会话的资源管理器环境-可以执行以下操作:

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

请注意,这并不代替


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

因为如果目标是机器,当前PowerShell会话中的值不会改变。(您可以使用字符串'Process''Machine'来代替[EnvironmentVariableTarget] :: Process[EnvironmentVariableTarget] :: Machine)。


顺便提一下,新的PowerShell会话总是使用注册表中的Path值而不是从父进程继承的值。这个行为仅适用于Path;所有其他环境变量都是从父进程继承的。有关更多信息,请参见this answer


1
我应该在问题中提到这一点。无论如何,这个问题的问题在于[Environment] :: SetEnvironmentVariable会更改注册表条目类型为REG_SZ,因此嵌套变量不会扩展,导致路径被破坏。 - jedatu
很可能 SetEnvironmentVariable(x,y,'Machine') 以通常的方式工作,即通过在注册表中进行更改,然后广播一条消息告诉感兴趣的应用程序重新读取注册表键。这意味着,如果您直接对注册表进行更改,然后使用 SetEnvironmentVariable 进行不同更改,那么两个更改都应该生效。 - Harry Johnston
是的,我正在使用进程来完成这个操作,因为我不关心已经扩展的路径是否设置为进程。但是,通过那个 API 来设置机器会破坏注册表。我尝试过仅使用 SetEnvironmentVariable 来更改,但它仍会将 RegistryKey 类型更改为 REG_SZ。 - jedatu
我突然想到可以使用 SetEnvironmentVariable API 来更新资源管理器中的扩展路径,然后使用 RegistryKey 来纠正注册表。 - jedatu

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