PowerShell Binary Clock: Round 2

As you have seen in my previous post where I built a “proof of concept” binary clock (which I think was the first binary clock built using PowerShell, but have not been able to confirm yet), it worked as well as you would expect a binary clock to work, but I really wasn’t liking the look of it. I mean, it looks like a binary clock, but it really doesn’t look all that cool. The use of radial buttons and a weak background color doesn’t exactly scream out “Hey, place me on your desktop!”.

The first PowerShell binary clock.

Ryan Grant took out my call for others to build a binary clock and put out a nice version of a Sexagesimal binary clock using Show-UI that is great!

image

My latest update to my original Binary Clock makes it seem more like a WPF clock using some of better features that my other clock lacked.  I’ve removed the radial buttons in place of using ellipses to handle the on/off ‘lights’ to show when the binary clock ellipses are set to a 1 or 0. I also wanted to do away with the background and make only the ellipses visible. So with that, my clock looks like this:

image

The white background you see is actually the color of the background behind the clock, not the actual clock itself.

I configured the script to either allow for user input to determine the colors for both the on/off colors and also for the date stamp on the clock.

For instance, after you dot source the script, you can use the following code to set the colors to whatever you want.

.\Binary_Clock.ps1
Start-BinaryClock -OnColor Blue -OffColor Green -DateColor Silver

image

If you choose to not define the colors, randomly specified colors will be used instead.

Start-BinaryClock

image

Key inputs

I added some features to this clock such as being able to resize the clock by using the “r” key to being up the resizer in the bottom right hand corner. Another feature is to change the opacity using the ““ and “+” keys to make more transparent and less transparent. The “o” key toggles whether the clock remains at the top of all windows or not. Lastly, the “d” key shows or hides the date on the clock.

I use the following code to add the key inputs for the clock.

$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'             
                }       
            } 
        "d" {
            Switch ($MonthLabel.visibility) {
                'Collapsed' {$MonthLabel.visibility = 'Visible';$DayLabel.Visibility = 'Visible'}
                'Visible' {$MonthLabel.visibility = 'Collapsed ';$DayLabel.Visibility = 'Collapsed '}
                }
            }    
        "o" {
            If ($Window.TopMost) {
                $Window.TopMost = $False
                }
            Else {
                $Window.TopMost = $True
                }
            }     
        }
    })

Here is a example of the clock with the opacity turned down using the “-“ key:

image

Here is an example of the clock without the date using the “d” key:

image

Lastly, here is the clock with the re-size grip enabled using the “r” key:

image

Another thing about re-sizing the clock is that everything adjusts accordingly and will stretch in the direction that you drag using the re-sizer grip. A couple examples:

image

image

image

I was able to make this work by adding a TextBlock inside of a ViewBox. The viewbox properties include the stretchdirection and stretch properties that basically lets the text inside the textblock to stretch in any direction that you choose.  Here is the XAML code that allows me to stretch the text:

            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' Grid.Column = '12' Grid.RowSpan = '7'
            StretchDirection = 'Both' Stretch = 'Fill'>
                <TextBlock x:Name = 'DayLabel' VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch'
                FontWeight = 'Bold'> 
                    <TextBlock.LayoutTransform>
                        <RotateTransform Angle="90" />
                    </TextBlock.LayoutTransform>            
                </TextBlock>
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' Grid.Column = '13' Grid.RowSpan = '7'
            StretchDirection = 'Both' Stretch = 'Fill'>
                <TextBlock x:Name = 'MonthLabel' VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch'
                FontWeight = 'Bold'>
                    <TextBlock.LayoutTransform>
                        <RotateTransform Angle="90" />
                    </TextBlock.LayoutTransform>               
                </TextBlock>
            </Viewbox>

Mouse Inputs

You can use the left-button to drag and move the clock around and you can close the clock by right-clicking on the clock. Code for this action is below:

$Window.Add_MouseRightButtonUp({
    $This.close()
    })
$Window.Add_MouseLeftButtonDown({
    $This.DragMove()
    }) 

 

