问题描述
大家好,下午好!
我有一个关于 -asjob
与 invoke-command
一起运行的快速问题。
如果我使用 Invoke-Command
运行 2 个 -asjob
,当我尝试接收输出时它是否同时运行?这是否意味着 wait-job
会等到指定的第一个作业完成运行才能获得下一个结果?
Write-Host "Searching for PST and OST files. Please be patient!" -BackgroundColor White -ForegroundColor DarkBlue
$pSTlocation = Invoke-Command -ComputerName localhost -ScriptBlock {Get-Childitem "C:\" -Recurse -Filter "*.pst" -ErrorAction SilentlyContinue | % {Write-Host $_.FullName,$_.lastwritetime}} -AsJob
$OSTlocation = Invoke-Command -ComputerName localhost -ScriptBlock {Get-Childitem "C:\Users\me\APpdata" -Recurse -Filter "*.ost" -ErrorAction SilentlyContinue | % {Write-Host $_.FullName,$_.lastwritetime} } -AsJob
$pSTlocation | Wait-Job | Receive-Job
$OSTlocation | Wait-Job | Receive-Job
另外,另一个问题:我可以将作业的输出保存到一个变量而不显示在控制台上吗?我试图让它检查是否有任何回报,如果有输出,但如果没有做其他事情。
我试过了:
$job1 = $pSTlocation | Wait-Job | Receive-Job
if(!$job1){write-host "PST Found: $job1"} else{ "No PST Found"}
$job2 = $OSTlocation | Wait-Job | Receive-Job
if(!$job2){write-host "OST Found: $job2"} else{ "No OST Found"}
解决方法
注意:这个答案不直接回答问题 - 请参阅the other answer;相反,它显示了一个可重用的习惯用法,用于以非阻塞方式等待多个作业完成。
以下示例代码使用基于子进程的 Start-Job
cmdlet 创建本地作业,但该解决方案同样适用于本地线程 基于 Start-ThreadJob
创建的作业以及基于 远程 执行 Invoke-Command -ComputerName ... -AsJob
命令的作业,如问题中所用。
它显示了一个可重用的习惯用法,用于以非阻塞方式等待多个作业完成,允许在等待时进行其他活动,以及收集数组中的每个作业输出。
此处,仅在每个作业完成后收集输出,但请注意,在可用时分块收集它也是一种选择,使用(可能多个)Receive-Job
调用甚至在工作完成之前。
# Start two jobs,which run in parallel,and store the objects
# representing them in array $jobs.
# Replace the Start-Job calls with your
# Invoke-Command -ComputerName ... -AsJob
# calls.
$jobs = (Start-Job { Get-Date; sleep 1 }),(Start-Job { Get-Date '1970-01-01'; sleep 2 })
# Initialize a helper array to keep track of which jobs haven't finished yet.
$remainingJobs = $jobs
# Wait iteratively *without blocking* until any job finishes and receive and
# output its output,until all jobs have finished.
# Collect all results in $jobResults.
$jobResults =
while ($remainingJobs) {
# Check if at least 1 job has terminated.
if ($finishedJob = $remainingJobs | Where State -in Completed,Failed,Stopped,Disconnected | Select -First 1) {
# Output the just-finished job's results as part of custom object
# that also contains the original command and the
# specific termination state.
[pscustomobject] @{
Job = $finishedJob.Command
State = $finishedJob.State
Result = $finishedJob | Receive-Job
}
# Remove the just-finished job from the array of remaining ones...
$remainingJobs = @($remainingJobs) -ne $finishedJob
# ... and also as a job managed by PowerShell.
Remove-Job $finishedJob
} else {
# Do other things...
Write-Host . -NoNewline
Start-Sleep -Milliseconds 500
}
}
# Output the jobs' results
$jobResults
注意:
- 尝试
$remainingJobs | Wait-Job -Any -Timeout 0
暂时检查是否终止任何一项作业而不阻止执行是很诱人的,但是从 PowerShell 7.1 开始,这不起作用预期:即使已经完成的作业也永远不会返回 - 这似乎是 bug,在 GitHub issue #14675 中讨论过。
如果我使用 -asjob 运行 2 个 Invoke-Command,它是否在我尝试接收输出时同时运行?
是的,PowerShell jobs 始终并行运行,无论它们是否远程执行,就像您的情况一样(使用 Invoke-Command -AsJob
,假设问题中的 localhost
只是不同计算机实际名称的占位符)或本地(使用 Start-Job
或 Start-ThreadJob
)。
但是,通过使用(单独的)Wait-Job
调用,您同步等待每个作业完成(也以固定顺序)。也就是说,每个 Wait-Job
调用都会阻止进一步的执行,直到目标作业终止。[1]
但是请注意,两个作业会在您等待第一个作业完成时继续执行。
如果您不想以阻塞方式等待,而是希望在等待两个作业完成的同时执行其他操作,则需要另一种方法,详见 the other answer。
我可以将作业的输出保存到一个变量而不显示在控制台上吗?
是的,但问题是在您远程执行的脚本块 ({ ... }
) 中,您错误地使用了 Write-Host
来尝试输出数据。
Write-Host
is typically the wrong tool to use,除非意图是将仅写入显示器,绕过 success output stream 并具有将输出发送到其他命令的能力,将其捕获在变量,或将其重定向到文件。要输出一个值,请单独使用;例如,$value
而不是 Write-Host $value
(或使用 Write-Output $value
,尽管很少需要);见this answer。
因此,您在变量中收集作业输出的尝试失败了,因为 Write-Host
输出绕过变量赋值捕获并转到的成功输出流直接联系主机(控制台):
# Because the job's script block uses Write-Host,its output goes to the *console*,# and nothing is captured in $job1
$job1 = $pSTlocation | Wait-Job | Receive-Job
(顺便说一句,该命令可以简化为
$job1 = $pSTlocation | Receive-Job -Wait
).
[1] 请注意,Wait-Job
有一个可选的 -Timeout
参数,它允许您将等待时间限制为最多给定的秒数,并且如果目标作业没有输出则返回而不输出还没完。但是,从 PowerShell 7.1 开始,-Timeout 0
用于非阻塞轮询作业是否已完成不起作用 - 请参阅GitHub issue #14675。