使用 Powershell 将路径永久添加到窗口似乎不起作用



我遵循了这个过程,以便使用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-ItemPropertyGet-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 中重新加载路径

最新更新