How to add a progress bar to your PowerShell script

[Update] If you want to learn how to calculate seconds remaining when using progress bars, head on over to this post!

A very typical workflow when working with PowerShell, is to iterate through a collection of data. Inside each loop, you would perform some action, like getting or setting some data for instance. Depending on the size of the array, and the particular work you are doing within each iteration, this might often be a part of your script where users get little-to-none feedback that the script is still running.

Usability-wise this is bad, and you should strive to give the user some feedback that ‘stuff is happening‘. In this post I will show you how you can use the Write-Progress cmdlet to add a progress bar to your script, to give the user the needed visibility to know it’s running.

Write-Progress takes several parameters, but only Activity is mandatory. So in it’s simplest form you can create a visual feedback of progress like this:

$computerList = 'server01', 'server02', 'server03', 'server04', 'server05', 'server06', 'server07', 'server08', 'server09', 'server10'
foreach ($computer in $computerList) {
Write-Progress -Activity 'Processing computers'
Start-Sleep -Milliseconds 200
}

Write-Progress01

This is just slightly better than nothing though, as even though you see the progress bar, you don’t really get any feedback that something is happening.

Since the activity parameter takes a string as its input, we could of course add some more useful information to it:

$computerList = 'server01', 'server02', 'server03', 'server04', 'server05', 'server06', 'server07', 'server08', 'server09', 'server10'
foreach ($computer in $computerList) {
Write-Progress -Activity "Processing $computer"
Start-Sleep -Milliseconds 200
}

Write-Progress02

Good, now we are at least seeing some dynamic information. Perhaps this is all you need, but let’s explore further.

Write-Progress also includes other parameters that we can use to customize how the progress bar displays information to the user.

Activity
This we have already used, and controls the first line of text in the heading above the status bar. Try to use it for what its name implies it should be used for; to describe the main activity you are performing.

Status
This parameter controls the second line in the heading, and according to the help information is designed to describe the current state of the activity. If it’s not used, it defaults to showing ‘Processing.’ as you have seen in the previous examples.

CurrentOperation
Controls the text below the progress bar according to the help information, and should be used to describe the operation currently taking place. Please note that even though the help information states that this text will display beneath the progress bar, this is only true when used in the console. If used in ISE, it will display on the same line as the text from the Status parameter.

Ok, let’s update our code to use these new parameters:

$computerList = 'server01', 'server02', 'server03', 'server04', 'server05', 'server06', 'server07', 'server08', 'server09', 'server10'
foreach ($computer in $computerList) {
Write-Progress -Activity 'Processing computers' -CurrentOperation $computer
Start-Sleep -Milliseconds 200
}

Write-Progress03

Here I have set the Activity parameter back to it’s static text and use the CurrentOperation parameter to show the current computer being processed. The reason I haven’t used Status, is that for the time being, I’m happy with the default text of ‘Processing’.

But we are strictly speaking not seeing any progress bars yet are we? Lets fix that.

The parameter needed for this is called PercentComplete. To be able to use this, we will need to have some kind of counter, and also know the total size of our array, and do some simple maths to create a percentage value.

$computerList = 'server01', 'server02', 'server03', 'server04', 'server05', 'server06', 'server07', 'server08', 'server09', 'server10'
$counter = 0
foreach ($computer in $computerList) {
$counter++
Write-Progress -Activity 'Processing computers' -CurrentOperation $computer -PercentComplete (($counter / $computerList.count) * 100)
Start-Sleep -Milliseconds 200
}

Write-Progress04

There, we finally got our status bar working!

Multiple Progress Bars

So far we have only dealt with a single array, but sometimes you might be doing sub iterations inside a loop, and Write-Progress supports showing multiple progress bars as well.

Let’s expand our example code to see this in action:

$computerList = 'server01', 'server02', 'server03', 'server04', 'server05', 'server06', 'server07', 'server08', 'server09', 'server10'

$c1 = 0

foreach ($computer in $computerList) {
$c1++
Write-Progress -Id 0 -Activity 'Checking servers' -Status "Processing $($c1) of $($computerList.count)" -CurrentOperation $computer -PercentComplete (($c1/$computerList.Count) * 100)
$services = Get-Service
$c2 = 0
foreach ($service in $services) {
$c2++
Write-Progress -Id 1 -ParentId 0 -Activity 'Getting services' -Status "Processing $($c2) of $($services.count)" -CurrentOperation $service.DisplayName -PercentComplete (($c2/$services.count) * 100)
$c3 = 0
if ($service.ServicesDependedOn) {
foreach ($dependency in $service.ServicesDependedOn) {
$c3++
Write-Progress -Id 2 -ParentId 1 -Activity 'Getting dependency services' -Status "Processing $($c3) of $($service.ServicesDependedOn.Count)" -CurrentOperation $dependency.Name -PercentComplete (($c3/$service.ServicesDependedOn.count) * 100)
Start-Sleep -Milliseconds 50
}
}

else {
Write-Progress -Id 2 -ParentId 1 -Activity 'Getting dependency services' -PercentComplete 100
}

Start-Sleep -Milliseconds 50
}

Start-Sleep -Milliseconds 50
}

Write-Progress05

There are several things going on here, so I’ll do my best to explain. As with the earlier examples my outermost loop is going through a list of computers. Then I’m getting all services and looping through each of these, and in the innermost loop I’m iterating through each dependency service of each service found. For each loop I’m maintaining a counter so I can properly show a relevant progress bar.

When working with multiple progress bars, use the ID and ParentID parameters to show the relations between them.

One interesting thing to notice is that Write-Progress will look at the available space of the console, and replace the progress bar to show percentages if needed.

Write-Progress06

This is the same example as above, but before I ran the script, I resized the console window.

As you see, it doesn’t take much to add progress bars to your script, and believe me; your users will thank you for doing it! I hope this was helpful, and if you have any suggestions for improvements, or notice any errors, leave a comment below!

12 comments

  1. I have read quite a few articles on this, but yours is superb! Well done and keep up the good writing.

    Like

  2. Nice Article. I like how you built it up with each step. Anyone ever figured out a way to add a use a progress bar with he percent complete when your function takes input from the pipeline?

    Like

    1. Thanks. To get progress bar working with input from the pipeline, you need to collect the data from the pipeline by assigning it to an array in the Process section, and process it in the End section. This is where you would add your progress bar. The only drawback of course being that you kind-of break the (benefit of the) pipeline by doing it this way.

      Like

  3. Holly Molly!!!!! just went I thought it was a good level script, you took it to the next level and made it legendary!!!!

    Like

Leave a comment