Friday Fun: PowerShell Countdown

Recently, Josh Atwell posted a PowerShell script that performed a countdown. Naturally, I was inspired and did the whole “embrace and extend” thing. Don’t get me wrong: Josh’s script is perfectly fine. I saw some opportunities to try some things and use it as a teaching device. If nothing else, let’s have a little fun.

First off, here’s my version, then I’ll go over a few highlights.


Function Start-Countdown {
<# comment based help is in the download version #>

Param(
[Parameter(Position=0,HelpMessage="Enter seconds to countdown from")]
[Int]$Seconds = 10,
[Parameter(Position=1,Mandatory=$False,
HelpMessage="Enter a scriptblock to execute at the end of the countdown")]
[scriptblock]$Scriptblock,
[Switch]$ProgressBar,
[Switch]$Clear,
[String]$Message = "Blast Off!"
)

#save beginning value for total seconds
$TotalSeconds=$Seconds

#get current cursor position
$Coordinate = New-Object System.Management.Automation.Host.Coordinates
$Coordinate.X=$host.ui.rawui.CursorPosition.X
$Coordinate.Y=$host.ui.rawui.CursorPosition.Y

If ($clear) {
Clear-Host
#find the middle of the current window
$Coordinate.X=[int]($host.ui.rawui.WindowSize.Width/2)
$Coordinate.Y=[int]($host.ui.rawui.WindowSize.Height/2)
}

#define the Escape key
$ESCKey = 27

#define a variable indicating if the user aborted the countdown
$Abort=$False

while ($seconds -ge 1) {

if ($host.ui.RawUi.KeyAvailable)
{
$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp,IncludeKeyDown")

if ($key.VirtualKeyCode -eq $ESCkey)
{
#ESC was pressed so quit the countdown and set abort flag to True
$Seconds = 0
$Abort=$True
}
}

If($ProgressBar){
#calculate percent time remaining, but in reverse so the progress bar
#moves from left to right
$percent=100 - ($seconds/$TotalSeconds)*100
Write-Progress -Activity "Countdown" -SecondsRemaining $Seconds -Status "Time Remaining" -PercentComplete $percent
Start-Sleep -Seconds 1
} Else {
if ($Clear) {
Clear-Host
}
$host.ui.rawui.CursorPosition=$Coordinate
#write the seconds with padded trailing spaces to overwrite any extra digits such
#as moving from 10 to 9
$pad=($TotalSeconds -as [string]).Length
if ($seconds -le 10) {
$color="Red"
}
else {
$color="Green"
}
Write-Host "$(([string]$Seconds).Padright($pad))" -foregroundcolor $color
Start-Sleep -Seconds 1
}
#decrement $Seconds
$Seconds--
} #while

if (-Not $Abort) {
if ($clear) {
#if $Clear was used, center the message in the console
$Coordinate.X=$Coordinate.X - ([int]($message.Length)/2)
}

$host.ui.rawui.CursorPosition=$Coordinate

Write-Host $Message -ForegroundColor Green
#run the scriptblock if specified
if ($scriptblock) {
Invoke-Command -ScriptBlock $Scriptblock
}
}
else {
Write-Warning "Countdown aborted"
}
} #end function

The basic premise hasn’t really changed. Run the function specifying the number of seconds to count down and at the end of the countdown display a message. I thought why not really do something? So I added an optional Scriptblock parameter. At the end of the countdown, it will be executed using Invoke-Command.


if ($scriptblock) {
Invoke-Command -ScriptBlock $Scriptblock
}

Next, I decided to add a way to abort the countdown. If you run the function in the ISE and use the Progress bar parameter, you can click the Stop button. In the console, you could press Ctrl+C but that seemed a little heavy handed to me so I inserted code to watch for a key press, specifically the ESC key. If the key press is detected, I set a flag variable to False and the seconds to 0 to break out of the While loop.


#define the Escape key
$ESCKey = 27

#define a variable indicating if the user aborted the countdown
$Abort=$False

while ($seconds -ge 1) {

if ($host.ui.RawUi.KeyAvailable)
{
$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp,IncludeKeyDown")

if ($key.VirtualKeyCode -eq $ESCkey)
{
#ESC was pressed so quit the countdown and set abort flag to True
$Seconds = 0
$Abort=$True
}
}
...

After the loop I inserted commands to check if the countdown was aborted or not. If not, then I display the message and run the scriptblock, if supplied.


if (-Not $Abort) {
if ($clear) {
#if $Clear was used, center the message in the console
$Coordinate.X=$Coordinate.X - ([int]($message.Length)/2)
}

$host.ui.rawui.CursorPosition=$Coordinate

Write-Host $Message -ForegroundColor Green
#run the scriptblock if specified
if ($scriptblock) {
Invoke-Command -ScriptBlock $Scriptblock
}
}
else {
Write-Warning "Countdown aborted"
}

I like the use of Write-Progress. I don’t think it gets enough attention. I decided to add in the progress bar which is incremented based on a percentage of how much time is to go.


$percent=100 - ($seconds/$TotalSeconds)*100
Write-Progress -Activity "Countdown" -SecondsRemaining $Seconds -Status "Time Remaining" -PercentComplete $percent

I’m subtracting the percent value from 100 so that the progress bar moves from left to right. It looked odd to me to move from “Full” to “Empty”, although I suppose for a countdown that is more accurate. You can switch this back.

My last change is how the countdown is handled in the console, if you don’t use the progress bar. In Josh’s version he cleared the screen and then wrote the current time, clearing the screen each time. The effect is you see the number change in the upper left corner of the console. But what if you don’t want to clear the screen? Writing the number will use a new line for each value. Using Write-Host with -NoNewLine will simply append to the current line and look ugly. So I decided to have some fun with raw host data.

First, I grab the coordinates of the cursor’s current position using the RawUI property from $host.


#get current cursor position
$Coordinate = New-Object System.Management.Automation.Host.Coordinates
$Coordinate.X=$host.ui.rawui.CursorPosition.X
$Coordinate.Y=$host.ui.rawui.CursorPosition.Y

I want this because I can use the coordinates to put the cursor to a specific location in the console.


#write the seconds with padded trailing spaces to overwrite any extra digits such
#as moving from 10 to 9
$pad=($TotalSeconds -as [string]).Length
if ($seconds -le 10) {
$color="Red"
}
else {
$color="Green"
}
Write-Host "$(([string]$Seconds).Padright($pad))" -foregroundcolor $color
Start-Sleep -Seconds 1
}

I also added code and logic so that the number is displayed in Green using Write-Host until it reaches 10, at which point it is written to the console in red.

I also use the coordinates for some fun if you use -Clear. I put the countdown timer in the center of the console window. In order to do that I had to find the dimensions of the window and divide by 2, treating each value as an [INT].


#find the middle of the current window
$Coordinate.X=[int]($host.ui.rawui.WindowSize.Width/2)
$Coordinate.Y=[int]($host.ui.rawui.WindowSize.Height/2)

Finally, I decided to also center the message. This meant figuring out the message length and then adjusting the X coordinate.


if ($clear) {
#if $Clear was used, center the message in the console
$Coordinate.X=$Coordinate.X - ([int]($message.Length)/2)
}
$host.ui.rawui.CursorPosition=$Coordinate

Curious to see what this looks like? (the clip has no audio)

You can download Countdown2 and load the Start-Countdown function into your PowerShell session. Be sure to read the help.

3 thoughts on “Friday Fun: PowerShell Countdown

    • Ignore that. Trying to get my blog “claimed” on Technorati but it is turning into more work than it is probably worth.

Comments are closed.