【PowerShell模组开发日志】环境变量路径`$Env:Path`的简单管理(添加、删除、去重)
本文是PowerShell模组 PSComputerManagementZp
的使用案例分享。记录了通过该自定义模组,管理多个层级$Env:Path
的过程。包括添加、删除、去重等。本文的案例非常适合于使用PowerShell进行系统管理,工具调用等行为的场景。
基本概念与动机
环境变量:环境变量是一个动态命名的值,可以影响计算机上进程的行为方式。例如一个正在运行的进程可以查询TEMP环境变量的值,以发现一个合适的位置来存储临时文件,或者查询HOME或USERPROFILE变量,以找到运行该进程的用户所拥有的目录结构。
$Env:Path
: PATH是类Unix系统、DOS、OS/2和Microsoft Windows操作系统上的一个环境变量,用于设置一组包含可执行文件的目录。
在所有的环境变量中,我们关心的最多的,一般就是$Env:Path
,因为其涉及诸多软件工具之间的相互配合与调用流程。当我们使用PowerShell等命令工具(或者说是shell)还安装使用
Miniconda, MSYS2
等带有包管理器,实现独立的包管理模式
的工具时,$Env:Path
将可能给我们带来一些不便之处。
Windows 系统环境变量的三个层级
参考 EnvironmentVariableTarget
Enum (System),在Windows系统中,有三个级别的环境变量,分别是
Machine
级别 、Process
级别和User
级别。如果将“控制和管理Windows系统”的过程抽象为“用户通过一定的接口与行为模式,与系统进行交互”,那么Windows系统的特殊性就在于,通过GUI和通过命令行,都可以实现大部分的用户需求。具体到环境变量时,我们至少有三种用户层面的控制环境变量的方案:
- 通过注册表(registry)编辑器GUI控制:修改和存储
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment
和HKEY_CURRENT_USER\Environment
的记录值。 - 通过Windows系统设置GUI控制:打开
设置>系统>系统信息>高级系统设置>环境变量
进行修改和存储。 - 通过命令行控制:可以通过cmd或者PowerShell,以及对应的命令,修改和存储环境变量。
上述三种方法之所以称之为用户层面,是因为其实现逻辑在系统层面可能是有重叠的、甚至是一致的。但是作为用户而言,我们不需要也不应当去关心系统层面的逻辑,只需要关注自己的行为模式是否有区分性即可。
$Env:Path
的不便之处
一般情况下,当我们在Windows终端使用PowerShell时,使用
Process
级别的环境变量,其会按照一定的优先级,包含并整合Machine
级别和
User
级别环境变量中设置的内容。鉴于控制环境变量的方式很多,意味着环境变量值的来源就很多,在我们日常使用中,就很可能遇到一些问题,特别是环境变量中的路径$Env:Path
:
Machine
级别和User
级别环境变量$Env:Path
中存在重复:安装某些软件时,会根据用户的选择,向系统注入Machine
级别或者User
级别$Env:Path
追加内容,但卸载软件时,不一定会自动清除。如果用户前后反复卸载,安装,且选择不一致,可能会导致重复。这种重复不会直接导致问题,但会构成隐患。可能会导致根据
$Env:Path
寻找到软件或工具时,其版本不是目标版本等问题。Machine
级别和User
级别环境变量$Env:Path
中存在空值:某些软件卸载后,虽然会清除$Env:Path
中的相应路径,但可能会引入空值。这种空值不会直接导致问题,且大部分情况下,都不影响使用。但是,如果基于
$Env:Path
的下游程序,需要对$Env:Path
进行继承、修改时,空值可能会导致一些意外情况,尤其是空值出现在$Env:Path
的中部而非首尾时。
再例如:当我们同时使用Miniconda, MSYS2 等带有包管理器,实现独立的包管理模式 的工具时,我们如果想安装Git,就可以有三种选择:
- 安装到
conda
的虚拟环境中 - 安装到
msys2
的环境中 - 安装到一般的Windows程序文件夹
Program Files
中
然后,我们可能会在$Env:Path
的多个子条目路径中,寻找到git.exe
。为了确保使用的是我们期望的git.exe
,
我们可能会在PowerShell的配置文件$PROFILE.CurrentUserAllHosts
中进行提前的处理,将我们期望的路径前置。如果我们每安装一个应用,调用时都要考虑类似于git.exe
在$Env:Path
中的前置问题,将会比较繁琐,麻烦。
动机总结
上述$Env:Path
中的处理过程尽管麻烦,但是其本身依旧是不可或缺的。但是,这套处理过程是可以整合的,以减少在$PROFILE.CurrentUserAllHosts
配置文件中的配置行数,让用户从关心如何处理$Env:Path
的过程中解放出来,只需要关心是否需要将某个路径加入到$Env:Path
中。因此,PowerShell模组
PSComputerManagementZp
将一些处理$Env:Path
的逻辑进行了打包和封装,参见EnvPath.ps1
和 Manager.Env.ps1。后文就将介绍使用这套逻辑的案例,以更便捷的方式管理$Env:Path
。
准备工作
本节简述如何配置工具与环境
将PowerShell更新到7.0以上
可以直接从Release of PowerShell · PowerShell/PowerShell (github.com) 下载安装,或者使用 winget-cli:
1 | winget search powershell |
安装PowerShell模组PSComputerManagementZp
参考 PowerShell Gallery | PSComputerManagementZp 0.1.0:
1 | Install-Module -Name PSComputerManagementZp -Force |
使用案例
以管理员权限运行PowerShell,再下面的流程中不要退出该shell窗口。
以下所有函数,都基于一个自定义的类 EnvPath,该类被初始化时,会自动对每个层级的$Env:Path
内部去重,删除空值和点值.
面向Process
级别$Env:Path
的函数,同样适用于Linux和Mac系统中,已被PSComputerManagementZp支持,并通过了测试。
将Machine
层级与User
层级的$Env:Path
中的重复项合并到User
层级
1 | Merge-RedundantEnvPathFromCurrentMachineToCurrentUser |
此函数将帮助用户,检查Machine
层级与User
层级的$Env:Path
中的重复项。对于重复项,将删除Machine
层级中的,只保留User
层级中的。这个方法非常适用于单用户的Windows系统,曾经多次以不同权限安装、卸载过某个软件,而$Env:Path
残留了安装路径的情况。
对于这种行为,可能会有人不解、困惑甚至反对。但此处提现的思想是,希望用户配置对系统的影响范围最小。即使用户是管理员,我们也认为,用户的配置应当尽可能只影响以其账号登录的系统,而尽可能不影响其他账号登录的系统。如果保留Machine
层级,删除User
层级,那么,新用户将拥有其不知晓的$Env:Path
内容,这在一般意义上,我们认为是不够好的。当然,这个做法不完美,最终用户是否选择使用,需要结合具体情况考虑。
将某个路径添加到$Env:Path
添加到当前
Process
级别$Env:Path
的开头:添加到当前
Process
级别意味着仅在当前运行的PowerShell的Scope有效,重启PowerShell等重置Scope的方法将会导致对当前Process
级别$Env:Path
的配置失效。但这也是我们在命令行中使用一些工具的一般做法,即,使用时再临时添加,不保存,以避免不同场景下的路径冲突。1
Add-PathToCurrentProcessEnvPath -Path 'C:\Program Files\Git\cmd' # Default is prepend
添加到当前
Process
级别$Env:Path
的末尾:1
Add-PathToCurrentProcessEnvPath -Path 'C:\Program Files\Git\cmd' -IsAppend
添加到当前
User
级别$Env:Path
的开头:1
Add-PathToCurrentUserEnvPath -Path 'C:\Program Files\Git\cmd' # Default is prepend
添加到当前
User
级别$Env:Path
的末尾:1
Add-PathToCurrentUserEnvPath -Path 'C:\Program Files\Git\cmd' -IsAppend
添加到当前
Machine
级别$Env:Path
的开头:1
Add-PathToCurrentMachineEnvPath -Path 'C:\Program Files\Git\cmd' # Default is prepend
添加到当前
Machine
级别$Env:Path
的末尾:1
Add-PathToCurrentMachineEnvPath -Path 'C:\Program Files\Git\cmd' -IsAppend
将某个路径从$Env:Path
中删除
从当前
Process
级别的$Env:Path
中删除:1
Remove-PathFromCurrentProcessEnvPath -Path 'C:\Program Files\Git\cmd'
从当前
User
级别的$Env:Path
中删除:1
Remove-PathFromCurrentUserEnvPath -Path 'C:\Program Files\Git\cmd'
从当前
Machine
级别的$Env:Path
中删除:1
Remove-PathFromCurrentMachineEnvPath -Path 'C:\Program Files\Git\cmd'
上述命令会变量所有$Env:Path
子项,当检测到子项值与给定的输入$Path
一致时,对其进行删除操作。判断一致的逻辑与PowerShell字符串的相等运算符一致。
将匹配某个Pattern的路径从$Env:Path
中删除
从当前
Process
级别的$Env:Path
中删除:1
Remove-MatchedPathsFromCurrentProcessEnvPath -Pattern 'Git'
从当前
User
级别的$Env:Path
中删除:1
Remove-MatchedPathsFromCurrentUserEnvPath -Pattern 'Git'
从当前
Machine
级别的$Env:Path
中删除:1
Remove-MatchedPathsFromCurrentMachineEnvPath -Pattern 'Git'
上述命令会变量所有$Env:Path
子项,当检测到子项值与给定的输入$Pattern
匹配时,进行删除操作。判断匹配的逻辑与PowerShell字符串的匹配运算符一致。
总结与讨论
本文是PowerShell模组 PSComputerManagementZp
的使用案例分享。可以更方便地帮助用户,在使用PowerShell时,管理
$Env:Path
。上述的案例非常适合于使用PowerShell、同时使用Miniconda, MSYS2
等带有包管理器,实现独立的包管理模式
的工具的场景。
- 缺点:需要安装PowerShell模组
- 优点:让用户从关心如何处理
$Env:Path
的过程中解放出来,只需要关心是否需要将某个路径加入到$Env:Path
中。