Write-Progress: Display Progress Bars in Console

Published:18 June 2019 - 3 min. read

This article will discuss how to use Powershell Write-Progress. Most of the time I prefer to display the working status of my script using Write-Verbose. Write-Verbose is an excellent way to send periodic messages out to the console to let the user know what’s going on. It’s also nice because you can quickly switch it on and off with the built-in Verbose switch.

Using the PowerShellVerbose parameter
Using the PowerShellVerbose parameter

However, Write-Verbose is pretty simplistic. It’s just a periodic text display to the console. There’s not much else to it. There are times when you need something more pronounced and a better way to indicate to the script user just how far along in the process your script is. For this, I use the Write-Progress Write-Progress cmdlet.

This Powershell cmdlet is ideal for displaying a graphical progress bar right in the console. It’s an intuitive way to not only display status messages to the user but also to have a progress bar to indicate to the user how far the script is along in its execution.

Write-Progress output
Write-Progress output

How to Use the Write-Progress Cmdlet

Using this cmdlet is pretty straightforward. At its most basic, you simply need to specify the title of the progress bar using the Activity parameter, the status message to display using the Status parameter and how much to fill in the progress bar using the PercentComplete parameter.

Write-Progress -Activity 'Title' -Status 'Doing something' -PercentComplete 0

Newcomers to PowerShell may see this and immediately try to do this something like this:

Write-Progress -Activity 'Title' -Status 'Doing something 2' -PercentComplete 25

## Some process here

Write-Progress -Activity 'Title' -Status 'Doing something 3' -PercentComplete 50

## Some process here

Write-Progress -Activity 'Title' -Status 'Doing something 4' -PercentComplete 75

## Some process here

At first glance, you might not notice a problem here but think about how this is maintained. Notice the PercentComplete parameter. Notice that I had to statically assign the values 0,25,50 and 75? We never want to statically code anything unless necessary. Also, I’m not following the DRY principle here by repeating the same string Write-Progress -Activity 'Title' -Status 'Doing something' four different times. We need to refactor this code to come up with a more dynamic way to specify the PercentComplete parameter and remove as much code duplication as possible.

To do this, I’ll first need to figure out a way to prevent having to hard code the PercentComplete value in each Write-Progress call. To do this, I first need to find out how many Write-Progress calls I have. Once I figure this out, I can then do some arithmetic to get the actual percentage values.

I can either do this manually or not. I choose not. However, it gets a little hairy at this point. I’ll need to use the PowerShell parser to find each reference to Write-Progress just inside of the function itself.

$steps = ([System.Management.Automation.PsParser]::Tokenize((gc "$PSScriptRoot\$($MyInvocation.MyCommand.Name)"), [ref]$null) | where { $_.Type -eq 'Command' -and $_.Content -eq 'Write-Progress' }).Count

It now doesn’t matter if I add or remove Write-Progress function calls. $steps will always have the total number of Write-Progress calls in the function.

We can now dynamically pass a value to PercentComplete by incrementing a $stepcounter variable and creating a percentage value from it.

$steps = ([System.Management.Automation.PsParser]::Tokenize($MyInvocation.MyCommand.Definition, [ref]$null) | where { $_.Type -eq 'Command' -and $_.Content -eq 'Write-Progress' }).Count

$stepCounter = 0

Write-Progress -Activity 'Title' -Status 'Doing something' -PercentComplete ((($stepCounter++) / $steps) * 100)

## Some process here

Write-Progress -Activity 'Title' -Status 'Doing something' -PercentComplete ((($stepCounter++) / $steps) * 100)

## Some process here

Write-Progress -Activity 'Title' -Status 'Doing something' -PercentComplete ((($stepCounter++) / $steps) * 100)

## Some process here

Great! But this still looks ugly. Look at all that code duplication! It’s time to build a helper function. I’ll build one now.

function Write-ProgressHelper {
    param(
        [int]$StepNumber,
        [string]$Message
    )
    
    Write-Progress -Activity 'Title' -Status $Message -PercentComplete (($StepNumber / $steps) * 100)
}

Now, we can simply call this helper function from within the function itself.

$stepCounter = 0

Write-ProgressHelper -Message 'Doing something' -StepNumber ($stepCounter++)

## Some process here

Write-ProgressHelper -Message 'Doing something2' -StepNumber ($stepCounter++)

## Some process here

Write-ProgressHelper -Message 'Doing something3' -StepNumber ($stepCounter++)

## Some process here

Even though we’ve still got some code duplication, we were able to remove some of it. Since we replaced those Write-Progress references to Write-ProgressHelper, we’ll have to change the code to find the steps since our steps code was looking for function reference names.

$steps = ([System.Management.Automation.PsParser]::Tokenize((gc "$PSScriptRoot\$($MyInvocation.MyCommand.Name)"), [ref]$null) | where { $_.Type -eq 'Command' -and $_.Content -eq 'Write-ProgressHelper' }).Count

This finally leaves us with this script.

function Write-ProgressHelper {
	param (
	    [int]$StepNumber,
	    [string]$Message
	)

	Write-Progress -Activity 'Title' -Status $Message -PercentComplete (($StepNumber / $steps) * 100)
}

$script:steps = ([System.Management.Automation.PsParser]::Tokenize((gc "$PSScriptRoot\$($MyInvocation.MyCommand.Name)"), [ref]$null) | where { $_.Type -eq 'Command' -and $_.Content -eq 'Write-ProgressHelper' }).Count

$stepCounter = 0

Write-ProgressHelper -Message 'Doing something' -StepNumber ($stepCounter++)
Start-Sleep -Seconds 5

## Some process here

Write-ProgressHelper -Message 'Doing something2' -StepNumber ($stepCounter++)

Start-Sleep -Seconds 5

## Some process here

Write-ProgressHelper -Message 'Doing something3' -StepNumber ($stepCounter++)

Start-Sleep -Seconds 5

## Some process here

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!