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.