Site icon Think PowerShell

How to Make a PowerShell Progress Bar

 

Roll your own PowerShell progress bar.
Roll your own PowerShell progress bar.

Some cmdlets have a progress bar built-in. Here’s how to make your own Powershell progress bar with the Write-Progress cmdlet.

What’s happening?

In my last post, I covered how to hide PowerShell progress bars you encounter with some built-in cmdlets. More often than not though, you probably want to keep these progress bars as they are typically there to give the script executor feedback that the script is indeed running, and how far along it is in step execution.

Microsoft has done a good job recognizing which cmdlets likely will have longer execution times (like Install-WindowsFeature), but what if you have a process that you know could take a while to complete? How can you give your user feedback that the script is actively working and to give an idea of how much work has been done, while also giving an impression of how much work is left to do?

Using Write-Progress for the DIY progress bar

PowerShell has included Write-Progress since version 1.0 for filling the need of scripted progress bars. Here are the available parameters:

Write-Progress
     [-Activity] <String>
     [[-Status] <String>]
     [[-Id] <Int32>]
     [-PercentComplete <Int32>]
     [-SecondsRemaining <Int32>]
     [-CurrentOperation <String>]
     [-ParentId <Int32>]
     [-Completed]
     [-SourceId <Int32>]
     [<CommonParameters>]

Let’s take a look at a frame and point out the how the different parameters factor in. Here is the sample script we’ll run to generate the progress bar. The sample output is shown for both the console and the PowerShell ISE, as they do have slightly different display formats. NOTE: if you are using VS Code, you won’t see a progress bar at all (known issue).

For ($i=0; $i -le 100; $i++) {
    Start-Sleep -Milliseconds 20
    Write-Progress -Activity "ActivityString" -Status "StatusString" -PercentComplete $i -CurrentOperation "CurrentOperationString"
}
Console progress bar
Console progress bar
ISE progress bar
ISE progress bar

 

Modifying our example above but taking intent into account, this would be one way to populate these parameters:

For ($i=0; $i -le 100; $i++) {
    Start-Sleep -Milliseconds 20
    Write-Progress -Activity "Counting to 100" -Status "Current Count: $i" -PercentComplete $i -CurrentOperation "Counting ..."
}
A custom progress bar example.
A custom progress bar example.

Nesting, replacing or superimposing progress bars

If you run Install-WindowsFeature, you will notice that it appears to only use the Status parameter, but that the value changes during execution as the progress bar reaches completion. Though this looks like a single progress bar, what we are actually watching is a new progress bar replace the previous progress bar. If we are running two progress bars simultaneously, we can control whether or not to nest them or superimpose them.

There is another parameter, Id, which determines if a new progress bar is shown under an already running progress bar, or if it is superimposed over the top. If Id is not set or is the same for two different Write-Progress executions, they will be superimposed. However if Id is different between the executions, the second progress will appear under the first progress bar. You commonly see this at play with nested operations.

Nested progress bars

Here is an example of nested progress bars with different ids, which will show us two progress bars:

For ($i=0; $i -le 100; $i++) {
    Start-Sleep -Milliseconds 1
    Write-Progress -Id 1 -Activity "First Write Progress" -Status "Current Count: $i" -PercentComplete $i -CurrentOperation "Counting ..."

    For ($j=0; $j -le 100; $j++) {
        Start-Sleep -Milliseconds 1
        Write-Progress -Id 2 -Activity "Second Write Progress" -Status "Current Count: $j" -PercentComplete $j -CurrentOperation "Counting ..."
    }
}
Nested progress bars.
Nested progress bars.

Replacing the previous progress bar

Much like Install-WindowsFeature, we can make it look like the same progress bar running but changing Activity, Status, or CurrentOperation text, though what we are really doing is replacing a progress bar as it completes with a new one.

For ($i=0; $i -le 100; $i++) {
    Start-Sleep -Milliseconds 20
    If ($i -le 50) {
        Write-Progress -Id 1 -Activity "First Write Progress" -Status "Current Count: $i" -PercentComplete $i -CurrentOperation "Counting ..."
    }
    Else {
        Write-Progress -Id 1 -Activity "Second Write Progress" -Status "Current Count: $i" -PercentComplete $i -CurrentOperation "Counting ..."
    }
}
Replacing a progress bar.
Replacing a progress bar.

Progress bars – a sequence of still frames

A key takeaway here is to understand that every time we run Write-Progress, we are in essence creating a a single frame in a reel of frames, which give us the impression of animation. In the previous example, the changing values within a status bar is an animation illusion; we have started painting a completely different set of frames.

Reference

Exit mobile version