Yet Another Countdown Timer Using PowerShell

So I have been literally sitting on this script for about a year now and am finally looking to publish it after seeing Josh Atwell’s awesome post about writing a countdown timer in the PowerShell console and using it as motivation to stop being lazy with it. I figured I would take a break from doing some judging in this years Scripting Games to finish up this little ‘project’ and get it out of the way.

My take on this doesn’t use the console to display a countdown timer, but instead uses WPF to display the countdown timer. Like I said, I actually had this back around July when I wanted to have a countdown of some sort to let me know when the Omaha Half Marathon was happening and whipped it together with the plan of cleaning it up later on and blogging about it. Well, fast forward several months and here we go!

My requirements for this countdown timer is that had to consist of the following things:

  • Allow me to specify an end date
  • Allow a custom message to state what the countdown was for
  • Allow different font colors and sizes for both the countdown numbers and the custom message
  • Allow resizing of the countdown display with everything resizing proportionally
  • Ability to change the opacity of the display
  • Allow display to remain on top or fall behind other windows
  • Some sort of action when the countdown reaches 0

Ok, with that out of the way, I went forth by first coming up with my UI template using XAML (of course) and then verifying that everything is lined up correctly.

    <Window
        xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
        x:Name='Window' ResizeMode = 'NoResize' WindowStartupLocation = 'CenterScreen' Title = '$title' Width = '860' Height = '321' ShowInTaskbar = 'True' WindowStyle = 'None' AllowsTransparency = 'True'>
        <Window.Background>
        <SolidColorBrush Opacity= '0' ></SolidColorBrush>
        </Window.Background>
        <Grid x:Name = 'Grid' HorizontalAlignment="Stretch" VerticalAlignment = 'Stretch' ShowGridLines='false'  Background = 'Transparent'>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>                
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height = '*'/>
                <RowDefinition Height = '*'/>
            </Grid.RowDefinitions>   
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '0'> 
                <Label x:Name='d_DayLabel' FontSize = '$FontSize' FontWeight = '$FontWeight' Foreground = '$CountDownColor' FontStyle = '$FontStyle' />
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '1'> 
                <Label x:Name='DayLabel' FontWeight = '$FontWeight' Content = 'Days' FontSize = '$FontSize' FontStyle = '$FontStyle' Foreground = '$MessageColor' />            
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '2'> 
                <Label Width = '5' /> 
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '3'> 
                <Label x:Name='d_HourLabel' FontSize = '$FontSize' FontWeight = '$FontWeight' Foreground = '$CountDownColor' FontStyle = '$FontStyle'/>
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '4'> 
                <Label x:Name='HourLabel' FontWeight = '$FontWeight' Content = 'Hours' FontSize = '$FontSize' FontStyle = '$FontStyle' Foreground = '$MessageColor' />
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '5'> 
                <Label Width = '5' /> 
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '6'> 
                <Label x:Name='d_MinuteLabel' FontSize = '$FontSize' FontWeight = '$FontWeight' Foreground = '$CountDownColor' FontStyle = '$FontStyle'/>
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '7'> 
                <Label x:Name='MinuteLabel' FontWeight = '$FontWeight' Content = 'Minutes' FontSize = '$FontSize' FontStyle = '$FontStyle' Foreground = '$MessageColor' />
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '8'> 
                <Label Width = '5' />
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '9'> 
                <Label x:Name='d_SecondLabel' FontSize = '$FontSize' FontWeight = '$FontWeight' Foreground = '$CountDownColor' FontStyle = '$FontStyle' />
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '0' Grid.Column = '10'> 
                <Label x:Name='SecondLabel' FontWeight = '$FontWeight' Content = 'Seconds' FontSize = '$FontSize' FontStyle = '$FontStyle' Foreground = '$MessageColor' />
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' StretchDirection = 'Both' Stretch = 'Fill' Grid.Row = '1' Grid.ColumnSpan = '11'> 
                <Label x:Name = 'TitleLabel' FontWeight = '$FontWeight' Content = '$Message' FontSize = '$FontSize' FontStyle = '$FontStyle' Foreground = '$MessageColor' />        
            </Viewbox>
        </Grid>
    </Window>

After that is done, I can then start writing the backend code to handle all of the events that will make this countdown work like it is supposed to and provide all of the requirements that I listed above.

Some code used for this here:

    ##Connect to controls
    $TitleLabel = $Global:Window.FindName("TitleLabel")
    $d_DayLabel = $Global:Window.FindName("d_DayLabel")
    $DayLabel = $Global:Window.FindName("DayLabel")
    $d_HourLabel = $Global:Window.FindName("d_HourLabel")
    $HourLabel = $Global:Window.FindName("HourLabel")
    $d_MinuteLabel = $Global:Window.FindName("d_MinuteLabel")
    $MinuteLabel = $Global:Window.FindName("MinuteLabel")
    $d_SecondLabel = $Global:Window.FindName("d_SecondLabel")
    $SecondLabel = $Global:Window.FindName("SecondLabel")

    ##Events
    $window.Add_MouseRightButtonUp({
        $this.close()
        })
    $Window.Add_MouseLeftButtonDown({
        $This.DragMove()
        })    
    #Timer Event
    $Window.Add_SourceInitialized({
        #Create Timer object
        Write-Verbose "Creating timer object"
        $Global:timer = new-object System.Windows.Threading.DispatcherTimer 
        #Fire off every 5 seconds
        Write-Verbose "Adding 1 second interval to timer object"
        $timer.Interval = [TimeSpan]"0:0:1.00"
        #Add event per tick
        Write-Verbose "Adding Tick Event to timer object"
        $timer.Add_Tick({
            If ($EndDate -gt (Get-Date)) {
                $d_DayLabel.Content = ([datetime]"$EndDate" - (Get-Date)).Days
                $d_HourLabel.Content = ([datetime]"$EndDate" - (Get-Date)).Hours
                $d_MinuteLabel.Content = ([datetime]"$EndDate" - (Get-Date)).Minutes
                $d_SecondLabel.Content = ([datetime]"$EndDate" - (Get-Date)).Seconds
            } Else {
                $d_DayLabel.Content = $d_HourLabel.Content = $d_MinuteLabel.Content = $d_SecondLabel.Content = 0    
                $d_DayLabel.Foreground = $d_HourLabel.Foreground = $d_MinuteLabel.Foreground = $d_SecondLabel.Foreground = Get-Random -InputObject $Colors
                $DayLabel.Foreground = $HourLabel.Foreground = $MinuteLabel.Foreground = $SecondLabel.Foreground = Get-Random -InputObject $Colors
                If ($EndFlash) {
                    $TitleLabel.Foreground = Get-Random -InputObject $Colors
                }
                If ($EndBeep) {
                    [console]::Beep()
                }
            }
            })
        #Start timer
        Write-Verbose "Starting Timer"
        $timer.Start()
        If (-NOT $timer.IsEnabled) {
            $Window.Close()
            }
        })   
    $Global:Window.Add_KeyDown({
        Switch ($_.Key) {
            {'Add','OemPlus' -contains $_} {
                If ($Window.Opacity -lt 1) {
                    $Window.Opacity = $Window.Opacity + .1
                    $Window.UpdateLayout()
                    }            
                }
            {'Subtract','OemMinus' -contains $_} {
                If ($Window.Opacity -gt .2) {
                    $Window.Opacity = $Window.Opacity - .1
                    $Window.UpdateLayout()
                    }             
                }
            "r" {
                If ($Window.ResizeMode -eq 'NoResize') {
                    $Window.ResizeMode = 'CanResizeWithGrip'
                    }      
                Else {
                    $Window.ResizeMode = 'NoResize'             
                    }       
                }     
            "o" {
                If ($Window.TopMost) {
                    $Window.TopMost = $False
                    }
                Else {
                    $Window.TopMost = $True
                    }
                }     
            }
        })     
    $Window.Topmost = $True   

