Progress bars are an essential user interface component that provides visual feedback to indicate the progress of long-running operations. In PowerShell, adding a progress bar to your scripts can greatly improve the user experience by showing task completion percentage and status messages.

In this comprehensive guide, we will explore various methods to implement progress bars in PowerShell.

Why Add a Progress Bar in PowerShell Scripts?

Here are some key benefits of adding a progress indicator in your PowerShell code:

  • Enhanced Visibility: A progress bar gives the user clarity on the ongoing process. Without it, long waits can create confusion whether the script is working or hung.

  • Professional Touch: Progress bars make your scripts feel more polished, responsive and user-friendly.

  • Track Progress: They allow tracking completion percentage of activities like file transfers, installations, data processing etc.

  • Debug Issues: Any stuck progress bars indicate issues for troubleshooting.

In summary, progress bars improve understandability and interactivity – making users more likely to adopt your scripts.

Built-in Cmdlet: Write-Progress

The Write-Progress cmdlet is used to display the progress bar in PowerShell.

Its syntax is:

Write-Progress -Activity <String> -Status <String> -PercentComplete <Int32>

The key parameters are:

  • Activity: The operation name displayed before the progress bar
  • Status: The custom status message shown after the bar
  • PercentComplete: The completion percentage that adjusts the progress bar

Additionally, helpful parameters include:

  • Id: Uniquely identifies each progress bar
  • ParentId: Groups nested activities into parent-child relationships
  • SecondsRemaining: Estimates time remaining

Now let‘s implement some examples of progress bars using Write-Progress.

Progress Bar for Foreach Loop

The simplest way to add a progress meter is inside a foreach loop iterating over a collection of items.

Here is code to display file copy progress for all JPG files:

$files = Get-ChildItem -Path C:\Pictures -Filter *.jpg
$i = 1  
$total = $files.Count

foreach ($file in $files) {

    $percentage = ($i / $total) * 100

    Copy-Item -Path $file.FullName -Destination E:\MyPhotos
    Write-Progress -Activity "Copying Files" -Status "$i of $total" -PercentComplete $percentage  
    $i++

}

This displays the following progress bar:

Copying Files
[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>] 100% Complete
120 of 120 

Let‘s understand how it works:

  • Calculate total files to copy
  • Initialize counter $i at 1
  • In loop, compute percentage complete
  • Display activity, custom status, and percentage
  • Increment counter

So for each file copy, the progress bar and text updates to indicate flow.

Progress Bar for Time Consuming Activities

For long custom functions or code blocks, you can manually update the progress bar to show execution stages.

For example, here is a progress indicator for a function that trains a Machine Learning model:

function Train-MLModel {

    Write-Progress -Activity "Training ML Model" -Status "Initializing" -PercentComplete 5

    # initialize hyperparameters
    Start-Sleep -Seconds 3

    Write-Progress -Activity "Training ML Model" -Status "Processing Data" -PercentComplete 30
    # process input data

    Start-Sleep -Seconds 2

    Write-Progress -Activity "Training ML Model" -Status "Building Model" -PercentComplete 50  
    # create and train model

    Start-Sleep -Seconds 4

    Write-Progress -Activity "Training ML Model" -Status "Evaluating Model" -PercentComplete 80

    Start-Sleep -Seconds 2

    Write-Progress -Activity "Training ML Model" -Status "Completed!" -PercentComplete 100

}

Train-MLModel

This generates an incrementally updating activity monitor:

Training ML Model
Initializing
[>>>>>>>                                            ]   5% Complete 

Training ML Model 
Processing Data
[===============================                   ]  30% Complete

Training ML Model
Building Model 
[==================================================]  50% Complete

Training ML Model
Evaluating Model
[==================================================================]  80% Complete

Training ML Model 
Completed!
[====================================================================]  100% Complete

So you can provide progress status at various logical stages of any custom function.

Progress Bar for Web Downloads

For file downloads off the web, we can scrape the percentage from the web response object itself to show a precise progress bar.

$url = "https://filesamples.com/samples/video/mp4/sample_960x400_ocean_with_audio.mp4"

