Inline Progress Bar in PowerShell

In a previous post, I have touched upon the fact that Write-Progress can be quite slow, especially when used in the console host (as opposed to in ISE). So I started to roll my own, with a crucial difference; this progress bar is used inline. This gives it a couple of benefits:

  • The progress bar will not disappear after it’s done
  • You will keep track of the how far the bar had progressed when an error occurs
  • It supports transcripts

On top of that it seems to be quite a lot faster than Write-Progress, according to my tests.

I actually started on this project months ago, got it working but never finished it. Too many other exiting ideas I had to explore I guess. I recently created an animated gif of it in action and put it up on Twitter, and it generated some excitement and people were telling me to release it. That was just the thing to motivate me to finish it. So I did 🙂

Not only that, but I published it on the PowerShell Gallery (must first published module – yay!).

I made the function as similar to Write-Progress as I could, with some added stuff, like the ability to customize the progress bars visuals. To be able to keep it inline, I had to use the PSHost type, and record the cursor position and jumping back a lot. This means that you need to make sure you are not outputting anything to the host inside the loop, or it will break (visually).

I have written some example code of how to use it, and here you can see a screenshot of running that code:
InlineProgressbarTests

And here are the test code that I used to produce the above:


# testing psInlineProgress
$null = Start-Transcript Path C:\users\grave\Scripts\InlineProgressBar\transcript.txt
Write-Host ''
# Simple progressBar
Write-Host 'Example of default behaviour' ForegroundColor Magenta
Write-Host ''
$collection = 0..12
$count = 0
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
}
Write-InlineProgress Activity 'Finished processing all items' Complete UseWriteOutput
Write-Host ''
# ProgressBar with more information
Write-Host 'Example with SecondsRemaining and SecondsElapsed' ForegroundColor Magenta
Write-Host ''
$collection = 0..12
$count = 0
$start = Get-Date
$secondsRemaining = 0
$secondsElapsed = 0
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete SecondsRemaining $secondsRemaining SecondsElapsed $secondsElapsed.TotalSeconds
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
# calculating seconds elapsed and remaining
$secondsElapsed = (Get-Date) $start
$secondsRemaining = ($secondsElapsed.TotalSeconds / $count) * ($collection.Count $count)
if ($secondsRemaining -lt 0) {
$secondsRemaining = 0
}
}
Write-InlineProgress Activity 'Finished processing all items' Complete UseWriteOutput SecondsRemaining 0 SecondsElapsed $secondsElapsed.TotalSeconds
Write-Host ''
# Simple progressBar without Percent
Write-Host 'Example of progress bar without percent' ForegroundColor Magenta
Write-Host ''
$collection = 0..12
$count = 0
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete ShowPercent:$false
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
}
Write-InlineProgress Activity 'Finished processing all items' Complete UseWriteOutput ShowPercent:$false
Write-Host ''
# With error handling
Write-Host 'Example of error handling' ForegroundColor Magenta
Write-Host ''
$collection = 0..12
$count = 0
$error = $false
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete
try {
if ($item -eq 9) {
throw 'ERROR HAPPENED!'
}
else {
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
}
}
catch {
Write-InlineProgress Stop OutputLastProgress
$error = $true
$errorMessage = $_.Exception.Message
break
}
}
if (-not $error) {
Write-InlineProgress Activity 'Finished processing all items' Complete
}
else {
# workaround: this Write-Host is needed to be sure that the warning is written on the next line
Write-Host ''
Write-Warning $errorMessage
}
Write-Host ''
$null = Stop-Transcript
# customized progress bar
Write-Host 'Examples of customized progress bars' ForegroundColor Magenta
Write-Host ''
$collection = 0..12
$count = 0
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete ProgressCharacter ([char]9632) ProgressFillCharacter ([char]9632) ProgressFill ([char]183) BarBracketStart $null BarBracketEnd $null
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
}
Write-InlineProgress Activity 'Finished processing all items' Complete ProgressCharacter ([char]9632) ProgressFillCharacter ([char]9632) ProgressFill ([char]183) BarBracketStart $null BarBracketEnd $null
$collection = 0..12
$count = 0
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete ProgressCharacter ([char]9608) ProgressFillCharacter ([char]9608) ProgressFill ([char]183) BarBracketStart '|' BarBracketEnd '|'
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
}
Write-InlineProgress Activity 'Finished processing all items' Complete ProgressCharacter ([char]9608) ProgressFillCharacter ([char]9608) ProgressFill ([char]183) BarBracketStart '|' BarBracketEnd '|'
$collection = 0..12
$count = 0
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete ProgressCharacter ([char]9472) ProgressFillCharacter '' ProgressFill ''
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
}
Write-InlineProgress Activity 'Finished processing all items' Complete ProgressCharacter ([char]9472) ProgressFillCharacter '' ProgressFill ''
$collection = 0..12
$count = 0
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete ProgressCharacter '' ProgressFillCharacter '|' ProgressFill '\' BarBracketStart '|' BarBracketEnd '|'
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
}
Write-InlineProgress Activity 'Finished processing all items' Complete ProgressCharacter '' ProgressFillCharacter '|' ProgressFill '\' BarBracketStart '|' BarBracketEnd '|'
$collection = 0..12
$count = 0
foreach ($item in $collection) {
$count++
$percentComplete = ($count / $collection.Count) * 100
Write-InlineProgress Activity "Processing item #$($item)" PercentComplete $percentComplete ProgressCharacter 'C' ProgressFillCharacter '.' ProgressFill 'o'
Start-Sleep Milliseconds (Get-Random Minimum 160 Maximum 400)
}
Write-InlineProgress Activity 'Finished processing all items' Complete ProgressCharacter 'C' ProgressFillCharacter '.' ProgressFill 'o'
Write-host ''

As I mentioned above, and you probably have noticed from the code examples, I have used transcripts for a couple of the first examples. Here is how the transcript file looks:
InlineProgressbarTranscript

A little warning though, there most likely are bugs left in the code, so if you notice any please let me know in the comments sections below. Or even better, help me fix them. The code is on GitHub (https://github.com/gravejester/psInlineProgress).

 

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s