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
$iat 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.
Uses PrimalForms to build a WPF app for file explorer-like copy with progress bar.
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:
- PSWriteProgress – Mimics Unix command line loader
- progressbar – Renders an updating progress spinner
- posh-dotnet – Enables colored progress bars
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.


