繁体   English   中英

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

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

我按照此过程使用 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设置的路径不一致? 我遵循的链接中显示的程序是否有错误? 我该如何纠正?

我应该提到我已经尝试重新启动外壳,但它没有帮助。

笔记:

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

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


链接博客文章中程序原则上是有效,但缺少一个关键信息/附加步骤

如果您直接通过注册表修改环境变量- 不幸的是,这是对基于REG_EXPAND_SZ的环境变量(例如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# 中通过Add-TypeSendMessageTimeout()进行临时编译的 P/Invoke 调用来调用 Windows API 的解决方法:

    • 虽然这是一个合适的解决方案,但由于第一次在会话中运行时的临时编译,它总是会导致明显的性能损失

    • 有关详细信息,请参阅此博客文章

博客文章中的方法还有另一个问题

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

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


以下Add-Path辅助函数

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

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

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

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

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

注意:根据定义(由于使用注册表),此功能仅适用于 Windows

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

Add-Path C:\Users\921479\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 个字符,其他字符会被截断,尽管有警告(至少从 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问题。

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

调用 setx 后,只需在当前会话中手动更新 Path 环境。 Powershell:在 PowerShell 中重新加载路径

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM