How to capture the exception raised in the scriptblock of start-job?
Using throw
will change the job object's State
property to "Failed". The key is to use the job object returned from Start-Job
or Get-Job
and check the State
property. You can then access the exception message from the job object itself.
Per your request I updated the example to also include concurrency.
$createZip = {
Param ( [String] $source, [String] $zipfile )
if ($source -eq "b") {
throw "Failed to create $zipfile"
} else {
return "Successfully created $zipfile"
}
}
$jobs = @()
$sources = "a", "b", "c"
foreach ($source in $sources) {
$jobs += Start-Job -ScriptBlock $createZip -ArgumentList $source, "${source}.zip"
}
Wait-Job -Job $jobs | Out-Null
foreach ($job in $jobs) {
if ($job.State -eq 'Failed') {
Write-Host ($job.ChildJobs[0].JobStateInfo.Reason.Message) -ForegroundColor Red
} else {
Write-Host (Receive-Job $job) -ForegroundColor Green
}
}
TLDR:
# Works with both terminating and non terminating errors
$j = start-job {1/0} | wait-job; try { receive-job $j -ErrorAction Stop } catch { "err $_" }
I was able to "rethrow" the exception in the main thread by using:
Receive-Job $job -ErrorAction Stop
I'll my use case as an example. It can easily be applied to the OP.
$code = {
$Searcher = New-Object -ComObject Microsoft.Update.Searcher
#Errors from Search are not terminating, but will be present in the output none the less.
$Results = $Searcher.Search('IsInstalled=0 and IsHidden=0')
$Results.Updates
};
$job = Start-Job -ScriptBlock $code;
$consume = Wait-Job $job -Timeout 600;
if ($job.state -eq 'Running') {
Stop-Job $job
throw 'Windows update searcher took more than 10 minutes. Aborting'
};
#Captures and throws any exception in the job output
Receive-Job $job -ErrorAction Stop;
Write-Host "Finished with no errors"; #this will not print if there was an error
Works in v2.0.
Note that if the error within the job is non-terminating, the subsequent lines will continue to execute. But, this will not be obvious in the output returned from Receive-Job, as Receive-Job "terminates half way thorugh" - it throws out of it's self when the error object is encountered.
One way to avoid that is to wrap the whole block in a try {} catch{throw;}
Also, Job state will not be 'Failed' if the exception is non-terminating
This should be a comment really, but I don't have the reputation to leave comments.
My answer is that you should use Andy Arismendi's answer, but also output $job.ChildJobs[0].Error
As $job.ChildJobs[0].JobStateInfo.Reason.Message
isn't always useful.