Once that was done, I decided that I wanted to make sure that I kept the console available for other commands (when you typically run a UI it will take over the console, preventing you from doing anything else), I decided to create a background runspace and send all of the parameters to that runspace and call the code. By doing this, the countdown timer will display AND I can still run commands and whatever else from the console.

Some of the code is here that I used to make this happen:

$rs = [RunspaceFactory]::CreateRunspace()
$rs.ApartmentState = “STA”
$rs.ThreadOptions = “ReuseThread”
$rs.Open() 
$rs.SessionStateProxy.SetVariable('EndDate',$EndDate) 
$rs.SessionStateProxy.SetVariable('Message',$Message) 
$rs.SessionStateProxy.SetVariable('Title',$Title) 
If ($PSBoundParameters['EndFlash']) {
    $rs.SessionStateProxy.SetVariable('EndBeep',$EndBeep)
}
If ($PSBoundParameters['EndBeep']) {
    $rs.SessionStateProxy.SetVariable('EndFlash',$EndFlash)
}
$rs.SessionStateProxy.SetVariable('FontWeight',$FontWeight) 
$rs.SessionStateProxy.SetVariable('FontStyle',$FontStyle) 
$rs.SessionStateProxy.SetVariable('FontSize',$FontSize) 
$rs.SessionStateProxy.SetVariable('CountDownColor',$CountDownColor) 
$rs.SessionStateProxy.SetVariable('MessageColor',$MessageColor) 
$psCmd = {Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase}.GetPowerShell() 
$psCmd.Runspace = $rs 
$psCmd.Invoke() 
$psCmd.Commands.Clear() 
$psCmd.AddScript({ 
...
}).BeginInvoke() | out-null

So lets see this thing in action:

.\Start-CountDownTimer.ps1 -EndDate (Get-Date "06/11/2012 12:00:00 AM") `
-Message "Time Until Tech-Ed 2012" -MessageColor Green `
-CountDownColor Black -EndBeep -EndFlash

image

And there you go! You can also change the opacity by using the + and – keys:

image

As well as resizing by clicking ‘r’ and simply resizing by dragging on the lower right hand corner.

image

image

Move the timer by holding down the left mouse button and dragging it around. Close the countdown timer by right-clicking on the countdown timer.

Now to show off using the –EndFlash parameter to watch the countdown ui show different colors once it reaches 0. I would show the –EndBeep, but you might not be able to hear it through the internet Smile.

.\Start-CountDownTimer.ps1 -EndDate (Get-Date) -EndBeep -EndFlash

image

image

As you can see, the colors for both the timer and message will change with each tick.

With that, I met all of my requirements that I wanted to have with this script. I know that there are a lot more things that I could add to this, but in this case, I am satisfied with the end results.

Download

You can download the script here.

Key Input Tips:
r: Toggles the resize mode of the clock so you can adjust the size.
o: Toggles whether the countdown remains on top of windows or not.
+: Increases the opacity of the clock so it is less transparent.
-: Decreases the opacity of the clock so it appears more transparent.

Let me know what you think of it!

Posted in GUI, powershell, scripts | Tagged , , , | Leave a comment

Scripting Games 2012: Know When To Use Format-Table

image

This was a common occurrence during the Scripting Games. Someone writing a great script or one liner that gave me everything that I needed. But then right at the end I see something like this:

| Format-Table

Ok, what is the big deal you ask? They are only outputting the data as a nicely formatted table which makes it easier to read on the console. Well, I won’t deny that it looks pretty on the console, but guess what you just did?

Well, the problem is that once you use Format-Table (or any of the Format* cmdlets for that matter) you immediately lose the original object that was being outputted by the previous command. This means that you cannot pipe the output into most commands as its new object type is not usable by other commands (except for some commands that redirect the output such as Out-File and such). Sorting no longer becomes an option as it doesn’t know how to deal with the new object. Don’t even bother trying to run an Export-CSV to output the data as a CSV file, because trust me, it will give you a pretty nicely unusable file that looks nothing like what you saw in the console.

