Skip to content

Instantly share code, notes, and snippets.

@gravejester
Created June 5, 2025 06:57
Show Gist options
  • Save gravejester/9aa5f7bbeca88cb370dee919a65209b9 to your computer and use it in GitHub Desktop.
Save gravejester/9aa5f7bbeca88cb370dee919a65209b9 to your computer and use it in GitHub Desktop.
WPF Progress Bar in PowerShell
function New-wpfProgressBar {
param(
[string] $WindowTitle = 'Progress',
[int] $WindowWidth = 450,
[int] $WindowHeight = 140,
[string] $Text = "Processing...",
[switch] $IsIndeterminate,
[switch] $ShowPercentage,
[switch] $Topmost
)
try {
# load .NET classes needed for wpf
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
$ErrorActionPreference = 'Stop'
# hastable to handle communication between functions
# as well as between calling thread and the UI thread
$GLOBAL:pbSharedData = [hashtable]::Synchronized(@{ })
$pbSharedData.WindowReady = $false
$pbSharedData.ShouldClose = $false
$pbSharedData.IsIndeterminate = $IsIndeterminate
# pack input parameters into a single object
$params = @{
WindowTitle = $WindowTitle
WindowWidth = $WindowWidth
WindowHeight = $WindowHeight
IsIndeterminate = $IsIndeterminate
ShowPercentage = $ShowPercentage
Text = $Text
Topmost = $Topmost
}
# lets create a new runspace for our UI thread to live in
$runspace = [runspacefactory]::CreateRunspace()
$runspace.ApartmentState = 'STA'
$runspace.ThreadOptions = 'ReuseThread'
$runspace.Open()
# the UI thread need access to pbSharedData and the parameters
$runspace.SessionStateProxy.SetVariable('pbSharedData', $GLOBAL:pbSharedData)
$runspace.SessionStateProxy.SetVariable('params', $params)
# add a reference to the runspace itself in pbSharedData (just in case)
$pbSharedData.Runspace = $runspace
# create a new powershell process for the ui thread
$uiThread = [powershell]::Create()
# and inject the code that we want to run
[void]$uiThread.AddScript({
# this is our UI window
$window = New-Object -TypeName System.Windows.Window
$window.Title = $params.WindowTitle
$window.Width = $params.WindowWidth
$window.Height = $params.WindowHeight
$window.WindowStartupLocation = 'CenterScreen'
$window.ResizeMode = 'NoResize'
$window.WindowStyle = 'SingleBorderWindow'
$window.Topmost = $params.Topmost
$pbSharedData.Window = $window
# grids for layout
$grid = New-Object -TypeName System.Windows.Controls.Grid
$grid.Margin = '20'
$r1 = New-Object -TypeName System.Windows.Controls.RowDefinition
$r1.Height = 'Auto'
$r2 = New-Object -TypeName System.Windows.Controls.RowDefinition
$r2.Height = '*'
[void]$grid.RowDefinitions.Add($r1)
[void]$grid.RowDefinitions.Add($r2)
$pbGrid = New-Object -TypeName System.Windows.Controls.Grid
$pbGrid.Height = 25
$pbGrid.Margin = '0,0,0,10'
[System.Windows.Controls.Grid]::SetRow($pbGrid, 0)
# the progress bar
$progressBar = New-Object -TypeName System.Windows.Controls.ProgressBar
$progressBar.Name = 'ProgressBar'
$progressBar.Minimum = 0
$progressBar.Maximum = 100
$progressBar.Value = 0
$progressBar.IsIndeterminate = $params.IsIndeterminate
[void]$pbGrid.Children.Add($progressBar)
# show percent complete text on the bar (if applicable)
if ($ShowPercentage) {
$percentageText = New-Object -TypeName System.Windows.Controls.TextBlock
$percentageText.Name = 'PercentageText'
$percentageText.Text = '0%'
$percentageText.HorizontalAlignment = 'Center'
$percentageText.VerticalAlignment = 'Center'
$percentageText.FontWeight = 'Bold'
$percentageText.Foreground = 'Black'
[void]$pbGrid.Children.Add($percentageText)
}
[void]$grid.Children.Add($pbGrid)
# the progress text
$progressText = New-Object -TypeName System.Windows.Controls.TextBlock
$progressText.Name = 'ProgressText'
$progressText.Text = $params.Text
$progressText.HorizontalAlignment = 'Left'
$progressText.Margin = '2,0,10,10'
$progressText.VerticalAlignment = 'Top'
$progressText.TextWrapping = 'Wrap'
$progressText.FontSize = 12
[System.Windows.Controls.Grid]::SetRow($progressText, 1)
[void]$grid.Children.Add($progressText)
# add the main grid to the window
$pbSharedData.Window.Content = $grid
# show the wpf window
$pbSharedData.WindowReady = $true
$pbSharedData.Window.Show()
# lets add any error messages to pbSharedData
$pbSharedData.Error = $Error
# ui thread message loop
while (-not $pbSharedData.ShouldClose -and $pbSharedData.Window.IsVisible) {
# check for updates to the progress text
if ($pbSharedData.ProgressText) {
$progressText.Text = $pbSharedData.ProgressText
}
# check for updates to the progress bar value
if ($pbSharedData.ProgressBarValue) {
$progressBar.Value = $pbSharedData.ProgressBarValue
}
# check for updates to the percentage complete text
if ($pbSharedData.PercentageText) {
$percentageText.Text = $pbSharedData.PercentageText
}
# update ui thread
[System.Windows.Threading.Dispatcher]::CurrentDispatcher.Invoke(
[System.Windows.Threading.DispatcherPriority]::Background,
[System.Action] {})
# for the IsIndeterminate animation to play smoothly we need a quicker loop
if ($params.IsIndeterminate) {
Start-Sleep -Milliseconds 10
} else {
Start-Sleep -Milliseconds 500
}
}
# if we get this far, we are out of the loop above
# and if we are - it means we want to close the UI window
if ($pbSharedData.Window.IsVisible) {
$pbSharedData.Window.Close()
}
})
# tell the ui thread to use the new runspace we created
$uiThread.Runspace = $runspace
# invoke the code in the ui thread
$invokeHandle = $uiThread.BeginInvoke()
# save a reference in pbSharedData
$pbSharedData.invokeHandle = $invokeHandle
} catch { Write-Warning "Error creating progress window: $($_.Exception.Message)" }
}
function Close-wpfProgressBar {
if ($pbSharedData) {
try {
$pbSharedData.ShouldClose = $true
Start-Sleep -Milliseconds 300
try {
# some clean-up of the runspace (if it exists)
$pbSharedData.Runspace.Close()
$pbSharedData.Runspace.Dispose()
} catch {}
# delete the shared hashtable variable
Remove-Variable -Name 'pbSharedData' -Scope Global -Force
} catch { Write-Warning "Error closing progress window: $($_.Exception.Message)" }
}
}
function Write-wpfProgressBar {
param(
[ValidateRange(0, 100)]
[int]$PercentComplete = 0,
[string]$Text
)
try {
$ErrorActionPreference = 'Stop'
if (-not $pbSharedData) { Write-Warning "pbSharedData not found - please use New-wpfProgressBar!"; return }
# update progress text
$pbSharedData.ProgressText = $Text
# update progress bar vale
$pbSharedData.ProgressBarValue = $PercentComplete
# update percentage complete text
$pbSharedData.PercentageText = "$PercentComplete%"
} catch { Write-Warning "Failed to update progress window: $($_.Exception.Message)" }
}
<#
# Example usage:
# Create a standard progress window with percentage display
New-wpfProgressBar -WindowTitle "File Processing" -Text "Starting file processing..." -ShowPercentage
# Simulate some work with progress updates
for ($i = 1; $i -le 10; $i++) {
Start-Sleep -Milliseconds 500
$percent = ($i / 10) * 100
Write-wpfProgressBar -PercentComplete $percent -Text "Processing file $i of 10..."
}
Close-wpfProgressBar
# Example with indeterminate progress bar
New-wpfProgressBar -WindowTitle "Loading Data" -IsIndeterminate -Text "Loading, please wait..."
Start-Sleep -Seconds 5
Close-wpfProgressBar
#>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment