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:

Here's the source of invoke-background.ps1:
- param([string]$scriptName)
-
-
-
- $rs = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
- $rs.ApartmentState, $rs.ThreadOptions = “STA”, “ReuseThread”
- $rs.Open()
-
-
- $psCmd = {Add-Type}.GetPowerShell()
- $psCmd.SetRunspace($rs)
- $psCmd.AddParameter("AssemblyName", "PresentationCore").Invoke()
- $psCmd.Command.Clear()
-
- $psCmd = $psCmd.AddCommand("Add-Type")
- $psCmd.AddParameter("AssemblyName", "PresentationFramework").Invoke()
- $psCmd.Command.Clear()
-
- $psCmd = $psCmd.AddCommand("Add-Type")
- $psCmd.AddParameter("AssemblyName", "WindowsBase").Invoke()
-
- $sb = $executionContext.InvokeCommand.NewScriptBlock(
- (Join-Path $pwd $scriptname)
- )
-
- $psCmd = $sb.GetPowerShell()
- $psCmd.SetRunspace($rs)
- $null = $psCmd.BeginInvoke()
Next, here's the modified clock script:
- param (
- [timespan]$period = (New-Object system.TimeSpan(0,5,0)),
- $clockxaml="<path to xaml file>\clock.xaml"
- )
-
-
- Add-Type -Assembly PresentationFramework
- Add-Type -Assembly PresentationCore
-
- $clock = [Windows.Markup.XamlReader]::Load(
- (New-Object System.Xml.XmlNodeReader (
- [Xml](Get-Content $clockxaml) ) ) )
-
- $then = [datetime]::Now
-
- $red = [System.Windows.Media.Color]::FromRgb(255,0,0)
- $redbrush = new-object system.windows.media.solidcolorbrush $red
- $label = $clock.FindName("ClockLabel")
- $done = $false
-
-
- $updateBlock = {
- if (!$done) {
-
- $elapsed = ([datetime]::Now - $then)
- $remaining = $null;
-
- if ($elapsed -lt $period) {
- $remaining = ($period - $elapsed).ToString().substring(0,8)
- } else {
- $label.Foreground = $redbrush
- $remaining = "00:00:00"
- $done = $true
- }
- $clock.Resources["Time"] = $remaining
- }
- }
-
-
- $clock.Add_SourceInitialized( {
-
-
- $timer = new-object System.Windows.Threading.DispatcherTimer
-
- $timer.Interval = [TimeSpan]"0:0:0.50"
-
- $timer.Add_Tick( $updateBlock )
-
- $timer.Start()
- if(! $timer.IsEnabled ) {
- $clock.Close()
- }
- } )
-
- $clock.Add_MouseLeftButtonDown( {
- $_.Handled = $true
- $clock.DragMove()
- } )
-
- $clock.Add_MouseRightButtonDown( {
- $_.Handled = $true
- $timer.Stop()
- $clock.Close()
- } )
-
-
- &$updateBlock
-
- $clock.ShowDialog()
...and finally the modified clock.xaml file:
- <Window xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:system="clr-namespace:System;assembly=mscorlib"
- WindowStyle='None' AllowsTransparency='True'
- Topmost='True' Background="Transparent" ShowInTaskbar='False'
- SizeToContent='WidthAndHeight' WindowStartupLocation='CenterOwner' >
- <Window.Resources>
- <system:String x:Key="Time">12:34.56</system:String>
- </Window.Resources>
-
- <Grid Height="2.2in">
- <Grid.ColumnDefinitions>
- <ColumnDefinition/>
- </Grid.ColumnDefinitions>
- <Label Name="ClockLabel" Grid.Column="2" Opacity="0.7" Content="{DynamicResource Time}" FontFamily="Impact, Arial" FontWeight="800" FontSize="2in" >
- <Label.Foreground>
- <LinearGradientBrush>
- <GradientStop Color="#CC064A82" Offset="1"/>
- <GradientStop Color="#FF6797BF" Offset="0.8"/>
- <GradientStop Color="#FF6797BF" Offset="0.4"/>
- <GradientStop Color="#FFD4DBE1" Offset="0"/>
- </LinearGradientBrush>
- </Label.Foreground>
- </Label>
- </Grid>
- </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!