Windows很多预置的Path项中包含变量,形如:
%SystemRoot%\System32\Wbem
其中%SystemRoot%
就代表一个系统内置变量(Path实际上也是这样的变量),程序或者我们读取这个Path时会自动将之展开成:
C:\WINDOWS\System32\Wbem
这是因为Path是以注册表值的形式存储在系统中的,而它的值类型是REG_EXPAND_SZ
,即可展开字符串。这种自动展开的行为对于要使用Path的程序来说是正确的,或者说它的设计目标就是如此。
但对于小部分用法却会造成一定的副作用,比如,我们需要修改Path。在修改Path时我们首先需要读取Path,我们的预期目标是,读取出Path的字面值,然后加上或者去掉我们要修改的内容,再把它写回注册表。
然而,常规的读取方法,无论是注册表、.net类库还是PowerShell的内置方法,读取Path时返回的都不是字面值而是其中变量展开后的形式。这就造成一个后果:我们修改完Path再写回时,Path中原有条目的形式都被展开了,虽然实际效果没变,但这也是一种副作用。
作为一个强迫症,我不希望我的程序去改动用户原有的不相关配置。所以我一直在寻找解决方案,终于,还是PowerShell官方开源仓库论坛里的老哥提供了有效的方法,问答链接在这里。写法如下:
(Get-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment").GetValue(
"PATH",
"",
[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
而关于管理Path这件事,我也参考网上已有的方法封装了三个函数:
# Author: Curious
# 作者:Curious
function Get-Path-Crs {
[CmdletBinding()]
param (
[Parameter(ParameterSetName = 'GetPath')]
[Switch]$Machine
)
$EnvironmentRegisterKey = 'HKCU:\Environment'
if ($Machine) {
$EnvironmentRegisterKey = 'HKLM:\SYSTEM\ControlSet001\Control\Session Manager\Environment\'
}
(Get-Item -Path $EnvironmentRegisterKey).GetValue(
"PATH",
"",
[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) -split ';'
}
function Add-Path-Crs {
[CmdletBinding()]
param (
# 声明可以接受管道输入的参数 $InputObject ,PowerShell原生cmdlet中承接管道输入的参数都叫这个名字,因此,
# 在自定义函数中尽量也要继承这个写法,以增加代码的可读性。
[Parameter(Mandatory = $true, ParameterSetName = 'AddPath', ValueFromPipeline = $true, Position = 0)]
[String[]]$InputObject,
[Parameter(ParameterSetName = 'AddPath')]
[Switch]$Machine
)
PROCESS {
$EnvironmentRegisterKey = 'HKCU:\Environment'
if ($Machine) {
$EnvironmentRegisterKey = 'HKLM:\SYSTEM\ControlSet001\Control\Session Manager\Environment'
}
$InputCombiner = New-Object -TypeName System.Text.StringBuilder
# 将要添加的值拼接到一个可变字符串中:
$InputObject | ForEach-Object {
[Void]$InputCombiner.Append(';')
[Void]$InputCombiner.Append($_.Trim())
}
# 获取环境变量Path:
$Path = (Get-Item -Path $EnvironmentRegisterKey).GetValue(
"PATH",
"",
[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
if ($Path.EndsWith(';')) {
$Path = $Path.Remove($Path.length - 1, 1)
}
# 更新环境变量Path:
$Path = $Path + $InputCombiner.ToString()
Set-ItemProperty -Path $EnvironmentRegisterKey -Name Path -Value $Path
}
}
function Remove-Path-Crs {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ParameterSetName = 'RemovePath', ValueFromPipeline = $true, Position = 0)]
[String[]]$InputObject,
[Parameter(ParameterSetName = 'RemovePath')]
[Switch]$Machine
)
PROCESS {
$EnvironmentRegisterKey = 'HKCU:\Environment'
if ($Machine) {
$EnvironmentRegisterKey = 'HKLM:\SYSTEM\ControlSet001\Control\Session Manager\Environment'
}
# 遍历 $InputObject ,删除环境变量Path中的对应的路径:
$InputObject | ForEach-Object {
$Path = (Get-Item -Path $EnvironmentRegisterKey).GetValue(
"PATH",
"",
[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
# 在参数开头添加分号,末尾添加反斜杠,以便后续先进行条件最严格的匹配,再逐步退让匹配条件,
# 以免在Path中留下残余的无用符号。
$EntryToRemove = ';' + $_.Trim()
if (!$EntryToRemove.EndsWith('\')) {
$EntryToRemove += '\'
}
$EntryToRemove = [Regex]::Escape($EntryToRemove)
# 检测 $EntryToRemove 在 $Path 中是否有匹配,如果 $EntryToRemove 在 $Path 中没有匹配,
# 存在 $Path 中相应条目不以反斜杠结尾的可能性,尝试去掉 $EntryToRemove 末尾的反斜杠。
if (!($Path -match $EntryToRemove)) {
$EntryToRemove = $EntryToRemove.SubString(0, $EntryToRemove.LastIndexOf('\') - 1)
}
# 检测 $EntryToRemove 在 $Path 中是否有匹配,若存在对应值,则执行删除操作,并提前返回,
# ForEach-Object 将处理管道传入的集合中的下一个对象。
if ($Path -match $EntryToRemove) {
$Path = $Path -replace $EntryToRemove, ""
Set-ItemProperty -Path $EnvironmentRegisterKey -Name Path -Value $Path
return
}
# 如果上述判断没有匹配,考虑到Path的结构形式,那么除了确实没有相应条目以外,还存在相应条目在
# Path第一条,以至于前边没有分号的可能性。接下来尝试使用不加分号的值进行匹配,流程与前述类似。
$EntryToRemove = $_.Trim()
if (!$EntryToRemove.EndsWith('\')) {
$EntryToRemove += '\'
}
# 转义反斜杠以适应正则表达式:
$EntryToRemove = [Regex]::Escape($EntryToRemove)
if (!($Path -match $EntryToRemove)) {
$EntryToRemove = $EntryToRemove.Substring(0, $EntryToRemove.LastIndexOf('\') - 1)
}
if ($Path -match $EntryToRemove) {
$Path = $Path -replace $EntryToRemove, ""
Set-ItemProperty -Path $EnvironmentRegisterKey -Name Path -Value $Path
}
# 如果仍然没有匹配,那么说明确实没有对应条目,流程运行完毕自动退出。
}
}
}
关于这三个函数实现细节的讨论可以参考此链接下的评论区。