Home > database >  Add Write-Progress to Get-Job/Wait-Job
Add Write-Progress to Get-Job/Wait-Job

Time:04-20

I'm using the below code to display the results of PowerShell Jobs with a timeout of 120 seconds. I would like to enhance this code by incorporating Write-Progress (based on number of jobs completed). I tried using this example as a reference, however, when I try to incorporate that code, the progress bar displays briefly after all the jobs are all done already.

    $Jobs = @()
    $ForceStoppedIds = @{}
    
    $Jobs  = Get-Job
    $Jobs | Wait-Job -Timeout 120 | Out-Null
    $Jobs | ?{$_.State -eq 'Running'} | Stop-Job -PassThru | %{$ForceStoppedIds[$_.Id] = $true}
    
    foreach ($Job in $Jobs) {
    
        $Name = $Job.Name
        $Output = (Get-Job -Name $Name | Receive-Job)
    
        if ($ForceStoppedIds.Contains($Job.Id)) {
    
            Write-Output "$($Name) - Device unable to process request within 2 minutes"
    
        } else {
    
            Write-Output $Output
    
        }
    
    }

CodePudding user response:

Wait-Job -Timeout 120 will block the thread until the specified timeout or all jobs have completed, hence, is not possible to display progress and wait for them at the same time. There are 2 alternatives that I can think of, the first one would be to create a proxy function of Wait-Job following the indications in this helpful answer and the other is to define your own function that does a similar work as Wait-Job but, instead of blocking the thread, you can add a loop that will run based on 2 conditions:

  • That the elapsed time is lower than or equal to the Timeout we passed as argument to the function (we can use Diagnostics.Stopwatch for this).
  • And, that the jobs are still Running (the $jobs ArrayList is still populated).

Note, the function below should work in most cases however is purely for demonstration purposes only and should not be relied upon. It's also worth mentioning that this function will only work as a pipeline function since it relies on the automatic variable $input.

First we define a new function that can be used to display progress as well as wait for our jobs based on a timeout:

function Wait-JobWithProgress {
    # `[int]::MaxValue` in case someone decides to use this without using this parameter :(
    param([int] $TimeOut = [int]::MaxValue)

    $jobs = [System.Collections.ArrayList]::new(@($input))
    $timer = [System.Diagnostics.Stopwatch]::StartNew()
    $total = $jobs.Count; $completed = 0

    do {
        $progress = @{
            Activity = 'Waiting for Jobs'
            Status = 'Remaining Jobs {0} of {1}' -f
                ($jobs.State -eq 'Running').Count, $total
            PercentComplete = $completed / $total * 100
        }
        Write-Progress @progress

        $id = [System.Threading.WaitHandle]::WaitAny($jobs.Finished, 200)
        if($id -ne 258) {
            # output this job
            $jobs[$id]
            # remove this job
            $jobs.RemoveAt($id)
            $completed  
        }
    } while($timer.Elapsed.Seconds -le $TimeOut -and $jobs)

    # Stop the jobs not yet Completed and remove them
    $jobs | Stop-Job -PassThru | ForEach-Object {
        Write-Warning ("Job [#{0} - {1}] did not complete on time and was removed..." -f $_.Id, $_.Name)
        Remove-Job $_
    }
}

Then for testing it, we can create a few jobs with a random timer:

0..10 | ForEach-Object {
    Start-Job {
        Start-Sleep (Get-Random -Maximum 15)
        [pscustomobject]@{
            Job    = $using:_
            Result = "Hello from Job# $using:_"
        }
    }
} | Wait-JobWithProgress -TimeOut 10 |
Receive-Job -AutoRemoveJob -Wait | Format-Table -AutoSize
  • Related