Summary: Use [DATETIME] variables and arrays to produce a countdown timer.
Honorary Scripting Guy, Sean Kearney, here filling in for our good friend, Ed. With the New Year coming up in a few days, I decided to present something silly and geeky. I want to use Windows PowerShell to produce a New Year’s countdown timer.
Figuring out the difference between two dates and times in Windows PowerShell is actually surprisingly easy. That is one of the reasons I love using and playing with Windows PowerShell…the difficult is usually very easy!
So first we need define the time to countdown to. We’re going to define the date and time with a simple string:
However, in my scenario, I decided I would have my script be reusable for each year, so I have a parameter called $CountdownYear:
So now I can substitute in the year automatically like this:
With our final countdown time built, we can figure out the difference between our future date and now by using some simple math:
If we look at the $data object, we’ll see that it yields some very useful information that I don’t even have to dig for:
From this object, I immediately have all the information I want…the difference in days, hours, minutes, and seconds!
OK, that’s the easy part. Now I need a two-dimensional array to hold my numbers. I am literally going to build a large digital clock with Windows PowerShell. The first challenge is to build a function to draw numbers on the console. We’re going to accept two parameters: The Number we’d like to show and its column position on the screen.
$Number=New-object 'object[,]' 11,8
$Number[0,1]="* * "
$Number[0,2]="* * "
$Number[0,3]="* * "
$Number[0,4]="* * "
$Number[0,5]="* * "
$Number[1,0]=" * "
$Number[1,1]=" * "
$Number[1,2]=" * "
$Number[1,3]=" * "
$Number[1,4]=" * "
$Number[1,5]=" * "
$Number[1,6]=" * "
Each member of the array will hold a simple 8x8 representation of a number. The pattern of the arrays will continue all the way until number 9, with an additional spot to hold the colon. I’ve done this as a function for two reasons. One, it is a repeated action that made sense at the time. Two, if you were to get creative with the WPK, PowerBoots, or even something with Windows Forms, you could (in theory) rewrite this script with some custom GUI output.
Now we’ll determine the physical starting column on screen from the position object passed from the $column object. Because each digit is a multiple of 8 in size, we’ll factor that into determining the starting column position:
Now for the fun drawing stuff…
We’re going to remember where the cursor is now before we muck about:
Now we’ll build the new location for the cursor based on the adjusted value:
Then we use a small loop to extract the values from the array and draw them onto the screen. They will draw an array member and reposition to exactly one row below where they drew it last. Just for fun, we’re going to make the numbers yellow and bright on the screen:
for($a=0;$a -le 7;$a++)
write-host -foregroundcolor Yellow -object $number[$digit,$a] -NoNewline
When we’re done, we’ll reset everything back:
What will this function do? It works like this:
DRAW-Number 5 3
This will draw the number 5 in column 3.
DRAW-Number 2 6
This will draw the number 2 in the column 6.
Can you see where this could now go? We can parse the individual numbers and draw them on the screen, so we just need a basic loop to track the differences, display the numbers, and wait until the New Year ends.
WRITE-HOST -ForegroundColor Cyan -Object "`n`n It is now $Days Day(s) until New Years Eve $CountdownYear and ...`n`n"
WRITE-HOST -ForegroundColor Green " HOURS MINUTES SECONDS`n`n"
We need to trap situations where this is a single digit in the numeric data. In all cases, I want to have two digits displayed. We can solve that by using the PadLeft method, which will allow us to literally pad left of the string with as many characters as we want. In this case, we want to pad 2 characters, always being the number 0.
When we have that, we simply use a substring to grab the individual digits:
Draw-Number ($data.hours.tostring().padleft(2,"0").substring(0,1)) 0
Draw-Number ($data.hours.tostring().padleft(2,"0").substring(1,1)) 1
Draw-Number 10 2
Draw-Number ($data.minutes.tostring().padleft(2,"0").substring(0,1)) 3
Draw-Number ($data.minutes.tostring().padleft(2,"0").substring(1,1)) 4
Draw-Number 10 5
Draw-Number ($data.seconds.tostring().padleft(2,"0").substring(0,1)) 6
Draw-Number ($data.seconds.tostring().padleft(2,"0").substring(1,1)) 7
Believe it or not, we need to slow this down to get rid of the cursor flickering all over the screen. So we’ll sleep for just under a second each time:
start-sleep -Seconds .75
until((GET-DATE) -gt $Countdown)
Whoa! Quite a mouthful on the screen! Of course we should do something after the countdown has completed. I decided on some simple flashy lights saying “Happy New Year 2014!”
We’ll just pop those over and over and over ad infinitum with some random colors:
WRITE-HOST -foregroundColor (GET-RANDOM ("Black","Blue","Cyan","Gray","Green","Magenta","Red","White","Yellow")) -OBJECT "H A P P Y N E W Y E A R $countdownyear" -nonewline
} until ($FALSE)
When this is all together and running with the pretty colors (and even a little trickery to reshape the console), we’ll have a running clock!
If you’d like this avoid any typing (highly recommended) and let the computer do all the work, you can download the script from the Script Center Repository: New Year's Countdown Script.
Launch your countdown.ps1 by default, and you’ll get this result (depending on your local date and time):
But you can also launch it like this:
./countdown.ps1 –countdownyear 2014
./countdown.ps1 –countdownyear 2015
It’s the script that just keeps on giving! Enjoy yourself in the upcoming New Year, and remember, Keep on Scriptin’!
I invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to firstname.lastname@example.org, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Sean Kearney, Honorary Scripting Guy and Windows PowerShell MVP