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 comment