Note:
The 程序中的链接的博客文章是有效的原则,但缺少一条关键信息/附加步骤:
如果直接修改环境变量通过注册表- 不幸的是,is the right这样做的方法REG_EXPAND_SZ
基于环境变量,例如Path
- 你需要广播一个WM_SETTINGCHANGE
message这样 Windows (GUI) shell(及其组件、文件资源管理器、任务栏、桌面、开始菜单,全部通过explorer.exe
进程)收到环境变化的通知,并且reloads来自注册表的环境变量。随后启动的应用程序将继承更新后的环境。
- 如果这条消息是not发送后,未来的 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
[string] $dummyName = New-Guid
# 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')
- Note: 这个答案解释原因using
[Environment]::SetEnvironmentVariable()
to directly更新PATH
变量是not一个选项从 .NET 8 开始。
-
解决方法调用Windows API通过临时编译的 P/Invoke 调用SendMessageTimeout()
在 C# 中,通过Add-Type:
博客文章中的方法有另一个有问题的方面:
- It 检索expanded来自注册表的环境变量值,因为这就是Get-ItemProperty and Get-ItemPropertyValue总是这样做。也就是说,如果值中的目录是根据以下定义的其他环境变量 (e.g.,
%SystemRoot%
or %JAVADIR%
),返回值不再包含这些变量,而是它们的当前值。请参阅底部部分了解为什么这可能会出现问题。
下一节中讨论的辅助函数可以解决所有问题,同时还确保修改对当前会话 too.
下列Add-Path
辅助函数:
-
添加(附加)给定的单个目录路径到持久用户级 Path
默认环境变量;使用-Scope Machine
以针对机器级定义,这需要海拔(以管理员身份运行)。
-
触发一个WM_SETTINGCHANGE
消息广播以通知 Windows shell 有关更改。
-
还更新了当前会话的 $env:Path
变量值。
-
It is unfortunate that PowerShell doesn't ship with this functionality; a previous discussion about providing a cmdlet for robustly and selectively updating $env:PATH
has gone nowhere - see the abandoned GitHub RFC #92 (which covered providing cmdlets for managing persistent environment variables in general).[1]
注意:根据定义(由于使用注册表),该函数是仅限 Windows.
通过定义下面的函数,您想要的Path
添加可以执行如下,修改当前用户的执着的Path
定义:
Add-Path C:\Users\921479\AppData\Local\SumatraPDF
如果您确实想更新机器级定义(在HKEY_LOCAL_MACHINE
注册表配置单元,这对于用户特定路径), add -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]
# Note the use of the .GetValue() method to ensure 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 个字符,还有其他的得到被截断的,尽管有一个warning(至少从 Windows 10 开始)。
-
The environment variable that is (re)created is with type REG_SZ
if the new value happens NOT to contain environment-variable references such as %SystemRoot%
.[2] However, Path
is originally of type REG_EXPAND_SZ
and indeed contains directory paths based on other environment variables, such as %SystemRoot%
and %JAVADIR%
.
- 如果新值仅包含literal可能没有的路径(没有环境变量引用)即时不良影响,但是,例如,最初依赖于的条目
%JAVADIR%
将停止工作,如果%JAVADIR%
是后来改变的。同样,如果您稍后添加一个以其他环境变量表示的条目,则此类条目将not被扩大。
-
此外,如果您根据当前会话的更新值$env:Path
值,你最终会得到复制条目,因为过程级 $env:Path
值是一个合成的机器级别和当前用户级别值。
-
这会增加遇到 1024 个字符限制的风险,特别是在使用该技术的情况下反复。它还承担重复值的风险挥之不去原始条目从原始范围中删除后。
-
虽然您可以通过直接从注册表检索特定于范围的值来避免此特定问题,或者总是在expanded形式-通过[Environment]::GetEnvironmentVariable('Path', 'User')
or [Environment]::GetEnvironmentVariable('Path', 'Machine')
,仍然没有解决REG_EXPAND_SZ
上面讨论的问题。
[1] A prototype implementation of new cmdlets for managing persistent environment variables is available via the PowerShell Gallery, though it seems to languish and, crucially, lacks support for expandable environment variables (REG_EXPAND_SZ
) as of this writing, which makes it unsuitable for PATH
updates.
[2] That is, setx.exe
decides whether to (re)create the underlying registry value as REG_SZ
(static) or REG_EXPAND_SZ
purely based on the presence of environment-variable references such as %SystemRoot%
in the new value - irrespective of a preexisting registry value's current type.