注意:
请参考中间部分的“辅助函数Add-Path”。
请参考底部部分,了解为什么应避免使用setx.exe来更新Path环境变量。
在链接的博客文章中的步骤原则上是有效的,但缺少了一个关键的信息/额外的步骤:
如果您直接通过注册表修改环境变量 - 不幸的是,这是对于基于
REG_EXPAND_SZ
的环境变量(如
Path
)来说是正确的方法 -
您需要发送一个WM_SETTINGCHANGE
消息,以便通知Windows(GUI)shell(及其组件,文件资源管理器,任务栏,桌面,开始菜单,所有由
explorer.exe
进程提供)有关环境更改的信息,并从注册表重新加载其环境变量。然后启动的应用程序将继承更新后的环境。
- 如果未发送此消息,则未来的PowerShell会话(和其他应用程序)在下次登录/重启之前都无法看到修改。
很遗憾,从PowerShell没有直接的方法来做到这一点,但有一些变通方法:
Brute-force workaround - simple, but visually disruptive and closes all open File Explorer windows:
Stop-Process -Name explorer
通过.NET API的解决方法:
[string] $dummyName = New-Guid
[Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
[Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')
- 注意:这个答案解释了为什么使用
[Environment]::SetEnvironmentVariable()
直接更新 PATH
变量作为 .NET 8 的选项。
通过调用 Windows API来绕过,通过 C# 中的临时编译的 P/Invoke 调用 SendMessageTimeout()
,通过 Add-Type
:
博客文章中的方法有另一个问题:
下一节讨论的辅助函数解决了所有问题,并确保修改对当前会话也生效。
以下是
Add-Path
助手函数:
默认情况下,将给定的单个目录路径添加(附加)到持久的用户级别Path
环境变量中;使用-Scope Machine
来定位机器级别的定义,这需要提权(以管理员身份运行)。
触发一个WM_SETTINGCHANGE
消息广播,通知Windows shell发生了变化。
同时更新当前会话的$env:Path
变量值。
很遗憾的是,PowerShell没有内置此功能;之前讨论过为稳健且可选择地更新$env:PATH
提供一个cmdlet的问题没有进展——请参阅被放弃的GitHub RFC #92(其中涵盖了一般持久环境变量管理的cmdlet)。[1]
注意:根据定义(由于使用注册表),此函数仅适用于Windows。
通过下面定义的函数,您可以执行所需的Path
添加操作,修改当前用户的持久性Path
定义:
Add-Path C:\Users\921479\AppData\Local\SumatraPDF
如果你真的想要更新机器级别的定义(在HKEY_LOCAL_MACHINE注册表中,这与用户特定路径不符合逻辑),请添加-Scope Machine,但是请注意你必须以提权方式运行(作为管理员)。
Add-Path源代码:
function Add-Path {
param(
[Parameter(Mandatory, Position=0)]
[string] $LiteralPath,
[ValidateSet('User', 'CurrentUser', 'Machine', 'LocalMachine')]
[string] $Scope
)
Set-StrictMode -Version 1; $ErrorActionPreference = 'Stop'
$isMachineLevel = $Scope -in 'Machine', 'LocalMachine'
if ($isMachineLevel -and -not $($ErrorActionPreference = 'Continue'; net session 2>$null)) { throw "You must run AS ADMIN to update the machine-level Path environment variable." }
$regPath = 'registry::' + ('HKEY_CURRENT_USER\Environment', 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment')[$isMachineLevel]
$currDirs = (Get-Item -LiteralPath $regPath).GetValue('Path', '', 'DoNotExpandEnvironmentNames') -split ';' -ne ''
if ($LiteralPath -in $currDirs) {
Write-Verbose "Already present in the persistent $(('user', 'machine')[$isMachineLevel])-level Path: $LiteralPath"
return
}
$newValue = ($currDirs + $LiteralPath) -join ';'
Set-ItemProperty -Type ExpandString -LiteralPath $regPath Path $newValue
$dummyName = [guid]::NewGuid().ToString()
[Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
[Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')
$env:Path = ($env:Path -replace ';$') + ';' + $LiteralPath
Write-Verbose "`"$LiteralPath`" successfully appended to the persistent $(('user', 'machine')[$isMachineLevel])-level Path and also the current-process value."
}
setx.exe
的局限性以及为什么不应该用它来更新Path
环境变量:
setx.exe
存在根本性的限制,使其在更新基于REG_EXPAND_SZ
类型的注册表值(如Path
)的环境变量时存在问题:
值被限制在1024个字符以内,超出部分将被截断,尽管会有警告(至少适用于Windows 10)。
如果新值不包含环境变量引用(例如%SystemRoot%),则重新创建的环境变量类型为REG_SZ。[2] 然而,Path最初是REG_EXPAND_SZ类型,并且确实包含基于其他环境变量(如%SystemRoot%和%JAVADIR%)的目录路径。
- 如果新值只包含文字路径(没有环境变量引用),可能不会立即产生任何负面影响。但是,如果原本依赖于%JAVADIR%的条目稍后停止工作,则会出现问题,如果%JAVADIR%的值稍后更改。同样地,如果您稍后添加一个以其他环境变量表示的条目,这些条目将不会被展开。
此外,如果您基于当前会话的$env:Path值来更新值,您将会复制条目,因为进程级别的$env:Path值是机器级别和当前用户级别值的组合。
这增加了遇到1024字符限制的风险,特别是如果该技术被重复使用。它还存在着原始条目从原始范围中删除后,重复值仍然存在的风险。
虽然您可以通过直接从注册表检索特定范围的值或通过[Environment]::GetEnvironmentVariable('Path', 'User')或[Environment]::GetEnvironmentVariable('Path', 'Machine')以展开的形式获取该值来避免此特定问题,但这仍然无法解决上述讨论的REG_EXPAND_SZ问题。
[1] 通过原型实现,可以使用PowerShell Gallery来管理持久环境变量的新命令。然而,目前它似乎不受关注,并且关键是缺乏对可扩展环境变量(REG_EXPAND_SZ
)的支持,这使得它不适用于PATH
更新。
[2] 换句话说,setx.exe
根据新值中是否存在环境变量引用(如%SystemRoot%
)来决定是否(重新)创建基础注册表值为REG_SZ
(静态)或REG_EXPAND_SZ
(可扩展),而不考虑预先存在的注册表值的当前类型。
* -Item
可以增强。 - mklement0refreshenv
版本即可。 - not2qubitSystem.Environment] :: SetEnvironmentVariable('PATH',$ SPATH,[System.EnvironmentVariableTarget] :: Machine)
,因为它将无论如何创建一个REG_SZ
值,而保留REG_EXPAND_SZ
值以及带有对其他环境变量的嵌入引用的条目非常重要-因此需要进行直接注册表操作。 - mklement0