注意:
linked blog post 中的程序原则上是有效的,但缺少关键信息/附加步骤:
如果您直接通过注册表修改环境变量 - 不幸的是,对于 @ 来说是 正确的方式基于 987654330@ 的环境变量,例如 Path - 您需要广播 WM_SETTINGCHANGE 消息,以便 Windows (GUI) shell(及其组件、文件资源管理器、任务栏,桌面,开始菜单,都通过explorer.exe 进程提供)被通知环境更改并从注册表重新加载其环境变量。之后启动的应用程序会继承更新后的环境。
- 如果未发送此消息,则未来的 PowerShell 会话(和其他应用程序)在下次登录/重新启动之前不会看到修改。
很遗憾,没有直接从 PowerShell 执行此操作的方法,但有解决方法:
-
蛮力解决方法 - 简单,但视觉上会破坏并关闭所有打开的文件资源管理器窗口:
# Kills all explorer.exe processes, which restarts the Windows shell
# components, forcing a reload of the environment from the registry.
Stop-Process -Name explorer
-
通过 .NET API 解决方法:
# Create a random name assumed to be unique
$dummyName = [guid]::NewGuid().ToString()
# Set an environment variable by that name, which makes .NET
# send a WM_SETTINGCHANGE broadcast
[Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
# Now that the dummy variable has served its purpose, remove it again.
# (This will trigger another broadcast, but its performance impact is negligible.)
[Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')
-
通过在 C# 中通过对 SendMessageTimeout() 的临时编译的 P/Invoke 调用(通过 Add-Type)调用 Windows API 来解决此问题:
博文中的方法有另一个问题方面:
下一节讨论的辅助函数解决了所有问题,同时还确保修改对当前会话也生效。
以下Add-Path辅助函数:
注意:根据定义(由于使用注册表),此功能仅限 Windows。
使用下面定义的函数,你想要的Path添加可以如下执行,修改当前用户的持久的Path定义:
Add-Path C:\Users\921479\AppData\Local\SumatraPDF
如果您真的想更新 机器级 定义(在 HKEY_LOCAL_MACHINE 注册表配置单元中,这对于 用户特定路径 没有意义) ,添加-Scope Machine,但不是您必须随后运行with elevation(以管理员身份)。
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]
# Note the use of the .GetValue() method to unsure that the *unexpanded* value is returned.
$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 ';'
# Update the registry.
Set-ItemProperty -Type ExpandString -LiteralPath $regPath Path $newValue
# Broadcast WM_SETTINGCHANGE to get the Windows shell to reload the
# updated environment, via a dummy [Environment]::SetEnvironmentVariable() operation.
$dummyName = [guid]::NewGuid().ToString()
[Environment]::SetEnvironmentVariable($dummyName, 'foo', 'User')
[Environment]::SetEnvironmentVariable($dummyName, [NullString]::value, 'User')
# Finally, also update the current session's `$env:Path` definition.
# Note: For simplicity, we always append to the in-process *composite* value,
# even though for a -Scope Machine update this isn't strictly the same.
$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 开始)。
-
(重新)创建的环境变量始终是REG_SZ 类型,而Path 最初是REG_EXPAND_SZ 类型并包含基于其他 环境变量的目录路径,例如%SystemRoot% 和 %JAVADIR%。
- 如果替换值仅包含可能没有立即不良影响的文字路径(没有环境变量引用),但是,例如,最初的条目如果稍后更改
%JAVADIR% 的值,则依赖于 %JAVADIR% 将停止工作。
-
此外,如果您基于当前会话的 $env:Path 值更新值,您最终会重复条目,因为 进程级别 $env:Path value 是机器级和当前用户级值的复合。
-
这会增加遇到 1024 个字符限制的风险,尤其是在重复使用该技术的情况下。它还承担了将原始条目从原始范围中删除后重复值逗留的风险。
-
虽然您可以通过直接从注册表或通过[Environment]::GetEnvironmentVariable('Path', 'User') 或[Environment]::GetEnvironmentVariable('Path', 'Machine') 以扩展 形式检索特定于范围的值来避免此特定问题,但仍然不能解决上面讨论的REG_EXPAND_SZ 问题。