Code

Script Repository

PoshCode

Updated code thanks to a great find by Robert!

Check out his blog here too! Crazy smart guy!

Turns out I couldn’t use the [system.windows.media.brush] for the color parameters at the start of the script because they require the following assemblies be loaded first:

#Load Required Assemblies
Add-Type –assemblyName PresentationFramework
Add-Type –assemblyName PresentationCore
Add-Type –assemblyName WindowsBase

So instead, I updated it to be a [string] and now it works great!

Function Start-BinaryClock {
<#
.SYNOPSIS
    This is a binary clock that lists the time in hours, minutes and seconds
    
.DESCRIPTION
    This is a binary clock that lists the time in hours, minutes and seconds.
    
    Key Input Tips:
    r: Toggles the resize mode of the clock so you can adjust the size.
    d: Toggles the date to hide/show
    o: Toggles whether the clock 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.
    
    Right-Click to close.
    Use left mouse button to drag clock.
    
.PARAMETER OnColor
    Define the color used for the active time (1).
    
.PARAMETER OffColor
    Define the color used for the inactive time (0).
    
.PARAMETER RandomColor    
    Default parameter if manual colors are not used. Picks a random color for On and Off colors.

.NOTES  
    Name: BinaryClock.ps1
    Author: Boe Prox
    DateCreated: 07/05/2011
    Version 2.0 

.EXAMPLE
    Start-BinaryClock
    
Description
-----------
Starts the binary clock using randomly generated colors 

.EXAMPLE
    Start-BinaryClock -OnColor Red -OffColor Gold -DateColor Black
    
Description
-----------
Starts the binary clock using using specified colors.        
#>
[cmdletbinding(
    DefaultParameterSetName = 'RandomColor'
    )]
Param (
    [parameter(Mandatory = 'True',ParameterSetName = 'SetColor')]
    [String] $OnColor,
    [parameter(Mandatory = 'True',ParameterSetName = 'SetColor')]
    [String] $OffColor,
    [parameter(ParameterSetName = 'RandomColor')]
    [Switch]$RandomColor, 
    [parameter(Mandatory = 'True',ParameterSetName = 'SetColor')]
    [String] $DateColor   
    )
If ($PSCmdlet.ParameterSetName -eq 'RandomColor') {
    [switch]$RandomColor = $True
    }
   
$Global:rs = [RunspaceFactory]::CreateRunspace()
$rs.ApartmentState = “STA”
$rs.ThreadOptions = “ReuseThread”
$rs.Open() 
$psCmd = {Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase}.GetPowerShell() 
$rs.SessionStateProxy.SetVariable('OnColor',$OnColor)
$rs.SessionStateProxy.SetVariable('OffColor',$OffColor)
$rs.SessionStateProxy.SetVariable('RandomColor',$RandomColor)
$rs.SessionStateProxy.SetVariable('DateColor',$DateColor)
$psCmd.Runspace = $rs 
$psCmd.Invoke() 
$psCmd.Commands.Clear() 
$psCmd.AddScript({ 

#Load Required Assemblies
Add-Type –assemblyName PresentationFramework
Add-Type –assemblyName PresentationCore
Add-Type –assemblyName WindowsBase

##Colors
If ($RandomColor) {
    #On Color
    $OnColor = "#{0:X}" -f (Get-Random -min 1 -max 16777215)
    Try {
        [system.windows.media.brush]$OnColor | Out-Null
        }
    Catch {
        $OnColor = "White"
        }
    #Off Color
    $OffColor = "#{0:X}" -f (Get-Random -min 1 -max 16777215)
    Try {
        [system.windows.media.brush]$OffColor | Out-Null
        }
    Catch {
        $OffColor = "Black"
        }
    #DateColor Color
    $DateColor = "#{0:X}" -f (Get-Random -min 1 -max 16777215)
    Try {
        [system.windows.media.brush]$DateColor | Out-Null
        }
    Catch {
        $DateColor = "Black"
        }        
    }

[xml]$xaml = @"
<Window
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
    x:Name='Window' WindowStyle = 'None' WindowStartupLocation = 'CenterScreen' Width = '170' Height = '101' ShowInTaskbar = 'True' 
    ResizeMode = 'NoResize' Title = 'Clock' AllowsTransparency = 'True' Background = 'Transparent' Opacity = '1' Topmost = 'True'>
        <Grid x:Name = 'Grid' HorizontalAlignment="Stretch" ShowGridLines='false'  Background = 'Transparent'>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="5"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2"/>
                <ColumnDefinition Width="*"/>                
                <ColumnDefinition Width="2"/>
                <ColumnDefinition Width="*" x:Name = 'DayColumn'/> 
                <ColumnDefinition Width="*" x:Name = 'MonthColumn'/> 
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height = '*'/>
                <RowDefinition Height = '2'/>
                <RowDefinition Height = '*'/>
                <RowDefinition Height = '2'/>
                <RowDefinition Height = '*'/>                
                <RowDefinition Height = '2'/>
                <RowDefinition Height = '*'/>
                <RowDefinition Height = '2'/>
                <RowDefinition x:Name = 'timerow' Height = '0'/>
            </Grid.RowDefinitions>
            <Ellipse Grid.Row = '0' Grid.Column = '0' Fill = 'Transparent'/>
            <Ellipse Grid.Row = '2' Grid.Column = '0' Fill = 'Transparent' />
            <Ellipse x:Name = 'HourA0' Grid.Row = '6' Grid.Column = '0' Fill = 'Transparent' />
            <Ellipse x:Name = 'HourA1' Grid.Row = '4' Grid.Column = '0' Fill = 'Transparent' />    
            <Ellipse x:Name = 'HourB0' Grid.Row = '6' Grid.Column = '2' Fill = 'Transparent'/>
            <Ellipse x:Name = 'HourB1' Grid.Row = '4' Grid.Column = '2' Fill = 'Transparent' />
            <Ellipse x:Name = 'HourB2' Grid.Row = '2' Grid.Column = '2' Fill = 'Transparent' />
            <Ellipse x:Name = 'HourB3' Grid.Row = '0' Grid.Column = '2' Fill = 'Transparent' />
            <Ellipse Grid.Row = '0' Grid.Column = '4' Fill = 'Transparent'/>
            <Ellipse x:Name = 'MinuteA0' Grid.Row = '6' Grid.Column = '4' Fill = 'Transparent' />
            <Ellipse x:Name = 'MinuteA1' Grid.Row = '4' Grid.Column = '4' Fill = 'Transparent' />
            <Ellipse x:Name = 'MinuteA2' Grid.Row = '2' Grid.Column = '4' Fill = 'Transparent' /> 
            <Ellipse x:Name = 'MinuteB0' Grid.Row = '6' Grid.Column = '6' Fill = 'Transparent'/>
            <Ellipse x:Name = 'MinuteB1' Grid.Row = '4' Grid.Column = '6' Fill = 'Transparent' />
            <Ellipse x:Name = 'MinuteB2' Grid.Row = '2' Grid.Column = '6' Fill = 'Transparent' />
            <Ellipse x:Name = 'MinuteB3' Grid.Row = '0' Grid.Column = '6' Fill = 'Transparent' />  
            <Ellipse Grid.Row = '0' Grid.Column = '8' Fill = 'Transparent'/>
            <Ellipse x:Name = 'SecondA0' Grid.Row = '6' Grid.Column = '8' Fill = 'Transparent' />
            <Ellipse x:Name = 'SecondA1' Grid.Row = '4' Grid.Column = '8' Fill = 'Transparent' />
            <Ellipse x:Name = 'SecondA2' Grid.Row = '2' Grid.Column = '8' Fill = 'Transparent' />  
            <Ellipse x:Name = 'SecondB0' Grid.Row = '6' Grid.Column = '10' Fill = 'Transparent'/>
            <Ellipse x:Name = 'SecondB1' Grid.Row = '4' Grid.Column = '10' Fill = 'Transparent' />
            <Ellipse x:Name = 'SecondB2' Grid.Row = '2' Grid.Column = '10' Fill = 'Transparent' />
            <Ellipse x:Name = 'SecondB3' Grid.Row = '0' Grid.Column = '10' Fill = 'Transparent' />                                                                                  
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' Grid.Column = '12' Grid.RowSpan = '7'
            StretchDirection = 'Both' Stretch = 'Fill'>
                <TextBlock x:Name = 'DayLabel' VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch'
                FontWeight = 'Bold'> 
                    <TextBlock.LayoutTransform>
                        <RotateTransform Angle="90" />
                    </TextBlock.LayoutTransform>            
                </TextBlock>
            </Viewbox>
            <Viewbox VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch' Grid.Column = '13' Grid.RowSpan = '7'
            StretchDirection = 'Both' Stretch = 'Fill'>
                <TextBlock x:Name = 'MonthLabel' VerticalAlignment = 'Stretch' HorizontalAlignment = 'Stretch'
                FontWeight = 'Bold'>
                    <TextBlock.LayoutTransform>
                        <RotateTransform Angle="90" />
                    </TextBlock.LayoutTransform>               
                </TextBlock>
            </Viewbox>
        </Grid>
</Window>
"@ 

$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Global:Window=[Windows.Markup.XamlReader]::Load( $reader )
$Global:DayLabel = $Global:window.FindName("DayLabel")
$Global:MonthLabel = $Global:window.FindName("MonthLabel")
$Global:DayColumn = $Global:window.FindName("DayColumn")
$Global:MonthColumn = $Global:window.FindName("MonthColumn")
$Global:TimeRow = $Global:window.FindName("TimeRow")
$Global:Grid = $Global:window.FindName("Grid")

##Events 
#Key Events
$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'             
                }       
            } 
        "d" {
            Switch ($MonthLabel.visibility) {
                'Collapsed' {$MonthLabel.visibility = 'Visible';$DayLabel.Visibility = 'Visible'}
                'Visible' {$MonthLabel.visibility = 'Collapsed ';$DayLabel.Visibility = 'Collapsed '}
                }
            }    
        "o" {
            If ($Window.TopMost) {
                $Window.TopMost = $False
                }
            Else {
                $Window.TopMost = $True
                }
            }     
        }
    }) 
        