Lets look as some simple examples here. First lets do a WMI query and look at an object and then pipe it into a Format-Table and see the object.

Get-WmiObject Win32_Service

image

Simple output from Get-WMIObject showing some information as a list. Not the easiest to read but it works great.

Get-WmiObject Win32_Service | Get-Member

image

As you can see, we have access to all of the properties and methods with this object. Now lets blow this useful object away with Format-Table.

Get-WmiObject Win32_Service | Format-Table

image

Sure, the output does look nice, but lets look under the hood at the new object.

Get-WmiObject Win32_Service | Format-Table | Get-Member

image

What you are seeing a whole lot of formatting of the object. All useful methods are gone and you cannot even see the properties anymore. Which leads into the next example: Sorting.

Get-WmiObject Win32_Service | Format-Table | Sort Name

What do you think will happen?

image

If your answer was fail, then congrats! As you can see this fails because of the new formatted object. If you try to pipe this into another cmdlet other than one that performs a redirection of output, you will become very familiar with this error message. What about using ForEach? Well, no error but at the same time, no output either.

Get-WmiObject Win32_Service | Format-Table | ForEach {$_.name}

image

Now I mentioned how writing to a CSV with Export-CSV would end in failure so let me show you just how pretty this output will be.

Get-WmiObject Win32_Service | Format-Table | Export-CSV -NoTypeInformation FTReport.csv
Invoke-Item FTReport.csv

image

Now that is a report that I would not want to send to my boss or anyone else for that matter. All you get now is a bunch of useless garbage that is taking up hard drive space.

Now I am not saying that you,  should never use Format-Table (or the other Format* cmdlets), I am just saying that use caution with them. In fact, unless you have a specific requirement to use these commands in  a script or function, DO NOT use them at all in your code! Leave it to the people using the script/function to decide if how they want to handle the object that you give them. Remember, the best thing you can do is give the original object if possible. Then they can do whatever they wish with it.

It is worth noting that some cmdlets output a table by default (Get-Process and Get-Service for instance) which can still be piped into another command or you can export to a CSV using Export-CSV. This is due to the  formatting files that are loaded when PowerShell starts up that define what is shown in a table format or list format but does not actually affect the original object itself. See this link some more information about these files. You can also run this command to get more information about formatting:

Help about_format

While not exactly on the same subject, one thing to keep in mind with one liners is this: Filter-Select-Sort. You want to filter as early as you can in the code to keep the returned objects as few as possible, then select only what you need and then sort/format the output.

So in closing, remember that while Format-Table does provide some nice output on the console, it can and will wreak havoc if you attempt to do much more with the newly formatted objects. Watch out when you use it!

Posted in powershell, Scripting Games 2012 | Tagged , , , | 3 Comments

Expert Commentator Post on Hey, Scripting Guy! For Advanced Event 2

imageMy Expert Commentator post on Hey, Scripting Guy! is live today. My focus today was on Advanced Event 2 with my own solution and explanation behind it. I had a fun time working on it and hope you enjoy it, too!

Expert Commentary: 2012 Scripting Games Advanced Event 2
Posted in News, powershell, Scripting Games 2012 | Tagged , , , | Leave a comment

Scripting Games 2012 Notes: Wait A Second, $True –eq “False”?!?

imageYes, what you are reading is correct,$True does in fact equal “False” in PowerShell. While this exact line was not used recently in the Scripting Games this year, people competing and submitting scripts did do something similar that gave a “false idea” that it was producing the correct data.

The event itself asked to find all running and stoppable services on a local or remote system. The solution was basically something like this:

Get-Service | Where {$_.CanStop}

Never mind that I am not using aliases or anything per the design guidelines as I am not worried about that here.

For the sake of a few examples, I am going to add a little more to this one-liner to get a more desirable output.

Get-Service | Where {$_.CanStop} | Select Name,CanStop

image

 

What I was instead seeing being submitted was something like this:

