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:
And here are the test code that I used to produce the above:
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
# 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:
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