$Window.Add_MouseRightButtonUp({
    $This.close()
    })
$Window.Add_MouseLeftButtonDown({
    $This.DragMove()
    })  
         
$update = {
$DayLabel.Text = "$(((Get-Date).ToLongDateString() -split ',')[0] -split '')"
$DayLabel.Foreground = $DateColor
$MonthLabel.Text = Get-Date -u "%B %d %G"
$MonthLabel.Foreground = $DateColor
$hourA,$hourB = [string](Get-Date -f HH) -split "" | Where {$_}
$minuteA,$minuteB = [string](Get-Date -f mm) -split "" | Where {$_}
$secondA,$secondB = [string](Get-Date -f ss) -split "" | Where {$_}

$hourAdock = $grid.children | Where {$_.Name -like "hourA*"}
$minuteAdock = $grid.children | Where {$_.Name -like "minuteA*"}
$secondAdock = $grid.children | Where {$_.Name -like "secondA*"}
$hourBdock = $grid.children | Where {$_.Name -like "hourB*"}
$minuteBdock = $grid.children | Where {$_.Name -like "minuteB*"}
$secondBdock = $grid.children | Where {$_.Name -like "secondB*"}

#hourA
[array]$splittime = ([convert]::ToString($houra,2)) -split"" | Where {$_}
[array]::Reverse($splittime)
$i = 0
ForEach ($hdock in $hourAdock) {
    Write-Verbose "i: $($i)"
    Write-Verbose "split: $($splittime[$i])"
    If ($splittime[$i] -eq "1") {
        $hdock.Fill = $OnColor
        }
    Else {
        $hdock.Fill = $OffColor
        }
    $i++
    }
$i = 0

#hourB
[array]$splittime = ([convert]::ToString($hourb,2)) -split"" | Where {$_}
[array]::Reverse($splittime)
$i = 0
ForEach ($hdock in $hourBdock) {
    Write-Verbose "i: $($i)"
    Write-Verbose "split: $($splittime[$i])"
    If ($splittime[$i] -eq "1") {
        $hdock.Fill = $OnColor
        }
    Else {
        $hdock.Fill = $OffColor
        }
    $i++
    }
$i = 0

#minuteA
[array]$splittime = ([convert]::ToString($minutea,2)) -split"" | Where {$_}
[array]::Reverse($splittime)
$i = 0
ForEach ($hdock in $minuteAdock) {
    Write-Verbose "i: $($i)"
    Write-Verbose "split: $($splittime[$i])"
    If ($splittime[$i] -eq "1") {
        $hdock.Fill = $OnColor
        }
    Else {
        $hdock.Fill = $OffColor
        }
    $i++
    }
$i = 0

#minuteB
[array]$splittime = ([convert]::ToString($minuteb,2)) -split"" | Where {$_}
[array]::Reverse($splittime)
$i = 0
ForEach ($hdock in $minuteBdock) {
    Write-Verbose "i: $($i)"
    Write-Verbose "split: $($splittime[$i])"
    If ($splittime[$i] -eq "1") {
        $hdock.Fill = $OnColor
        }
    Else {
        $hdock.Fill = $OffColor
        }
    $i++
    }
$i = 0

#secondA
[array]$splittime = ([convert]::ToString($seconda,2)) -split"" | Where {$_}
[array]::Reverse($splittime)
$i = 0
ForEach ($hdock in $secondAdock) {
    Write-Verbose "i: $($i)"
    Write-Verbose "split: $($splittime[$i])"
    If ($splittime[$i] -eq "1") {
        $hdock.Fill = $OnColor
        }
    Else {
        $hdock.Fill = $OffColor
        }
    $i++
    }
$i = 0

#secondB
[array]$splittime = ([convert]::ToString($secondb,2)) -split"" | Where {$_}
[array]::Reverse($splittime)
$i = 0
ForEach ($hdock in $secondBdock) {
    Write-Verbose "i: $($i)"
    Write-Verbose "split: $($splittime[$i])"
    If ($splittime[$i] -eq "1") {
        $hdock.Fill = $OnColor
        }
    Else {
        $hdock.Fill = $OffColor
        }
    $i++
    }
$i = 0
}

