PowerShell多重任务处理:并行脚本实战与性能优化指南
发布时间:2026/9/30 15:36:49来源:尧图网络
1. 写在前面别再让脚本傻等了搞PowerShell的人迟早会遇到同一个尴尬场景写好的脚本要处理200台服务器的信息循环跑一圈下来咖啡都凉了还没跑完。更难受的是脚本里头每个操作都是按顺序排队执行前面的卡住后面全部干瞪眼。这时候你就需要让PowerShell学会一心多用——也就是多重任务处理。PowerShell的多任务处理说白了就是让脚本在等待某件事的同时别闲着继续干别的活。它跟你在手机上一边下载大文件一边刷视频是一个道理。面对批量文件操作、多台主机连通性检测、大量HTTP请求、日志采集这类I/O密集场景并行处理带来的时间收益是肉眼可见的经常能把几十分钟的活压缩到几分钟。这篇文章我打算从最常用的几种玩法讲起覆盖ForEach-Object -Parallel、Start-Job后台作业、Runspace并发运行再到实际脚本怎么写、踩过哪些坑一次性讲透。不管你是刚接触PowerShell的新手还是已经写了几年脚本的老手这套内容都能让你少走不少弯路。2. 动手之前先想清楚串行还是并行2.1 为什么默认是一个接一个执行PowerShell脚本的默认执行方式跟大多数编程语言一样是一行一行往下走。这个设计本身没有错因为大多数时候我们的操作之间存在依赖关系先创建文件夹才能往里面复制文件先获取Token才能发起带认证的请求。一旦打乱顺序整个逻辑就崩了。所以多重任务处理的第一步不是急着写并行代码而是先判断你的任务到底能不能并行。我一般用两个问题来判断任务之间有没有数据依赖如果B操作的结果需要用到A操作的输出那基本注定只能串行。任务的瓶颈是CPU计算还是外部等待如果是请求网络、读取磁盘、等待服务响应这类空闲等待场景并行的收益最大。2.2 三类场景对照该用哪种并行方式PowerShell做多重任务可选方案不少但各有各的适用面。我把日常最常用的三种整理成了对照表你可以根据自己的PowerShell版本和任务类型直接选型并行方式适用版本适用场景优缺点ForEach-Object -ParallelPowerShell 7简单的并行循环无需复杂通信写法最简单但只能在管道里用Start-Job Receive-JobPowerShell 3.0需要独立进程跑任务数据量不大兼容老版本但启动慢、开销大Runspace/RunspacePoolPowerShell 3.0追求性能和并发控制脚本积累到一定规模性能最好但代码复杂度较高异步委托 BeginInvokePowerShell 3.0需要让脚本界面保持响应处理UI线程时有用但用得不多2.3 并行不是银弹小任务反而更慢这里必须泼一盆冷水。并行处理有开销尤其是创建线程、序列化数据、合并结果这些环节都要花时间。如果任务本身只需要几毫秒就完成比如读取一个只有10行的配置文件用并行的方式反而比直接循环更慢。我自己的经验是单个任务的耗时在1秒以上并且任务数量在10个以上并行才真正有意义。至于那些本身就是内存计算、几十毫秒就结束的活老老实实用普通循环就好别折腾。3. PowerShell 7的并行利器ForEach-Object -Parallel3.1 从一串代码看并行循环的基本用法PowerShell 7引入的ForEach-Object -Parallel是我现在处理批量任务的首选。先看一个最简单的例子批量检测一批IP地址的80端口是否开放。$ipList ( 192.168.1.1, 192.168.1.2, 192.168.1.3, 192.168.1.4, 192.168.1.5 ) $ipList | ForEach-Object -Parallel { $result Test-NetConnection -ComputerName $_ -Port 80 -WarningAction SilentlyContinue if ($result.TcpTestSucceeded) { [PSCustomObject]{ IP $_ Port80 Open } } else { [PSCustomObject]{ IP $_ Port80 Closed } } }Test-NetConnection是个比较费时的命令对单个IP能等上好几秒。五个IP串行跑可能就要二十秒用了并行以后它们几乎是同时开工整体时间缩短到最快那个机器的响应速度。这就是-Parallel参数最大的意义——把ForEach-Object处理每个元素时的逻辑扔到独立的线程里同时跑。3.2 控制并发数量ThrottleLimit参数直接无脑并行有时会出问题。比如你要同时访问同一个数据库并发一多数据库直接给你报连接数超限或者要同时往同一个远程共享目录写文件写冲突就来了。这时候必须限制同时运行的任务数量。-ThrottleLimit参数就是干这个的。默认值是$env:PSCOMPUTERNAME对应的处理器数量乘以500实测下来这个默认值在很多场景下偏激进。我一般手动指定$fileList Get-ChildItem -Path C:\Logs -Filter *.log $fileList | ForEach-Object -Parallel { $content Get-Content -Path $_.FullName -Tail 100 if ($content -match ERROR) { [PSCustomObject]{ File $_.Name HasError $true } } } -ThrottleLimit 10这里的-ThrottleLimit 10表示最多同时处理10个文件剩下的排队等候。如果你要访问的是外部API接口对方一般会限制每秒请求数这时候把并发数压到5甚至3才不会触发封禁。3.3 变量作用域$using:到底该怎么用用ForEach-Object -Parallel的时候最容易翻车的就是变量引用。并行脚本块运行在独立的线程空间中它看不到外部脚本里的普通变量。比如下面的写法会报错$threshold 1024 $files Get-ChildItem C:\Temp # 错误的写法parallel块里找不到$threshold $files | ForEach-Object -Parallel { if ($_.Length -gt $threshold) { $_.Name } }正确做法是使用$using:修饰符把外部变量显式传递到并行线程中$threshold 1024 $files Get-ChildItem C:\Temp $files | ForEach-Object -Parallel { if ($_.Length -gt $using:threshold) { $_.Name } }$using:threshold相当于把外部变量的值做了一份快照在并行脚本块内部可以正常读取。需要注意$using:只能传值不能把结果写回外部变量。在并行块内部对变量做修改不会影响外层。要是想把所有并行结果汇总起来正确做法是让并行块输出结果再用管道接收后统一处理就像前面代码里直接输出[PSCustomObject]那样。3.4 ForEach-Object -Parallel解决不了的场景ForEach-Object -Parallel虽好但它本质上还是对每个元素执行一段脚本它的缺点是不容易做复杂的任务组合比如一个任务跑完以后要根据结果决定是否启动另一个任务。不太适合需要长时间驻留的后台任务。如果你只是想启动一个任务让它慢慢跑过一会儿再来看结果用后台作业更合适。共享状态非常麻烦多个并行任务之间无法共享同一个哈希表并保证线程安全容易出现数据竞争。遇到这些情况往下看后台作业和Runspace。4. 经典方案Start-Job后台作业机制4.1 Start-Job到底是什么逻辑Start-Job是PowerShell老牌的多任务方案从PowerShell 3.0开始就有。它做的事情是在后台启动一个新的PowerShell进程在这个进程里跑你的脚本块。父进程和子进程之间通过PowerShell的作业基础设施通信。打个比方Start-Job就像你叫了五个外卖小哥分别去五家店取餐。你只管下单不用站在店里等等小哥陆续回来你再统一清点。启动后台作业的代码很简单$job1 Start-Job -ScriptBlock { Get-Process -Name notepad } $job2 Start-Job -ScriptBlock { Get-Service | Where-Object Status -eq Running } # 等所有作业完成 Wait-Job $job1, $job2 # 收取结果 Receive-Job $job1, $job24.2 作业生命周期管理启动、等待、收取、清理我见过不少初学者写完Start-Job后不接Wait-Job和Receive-Job直接运行结果屏幕上一片空白就以为代码写错了。这里补一个完整的作业生命周期管理流程启动作业Start-Job立即返回一个Job对象脚本在后台跑不会阻塞你的当前会话。等待完成Wait-Job会阻塞当前会话直到指定作业执行完毕。也可以加-Timeout参数设置最长等待秒数。接收结果Receive-Job获取作业的输出结果。记住结果只能接收一次如果没保存再次执行Receive-Job得到的是空的。清理作业Remove-Job删除作业对象释放资源。不删的话作业对象会一直挂在当前会话里占用内存。$job Start-Job -ScriptBlock { 1..100 | ForEach-Object { Start-Sleep -Milliseconds 100 } } # 最多等30秒 Wait-Job $job -Timeout 30 | Out-Null if ($job.State -eq Completed) { $result Receive-Job $job $result | Measure-Object | Select-Object -ExpandProperty Count } Remove-Job $job4.3 带参数的作业-ArgumentList的正确姿势想在作业里传参数用-ArgumentList然后在脚本块里用param()接收$serverName Server01 $port 445 $job Start-Job -ArgumentList $serverName, $port -ScriptBlock { param($svr, $prt) Test-NetConnection -ComputerName $svr -Port $prt -WarningAction SilentlyContinue } Wait-Job $job | Out-Null $result Receive-Job $job Remove-Job $job4.4 作业的坑启动慢、开销大、状态判断Start-Job最大的缺点是启动慢。每个作业都要拉起一个完整的PowerShell进程这个过程轻则几百毫秒重则几秒。要是同时启动50个作业光是进程创建开销就够喝一壶的。其次作业的状态判断往往让人摸不着头脑。一个作业可能处于Running、Completed、Failed、Stopped这些状态。但从Running到Completed并不代表脚本逻辑执行成功它只说明进程跑完了。如果你的脚本里抛了异常作业状态仍然可能是Completed。要判断脚本是否真的成功得检查Receive-Job返回的内容里有没有错误记录。所以我的建议是作业数量控制在20个以内超过这个量优先考虑Runspace。5. 性能与灵活性的进阶选择Runspace并发5.1 Runspace是什么和Start-Job有何区别Runspace是PowerShell执行命令的容器。每个PowerShell脚本都会在一个Runspace里运行。RunspacePool就是预先创建好的一组Runspace可以反复提交任务进去执行。跟Start-Job一比就清楚了Start-Job是每次开一个进程RunspacePool是开好一批线程等你往里扔任务。线程比进程轻量得多所以速度和资源占用完全不是一个量级。直接上一段可运行的示例用RunspacePool并发测试多个IP$ipList (192.168.1.1,192.168.1.2,192.168.1.3,192.168.1.4,192.168.1.5) $maxThreads 5 $powerShell [powershell]::Create() $runspacePool [runspacefactory]::CreateRunspacePool(1, $maxThreads) $runspacePool.Open() $powerShell.RunspacePool $runspacePool $scriptBlock { param($ip) $ping Test-Connection -ComputerName $ip -Count 1 -Quiet [PSCustomObject]{ IP $ip Reachable $ping } } $handles foreach ($ip in $ipList) { $powerShell.AddScript($scriptBlock).AddArgument($ip) $handle $powerShell.BeginInvoke() $handle } foreach ($handle in $handles) { $powerShell.EndInvoke($handle) } $runspacePool.Close() $powerShell.Dispose()注意这里有个关键点[powershell]::Create()创建的实例是可以复用提交多个任务的但每个任务之间要AddScript和AddArgument调用顺序必须对。5.2 用同步机制收集结果Runspace方式收集结果时需要一点技巧。BeginInvoke返回的是异步句柄EndInvoke会阻塞直到结果返回。一旦任务多还是大量时间花在等待上。更优的做法是使用[System.Management.Automation.PSDataCollection[PSObject]]和[System.Management.Automation.Runspace].Invoke()批量提交$runspacePool [runspacefactory]::CreateRunspacePool(1, 10) $runspacePool.Open() $results [System.Collections.Concurrent.ConcurrentBag[object]]::new() $jobs foreach ($ip in $ipList) { $ps [powershell]::Create() $ps.RunspacePool $runspacePool $ps.AddScript({ param($target) Test-Connection -ComputerName $target -Count 1 -Quiet }).AddArgument($ip) | Out-Null $async $ps.BeginInvoke() [PSCustomObject]{ Handle $async PS $ps } } foreach ($job in $jobs) { try { $output $job.PS.EndInvoke($job.Handle) $results.Add($output) } finally { $job.PS.Dispose() } } $runspacePool.Close()用ConcurrentBag收集结果的思路值得记住。它线程安全多个线程同时往里面添加结果不会互相踩踏。5.3 Runspace的线程安全与变量传递Runspace并发还有一个老生常谈的问题线程安全。脚本块里如果访问了共享对象比如一个普通的Hashtable多个线程同时写入会互相干扰甚至直接报错。解决办法无非两条用线程安全集合比如ConcurrentBag、ConcurrentDictionary。用lock之类的方式对共享对象加锁但PowerShell里实现锁比较繁琐能不用尽量不用。变量传递方面Runspace脚本块本身有自己的作用域外部变量同样需要通过参数传入用AddArgument就好。在-Parallel里用得习惯的$using:在这里不能直接用。6. 新式异步Start-ThreadJob和ForEach-Object并行结合6.1 用Start-ThreadJob替代传统作业Start-ThreadJob是PowerShell 6以后才有的命令它是Start-Job的线程版本不需要启动新的PowerShell进程只是基于.NET的ThreadJob模块实现。线程的开销比进程小得多因此启动速度明显更快非常适合需要频繁启动和销毁任务的小型并行场景。$job1 Start-ThreadJob -ScriptBlock { Get-Process } $job2 Start-ThreadJob -ScriptBlock { Get-Service } Wait-Job $job1, $job2 | Out-Null Receive-Job $job1, $job2 Remove-Job $job1, $job2用它来处理大量小任务比Start-Job实用得多。唯一的限制是线程之间运行在同一进程里如果脚本里有非线程安全的操作比如写同一个日志文件需要格外小心。6.2 结合ForEach-Object -Parallel做工作流有人问我ForEach-Object -Parallel能不能配合作业机制使用。当然可以但更常见的组合是先用一条命令收集数据再用ForEach-Object -Parallel处理最后统一汇总。比如批量检查一组URL的HTTP状态码$urls ( https://example.com, https://example.org, https://example.net ) $urls | ForEach-Object -Parallel { $resp Invoke-WebRequest -Uri $_ -Method Head -TimeoutSec 10 -UseBasicParsing [PSCustomObject]{ Url $_ StatusCode $resp.StatusCode } } -ThrottleLimit 5 | Sort-Object StatusCode这个方案的灵活性在于自己可以控制并发度同时管道输出还能继续往下游命令传递不会像Start-Job那样结果走一趟Receive-Job。7. 实战案例批量主机检测脚本的完整拆解7.1 需求背景与脚本设计思路假设现在需要检测局域网里的一批主机既要看它们是否在线又要看远程桌面3389端口是否开放。在线检测可以并发放ICMP请求端口检测也可以并行发起TCP连接。如果串行处理几十台机器这个脚本能跑到你怀疑人生用上并行几秒内出结果。脚本设计分三块第一块读取主机列表可以是手动数组也可以来自文本文件。第二块并行检测ICMP连通性和TCP端口。第三块汇总结果并导出到CSV文件。7.2 完整脚本示例可直接复制使用# 批量主机检测脚本 # 要求PowerShell 7 $hosts ( 192.168.1.1, 192.168.1.2, 192.168.1.3, 192.168.1.4, 192.168.1.5 ) $port 3389 $results $hosts | ForEach-Object -Parallel { $ip $_ $pingResult Test-Connection -ComputerName $ip -Count 1 -Quiet -ErrorAction SilentlyContinue $tcpResult Test-NetConnection -ComputerName $ip -Port $using:port -WarningAction SilentlyContinue [PSCustomObject]{ IP $ip Online $pingResult RDPOpen $tcpResult.TcpTestSucceeded Timestamp Get-Date -Format yyyy-MM-dd HH:mm:ss } } -ThrottleLimit 10 $results | Export-Csv -Path host_scan_result.csv -NoTypeInformation -Encoding UTF8 $results | Format-Table -AutoSize两个细节值得留意。第一Test-NetConnection本身也会做ICMP检测所以上面代码里既用了Test-Connection又用了Test-NetConnection其实有点重复。如果只是为了端口检测单独用Test-NetConnection获取TcpTestSucceeded就够了还省去一遍ICMP的等待时间。第二$using:port在这里发挥了作用直接把外部端口号传进并行块避免硬编码。7.3 加上进度显示避免看起来卡死了并行任务跑起来以后脚本没有任何输出总给人一种完了是不是卡住了的感觉。加上进度条以后体验完全不一样。PowerShell 7里的ForEach-Object -Parallel可以直接对管道里的进度做统计但更简单的做法是配合Write-Progress$results $hosts | ForEach-Object -Parallel { $ip $_ $tcpResult Test-NetConnection -ComputerName $ip -Port $using:port -WarningAction SilentlyContinue [PSCustomObject]{ IP $ip RDPOpen $tcpResult.TcpTestSucceeded } } -ThrottleLimit 5进度显示必须小心翼翼地处理。在-Parallel块里调Write-Progress因为并发执行进度状态会互相干扰显示经常乱跳。更稳妥的做法是把任务分批每批结束后在主线程统一更新进度或者干脆用ForEach-Object -Parallel自带的进度输出特性。从PowerShell 7.3开始-Parallel在运行时会自动显示任务完成度如果你用的版本较旧也可以给Write-Progress单独写个计数器不过说实话实用性有限不用强求。7.4 导出结果时不要踩编码的坑Export-Csv输出中文时乱码是PowerShell里高频出现的老问题归根到底是编码没选对。老版本Export-Csv默认编码是Unicode新版本是Utf8NoBOM如果你用Excel打开导出的CSV出现乱码改用-Encoding UTF8通常能解决因为Excel对带BOM的UTF-8识别更友好。$results | Export-Csv -Path result.csv -NoTypeInformation -Encoding UTF8顺带提一句-NoTypeInformation参数别漏不然CSV第一行会多出#TYPE System.Management.Automation.PSCustomObject虽然不影响数据读取但看着很碍眼。8. 把多个独立任务并行跑任务队列模式8.1 随机应变多个不同脚本块同时跑有些场景下要并行的不是同一批数据而是互不相关的多个任务。比如一边备份数据库一边清理临时文件一边下载更新包。这种任务队列模式用Start-Job或Start-ThreadJob最顺手。$jobBackup Start-ThreadJob -ScriptBlock { # 模拟数据库备份 Start-Sleep -Seconds 5 Database backup completed } $jobCleanup Start-ThreadJob -ScriptBlock { # 模拟清理临时文件 Start-Sleep -Seconds 3 Temp files cleaned } $jobDownload Start-ThreadJob -ScriptBlock { # 模拟下载 Start-Sleep -Seconds 8 Package downloaded } Wait-Job $jobBackup, $jobCleanup, $jobDownload | Out-Null $backupResult Receive-Job $jobBackup $cleanupResult Receive-Job $jobCleanup $downloadResult Receive-Job $jobDownload Remove-Job $jobBackup, $jobCleanup, $jobDownload三个任务同时跑总的执行时间由最长的那一个决定而不是三者相加。这种模式在自动化发布、系统巡检脚本里特别实用。8.2 等待技巧Wait-Job与轮询的区别Wait-Job是阻塞式的等到作业完成才继续往下走。如果任务可能跑很久你又不想让脚本一直卡在那就得用轮询每隔几秒检查一次作业状态。$job Start-ThreadJob -ScriptBlock { Start-Sleep -Seconds 30 done } while ($job.State -eq Running) { Write-Host 任务执行中... $(Get-Date -Format HH:mm:ss) Start-Sleep -Seconds 5 } Receive-Job $job | Write-Host Remove-Job $job轮询的好处是这期间主线程还能干别的比如把日志写到屏幕上。缺点是脚本变复杂了状态管理要自己来。我的建议是能Wait-Job就别自己轮询除非你有边等边展示信息这类需求。9. 常见问题与排查实战9.1 PowerShell版本不同导致命令不可用最大的坑其实是版本问题。ForEach-Object -Parallel只有在PowerShell 7里才有Start-ThreadJob在PowerShell 6里有老版本Windows自带的PowerShell 5.1既没有前者也没有后者。写脚本前先确认环境$PSVersionTable.PSVersion如果只能用5.1并行方案就退回到Start-Job或者Runspace。我给客户的脚本一般开头都会加个版本判断版本不够就报错提示if ($PSVersionTable.PSVersion.Major -lt 7) { throw 此脚本需要 PowerShell 7 及以上版本 }9.2 结果顺序错乱并行任务输出不保证有序并行处理的天然特点就是结果返回顺序不确定。第一个任务可能比第五个任务跑得慢最后输出的顺序是乱的。如果业务上对顺序有要求比如要按IP地址排序导出解决办法很简单结果收集完以后统一排序。$results $ipList | ForEach-Object -Parallel { [PSCustomObject]{ IP $_; ... } } $results $results | Sort-Object { [version]$_.IP }9.3 脚本被无限挂起超时与取消机制并行任务里如果某个任务卡在外部请求上迟迟不返回整个脚本就等在那里。Test-NetConnection这种命令偶尔就会遇到主机不理你、一直等到超时的情况。这类问题的解法是给任务显式设置超时或者使用本身支持超时参数的命令。以HTTP请求为例Invoke-WebRequest的-TimeoutSec一定要设置$urls | ForEach-Object -Parallel { try { $resp Invoke-WebRequest -Uri $_ -TimeoutSec 15 -UseBasicParsing [PSCustomObject]{ Url $_; Status $resp.StatusCode } } catch { [PSCustomObject]{ Url $_; Status Error } } } -ThrottleLimit 10加了try/catch和超时以后就算某个URL挂了也不会拖累整个并行任务。这个习惯一定要养成不然你永远在等一个不存在的响应。9.4 资源占用过高如何合理设置并发数并行度开太高会很爽直到你的机器内存被吃满、CPU飙升又或者目标服务器直接拒绝服务。合理的并发数设置需要考虑两个因素目标系统承受能力和你本机的资源余量。我的经验值供你参考场景建议并发数本地文件批量处理CPU核心数×2HTTP API请求5~10取决于接口限流网络设备连通性检测10~20数据库批量操作5~10Start-Job方式的作业数不超过209.5 作业收不到结果Receive-Job返回空这个坑我踩过很多次。问题出在Receive-Job默认只读取尚未读取过的数据。如果你先执行了一次Receive-Job把结果拿走了然后不小心又执行了一遍第二次返回的就是空的。更隐蔽的是作业启动以后马上执行Receive-Job但作业还没跑完PowerShell不会报错只会返回空结果。所以正确的顺序一定是先Wait-Job等任务完成再Receive-Job拿结果并且拿到结果后立刻存到变量里。9.6 并行任务中调用外部函数失败在ForEach-Object -Parallel块里调用一个定义在脚本外部的函数会直接报错——因为并行块里的线程作用域看不到外部函数定义。你需要把函数定义放到并行块内部或者用${function:}语法从外部获取函数定义后注入。实际项目中我倾向于把公共逻辑写成模块文件在使用前Import-Module虽然麻烦但长期维护更清晰。10. 补充一个常见的需求开机自启与计划任务结合10.1 让PowerShell脚本开机自启的正确姿势热词里提到PowerShell开机自启脚本这和使用场景经常绑定在一起。如果你希望一个执行多重任务的PowerShell脚本开机自动运行我建议不要靠把脚本放到启动文件夹这种粗放方式而是用任务计划程序至少可控性高得多。任务计划里创建触发器的要点$action New-ScheduledTaskAction -Execute powershell.exe -Argument -WindowStyle Hidden -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\multi_task_script.ps1 $trigger New-ScheduledTaskTrigger -AtStartup $settings New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries Register-ScheduledTask -TaskName MyMultiTaskScript -Action $action -Trigger $trigger -Settings $settings -RunLevel Highest几个参数的含义说一下-WindowStyle Hidden避免开机时弹出一个黑色窗口吓人一跳。-NoProfile不加载PowerShell个人配置文件可以有效避免因为配置问题导致脚本启动失败。-ExecutionPolicy Bypass确保脚本能顺利执行。这里解释一下ExecutionPolicy是PowerShell的脚本执行策略Bypass表示不拦截任何脚本适合在自启场景使用。但还是建议在脚本根目录做一轮测试后再部署。10.2 开机自启脚本里的并行任务注意事项如果自启脚本里用了并行任务有两点需要格外留意第一计划任务的工作目录默认不在脚本所在目录。如果你的脚本里用了相对路径读取文件一定要先切到脚本目录Set-Location -Path $PSScriptRoot第二并行任务跑完后进程不会立刻退出。尤其是Start-Job方式每个作业还有独立的PowerShell进程主脚本结束了子进程可能还没退出。要在脚本末尾统一Wait-Job并Remove-Job确保所有后台任务清理干净否则你会在任务管理器里看到一堆驻留的PowerShell进程。10.3 用日志验证自启是否成功自启脚本最怕的就是开机后静默失败又没日志可以排查。我习惯在脚本开头和结尾都写日志每完成一个关键阶段就往日志文件追加一行记录Start-Transcript -Path $PSScriptRoot\transcript.log -Append # 脚本主体... Stop-TranscriptStart-Transcript会把当前会话里所有输出都记到日志文件排查问题的时候翻一下日志至少能定位到卡在哪一步。11. 写给大家的几点实在建议PowerShell多重任务处理归根到底就三件事判断该不该并行、选对并行方式、管好并行资源。前两个决定了你累不累第三个决定你的脚本会不会把机器搞崩。结合我这些年的使用体会再啰嗦几句能用ForEach-Object -Parallel解决的就不要上升到Runspace。写着简单、读着也简单后续维护成本低。老版本环境跑任务优先Start-Job别一上来就追求极限性能。跑大批量任务前一定要先拿几条数据做小规模验证确认逻辑没问题再铺开。日志和错误捕获不能省。并行任务一旦出错定位成本比串行高得多没有日志就是大海捞针。版本检查一定要放在脚本最前面。一个用了ForEach-Object -Parallel的脚本扔到Windows PowerShell 5.1里报错能让你怀疑人生。多重任务处理本身不复杂真正决定脚本好坏的是细节超时设置、异常捕获、并发控制、日志记录。把这篇文里提到的问题都避开你的PowerShell脚本至少能在各种环境下稳稳当当地跑起来。
网站建设高端定制企业官网