如何防止同一PowerShell 7脚本同时运行多个实例?
跨会话/跨机器单实例运行PowerShell脚本的实现方案
你之前尝试的两种方案的核心问题都是没有用到操作系统级的全局资源做互斥校验,下面按可靠性从高到低给出可直接落地的方案:
方案1:全局命名Mutex(单机器场景首选,无竞态风险)
Mutex是操作系统原生提供的跨进程同步原语,带Global\前缀的命名Mutex可以跨所有PowerShell会话、跨用户进程识别,校验逻辑是内核级原子操作,完全不会出现两个实例同时启动的竞态问题。
直接把以下代码放在script.ps1的最开头即可,不需要额外的包装脚本:
# 全局互斥锁名称,自定义即可,注意必须保留Global\前缀 $mutexName = "Global\SingleInstanceLock_script.ps1" $mutex = $null $mutexCreated = $false # 尝试获取互斥锁所有权 $mutex = New-Object System.Threading.Mutex($true, $mutexName, [ref]$mutexCreated) if (-not $mutexCreated) { Write-Warning "检测到已有script.ps1实例运行,本次启动终止" exit 1 } # 注册脚本退出时的锁释放逻辑,兜底避免锁泄漏 Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { if ($mutex) { $mutex.ReleaseMutex() $mutex.Dispose() } } | Out-Null # ===== 以下为script.ps1原有业务逻辑 =====
注意:如果脚本会通过管理员权限和普通权限交叉启动,需要给Mutex配置对应的访问权限,否则普通权限的进程读不到管理员创建的全局Mutex。
方案2:独占文件锁(跨机器场景首选)
如果需要保证构建服务器、远程计算机两台设备上全局只跑一个script.ps1实例,可以用双方都能访问的共享文件做锁,利用文件系统的独占打开特性做互斥校验:
# 锁文件放在两台机器都能读写的共享存储路径下 $lockFilePath = "\\shared-folder\locks\script.ps1.lock" $lockStream = $null try { # 以完全独占模式打开锁文件,其他实例打开时会直接触发异常 $lockStream = [System.IO.File]::Open( $lockFilePath, [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None ) } catch { Write-Warning "检测到已有script.ps1实例运行,本次启动终止" exit 1 } # 注册退出时的锁释放逻辑 Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { if ($lockStream) { $lockStream.Close() $lockStream.Dispose() } } | Out-Null # ===== 以下为script.ps1原有业务逻辑 =====
注意:要保证所有运行脚本的账号对锁文件所在目录有读写权限,避免权限问题导致误判。
方案3:改进CIM进程查询(辅助校验方案,不推荐作为唯一判断逻辑)
你之前查Win32_Process拿不到对应脚本信息,是因为没有读取进程的CommandLine属性——这个属性会记录pwsh进程启动时的完整命令行参数,可以直接过滤出运行script.ps1的进程:
$runningInstance = Get-CimInstance Win32_Process -Filter "Name='pwsh.exe'" | Where-Object { $_.CommandLine -match [regex]::Escape("script.ps1") } if ($runningInstance.Count -gt 0) { Write-Warning "检测到已有script.ps1实例运行,对应PID: $($runningInstance.ProcessId -join ', ')" exit 1 }
这个方案的明显缺陷:
- 存在竞态窗口:如果两个实例同时执行查询,可能都检测不到运行中的实例,进而同时启动
- 匹配存在误差:如果其他脚本的路径或名称包含script.ps1字符串,会被误判为运行中实例
- 如果进程启动后修改了命令行参数(极端场景),会出现漏判
另外补充说明你之前用PowerShell后台作业的方案为什么失效:PowerShell Job是会话级资源,所有Job元数据都存在当前会话的内存里,会话关闭后Job元数据会直接销毁,哪怕Job对应的子进程还在后台运行,其他会话也完全访问不到对应的Job信息,天生不适合跨会话的单实例控制。
内容的提问来源于stack exchange,提问作者Joseph Saliba
相关产品推荐
相关产品推荐

