More PowerShell Scripting Candy
I hope the recent articles on embellishing your PowerShell code have been interesting and inspiring. Running code in the console doesn't have to be boring and pedestrian. Of course, if your code is being run in a non-interactive session, you might want to consider a different approach. But for those times when you are running code interactively, why not add a little flair? Today, I want to continue with this theme and show you how to create a simple progress bar using ANSI escape sequences.
In the past, I've written scripts that leverage the Write-Progress
cmdlet. However, the progress output is ephemeral. And in PowerShell 7 the new minimal view introduces new challenges. Don't get me wrong. I like Write-Progress
in PowerShell 7, but I need to write code that takes the minimal view into account. My older Windows PowerShell scripts won't show the same information.
That said, let's build an alternative using ANSI techniques. Along the way, we'll also play with custom characters and emojis. All of my samples should be considered proof-of-concept and not production-ready code. But I hope they will inspire you.
Let's get started with something simple and different, a progress spinner. A spinner is a simple way to indicate that something is happening. You've seen this sort of thing in Windows. I want to create one that runs in the PowerShell console.
A Simple Progress Spinner
A spinner is a simple animation of images like a flip book you may have created as a child. A series of images is displayed in the same space, each replacing the previous one. The result is an animation. In the console, we can create a similar effect by displaying a series of characters in the same location.
I'll define an array of characters that will be displayed in sequence. Here is a simple example.
$glyphs = @(
[char]::ConvertFromUtf32(0x25b2),
[char]::ConvertFromUtf32(0x25bA),
[char]::ConvertFromUtf32(0x25bc),
[char]::ConvertFromUtf32(0x25C4)
)
These characters I got using the charmap.exe utility.

The utility will show you the Unicode value for the character. In this example, I'm using triangle characters. To display them in the console, I need to convert the value to a character.
PS C:\> $glyphs
▲
►
▼
◄
Be sure to put the characters in the order you want them displayed. To create the animation, I'll loop through the array, displaying each character in the same location for a short period.
1..5 | ForEach-Object {
$glyphs | ForEach-Object {
Write-Host "`e[1A`e[2K$_ Working...." -ForegroundColor Cyan
Start-Sleep -Milliseconds 250
}
} -End {
Write-Host "`e[1A`e[2K"
}
I am using the ANSI-escape sequence e[1A
to move the cursor up one line and e[2K
to clear the line. I showed you these in a previous article. The result is a simple spinner.

You have to try the code to see the effect for yourself.
For a variation, you don't have to display the glyph in the same location. This code will scroll each image across the screen.
1..5 | ForEach-Object {
$glyphs | ForEach-Object {
Write-Host "`e[2K$_" -ForegroundColor Yellow -NoNewline
Start-Sleep -Milliseconds 250
}
} -End {
Write-Host "`e[2K"
}
The effect is a rolling set of characters. You might need to re-order them in the array to get the effect you want.
Using Emojis
There's no limit to what you can animate. You might find a series of emojis to use. I have a sample using the see-no-evil, hear-no-evil, and speak-no-evil monkey emojis. You will need to find the Unicode values for these characters. I found mine online.
$monkeys = @(
[char]::ConvertFromUtf32(0x1f435),
[char]::ConvertFromUtf32(0x1f648),
[char]::ConvertFromUtf32(0x1f649),
[char]::ConvertFromUtf32(0x1f64a)
)
You can also get the values with this process. Open the Windows Emoji picker by pressing Win+. or Win+: and find the emoji you want. Create a variable and assign the emoji to it. Then use the Get-Clipboard
cmdlet to get the Unicode value.
$m = '🙈'
Convert this string.
$t = [char]::ConvertToUtf32($m,0)
You can now use this value to define the emoji in your array.
$noSee = [char]::ConvertFromUtf32($t)
I'll use the same sample code to animate the monkey emojis. Although, I am also using ANSI escape sequences to format the line. You could also use $PSStyle
.
1..5 | ForEach-Object {
$monkeys | ForEach-Object {
#using ANSI formatting
Write-Host "`e[1A`e[2K$_ `e[3;38;5;190mWorking....`e[0m"
Start-Sleep -Milliseconds 250
}
} -End {
Write-Host "`e[1A`e[2K"
}

Again, I hope you'll try the demo because the effect is quite fun.
A Sample Function
I put together a sample function that uses this technique.
#requires -version 7.4
#requires -modules ThreadJob
Function Measure-ProgramFiles {
[cmdletbinding()]
Param ()
$monkeys = @(
[char]::ConvertFromUtf32(0x1f435),
[char]::ConvertFromUtf32(0x1f648),
[char]::ConvertFromUtf32(0x1f649),
[char]::ConvertFromUtf32(0x1f64a)
)
#get the top level folders
$Top = Get-ChildItem -Path $env:ProgramFiles -Directory
#process each folder using a ThreadJob
foreach ($item in $top) {
$job = Start-ThreadJob -Name FolderMeasure -ScriptBlock {
Param($Directory)
Try {
$measure = Get-ChildItem -Path $Directory.FullName -Recurse -File -ErrorAction Stop |
Measure-Object -Property Length -Sum
[PSCustomObject]@{
PSTypeName = 'ProgramFileInfo'
Name = $Directory.Name
Count = $measure.Count
Size = $measure.Sum
Created = $Directory.CreationTime
Path = $Directory.FullName
Computername = $env:COMPUTERNAME
}
} #try
Catch {
Write-Warning "Failed to measure $($Directory.FullName) $($_.Exception.Message)"
} #catch
} -ArgumentList $Item
} #foreach Item
Do {
$monkeys | ForEach-Object {
Write-Host "`e[1A`e[2K$_ `e[3;38;5;190mMeasuring $($Top.Count) top-level folders....`e[0m"
Start-Sleep -Milliseconds 250
}
#I am not using Wait-Job because that pauses the pipeline
} Until ((Get-Job FolderMeasure).State -NotContains 'running')
#clear the monkey spinner
Write-Host "`e[1A`e[2K"
Get-Job -Name FolderMeasure | Receive-Job
Remove-Job -Name FolderMeasure
} #end Measure-ProgramFiles
This is a potentially long-running task with multiple thread jobs running in parallel for performance. The function will display a monkey spinner while the jobs are running. When the jobs are complete, the function will display the results, and the spinner is cleared.

PS C:\>
Name : 1Password
Count : 82
Size : 409452949
Created : 7/11/2024 10:15:57 AM
Path : C:\Program Files\1Password
Computername : THINKX1-JH
Name : bottom
Count : 2
Size : 4623597
Created : 4/24/2024 12:04:12 PM
Path : C:\Program Files\bottom
Computername : THINKX1-JH
...
You could also use the ANSI escape sequences I showed in an earlier article to position the spinner on the screen such as at the bottom.
Summary
I'll admit there isn't much practical value to what I've shown you today. Other than the ability to inform the user that your code is running and not hung. Again, this is an opportunity to be creative and have some fun with your PowerShell scripting.
I have one more related topic to cover. I didn't want to overwhelm you today, and I wanted to give you a chance to try these examples. I'll wrap up this series next month. In the meantime, if you find a way to use what I've shown today, I would love to see what you've written.
It's okay if sometimes it's not very practical. A bit of fun and eye-candy didn't kill anyone. :)