Write-Progress – Feedback is Good
I’m not the most patient person in the world, nor the most trusting and this has always translated itself into my scripts by giving constant feedback on what’s going on. Write-Progress is a great mechanism for doing this within Powershell as it will provide you with a cool looking progress bar, and bonus, it’s really easy to use!
Monitor Background Job Progress
Remember the background jobs we created on Monday? Let’s take this to the next level by putting up a progress bar that will track of background threads so we can see how they’re progressing. Here’s the script from last week:
1..10 | % { $MaxThreads = 4 While (@(Get-Job | Where { $_.State -eq "Running" }).Count -ge $MaxThreads) { Write-Host "Waiting for open thread...($MaxThreads Maximum)" Start-Sleep -Seconds 3 } $Scriptblock = { Param ( [string]$CN ) Get-Process -ComputerName $CN } Start-Job -ScriptBlock $Scriptblock -ArgumentList "." } While (@(Get-Job | Where { $_.State -eq "Running" }).Count -ne 0) { Write-Host "Waiting for background jobs..." Get-Job Start-Sleep -Seconds 3 } $Data = ForEach ($Job in (Get-Job)) { Receive-Job $Job Remove-Job $Job } $Data | Select ProcessName,Product,ProductVersion | Format-Table -AutoSize
And using Write-Progress is pretty easy, here’s a simple syntax:
Write-Progress -ID [int] -Activity "Top Information Line" -Status "Second Information Line" -PercentComplete 50
You use ID to identify which progress bar you want to update, which means you can have multiple ones. Be careful with that though because while I like feedback you can definitely over do it! Write-Progress can provide you with 2 lines of text, top line and a second line and you use Activity and Status to update those. Last, PercentComplete tells the progress bar where to be.
Submission
In our jobs example, we know we’re going to have 10 threads, but let’s have a little fun and up that to 50. There are 3 distinct stages in our jobs script, submission, background jobs running and retrieval. We only need to have a progress bar for the first two–step three is so fast we wouldn’t be able to read the progress bar anyway so why put it in? For this script we want the progress bar to report how many of the background jobs have completed, but we can also report on how many we’ve submitted so far:
$MaxThreads = 4 $Submitted = 0 ForEach ($Job in (Get-Job)) { Remove-Job $Job } 1..50 | % { $JobCount = @(Get-Job | Where {$_.State -ne "Running"}).Count Write-Progress -Id 1 -Activity "Jobs Example: Get-Process" -Status "Submitting threads: $(50 - $Submitted)" -PercentComplete ($JobCount / 50 * 100) While (@(Get-Job | Where { $_.State -eq "Running" }).Count -ge $MaxThreads) { Write-Host "Waiting for open thread...($MaxThreads Maximum)" Start-Sleep -Seconds 3 } ...submit job here... $Submitted ++ }
First I’ve rearranged some of the variables to be outside of the loop because we need Submitted to track how many jobs we’ve put in, and MaxThreads should have always be on the outside anyway! Since this is for testing I also put in a little section to make sure all background jobs have been removed from the system. Than we go into the main loop itself! First we get JobCount since I’ll need that number in a couple of places it makes more sense to get it once instead of issuing more than one Get-Job command.
Next we Write-Progress with Activity and Status and we use Status to track how many threads we have minus how many we’ve submitted. Then comes the PercentComplete calculation which is simply the number of jobs we’ve submitted divided by the total number of jobs we have multiplied by 100. Luckily PercentComplete doesn’t require a whole number here so we don’t have to worry about rounding!
Then we simply submit the job as we normally would–or wait for an open thread–and increment our Submitted counter and continue the loop.
Wait For Background Jobs
Now let’s update the rest of the code to update the progress bar while we’re just waiting for the background jobs to finish.
While (@(Get-Job | Where { $_.State -eq "Running" }).Count -ne 0) { $JobCount = @(Get-Job | Where {$_.State -ne "Running"}).Count Write-Progress -Id 1 -Activity "Jobs Example: Get-Process" -Status "Waiting for background jobs to finish: $(50 - $JobCount)" -PercentComplete ($JobCount / 50 * 100) Start-Sleep -Seconds 3 }
Almost the same code here, but I’ve changed Status to give say “Waiting for background jobs to finish” and added a calculation of the total number of jobs submitted minus how many have completed. The PercentComplete parameter is calculated the exact same way. I also removed the previous feedback.
Retrieval
We’re really done at this point and you could just leave the progress bar alone and let the script finish out, because when the script exits it will take the progress bar with it. But we don’t want to do that, do we? No, we want to declare victory and then tear down the progress bar ourselves.
Write-Progress -Id 1 -Activity "Jobs Example: Get-Process" -Status "All background jobs completed!" -PercentComplete 100 Start-Sleep -Seconds 3 Write-Progress -Id 1 -Activity "Jobs Example: Get-Process" -Completed $Data = ForEach ($Job in (Get-Job)) { Receive-Job $Job Remove-Job $Job } $Data | Select ProcessName,Product,ProductVersion | Format-Table -AutoSize
Changed Status to say completed, simply put 100 in the PercentComplete parameter and the progress bar is good. Wait 3 seconds to make sure our user basks in the glory, then use the Completed parameter to tear down the progress bar. We have to specify the Activity parameter because it’s a required field.
Here’s the progress bar in action:
$MaxThreads = 4 $Submitted = 0 ForEach ($Job in (Get-Job)) { Remove-Job $Job } 1..50 | % { $JobCount = @(Get-Job | Where {$_.State -ne "Running"}).Count Write-Progress -Id 1 -Activity "Jobs Example: Get-Process" -Status "Submitting threads: $(50 - $Submitted)" -PercentComplete ($JobCount / 50 * 100) While (@(Get-Job | Where { $_.State -eq "Running" }).Count -ge $MaxThreads) { Write-Host "Waiting for open thread...($MaxThreads Maximum)" Start-Sleep -Seconds 3 } $Scriptblock = { Param ( [string]$CN ) Get-Process -ComputerName $CN } Start-Job -ScriptBlock $Scriptblock -ArgumentList "." $Submitted ++ } While (@(Get-Job | Where { $_.State -eq "Running" }).Count -ne 0) { $JobCount = @(Get-Job | Where {$_.State -ne "Running"}).Count Write-Progress -Id 1 -Activity "Jobs Example: Get-Process" -Status "Waiting for background jobs to finish: $(50 - $JobCount)" -PercentComplete ($JobCount / 50 * 100) Start-Sleep -Seconds 3 } Write-Progress -Id 1 -Activity "Jobs Example: Get-Process" -Status "All background jobs completed!" -PercentComplete 100 Start-Sleep -Seconds 3 Write-Progress -Id 1 -Activity "Jobs Example: Get-Process" -Completed $Data = ForEach ($Job in (Get-Job)) { Receive-Job $Job Remove-Job $Job } $Data | Select ProcessName,Product,ProductVersion | Format-Table -AutoSize
Enjoy!
[…] we defined in my last post here, with some multi-threading here and some progress feedback from here. The cool part is the resulting PSObject that comes back has all the information about network […]