Menu Close

Powershell Challenge: Create a clock

Last week I shared on twitter that I had build a clock.

Once I was down the rabbit-hole, I put way to much time in this project and ended up with this:

Look how fancy! And completely useless. But it’s fancy!

This made me realize though, that this is the way I thought myself how to use Powershell. I teach a lot of junior system admins and they often ask me how to get started with Powershell, as it can be so overwhelming. I always tell them to find a use-case for themselves, use Google and have fun. Use a computer you can break and find out what the different cmdlets do.

There is a disadvantage to this: You will learn to write very ugly code, as you have no clue what you are doing. But I think this is OK as long as you put in the effort to correct it along the way. Don’t forget to ask for reviews and support. I wouldn’t have gotten where I am now if I had started with the theory, as I would have gotten bored very fast. So I really like this style of learning personally.

This project had quit a few different factors to take into consideration and I want to show you how I did it.

Disclaimer: This code might not be the most clean or the most effective. If you know of better ways, please let me know in the comments so we can all learn a bit more 🙂

The Challenge

What my plan was: I wanted to create a clock interface with the numbers build up by the numbers itself. That was pretty much my whole idea. When I was done I added the seconds and made the clock run (in a very bad way I might add, but we will get to that later)

If you are thinking about some ideas which could accomplice the above output (or something similar), I would like to invite you to stop reading now and try to build it. Use Google and see how far you will come. Let me know how you’ve reached your goal.

=====================

The different elements
I made a bit of a plan in my head of the different elements I needed:
– The numbers needed to have the right layout to become a block that formed the number itself
– I wanted the numbers laid out next to each other, not underneath each other
– The numbers needed to represent the time it was at that point.
– This all needed to be a function that could easily be called on.

So I took this plan one step of the time.

Creating the numbers
The numbers I just drew out in the Visual Studio text box and then I replaced every hard return with `n. (This starts a new line within a string). I gave them all a variable and that’s it. This is de reason the numbers are a bit wonky, I didn’t plan the numbers at all, just started with one en went on with it. As a matter of fact, I had already created the clock before I realized I didn’t have a zero drawn out.

Code:

    $1 = "  1 `n  1 `n  1 `n  1 `n  1 `n  1 `n"
    $2 = " 222`n2   2`n   2`n  2`n 2   `n22222"  
    $3 = " 333`n3   3`n   3`n   3`n3   3`n 333`n"
    $4 = "4   4`n4   4`n4   4`n44444`n    4`n    4"
    $5 = "55555`n5`n5555`n    5`n5   5`n 555"
    $6 = " 6666`n6`n66666`n6    6`n6    6`n 6666"
    $7 = "777777`n    7`n   7`n  7`n 7`n7"
    $8 = " 8888`n8    8`n 8888`n8    8`n8    8`n 8888"
    $9 = " 9999`n9    9`n 9999`n     9`n     9`n 9999"
    $0 = " 0000`n0    0`n0    0`n0    0`n0    0`n 0000"
    $separator ="::`n::`n`n`n::`n::"

You can copy and paste these into Powershell and you will see the variable returns the number.

Lay the numbers next to each other
This would need to be some kind of table, otherwise they will be underneath each other.
A hash table doesn’t have enough options, as I need at least 5 elements. (2 numbers for the hours, 2 numbers for the minutes and a separator in between.
So an object could come in handy here.
A quick Google search quickly sends you to The Scripting Guy with a very clear explanation about creating a custom object.

$clock = [pscustomobject] @{
Hours1 = $hours1 
Hours2 = $hours2
seperator1 = $seperator
minutes1 = $minutes1
minutes2 = $minutes2
}

That small little line about the pscustomobject makes the variable $clock a lot more usable
If I now call for $clock, I get a mess

I assigned a few numbers to the different members to get an output to work with

$clock.Hours1 = $1
$clock.Hours2 = $2
$clock.minutes1 = $3
$clock.minutes2 = $4

The numbers are there, but they need to be next to each other

I knew format-table could help me out here. But if I didn’t, I would have set up a Google search for “powershell object output next to each other”
The second link already helps. So I used $clock | format-table -wrap

Alright, we are getting somewhere!

Now, it has to actually tell the real time.
Get-date is easily thought off. So let’s start there.

$hours = (get-date).Hour
$minutes = (get-date).Minute

The first problem: I needed to split the values as I had variables for each number. So Minutes1 and Minutes2 had to be separated.
To achieve this, I set up two substrings for each variable:

$hours.Substring(0,1))
$hours.Substring(1,1))

