By: Mason Prewett
We’ve written many blog articles about PowerShell and how incredibly useful the Microsoft framework can be, and this article is another example. PowerShell is a great tool for performing long running tasks. A common strategy used to speed up a long running process is to separate tasks into asynchronous jobs so they can be executed in parallel.
PowerShell offers a very simple way to create these jobs using the Start-Job command, which is what I will be using in this demonstration. It is unfortunately difficult to put together a visual method of monitoring job progress as jobs are processing.
This article will show you how to create a PowerShell progress bar per job. Using the provided code, your PowerShell progress bar will look like this:
PowerShell Progress Bar – The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#Accepts a Job as a parameter and writes the latest progress of it function WriteJobProgress { param($Job) #Make sure the first child job exists if($Job.ChildJobs[0].Progress -ne $null) { #Extracts the latest progress of the job and writes the progress $jobProgressHistory = $Job.ChildJobs[0].Progress; $latestProgress = $jobProgressHistory[$jobProgressHistory.Count - 1]; $latestPercentComplete = $latestProgress | Select -expand PercentComplete; $latestActivity = $latestProgress | Select -expand Activity; $latestStatus = $latestProgress | Select -expand StatusDescription; #When adding multiple progress bars, a unique ID must be provided. Here I am providing the JobID as this Write-Progress -Id $Job.Id -Activity $latestActivity -Status $latestStatus -PercentComplete $latestPercentComplete; } } #Test Async Job 1. Iterates through 10 integers and sleeps 1 second in between $job1 = Start-Job –Name Sleep1 –Scriptblock { for($i=0;$i -lt 10;$i++) { $percentComplete = ($i + 1) * 10; $status = $percentComplete.ToString() + "% Complete "; $activity = "Job 1 - Processing Iteration " + ($i + 1); Write-Progress -Activity $activity -Status $status -PercentComplete $percentComplete; Start-Sleep -Seconds 1; } } #Test Async Job 2. Iterates through 10 integers and sleeps 2 second in between $job2 = Start-Job –Name Sleep2 –Scriptblock { for($i=0;$i -lt 10;$i++) { $percentComplete = ($i + 1) * 10; $status = $percentComplete.ToString() + "% Complete "; $activity = "Job 2 - Processing Iteration " + ($i + 1); Write-Progress -Activity $activity -Status $status -PercentComplete $percentComplete; Start-Sleep -Seconds 2; } } #Monitor all running jobs in the current sessions until they are complete #Call our custom WriteJobProgress function for each job to show progress. Sleep 1 second and check again while((Get-Job | Where-Object {$_.State -ne "Completed"}).Count -gt 0) { WriteJobProgress($job1); WriteJobProgress($job2); Start-Sleep -Seconds 1 } |
Code Explanation
The code above creates two jobs that iterate through a “for loop” and sleeps after each iteration. This is used to simulate some heavy lifting being done. In each of these jobs, I am using the Write-Progress method to report the activity that is being completed, the status of the job, and the percentage complete. If the Write-Progress method is called outside of an asynchronous job, it will display a progress bar similar to my screenshot above but it will not display when called inside of an asynchronous job.
Instead, each Write-Progress call creates a record in the Job.Progress object that is retained throughout the life of the job. I have seen many people trying to achieve this solution and I believe this concept is the part that holds everyone back, as it is not documented very well. I learned a lot about this from reading Boe Prox’s article on PowerShell background jobs. I highly recommend reading it.
After creating the jobs, I used a “while loop” to check if there are any jobs that are not complete. In this loop, I am calling my custom function for displaying the progress of each job and then “sleeping” for one second before repeating the process.
The custom function WriteJobProgress accepts a job as a parameter. The first thing I do is make sure that the job has a child job. This is also explained well in Boe Prox’s article, as the parent job not actually doing the work but just being a container for child jobs that handle the processing. So this means you want to check Job.ChildJobs[0].Progress for the Write-Progress information that was done in the job. Since the Job.Progress object contains a history of the progress written, you will need to get the latest record to get the latest progress.
This is done by the following two lines of code:
1 2 |
$jobProgressHistory = $Job.ChildJobs[0].Progress; $latestProgress = $jobProgressHistory[$jobProgressHistory.Count - 1]; |
The $latestProgress variable is set to the last progress record in the array by using the position of the count of all progress records minus one. I am then accessing each property of the progress record by using the Select –expand method. Finally, I am writing the progress of the latest progress record using the Write-Progress method, which will be displayed to the end user since it is not being called from inside a job. Since there will be multiple progress bars, it is necessary to set the –Id property of the Write-Progress call to uniquely identify them. In my case, I am using the id of the job.
References
1. Article by Boe Prox – https://learn-powershell.net – Quick Tip To Find Out What Your Background Jobs Are Doing – https://learn-powershell.net/2011/11/04/quick-tip-to-find-out-what-your-background-jobs-are-doing/
Questions?
Thanks for reading! We hope you found this blog post to be useful. Do let us know if you have any questions or topic ideas related to BI, analytics, the cloud, machine learning, SQL Server, (Star Wars), or anything else of the like that you’d like us to write about. Simply leave us a comment below, and we’ll see what we can do!
Keep your data analytics sharp by subscribing to our mailing list
Get fresh Key2 content around Business Intelligence, Data Warehousing, Analytics, and more delivered right to your inbox!
Key2 Consulting is a data warehousing and business intelligence company located in Atlanta, Georgia. We create and deliver custom data warehouse solutions, business intelligence solutions, and custom applications.