Search code examples
powershelljobs

How to modify the output of PowerShell Jobs if they take too long


I have a relatively long/complex script that blindly runs a batch of commands against several devices (as jobs). Once in awhile, a couple of these jobs continue to run indefinitely. I’ve added a Wait-Job -Timeout command to my script (see below) in order to force-stop jobs that are taking too long to run.

I’d like to change the output for these hung jobs to read “This device is busy or unstable”. How can I do this? I'm guessing I need to add something to the tail-end of the pipeline (in the last line of code below).

$Jobs += Get-Job
$jobs | Wait-Job -Timeout 5 | out-null  

Get-Job | ? {$_.State -eq 'Running'} | Stop-Job -PassThru 

Solution

  • One way is to iterate over the jobs that are currently running and write your message for each one:

    $Jobs += Get-Job
    $Jobs | Wait-Job -Timeout 5 | Out-Null
    
    Get-Job | ? { $_.State -eq 'Running' } | Stop-Job -PassThru | % { Write-Host "This device is busy or unstable" }
    

    You can also add info from the jobs that are being stopped, like the job ID for example:

    Get-Job | ? { $_.State -eq 'Running' } | Stop-Job -PassThru | % { Write-Host "This device is busy or unstable: $($_.Id)" }
    

    UPDATE: You can use a hashtable to store the job IDs that were "force stopped". Then iterate through the Jobs using Receive-Job to get the output, and check if the job is in the ForceStoppedIds table. If it is write your custom message, otherwise just output the message. Here's a simple test I ran.

    Start-Job -ScriptBlock { Write-Output "Starting 1."; Start-Sleep -Seconds 3; Write-Output "1 complete."; } | Out-Null
    Start-Job -ScriptBlock { Write-Output "Starting 2."; Start-Sleep -Seconds 60; Write-Output "2 complete."; } | Out-Null
    Start-Job -ScriptBlock { Write-Output "Starting 3."; Start-Sleep -Seconds 2; Write-Output "3 complete."; } | Out-Null
    $Jobs += Get-Job
    $Jobs | Wait-Job -Timeout 5 | Out-Null
    $ForceStoppedIds = @{}
    $Jobs | ? { $_.State -eq 'Running' } | Stop-Job -PassThru | % { $ForceStoppedIds[$_.Id] = $true }
    foreach ($job in $Jobs) {
        $jobOutput = Receive-Job $job -Wait
        if ($ForceStoppedIds.Contains($job.Id)) {
            Write-Host "Custom message about stopped job: $($jobOutput)"
        }
        else {
            Write-Host $jobOutput
        }
    }
    

    One thing to be cautious of is how jobs output information (ie. Write-Host, Write-Output, return, etc.). If you're not getting the results you expect, double check the job's ScriptBlock to see how the information is being written/returned/etc. I'm sure there are much more elegant ways of doing this, but hopefully this will help.