我遵循了这个过程,以便使用powershell永久添加SumatraPDF的路径。链接中的最后几个命令旨在检查是否确实添加了路径。
当我使用以下命令访问路径时,
(get-itemproperty -path 'Registry::HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerEnvironment' -Name PATH).Path.split(';')
结果包括苏门答腊PDF 的路径
C:Windowssystem32
C:Windows
C:WindowsSystem32Wbem
C:WindowsSystem32WindowsPowerShellv1.0
C:WindowsSystem32OpenSSH
C:ProgramDatachocolateybin
C:texlive2021binwin32
C:Users921479AppDataLocalSumatraPDF
然而,当我使用以下命令访问它时,
($env:path).split(';')
结果不包含苏门答腊PDF:的路径
C:Windowssystem32
C:Windows
C:WindowsSystem32Wbem
C:WindowsSystem32WindowsPowerShellv1.0
C:WindowsSystem32OpenSSH
C:ProgramDatachocolateybin
C:texlive2021binwin32
C:Users921479AppDataLocalMicrosoftWindowsApps
最后,实际上传递sumatrapdf
是不起作用的,这向我表明实际路径是使用get-itemproperty
命令访问的路径。
为什么注册表中设置的路径与$env:path
中设置的不对应?我所遵循的链接中显示的程序是否有错误?我该如何更正?
我应该提一下,我已经尝试过重新启动shell,但没有帮助。
注意:
-
助手函数
Add-Path
请参见中间部分 -
请参阅底部部分了解为什么在更新
Path
环境变量时应避免使用setx.exe
。
链接博客文章中的程序原则上是有效的,但缺少一条关键信息/附加步骤:
如果您通过注册表直接修改环境变量-不幸的是,是对基于REG_EXPAND_SZ
的环境变量(如Path
-)执行此操作的正确方法您需要广播WM_SETTINGCHANGE
消息,以便Windows(GUI)外壳(及其组件、文件资源管理器、任务栏、桌面、开始菜单,所有这些都通过explorer.exe
进程提供)收到环境更改通知,并且重新加载其环境变量从注册表中删除。之后启动的应用程序将继承更新后的环境。
- 如果未发送此消息,则在下次登录/重新启动之前,未来的PowerShell会话(和其他应用程序)将不会看到修改
不幸的是,没有从PowerShell直接执行此操作的方法,但有解决方案:
-
Brute force解决方法-简单,但视觉破坏性强,关闭所有打开的文件资源管理器窗口:
# 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')
- 注意:这个答案解释了为什么使用
[Environment]::SetEnvironmentVariable()
来直接更新PATH
变量是而不是选项
- 注意:这个答案解释了为什么使用
-
通过调用Windows API,通过
Add-Type
:,通过对C#中SendMessageTimeout()
的临时编译p/Invoke调用来解决问题-
虽然这是一个合适的解决方案,但由于在会话中首次运行临时编译,它总是会导致显著的性能损失。
-
有关详细信息,请参阅此博客文章。
-
博客文章中的方法还有另一个问题:
- 它从注册表中检索扩展的环境变量值,因为
Get-ItemProperty
和Get-ItemPropertyValue
总是这样做。也就是说,如果值中的目录是根据其他环境变量(例如%SystemRoot%
或%JAVADIR%
)定义的,则返回的值不再包含这些变量,而是包含它们的当前值。请参阅底部部分,了解为什么会出现问题
下一节中讨论的helper函数解决了所有问题,同时也确保修改对当前会话生效。
以下Add-Path
辅助函数:
-
默认情况下,将给定的单个目录路径添加(追加)到持久用户级
Path
环境变量;使用-Scope Machine
以机器级定义为目标,该定义需要提升(以管理员身份运行)。-
如果目录已存在于目标变量中,则不采取任何操作。
-
相关注册表值是更新的,它根据现有的未扩展值保留其
REG_EXPAND_SZ
数据类型,也就是说,对其他环境变量的引用也会原样保留(例如%SystemRoot%
),并且也可以在添加的新条目中使用。
-
-
触发
WM_SETTINGCHANGE
消息广播,通知Windows外壳程序更改。 -
同时更新当前会话的
$env:Path
变量值。 -
遗憾的是,PowerShell没有附带此功能;之前关于提供一个用于稳健和选择性更新
$env:PATH
的cmdlet的讨论没有进展——请参阅废弃的GitHub RFC#92(它涵盖了提供用于管理持久环境变量的cmdlet)[1]
注意:根据定义(由于使用注册表),此函数仅适用于Windows。
使用下面定义的函数,可以按如下方式执行所需的Path
添加,修改当前用户的持久Path
定义:
Add-Path C:Users921479AppDataLocalSumatraPDF
如果您真的想更新机器级定义(在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_USEREnvironment', 'HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerEnvironment')[$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个字符,其他值被截断,尽管带有警告(至少从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]用于管理持久性环境变量的新cmdlet的原型实现可通过PowerShell库获得,尽管它似乎停滞不前,而且至关重要的是,截至本文撰写之时,它缺乏对可扩展环境变量(REG_EXPAND_SZ
)的支持,这使得它不适合PATH
更新
[2]也就是说,setx.exe
纯粹基于新值中存在的环境变量引用(如%SystemRoot%
)来决定是(重新)创建底层注册表值为REG_SZ
(静态)还是REG_EXPAND_SZ
,而与先前存在的注册表值的当前类型无关
使用setx永久更新环境变量。不要破解注册表。
调用setx之后,只需在当前会话中手动更新Path环境即可。Powershell:在Powershell 中重新加载路径