I use the code shown below to display a progress bar in my PowerShell script. It works, but it progress bar is displayed it in the PowerShell console.
However, I would like to create a GUI progress bar in a separate window when I click somewhere, as shown in this video: link.
Can someone help please with an example?
Here is the code of my console-based progress bar, adapted from this answer:
Write-Progress -Activity $activity -PercentComplete 0
$totalMinutes = 0.15 # Combien de temps faut-il afficher la barre de progression ?
$timerIntervalSecs = 0.1 # L'intervalle de déclenchement des événements de la minuterie
$global:continueWhile = $true # Variable globale qui permet de continuer ou d'arrêter la barre de chargement
$timer = [System.Timers.Timer]::new($timerIntervalSecs * 1000)
$eventJob = Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
$endTime, $totalMinutes, $activity = $Event.MessageData.EndTime, $Event.MessageData.TotalMinutes, $Event.MessageData.Activity
$timeLeft = $endTime - (Get-Date)
$percent = (1 - $timeLeft.TotalSeconds / ($totalMinutes * 60)) * 100
if ($timeLeft -lt 0) { $percent = 100; $timeLeft = [timespan] 0; $global:continueWhile = $false } # une fois entièrement écoulée, passe $continueWhile = $false
Write-Progress -Activity $activity -Status "$([math]::Floor($percent))% complete, $([math]::Ceiling($timeLeft.TotalSeconds)) seconds remaining..." -PercentComplete $percent
} -MessageData (@{ EndTime = (Get-Date).AddMinutes($totalMinutes); TotalMinutes = $totalMinutes; Activity = $activity })
$timer.Start() # Démarre la minuterie.
try {
Write-Output "test output"
while ($continueWhile) {
Start-Sleep -Seconds 0.5
}
} finally {
$timer.Stop()
Remove-Job $eventJob -Force
Write-Progress -Activity $activity -Completed
}
Here's the GUI equivalent of your console-based progress bar, using the WinForms ProgressBar
control.
Note:
Instead of using a global variable to communicate a value to the caller from the event script block, I've added entries to the hashtable that is passed to the -MessageData
parameter, which now acts as a bidirectional DTO (data-transfer object).
The form can be closed manually, in which case the percentage reached is communicated to the caller.
The form with the progress bar will appear topmost, i.e. it will stay visible even if you switch to other applications. Remove the TopMost = $true
hashtable entry, if that is undesired.
Your approach is somewhat unusual in that you're letting a fixed amount of time drive the progress display rather than an actual task being performed in the main thread. However, you could adapt it to report actual progress via the DTO instead, but note the fundamental limitation (which equally applies to your console-based progress bar):
while
loop in the main part of your script - where Start-Sleep -Seconds 0.5
is currently used as a stand-in for such operations - must have a short runtime, because the timer event only fires between commands. For instance, if a command in that loop runs for 2 minutes, updates to the progress bar are blocked during that time.Add-Type -AssemblyName System.Windows.Forms
# Parameters
$activity = 'Running...'
$totalMinutes = 0.15 # How many minutes to run in total
$timerIntervalSecs = 0.1 # The timer-event firing interval
$timer = [System.Timers.Timer]::new($timerIntervalSecs * 1000) # create the timer
# A data-transfer object used for communicating with
# the -Action script block below.
$dto = @{
Activity = $activity
EndTime = (Get-Date).AddMinutes($totalMinutes)
TotalMinutes = $totalMinutes
Done = $false # will be set to $true when the progress bar has reached 100% or the form was closed manually.
AbortedAt = $null # if the form was closed manually, will be set to the percent-complete value at that time.
Form = $null # will be filled in the -Action script block to store the form object between invocations.
}
# Register a script block as the delegate for the timer's "Elapsed" event
# (-Action parameter).
# The data-transfer object is passed via the -MessageData parameter.
$eventJob = Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action {
$endTime, $totalMinutes, $activity, $form = $Event.MessageData.EndTime, $Event.MessageData.TotalMinutes, $Event.MessageData.Activity, $Event.MessageData.Form
# Create the form on demand.
if (-not $form) {
$form = $Event.MessageData.Form = [System.Windows.Forms.Form] @{ TopMost = $true; Text = $activity; MinimizeBox = $false; MaximizeBox = $false; Width = 290; Height = 100; StartPosition = 'CenterScreen' }
$form.Controls.AddRange(@(
[System.Windows.Forms.Label] @{ Name = 'lbl'; Left = 10; Width = 250 }
[System.Windows.Forms.ProgressBar] @{ Name = 'pb'; Minimum = 0; Maximum = 100; Top = 30; Left = 10; Width = 250 }
))
$form.Show()
}
# Calculate the progress...
$timeLeft = $endTime - (Get-Date)
if ($timeLeft -lt 0) { $timeLeft = [timespan] 0 }
$completed = $timeLeft -eq 0
$percent = (1 - $timeLeft.TotalSeconds / ($totalMinutes * 60)) * 100
# ... and update the status label and progress bar
# Write-Progress -Activity $activity -Status "$([math]::Floor($percent))% complete, $([math]::Ceiling($timeLeft.TotalSeconds)) seconds remaining..." -PercentComplete $percent
$form.Controls['lbl'].Text = "$([math]::Floor($percent))% complete, $([math]::Ceiling($timeLeft.TotalSeconds)) seconds remaining..."
$form.Controls['pb'].Value = $percent
# NOTE: This is crucial to make the form paint and to to keep it responsive.
# If the timer interval is too long, the form will be sluggish to respond to attempts to move or close it.
[System.Windows.Forms.Application]::DoEvents()
# If the time has elapsed or the form has been closed, close the form (if it isn't already closed),
# and signal that fact to the calling thread.
if ($completed -or -not $form.Visible) { $Event.MessageData.AbortedAt = if (-not $form.Visible) { $percent }; $form.Dispose(); $Event.MessageData.Done = $true }
} -MessageData $dto
# Start the timer, which displays the form with the progress bar.
$timer.Start()
try {
"Test output"
# Wait until the timer has elapsed or the form has been closed.
while (-not $dto.Done) {
Start-Sleep -Seconds 0.5
}
if ($dto.AbortedAt) {
Write-Warning "Progress-bar window as manually closed at $($dto.AbortedAt.ToString('N2'))% complete."
} else {
"Progress-bar window auto-closed on completion."
}
} finally {
# Clean up.
$timer.Stop()
Remove-Job $eventJob -Force
}