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.
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.
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