This gave me the two numbers separately.

Now the next issue was to get my script to understand that if the number was 2, I wanted to call $2. This was harder then I suspected. The bad way would have been to set up a very long if-statement.
If ($hours.Substring(0,1)) -eq 2){$clock.hours1 = $2)
So that times 10. And then a foreach-loop for each member. Ain’t nobody got time for that
(although it would have worked. Which would have made it technically correct.)

I found that I could call the variable I needed with get-variable.
This would look for a variable with the name equal to the name of my substring. It would then call the Name and Value. So I just needed to get the value for my script and I had a nice way to get it all together

$clock.Hours1 = (get-Variable "$($hours.Substring(0,1))").Value
$clock.Hours2 = (get-Variable "$($hours.Substring(1,1))").Value
$clock.minutes1 = (Get-Variable "$($minutes.Substring(0,1))").Value
$clock.minutes2 = (Get-Variable "$($minutes.Substring(1,1))").Value

Together with format-table -wrap, I actually got the clock I was aiming for and I called for my first victory

But then, I found an issue

Hm. Let’s see the output of $minutes

Ahhh, when you call time, it doesn’t add zero’s at the start. So now the string is one number instead of two and the substringcommand fails. So a quick and dirty fix:

if ($hours.Length -eq 1){$hours = "0"+$hours}
if ($minutes.Length -eq 1){$minutes = "0"+$minutes}

Alright!
Only those headers don’t add much to the product. Good thing we can rid of them with
$clock | Format-Table -Wrap -HideTableHeaders

It was at this point that I posted the clock on Twitter as a very good way to have spent the evening.

My colleague @JPlugge1 saw the picture and asked me “does it run?”. And I answered “sure, if I put it in a loop with a start-sleep for a minute’.

Well that would just be silly. We should add seconds and loop it with 1 second in between! So I did.

while($true){
runningclock
start-sleep 1
}

Alright it’s cheating a bit. But in my defense, this is a very very useless project.

So I’ve got a loop with seconds running. But the output got a bit wide now

Doesn’t really improve readability. Hm… what to do.

So this was actually new for me, as I had never had the need before to create a nice output within the Powershell-window. So I went on a google search.
“format-table column width” is actually a standard search, but I didn’t find any answers that didn’t require a whole new code-block. Oh well.

$clockvisual = @{Expression={$_.hours1}; Label="Hours 1 ";width=10}, `
@{Expression={$_.hours2};Label="Hours 2";width=10}, `
@{Expression={$_.seperator1};Label="seperator 1";width=10},
@{Expression={$_.minutes1};Label="minutes1";width=10},
@{Expression={$_.minutes2};Label="minutes 2";width=10},
@{Expression={$_.seperator2};Label="seperator 2";width=10},
@{Expression={$_.seconds1};Label="seconds 1";width=10},
@{Expression={$_.seconds2};Label="seconds2";width=10}

$clock | Format-Table $clockvisual -Wrap -HideTableHeaders

I did some playing around with these values and ended up with this

But now the window is far to big…
Google again. Hey, Scripting Guy again. .
Note that this link is 12 years old! There might be better ways now.

$pshost = get-host
$pswindow = $pshost.ui.rawui
$newsize = $pswindow.buffersize
$newsize.height = 3000
$newsize.width = 150
$pswindow.buffersize = $newsize
$newsize = $pswindow.windowsize
$newsize.height = 15
$newsize.width = 100
$pswindow.windowsize = $newsize

Just a bit of tweaking of the loop to make sure the screen is empty for the clock and there we go! A very nice, very useless project. But a nice way to relax and a great way to learn some new things!