$request = [System.Net.HttpWebRequest]::Create($url)  

$response = $request.GetResponse()  

$totalsize = [System.Math]::Floor($response.get_ContentLength()/1024)  
$downloadedBytes = 0
$percentCompleted = 0

$stream = [System.IO.Stream]$response.GetResponseStream()

$buffer = New-Object byte[] 1024
$count = $stream.Read($buffer,0,$buffer.length)

$output = [System.IO.FileStream]"$env:temp\sample.mp4"
$writer = New-Object System.IO.BinaryWriter $output

While ($count -gt 0){

    $writer.Write($buffer, 0, $count)
    $downloadedBytes = $downloadedBytes + $count
    $percent = ([int](($downloadedBytes/$totalBytes)*100))

    if($percent -ne $percentCompleted) {

       Write-Progress -Activity "Downloading File" `
            -Status "$downloadedBytes of $totalBytes kb downloaded.." `
            -PercentComplete $percent

       $percentCompleted = $percent
    }

$count = $stream.Read($buffer, 0, $buffer.length)
}

$writer.Close()
$output.Close()  
$stream.Close()
$response.Close()

As you can see, we are:

  • Getting total download size
  • Computing percentage downloaded
  • Updating progress bar only when percentage changes

This displays a precise progress indicator like:

Downloading File
[=============================             ]  84% Complete
7459 of 8867 kb downloaded..

So Wrapping API calls in a progress block is a useful technique.

Updating Progress Bar Title Dynamically

We can overwrite the Activity section of the progress bar to dynamically reflect the currently running sub-task.

Import-Module DBatools

$serverList = Get-Content "servers.txt" 

$total = $serverList.Count
$i = 1

foreach ($server in $serverList) {

    $percent = ($i / $total) * 100

    Write-Progress -Activity "Processing $server" -PercentComplete $percent

    Start-Sleep -Seconds 1

    $results = Invoke-DbaQuery -SqlInstance $server -Query "SELECT @@ServerName as [Server Name]"

    # output results to file

    $i++

}

Write-Progress -Activity "Completed Tasks" -Completed

Now, the progress bar heading shows the server name being processed:

Processing SERVER01
[>>>>                                           ] 20% Complete

Processing SERVER02                               
[>>>>>>>>>>>>>>>>>>>>>>>                      ] 40% Complete 

Processing SERVER03
[========================================        ] 60% Complete

This lets the user know the exact item or sub-task in progress.

Multi Progress Bars for Parallel Operations

You can render multiple progress bars on the console to track parallel running tasks.

The key is passing a unique ID value so that each gets a separate progress block.

# Start long running job 1
Start-Job -Name "Operation1" -ScriptBlock {

    1..100 | ForEach-Object {

        $percentage = $_         
        Write-Progress -Id 1 -Activity "Operation 1" -PercentComplete $percentage 
        Start-Sleep -Milliseconds 150

    }

} 

# Start long running job 2
Start-Job -Name "Operation2" -ScriptBlock {

    1..100 | ForEach-Object { 

        $percentage = $_

        Write-Progress -Id 2 -Activity "Operation 2" -PercentComplete $percentage
        Start-Sleep -Milliseconds 150

    }

}


While ($(Get-Job -State Running).Count -gt 0) {
 Start-Sleep -Seconds 2
}

Get-Job | Receive-Job -Wait

This will render two progress bars updating side by side:

Operation 1
[==================================================] 100% Complete
Operation 2
[==================================================] 100% Complete

So you can track multiple concurrent processes or jobs visually.

Relative Progress Display with Write-Progress

By default, the progress bar renders according to console width. So the percentage completed shown can differ from actual percentage across sessions due to console size variations.

To show consistent width-agnostic progress, use the SecondsRemaining parameter:

for($i=1; $i -le 100; $i++) {

  Write-Progress -Id 1 -Activity Updating -PercentComplete $i -SecondsRemaining (100-$i)

  Start-Sleep -Milliseconds 100

}

Now the bar will show relative progress instead of misleading percentage values:

