【问题标题】:Adding path permanently to windows using powershell doesn't appear to work使用 powershell 将路径永久添加到 Windows 似乎不起作用
【发布时间】:2021-11-13 02:39:45
【问题描述】:

我关注 this procedure 是为了使用 powershell 永久添加到 SumatraPDF 的路径。链接中的最后几个命令用于检查是否确实添加了路径。

当我使用以下命令访问路径时,

(get-itemproperty -path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path.split(';')

结果包括 SumatraPDF 的路径

C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\Windows\System32\OpenSSH\
C:\ProgramData\chocolatey\bin
C:\texlive\2021\bin\win32
C:\Users\921479\AppData\Local\SumatraPDF

但是,当我使用以下命令访问它时,

($env:path).split(';')

结果不包含 SumatraPDF 的路径:

C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\Windows\System32\OpenSSH\
C:\ProgramData\chocolatey\bin
C:\texlive\2021\bin\win32
C:\Users\921479\AppData\Local\Microsoft\WindowsApps

最后,实际上传递sumatrapdf 不起作用,这表明真正的路径是使用get-itemproperty 命令访问的路径。

为什么注册表中设置的路径与$env:path中设置的路径不对应?我遵循的link中显示的过程是否有错误?我该如何纠正?

我应该提一下,我已经尝试过重新启动 shell,但它没有帮助。

【问题讨论】:

    标签: windows powershell environment-variables


    【解决方案1】:

    注意:

    • 帮助函数Add-Path

      见中间部分
    • 请参阅底部为什么应避免使用setx.exe 来更新Path 环境变量


    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 来解决此问题:

      • 虽然这是一个合适的解决方案,但它总会招致明显的性能损失,因为它是在会话中首次运行时进行的临时编译。

      • 详情请见this blog post

    博文中的方法有另一个问题方面

    • 从注册表中检索扩展的环境变量值,因为Get-ItemPropertyGet-ItemPropertyValue 总是这样做。也就是说,如果值中的目录是根据其他环境变量定义的(例如,%SystemRoot%%JAVADIR%),则返回的值不再包含这些变量,而是它们的当前价值观。请参阅底部部分,了解为什么会出现问题。

    下一节讨论的辅助函数解决了所有问题,同时还确保修改对当前会话也生效。


    以下Add-Path辅助函数

    • 默认情况下将给定的单个目录路径添加(附加)到持久用户级 Path 环境变量;使用 -Scope Machine 定位 machine-level 定义,这需要 elevation(以管理员身份运行)。

      • 如果目录已经存在于目标变量中,则不采取任何行动。

      • 相关的注册表值是updated,它保留了它的REG_EXPAND_SZ数据类型,基于现有的unexpanded值——也就是说,保留了对其他环境变量的引用像这样(例如,%SystemRoot%),也可以用于正在添加的新条目中。

    • 触发WM_SETTINGCHANGE 消息广播以通知 Windows shell 更改。

    • 同时更新当前会话的 $env: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 问题。

    【讨论】:

      【解决方案2】:

      使用setx 永久更新环境变量。不要破解注册表。

      调用 setx 后,只需在当前会话中手动更新 Path 环境即可。 Powershell: Reload the path in PowerShell

      【讨论】:

      • 我使用setx 让它工作,就像这样:setx PATH "$env:path;C:\Users\921479\AppData\Local\SumatraPDF"。我猜写我所遵循的程序的人基本上不知道他们在说什么。
      • 使用setx.exe 很诱人,但应该避免,因为setx.exe 具有使其存在问题的基本限制,特别是它缺乏对更新环境变量的支持,例如基于PathREG_EXPAND_SZ 类型的注册表值上,通常是因为不支持超过 1024 个字符的值。
      猜你喜欢
      • 2018-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-12
      • 1970-01-01
      • 2021-05-11
      • 2013-10-11
      • 1970-01-01
      相关资源
      最近更新 更多