如何优化PowerShell批量计算机信息采集脚本以实现多线程并行执行提升效率?
如何优化PowerShell批量计算机信息采集脚本以实现多线程并行执行提升效率?
我太懂你这种每周跑脚本熬几小时的痛苦了!串行一台接一台处理确实效率拉胯,咱们直接改成并行执行就能把速度提升好几倍,下面给你拆解优化思路和修改后的脚本:
原脚本的核心问题
- 用
foreach串行循环,必须等一台电脑处理完才会开始下一台,完全浪费了网络和本地的空闲资源 - 多次单独调用
Invoke-Command,每一次都要建立新的远程连接,额外增加了不少耗时 - 强制重启WinRM服务的操作有点粗暴,除非你确定每次都需要,不然这步其实可以优化甚至移除
优化方案:用PowerShell原生的并行能力
PowerShell本身就有很好的并行处理工具,最适合这个场景的是Invoke-Command(它默认就会并行处理多台计算机),或者PS7+的ForEach-Object -Parallel。下面给你两种可行的优化版本:
版本1:兼容性最好(支持PS5及以上)
用Invoke-Command批量处理在线计算机,同时合并远程脚本块减少连接次数,再单独处理离线设备:
$localComputer = "MyComputerName" $computers = Import-Csv "\\$localComputer\path\computers.csv" $computerNames = $computers.name # 第一步:先筛选出在线的计算机 $onlineComputers = $computerNames | Where-Object { Test-Connection $_ -Count 1 -Quiet } $offlineComputers = $computerNames | Where-Object { $_ -notin $onlineComputers } # 第二步:并行采集在线计算机的信息 $onlineResults = Invoke-Command -ComputerName $onlineComputers -ScriptBlock { # 合并所有需要的查询,一次完成减少远程交互 $systemInfo = Get-CimInstance Win32_ComputerSystem $biosInfo = Get-CimInstance Win32_BIOS $osInfo = Get-ItemProperty -Path "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion" [PSCustomObject]@{ computer = $env:COMPUTERNAME ping = "Online" model = $systemInfo.Model serviceTag = $biosInfo.SerialNumber osVersion = $osInfo.DisplayVersion } } | Select-Object computer, serviceTag, model, osVersion, ping # 第三步:处理离线计算机的结果 $offlineResults = $offlineComputers | ForEach-Object { [PSCustomObject]@{ computer = $_ ping = "Offline" model = "N/A" serviceTag = "N/A" osVersion = "N/A" } } # 合并所有结果并导出 $allResults = $onlineResults + $offlineResults $allResults | Export-Csv "\\$localComputer\path\results.csv" -NoTypeInformation
版本2:PS7+专属(更简洁)
如果你的环境已经升级到PowerShell 7及以上,用ForEach-Object -Parallel可以更灵活地控制并行度:
$localComputer = "MyComputerName" $computers = Import-Csv "\\$localComputer\path\computers.csv" $allResults = $computers | ForEach-Object -Parallel { $computerName = $_.name $pingTest = Test-Connection $computerName -Count 1 -Quiet if (-not $pingTest) { [PSCustomObject]@{ computer = $computerName ping = "Offline" model = "N/A" serviceTag = "N/A" osVersion = "N/A" } } else { try { # 这里可以保留WinRM重启逻辑(如果确实需要),但建议先测试是否真的需要 # Get-Service -ComputerName $computerName -Name winrm | Restart-Service -Force # Start-Sleep -Seconds 1 $systemInfo = Get-CimInstance Win32_ComputerSystem -ComputerName $computerName $biosInfo = Get-CimInstance Win32_BIOS -ComputerName $computerName $osInfo = Invoke-Command -ComputerName $computerName -ScriptBlock { Get-ItemProperty -Path "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion" } [PSCustomObject]@{ computer = $computerName ping = "Online" model = $systemInfo.Model serviceTag = $biosInfo.SerialNumber osVersion = $osInfo.DisplayVersion } } catch { # 处理远程连接失败的情况(比如WinRM没开) [PSCustomObject]@{ computer = $computerName ping = "Online但连接失败" model = "N/A" serviceTag = "N/A" osVersion = "N/A" } } } } -ThrottleLimit 20 # 控制并行数量,避免网络过载 $allResults | Select-Object computer, serviceTag, model, osVersion, ping | Export-Csv "\\$localComputer\path\results.csv" -NoTypeInformation
几个额外的优化小技巧
- 把
Test-Connection换成Test-NetConnection或者直接依赖Invoke-Command的错误处理,有时候ping禁用但WinRM能通的设备会被误判 - 调整
-ThrottleLimit参数(默认是32),根据你的网络情况设置合适的并行数量,太多可能会导致网络拥堵 - 如果必须重启WinRM,建议只在
Invoke-Command失败时再尝试,而不是每次都重启 - 用
Get-CimInstance代替Get-WmiObject,WMI已经被CIM取代,性能和兼容性更好
备注:内容来源于stack exchange,提问作者IT4Gov