Updating
[#                                         ]
99 seconds remaining

This technique prevents progress illusion in scenarios where console width alters.

Progress Bars for External Commands

For long running external executables started using Start-Process, we need to stream the native progress from STDERR to update a PowerShell progress meter.

Here is an example for displaying robocopy progress in a bar:

$startInfo = New-Object System.Diagnostics.ProcessStartInfo 

$startInfo.FileName = "robocopy.exe"
$startInfo.Arguments = "C:\Source D:\Backup /MIR /R:3" 

$startInfo.RedirectStandardError = $true
$startInfo.UseShellExecute = $false

$process = New-Object System.Diagnostics.Process 
$process.StartInfo = $startInfo
$process.Start() 

$reader = $process.StandardError
$pattern = "\d{1,3}\s%|\d+/\d+"

While (-not $process.HasExited)
{

  $line = $reader.ReadLine()

  If ($line -match $pattern) {

     $status = $matches[0].Trim()
     $percent = $status.Split("/")[0].TrimEnd("%")

     Write-Progress -Activity "File Copy" -Status $status -PercentComplete $percent

  }

}

$process.WaitForExit()

The progress bar with live percentage and file count gets shown until Robocopy completes:

File Copy
[==================================          ]  84% (2053/2461)

This method works for any CLI tool that prints incremental progress data.

Throttling and Animating the Progress Bar

For smooth progress flow, you can throttle using Start-Sleep so update percentage increments gradually.

Creative animations also improve perceived performance. Here is an example:

Import-Module -Name Terminal-Icons

1..100 | ForEach-Object {

    $icon = Get-Random -Input @(
        [char]::ConvertFromUtf32(0x25D3), 
        [char]::ConvertFromUtf32(0x25D1),
        [char]::ConvertFromUtf32(0x25D0)
    )

    $percentage = $_

    Write-Progress -Activity ‘Loading...‘ -Status "$icon $percentage% Complete" -PercentComplete $percentage

    Start-Sleep -Milliseconds (Get-Random -Minimum 100 -Maximum 400)

}

This displays a smoothly updating progress bar with random animations:

Loading...
➓ 10% Complete

Loading...
➑ 17% Complete

Loading...
➐ 23% Complete 

Loading...

So use throttling and icons to build polished progress displays.

Complete Examples and Recipes

Here are some full-length PowerShell scripts showing practical progress bar usage:

1. Progress Bar for Web Download

Defines functions for multi-threaded file downloads from URLs with individual progress indicators.

2. Monitoring SQL Server Backup

Renders progress bar showing stages of SQL database backup process along with time statistics.

3. PowerShell File Copy GUI

Uses PrimalForms to build a WPF app for file explorer-like copy with progress bar.

4. Robocopy Wrapper Script

Encapsulates Robocopy file replication and monitoring its native progress output.

Visual Studio Code Extension

For writing progress-enabled scripts, the PowerShell Progress extension by James Dallaway is handy.

It shows a liveupdating progress bar during code debugging sessions within the VS Code editor.

Cosmetic Tweaks

To alter visual aspects like color, width and animation – tweak these before calling Write-Progress:

$ProgressPreference.ForegroundColor = ‘Cyan‘
$ProgressPreference.BackGroundColor = ‘Magenta‘  

$host.UI.RawUI.BufferSize = New-Object System.Management.Automation.Host.Size(80, 40)
$host.UI.RawUI.WindowSize = $host.UI.RawUI.BufferSize 
$host.UI.RawUI.CursorSize = 0

Write-Progress ...

Review this reference for all styling options.

Alternative Libraries

Besides the built-in Cmdlet, some community modules for enhanced progress displays are:

So check out these libraries for more choice.

Summary

With that, we have explored all key aspects around building progress bar workflows in PowerShell.

The techniques range from basic Foreach scenarios to throttling tricks for optimizing progress aesthetics and accuracy.

The Write-Progress cmdlet makes it quite simple to bake-in process tracking across your scripts. This improves visibility into long-running automation flows for end-users.

Feel free to apply these examples as handy snippets whenever you need to track PowerShell job execution.

Similar Posts