使用 powershell 将路径永久添加到 windows 似乎不起作用

Adding path permanently to windows using powershell doesn't appear to work

我遵循 this procedure 以便使用 powershell 永久添加 SumatraPDF 的路径。 link 中的最后几个命令用于检查路径是否确实已添加。

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

(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:\texlive21\bin\win32
C:\Users1479\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:\texlive21\bin\win32
C:\Users1479\AppData\Local\Microsoft\WindowsApps

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

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

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

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

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

注:

  • 辅助函数见中间部分Add-Path

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


linked blog post 中的 程序在原则上 是有效的 ,但缺少一条关键信息/附加步骤 :

如果直接通过注册表修改环境变量 - 不幸的是,正确 为基于 REG_EXPAND_SZ 的环境变量(例如 Path 执行此操作的方法 - 您需要广播一条 WM_SETTINGCHANGE 消息 这样 Windows (GUI) shell(及其组件、文件资源管理器、任务栏、桌面、开始菜单,全部通过 explorer.exe 进程提供)是收到环境更改通知,并从注册表中重新加载 其环境变量。之后启动的应用程序会继承更新后的环境。

  • 如果此消息发送,以后的 PowerShell 会话(和其他应用程序)将在下次登录/重新启动之前看不到修改。

遗憾的是,没有从 PowerShell 直接执行此操作的方法,但有解决方法

  • 蛮力 解决方法 - 简单,但 视觉上具有破坏性并关闭所有打开的文件资源管理器 windows:

    # 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 APIs:

    的解决方法
    # 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')
    
  • 解决方法是 通过临时编译的 P/Invoke 调用 Windows API [=21] =] 在 C# 中,通过 Add-Type:

    • 虽然这是一个合适的解决方案,但由于首次 运行 在会话中。

    • 详情见this blog post

博客 post 中的方法有 另一个有问题的方面:

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

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


以下Add-Path辅助函数

  • 默认将给定的单个目录路径添加(附加)到持久的用户级Path环境变量;使用 -Scope Machine 机器级别 定义为目标,这需要 elevation(运行 作为管理员)。

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

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

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

  • 同时更新当前会话的$env:Path变量值。

注意:根据定义(由于使用了注册表),此函数是Windows-only.

使用下面定义的函数,您想要的 Path 添加可以按如下方式执行,修改 当前用户的 持久性 Path 定义:

Add-Path C:\Users1479\AppData\Local\SumatraPDF

如果您真的想更新 机器级别 定义(在 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_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 个字符,额外的字符将 t运行 分类 ,尽管带有 警告(至少 Windows 10)。

  • (重新)创建的环境变量始终是 REG_SZ 类型,而 Path 最初是 REG_EXPAND_SZ 类型并且包含基于其他 环境变量,例如%SystemRoot%%JAVADIR%.

    • 如果替换值仅包含 文字 路径(无环境变量引用)可能没有 立即 不良影响,但是,例如,如果稍后更改 %JAVADIR% 的值,则最初依赖于 %JAVADIR% 的条目将停止工作。
  • 此外,如果您将更新值基于当前会话的 $env:Path 值,您将最终 复制 个条目,因为 进程级别 $env:Path 值是机器级别和当前用户级别值的复合

    • 这会增加 运行 进入 1024 个字符限制的风险,特别是如果 重复使用该技术 。它还承担了从原始范围中删除原始条目后重复值挥之不去的风险。

    • 虽然您可以通过直接从注册表中检索特定范围的值来避免此特定问题,或者 - 始终以 扩展 形式 - 通过 [Environment]::GetEnvironmentVariable('Path', 'User')[Environment]::GetEnvironmentVariable('Path', 'Machine'),仍然没有解决上面讨论的 REG_EXPAND_SZ 问题。