Background Timer PowerShell WPF Widget

UPDATE May 26th: You must run PowerShell v2.0 CTP in STA mode for this to work. Start the shell, then run "powershell -sta" from the command line to start a new version of the shell in "single thread apartment" mode (STA). This is required for WPF to work correctly.

That is a bit of a mouthful of a title for this post but it's the best I could come up with. This post takes some of James' scripty bits and Jaykul's scripty bits and shows you how to create a countdown timer written in PowerShell script that runs in the background without blocking input. Just like Jaykul's original clock, you can drag it around and right-clicking it will close it. His version was the current time and it also showed some system resources. I changed it into a countdown and removed the other nested graphs. When it hits 00:00:00 it turns red. Here's what it looks like:

countdown

Here's the source of invoke-background.ps1:

  1. param([string]$scriptName)  
  2.  
  3. # original script James Brundage (blogs.msdn.com/powershell)  
  4.  
  5. $rs = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()  
  6. $rs.ApartmentState, $rs.ThreadOptions = “STA”, “ReuseThread”  
  7. $rs.Open()  
  8.  
  9. # Reference the WPF assemblies  
  10. $psCmd = {Add-Type}.GetPowerShell()  
  11. $psCmd.SetRunspace($rs)  
  12. $psCmd.AddParameter("AssemblyName", "PresentationCore").Invoke()  
  13. $psCmd.Command.Clear()  
  14.  
  15. $psCmd = $psCmd.AddCommand("Add-Type")  
  16. $psCmd.AddParameter("AssemblyName", "PresentationFramework").Invoke()  
  17. $psCmd.Command.Clear()  
  18.  
  19. $psCmd = $psCmd.AddCommand("Add-Type")  
  20. $psCmd.AddParameter("AssemblyName", "WindowsBase").Invoke()  
  21.  
  22. $sb = $executionContext.InvokeCommand.NewScriptBlock(  
  23.     (Join-Path $pwd $scriptname)  
  24. )  
  25.  
  26. $psCmd = $sb.GetPowerShell()  
  27. $psCmd.SetRunspace($rs)  
  28. $null = $psCmd.BeginInvoke()  

Next, here's the modified clock script:

  1. param (  
  2.     [timespan]$period = (New-Object system.TimeSpan(0,5,0)),  
  3.     $clockxaml="<path to xaml file>\clock.xaml" 
  4. )  
  5.  
  6. ### Import the WPF assemblies  
  7. Add-Type -Assembly PresentationFramework  
  8. Add-Type -Assembly PresentationCore  
  9.  
  10. $clock = [Windows.Markup.XamlReader]::Load(   
  11.          (New-Object System.Xml.XmlNodeReader (  
  12.             [Xml](Get-Content $clockxaml) ) ) )  
  13.  
  14. $then = [datetime]::Now  
  15.  
  16. $red = [System.Windows.Media.Color]::FromRgb(255,0,0)  
  17. $redbrush = new-object system.windows.media.solidcolorbrush $red 
  18. $label = $clock.FindName("ClockLabel")  
  19. $done = $false 
  20.  
  21. # Create a script block which will update the UI  
  22. $updateBlock = {     
  23.    if (!$done) {  
  24.         # update the clock  
  25.         $elapsed = ([datetime]::Now - $then)  
  26.         $remaining = $null;  
  27.           
  28.         if ($elapsed -lt $period) {  
  29.             $remaining = ($period - $elapsed).ToString().substring(0,8)  
  30.         } else {  
  31.             $label.Foreground = $redbrush         
  32.             $remaining = "00:00:00" 
  33.             $done = $true 
  34.         }         
  35.         $clock.Resources["Time"] = $remaining 
  36.    }  
  37. }  
  38.  
  39. ## Hook up some event handlers   
  40. $clock.Add_SourceInitialized( {  
  41.    ## Before the window's even displayed ...  
  42.    ## We'll create a timer  
  43.    $timer = new-object System.Windows.Threading.DispatcherTimer  
  44.    ## Which will fire 2 times every second  
  45.    $timer.Interval = [TimeSpan]"0:0:0.50" 
  46.    ## And will invoke the $updateBlock  
  47.    $timer.Add_Tick( $updateBlock )  
  48.    ## Now start the timer running  
  49.    $timer.Start()  
  50.    if(! $timer.IsEnabled ) {  
  51.       $clock.Close()  
  52.    }  
  53. } )  
  54.  
  55. $clock.Add_MouseLeftButtonDown( {   
  56.    $_.Handled = $true 
  57.    $clock.DragMove() # WPF Magic!  
  58. } )  
  59.  
  60. $clock.Add_MouseRightButtonDown( {   
  61.    $_.Handled = $true 
  62.    $timer.Stop()  # we'd like to stop that timer now, thanks.  
  63.    $clock.Close() # and close the windows  
  64. } )  
  65.  
  66. ## Lets go ahead and invoke that update block   
  67. &$updateBlock 
  68. ## And then show the window  
  69. $clock.ShowDialog()  

...and finally the modified clock.xaml file:

  1. <Window xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
  2.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  3.         xmlns:system="clr-namespace:System;assembly=mscorlib" 
  4.         WindowStyle='None' AllowsTransparency='True' 
  5.         Topmost='True' Background="Transparent"  ShowInTaskbar='False' 
  6.         SizeToContent='WidthAndHeight' WindowStartupLocation='CenterOwner' > 
  7.    <Window.Resources> 
  8.       <system:String x:Key="Time">12:34.56</system:String> 
  9.    </Window.Resources> 
  10.  
  11.    <Grid Height="2.2in"> 
  12.       <Grid.ColumnDefinitions> 
  13.          <ColumnDefinition/> 
  14.       </Grid.ColumnDefinitions> 
  15.       <Label Name="ClockLabel" Grid.Column="2" Opacity="0.7" Content="{DynamicResource Time}" FontFamily="Impact, Arial" FontWeight="800" FontSize="2in" > 
  16.          <Label.Foreground> 
  17.             <LinearGradientBrush> 
  18.                <GradientStop Color="#CC064A82" Offset="1"/> 
  19.                <GradientStop Color="#FF6797BF" Offset="0.8"/> 
  20.                <GradientStop Color="#FF6797BF" Offset="0.4"/> 
  21.                <GradientStop Color="#FFD4DBE1" Offset="0"/> 
  22.             </LinearGradientBrush> 
  23.          </Label.Foreground> 
  24.       </Label> 
  25.    </Grid> 
  26. </Window> 

Important: you'll need to save all files into the same directory and fix up the path to the clock.xaml file in the start-countdown.ps1 script.

Have fun!

blog comments powered by Disqus

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List