PowerShell中Foreach -Parallel未并行运行的问题解决
问题描述
需要通过SMB协议检测多台计算机上\\$computer\users\foo\路径是否存在。由于大量计算机可能离线,SMB请求超时会耗费数秒,因此尝试用Foreach -Parallel实现并行执行,但实际运行速度和普通foreach无差异,未达到并行效果。
用于获取最近14天登录过的计算机并检测路径的代码如下:
workflow Find-FoosComputer { $computers = Get-Adcomputer -filter * -properties name,LastLogon | where {$_.lastlogon -ne $NULL} | where {[datetime]($_.lastlogon) -le (get-date).AddDays(-14)}|select -expandproperty name foreach -parallel ($computer in $computers){ ($computer, (Test-Path "\\$computer\users\foo\")) > "c:\temp\results.txt" } }
请问如何让Foreach -Parallel真正实现并行,同时对所有计算机执行Test-Path检测?
解决方案
1. 核心问题分析
你的代码没实现真正并行的关键原因有两个:
- Workflow的
foreach -parallel默认并行度极低,且本身有额外性能开销; - 所有并行任务直接写入同一个文件,触发文件锁冲突,任务被迫串行等待。
推荐放弃Workflow,改用PowerShell原生并行特性,效率更高。
2. 实用并行方案
方案一:PowerShell 7+ 用ForEach-Object -Parallel
这是当前最简洁高效的并行方式:
# 获取最近14天登录过的计算机列表 $computers = Get-AdComputer -Filter * -Properties Name, LastLogon | Where-Object { $_.LastLogon -ne $null -and [datetime]$_.LastLogon -ge (Get-Date).AddDays(-14) } | Select-Object -ExpandProperty Name # 并行检测,控制并发数避免网络拥堵 $results = $computers | ForEach-Object -Parallel { $computer = $_ $timeout = 5 # 超时时间(秒) $pathExists = $false # 先快速检测机器是否在线,减少SMB等待时间 if (Test-Connection -ComputerName $computer -Count 1 -Quiet -TimeoutSeconds 2) { # 用后台作业给Test-Path加超时 $job = Start-Job -ScriptBlock { Test-Path "\\$using:computer\users\foo\" } if (Wait-Job $job -Timeout $timeout) { $pathExists = Receive-Job $job } Remove-Job $job -Force } # 返回结构化结果 [PSCustomObject]@{ ComputerName = $computer PathExists = $pathExists Status = switch ($pathExists) { $true { "在线且路径存在" } $false { "在线但路径不存在" } default { "超时/离线" } } } } -ThrottleLimit 20 # 并发数根据环境调整,建议10-30 # 统一写入文件,避免冲突 $results | Export-Csv -Path "c:\temp\results.csv" -NoTypeInformation -Encoding UTF8
方案二:PowerShell 5.x 用Start-Job实现并行
如果仍在使用PowerShell 5.x,用后台作业实现:
$computers = Get-AdComputer -Filter * -Properties Name, LastLogon | Where-Object { $_.LastLogon -ne $null -and [datetime]$_.LastLogon -ge (Get-Date).AddDays(-14) } | Select-Object -ExpandProperty Name # 批量创建后台作业 $jobs = $computers | ForEach-Object { Start-Job -ScriptBlock { $computer = $args[0] $pathExists = $false try { if (Test-Connection -ComputerName $computer -Count 1 -Quiet -TimeoutSeconds 2) { $pathExists = Test-Path "\\$computer\users\foo\" } } catch {} [PSCustomObject]@{ ComputerName = $computer PathExists = $pathExists } } -ArgumentList $_ } # 等待所有作业完成并收集结果 $results = $jobs | Wait-Job | Receive-Job # 清理后台作业 $jobs | Remove-Job -Force # 写入结果文件 $results | Export-Csv -Path "c:\temp\results.csv" -NoTypeInformation -Encoding UTF8
3. 若坚持使用Workflow的修复方法
如果必须用Workflow,需修改文件写入逻辑并调整并行度:
workflow Find-FoosComputer { $computers = Get-Adcomputer -filter * -properties name,LastLogon | Where-Object { $_.lastlogon -ne $NULL -and [datetime]($_.lastlogon) -ge (get-date).AddDays(-14) } | Select-Object -expandproperty name # 先收集所有并行结果,再统一写入 $results = foreach -parallel -ThrottleLimit 20 ($computer in $computers){ $timeout = 5 $pathExists = $false # 用InlineScript+后台作业实现超时控制 $job = InlineScript { $comp = $using:computer if (Test-Connection -ComputerName $comp -Count 1 -Quiet -TimeoutSeconds 2) { Test-Path "\\$comp\users\foo\" } else { $false } } -AsJob if (Wait-Job $job -Timeout $timeout) { $pathExists = Receive-Job $job } Remove-Job $job -Force @($computer, $pathExists) } # 统一写入文件,避免锁冲突 $results | Out-File "c:\temp\results.txt" -Encoding UTF8 }
注意:Workflow性能不如原生并行方案,仅作为兼容选择。
内容的提问来源于stack exchange,提问作者Strill
相关产品推荐
相关产品推荐