Get-Service | Where {$_.CanStop -eq "True"}

The reason for this is because it is already a boolean value and does not need to be evaluated to $True via a comparison. If you wanted to show all non-stoppable services, you would use the –Not (!) before $_.CanStop which would return all $False valued properties.

Again, lets add a little more to get a better output:

Get-Service | Where {$_.CanStop -eq "True"} | Select Name,CanStop

image

Hmmm…. Well, if you run this, it does in fact return the correct information, but let me say that it is a false sense of being correct. I will now show you why this is not the best way to perform a comparison against a property that has a boolean value.

Get-Service | Where {$_.CanStop -eq "False"} | Select Name,CanStop

image

Wait a second! We were looking for anything that was False, so why didn’t this work? Well, the short of it is that no matter what non-empty string value you use, it will always come back as $True when you compare it to $True.

$True -eq "True"
$True -eq "False"
$True -eq "Fish"

image

Pretty wild, isn’t it? Same goes for $False as well. If it is a non-empty string, it will always return $False.

$False -eq "False"
$False -eq "True"
$False -eq "Fish"

image

 

And to finish this little piece up, while non-empty strings are always $True, empty strings will always be $False:

$True -eq ""
$False -eq ""

image

Believe it or not, this is by design. In fact, there was a PowerShell Connect item submitted regarding this that was closed being “By Design”.

In designing the PowerShell language we based the semantics of the language on *successful* prior art wherever possible. PowerShell’s boolean behaviour is based on well-established existing behaviour in scripting languages including JavaScript, Perl, Python, PHP, AWK, the various UNIX shells, etc. All of these languages treat a non-empty string as true and an empty string as false. (Ruby and LUA are a bit different – all strings are true, even empty ones. This behaviour is inherited from LISP. In LISP nil is false, non-nil is true so all strings are true.)
Regarding loss of precision, using a value in a boolean context is considered an explicit conversion where loss of precision is permitted.
Having shipped Version 1 of PowerShell, changing the boolean behaviour would be a massive (and therefore unacceptable) breaking change.
All of that said, the semantics of V1 PowerShell are very oriented towards small programs. We are looking at adding a “super-strict” mode for writing larger programs where many of the implicit conversions may be suppressed and/or generate errors. Enforcing explicit boolean conversions might be something we could add. We will not add support for treating “true” as true and “false” as false as it’s not portable to a non-english locale.
-bruce

The Bruce answering this being the one and only Bruce Payette.

While this references V1, it continues to hold $True through V2 and the upcoming V3 Beta.

Keep this in mind when you are writing code so you do not have unintended consequences when your code is run or reported potentially inaccurate data.

Posted in powershell, Scripting Games 2012 | Tagged , , | 2 Comments

Scripting Games 2012: Quick Tips

Some things have really been standing out for me during the Scripting Games that I can’t contain anymore. So with that, here are a small list of things that you really really need to watch out for:

  • Know when to use Where-Object and when not to use Where-Object. Knowing this will help you avoid not only losing points on a script, but also can avoid impacting performance on your script. This also applies very well to Real-World scenarios.
  • Know how to deal with boolean values.
  • Know when to format your output and when not to format your output.
  • If the event says to do something, then do it. Failure to do so can and will cost you points.
  • Use of Get-Help and Get-Command will help you find that special cmdlet that will make your job a lot easier and then learn how to use it more efficiently.
  • Consider what you are planning on posting as your code and that you have ‘scrubbed’ it prior to submission.
  • If you are going to have “parameters” in your script/function, then read up on how to make them “parameters”. Think Param()…
  • Understand about line continuations. You can use |, { and ` to break the line and continue writing more code on the following line, but DO NOT combine these such as this:
Get-Process | `
Select Name,ID

This will work better:

Get-Process |	
Select, Name, ID

Ok, most of these are somewhat vague (and they are meant to be) and I will expand more on these after the games have completed. But consider these general guidelines to follow and think about when writing your code.

Posted in News, Scripting Games 2012 | Tagged , , | 1 Comment