The final script is posted beneath. I want to add again that I was playing around and this might not be the most effective code. If you know better ways, don’t hesitate to put them in the comments so we can all learn a bit more!

function runningclock {
    #create the numbers
    $1 = "  1 `n  1 `n  1 `n  1 `n  1 `n  1 `n"
    $2 = " 222`n2   2`n   2`n  2`n 2   `n22222"  
    $3 = " 333`n3   3`n   3`n   3`n3   3`n 333`n"
    $4 = "4   4`n4   4`n4   4`n44444`n    4`n    4"
    $5 = "55555`n5`n5555`n    5`n5   5`n 555"
    $6 = " 6666`n6`n66666`n6    6`n6    6`n 6666"
    $7 = "777777`n    7`n   7`n  7`n 7`n7"
    $8 = " 8888`n8    8`n 8888`n8    8`n8    8`n 8888"
    $9 = " 9999`n9    9`n 9999`n     9`n     9`n 9999"
    $0 = " 0000`n0    0`n0    0`n0    0`n0    0`n 0000"
    $separator = "::`n::`n`n`n::`n::"
    
    #create a custom object to work with the numbers
    $clock = [pscustomobject] @{
        Hours1     = $hours1 
        Hours2     = $hours2
        seperator1 = $seperator
        minutes1   = $minutes1
        minutes2   = $minutes2
        seperator2 = $seperator
        seconds1   = $seconds1
        seconds2   = $seconds2
    }

    
    #get the time from get-date
    [string]$hours = (get-date).Hour
    [string]$minutes = (get-date).Minute
    [string]$seconds = (get-date).Second
    #add a zero to the strings if they have only 1 number
    if ($hours.Length -eq 1) {$hours = "0" + $hours}
    if ($minutes.Length -eq 1) {$minutes = "0" + $minutes}
    if ($seconds.Length -eq 1) {$seconds = "0" + $seconds}

    #set the variables based on the output of get-date
    $clock.Hours1 = (get-Variable  "$($hours.Substring(0,1))").Value
    $clock.Hours2 = (get-Variable  "$($hours.Substring(1,1))").Value
    $clock.minutes1 = (Get-Variable "$($minutes.Substring(0,1))").Value
    $clock.minutes2 = (Get-Variable "$($minutes.Substring(1,1))").Value
    $clock.seconds1 = (Get-Variable "$($seconds.Substring(0,1))").Value
    $clock.seconds2 = (Get-Variable "$($seconds.Substring(1,1))").Value
     
    #create a setup for the table-layout
    $clockvisual = @{Expression = {$_.hours1}; Label = "Hours 1 "; width = 10}, `
    @{Expression = {$_.hours2}; Label = "Hours 2"; width = 10}, `
    @{Expression = {$_.separator1}; Label = "separator 1"; width = 10},
    @{Expression = {$_.minutes1}; Label = "minutes1"; width = 10},
    @{Expression = {$_.minutes2}; Label = "minutes 2"; width = 10},
    @{Expression = {$_.separator2}; Label = "separator 2"; width = 10},
    @{Expression = {$_.seconds1}; Label = "seconds 1"; width = 10},
    @{Expression = {$_.seconds2}; Label = "seconds2"; width = 10}
   
    #call the clock with the right layout
    $clock | Format-Table $clockvisual -Wrap -HideTableHeaders 



}

#change the size of the powershell window
$pshost = get-host
$pswindow = $pshost.ui.rawui
$newsize = $pswindow.buffersize
$newsize.height = 3000
$newsize.width = 150
$pswindow.buffersize = $newsize
$newsize = $pswindow.windowsize
$newsize.height = 15
$newsize.width = 100
$pswindow.windowsize = $newsize

#run through the loop so the clock will run
while ($true) {
    cls
    write-host "klok"
    write-host " " 
    runningclock
    start-sleep 1
}
    
    

And because the point needs to be made every time: A speed-comparison between Powershell 5.1 and Powershell Core

Leave a Reply

Your email address will not be published. Required fields are marked *