$Global:Window.Add_KeyDown({
    If ($_.Key -eq "F5") {
        &$update 
        }
    })

#Timer Event
$Window.Add_SourceInitialized({
    #Create Timer object
    Write-Verbose "Creating timer object"
    $Global:timer = new-object System.Windows.Threading.DispatcherTimer 

    Write-Verbose "Adding interval to timer object"
    $timer.Interval = [TimeSpan]"0:0:.10"
    #Add event per tick
    Write-Verbose "Adding Tick Event to timer object"
    $timer.Add_Tick({
        &$update
        Write-Verbose "Updating Window"
        })
    #Start timer
    Write-Verbose "Starting Timer"
    $timer.Start()
    If (-NOT $timer.IsEnabled) {
        $Window.Close()
        }
    })

&$update
$window.Showdialog() | Out-Null             
}).BeginInvoke() | out-null
}

About Boe Prox

Microsoft Cloud and Datacenter MVP working as a SQL DBA.
This entry was posted in powershell, scripts and tagged , , , . Bookmark the permalink.

One Response to PowerShell Binary Clock: Round 2

  1. Willi Fritz says:

    Great Powershell/WPF example! When I exit the Powershell ISE after I had run the Start-BinaryClock script, the the Powershell ISE window closes but the Powershell_ise.exe process does not terminate. Any idea why this happens and how it could be prevented? I observed this behaviour also with other scripts that invoke WPF asynchronously.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s