Created
February 9, 2023 15:52
-
-
Save scriptingstudio/29d36563f9175e40958f81b4c9f0823c to your computer and use it in GitHub Desktop.
Simple one-level asynchronous wrapper over the builtin PowerShell progress bar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Displays an asynchronous progress bar within a PowerShell command window. | |
.DESCRIPTION | |
A simple one-level asynchronous wrapper over the builtin PowerShell progress bar. | |
Features: | |
- Non-blocking screen output | |
- Separate run phases: start,next,stop | |
- Automatic percentage calculation | |
- Automatic state control | |
- Automatic overflow control | |
- Input object counter step | |
- Internal variables | |
.PARAMETER Start | |
Indicates to initialize the progress bar. | |
.PARAMETER Next | |
Indicates to increase the counter of the progress bar. | |
.PARAMETER Stop | |
Indicates to stop the progress bar. | |
.PARAMETER Range | |
Specifies the data range. | |
.PARAMETER Frequency | |
Specifies input object counter step - progress bar update frequency - once a step. This parameter provides eventing deceleration that can be useful on big data series. | |
.PARAMETER Activity | |
Specifies the first line of text in the heading above the status bar. This text describes the activity whose progress is being reported. | |
.PARAMETER Status | |
Specifies the second line of text in the heading above the status line. This text describes current state of the activity. | |
.PARAMETER ProgressBar | |
Specifies the progress bar control data structure. | |
.PARAMETER PcDigit | |
Specifies the percent precision to display in the status line. | |
.PARAMETER InputObject | |
Experimental. Specifies the current input object from the data pipeline. | |
.PARAMETER Keep | |
Experimental. Enables to leave the progress bar on the screen after stopping it. | |
.PARAMETER AutoFinish | |
Experimental. Enables autofinish upon progress bar complete. | |
.INPUTS | |
None | |
You can't pipe objects to this cmdlet. | |
.OUTPUTS | |
Object/None | |
Upon initialization this cmdlet returns Progress Bar control data structure. Otherwise the cmdlet returns no output. | |
.NOTES | |
Version: 2.1 | |
Internal variables you can use in Status line: | |
- %PercentComplete | |
- %Progress | |
- %Inputobject | |
On crash or pressing Ctrl-C event handler will keep running in background. | |
.EXAMPLE | |
$range = 100 | |
$jobtimeMS = 50 | |
$frequency = 1 | |
$param = @{ | |
range = $range | |
activity = 'Progress Bar [range - {0}; step - {1}; pct - {2}]' -f $range,$frequency,($range/100) | |
status = "'Complete: {0:P1} [{1}]' -f (%PercentComplete/100), %progress" | |
pcdigit = $Pcdigit | |
} | |
$pb = Write-ProgressBar -start @param | |
1..$range | % { | |
Write-ProgressBar -next -progressbar $pb | |
Start-Sleep -Milliseconds $jobtimeMS | |
} | |
Write-ProgressBar -stop -progressbar $pb | |
#> | |
function Write-ProgressBar { | |
#[CmdletBinding()] | |
param ( | |
# PB controls/commands | |
$progressbar, | |
[switch] $start, # starter | |
[switch] $next, # iterator | |
[alias('close')][switch] $stop, # cleaner | |
# runtime options | |
[int] $range, | |
[alias('step')][int] $frequency, # input object counter step | |
[string] $activity, | |
[string] $status, | |
[alias('precision')][int] $pcdigit, # percent precision | |
$inputobject, # experimental; how to use? | |
[switch] $autofinish, # experimental; | |
[switch] $keep # experimental; leave progress bar on screen | |
) | |
# parameterset autoresolver | |
if (($start,$next,$stop).Where{$_}.count -gt 1) { | |
Write-Warning "Cannot resolve a specified parameter set." | |
return | |
} | |
if ($next) { # iterator | |
if (-not $progressbar -or -not $progressbar['event']) {return} | |
if ($progressbar.overflow) {return} # set by event handler | |
$progressbar.Progress++ | |
# eventing decelerator | |
if (($progressbar.progress - $progressbar.frequency) -lt $progressbar.prev) { | |
return | |
} | |
$progressbar.Inputobject = if ($inputobject) {$inputobject} else {$null} | |
# remove old data to prevent high memory consumption | |
if ($progressbar.range -gt 100 -and $progressbar.trigger.count) {$progressbar.trigger.clear()} # {$progressbar.trigger.removeAt(0)} | |
# trigger the next progress event | |
$progressbar.trigger.add($progressbar) | |
} # next | |
elseif ($stop) { # cleaner | |
if (-not $progressbar -or -not $progressbar['event']) {return} | |
if (-not $keep) { | |
Write-Progress -PercentComplete 100 -Activity $progressbar['Activity'] -Completed | |
} | |
Get-EventSubscriber -SourceIdentifier $progressbar['eventId'] | Unregister-Event | |
$progressbar['event'] | Remove-Job -Force | |
$progressbar['event'] = $null | |
$progressbar['trigger'].clear() | |
$progressbar.clear() | |
} # stop | |
elseif ($start) { # initializer | |
if ($range -lt 1) { | |
Write-Host 'No data range specified.' -ForegroundColor Red | |
return | |
} | |
# input defaults | |
if (-not $activity) {$activity = 'Progress Bar'} | |
if ($status) { | |
$status = $status -replace '%PercentComplete','$param["PercentComplete"]' -replace '%progress','$progress.progress' -replace '%inputobject','$progress.inputobject' | |
# %inputobject can be object!!! | |
} elseif ($psversiontable.PSVersion.major -gt 5) { | |
$status = '$param["PercentComplete"]' | |
} | |
# progress bar control descriptor | |
$pbstate = [hashtable]::Synchronized(@{ | |
Inputobject = $null | |
Progress = 0 | |
Prev = 0 # previous progress value | |
Overflow = $false | |
Range = $range | |
Percent = $range/100 # items per percent; how to use? | |
Frequency = if ($frequency -gt 1) {$frequency} else {1} | |
Activity = $activity | |
Status = if ($status) {[scriptblock]::create($status)} else {$null} | |
Autofinish = $autofinish | |
Pcdigit = if ($range -gt 5000) {2} elseif ($range -gt 500) {1} else {0} | |
Trigger = [System.Collections.ObjectModel.ObservableCollection[object]]::new() | |
}) | |
if ($pcdigit -lt 0) {$pcdigit = 0} | |
if ($pcdigit -gt 0) {$pbstate['pcdigit'] = $pcdigit} | |
$pbstate['eventId'] = [guid]::newguid().guid | |
$evparam = @{ | |
InputObject = $pbstate['trigger'] | |
SourceIdentifier = $pbstate['eventId'] | |
EventName = 'CollectionChanged' | |
MessageData = $pbstate | |
} | |
$pbstate['event'] = Register-ObjectEvent @evparam -Action { | |
if ($Event.SourceEventArgs.Action.ToString() -ne 'Add') {return} # filter | |
$progress = $Event.MessageData | |
# display progress bar | |
$pcdigit = $progress.pcdigit | |
$w = if ($pcdigit) {5} else {4} | |
$pcwidth = $w + $pcdigit # 4-0 6-1 7-2 8-3... | |
$param = @{ | |
#$progress.inputobject | |
PercentComplete = [int](($Event.SourceEventArgs.NewItems[-1].progress*100)/$progress.Range) | |
Activity = $progress.Activity | |
} | |
# stop eventing if scale is overflow | |
if ($param['PercentComplete'] -gt 100) { | |
$progress.Overflow = $true | |
$progress.Trigger.clear() | |
return | |
} | |
$param['Status'] = if ($progress.Status) {. $progress.Status} | |
else { # default status line | |
"Complete: {0,${pcwidth}:P${pcdigit}} [{1}]" -f ($param['PercentComplete']/100),$progress.progress | |
} | |
Write-Progress @param | |
$progress.prev = $progress.progress | |
<#if ($param['PercentComplete'] -eq 100 -and $progress.autofinish) { | |
Write-Progress -PercentComplete 100 -Activity $progress['Activity'] -Completed | |
Get-EventSubscriber -SourceIdentifier $progress['eventId'] | Unregister-Event | |
$progress['event'] | Remove-Job -Force | |
$progress['event'] = $null | |
$progress['trigger'].clear() | |
$progress.clear() | |
}#> | |
} # event handler | |
$pbstate | |
} # start | |
} # END Write-ProgressBar | |
# EXAMPLE | |
$range = if ($range) {$range} else {100} | |
$frequency = if ($frequency -gt 1) {$frequency} else {1} | |
if (-not $jobtimeMS) {$jobtimeMS = 100} | |
$param = @{ | |
range = $range | |
activity = 'Progress Bar [range - {0}; step - {1}; pct - {2}]' -f $range,$frequency,($range/100) | |
status = "'Complete: {0,6:P1} [{1}]' -f (%PercentComplete/100), %progress" | |
pcdigit = $Pcdigit | |
frequency = $frequency | |
} | |
$pb = Write-ProgressBar -start @param | |
1..$range | % { | |
Write-ProgressBar -next -progressbar $pb -inputobject $_ | |
Start-Sleep -Milliseconds $jobtimeMS | |
} | |
Write-ProgressBar -stop -progressbar $pb |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment