# encoding: ascii # api: powershell # title: Start-BootsTimer # description: Updated for PowerBoots 0.2: http://screencast.com/t/drKhZbMv0S A stay-on-top (borderless window) countdown timer, with a percentage-based color progress bar as well as sizable font … as well as audio file and voice alarms. # version: 1.0 # type: function # author: Joel Bennett # license: CC0 # function: Start-BootsTimer # x-poshcode-id: 1239 # x-archived: 2009-08-02T06:56:22 # # Refactored functions into main window # Fixed bad variable typo # Add-BootsFunction -Type "System.Windows.Threading.DispatcherTimer" function Start-BootsTimer { #.Syntax # Creates a stay-on-top countdown timer #.Description # A WPF borderless count-down timer, with audio/voice alarms and visual countdown + colored progress indication #.Parameter EndMessage # The message to be spoken by a voice when the time is up... #.Parameter StartMessage # A message to be spoken at start up (just to let you know, audibly, what's up). #.Parameter Minutes # Some minutes to add to the timer #.Parameter Seconds # Some seconds to add to the timer #.Parameter Hours # Some hours to add to the timer #.Parameter SoundFile # A .wav file to play as the alarm #.Parameter FontSize # The size of the timer text #.Parameter SingleAlarm # Only sound the alarm once #.Example # Start-BootsTimer 180 "The three minute egg is ready!" # # Starts a three minute timer with the specified voice alert at the end # #.Example # Start-BootsTimer -End "The three minute egg is ready!" -Minute 3 # # Starts a three minute timer with the specified voice alert at the end # #.Example # Start-BootsTimer "Your turn is over!" -Minutes 10 -Single -FontSize 18 # # Starts a 10 minute timer that only plays the alert once at the end, and has a small font, which would fit over the task bar or a window title bar... [CmdletBinding(DefaultParameterSetName="Times")] PARAM( [Parameter(Position=2,ParameterSetName="Times",Mandatory=$false)] [Parameter(Position=1,ParameterSetName="Reasons", Mandatory=$true)] [string]$EndMessage , [Parameter(Position=2,ParameterSetName="Reasons", Mandatory=$false)] [string]$StartMessage , [Parameter(Position=3,ParameterSetName="Reasons", Mandatory=$false)] [int]$minutes = 0 , [Parameter(Position=4,ParameterSetName="Reasons", Mandatory=$false)] [Parameter(Position=1,ParameterSetName="Times", Mandatory=$true)] [int]$seconds = 0 , [Parameter()] [int]$hours = 0 , [Parameter()] $SoundFile = "$env:SystemRoot\Media\notify.wav" , [Parameter()] $FontSize = 125 , [Parameter()] [Switch]$SingleAlarm ) # Default to 10 seconds ... without adding 5 seconds when people specify minutes if(($seconds + $Minutes + $hours) -eq 0) { $seconds = 10 } $start = [DateTime]::Now ## We have to store all this stuff, because the powerboots window lasts longer than the script $TimerStuff = @{} $TimerStuff["seconds"] = [Math]::Abs($seconds) + [Math]::Abs([int]($minutes*60)) + [Math]::Abs([int]($hours*60*60)) $TimerStuff["TimerEnd"] = $start.AddSeconds( $TimerStuff["seconds"] ) $TimerStuff["SingleAlarm"] = $SingleAlarm ## Take care of as much overhead as we can before we need it... if(Test-Path $soundFile) { $TimerStuff["Sound"] = new-Object System.Media.SoundPlayer $TimerStuff["Sound"].SoundLocation=$SoundFile } if($EndMessage -or $StartMessage) { $TimerStuff["Voice"] = new-object -com SAPI.SpVoice } ## Store the "EndMessage" message if($EndMessage) { $TimerStuff["EndMessage"] = $EndMessage } ## If they provided a second status message, read it out loud if($StartMessage) { $null = $TimerStuff["Voice"].Speak( $StartMessage, 1 ) } $TimerStuff["FontSize"] = $FontSize ## Make the window ... PowerBoots\New-BootsWindow -WindowStyle None -AllowsTransparency -Tag $TimerStuff { Param($window) TextBlock "00:00:00" -FontSize $window.Tag.FontSize -FontFamily Impact -margin 20 ` -BitmapEffect $(OuterGlowBitmapEffect -GlowColor White -GlowSize 15) ` -Foreground $(LinearGradientBrush -Start "1,1" -End "0,1" { GradientStop -Color Black -Offset 0.0 GradientStop -Color Black -Offset 0.95 GradientStop -Color Red -Offset 1.0 GradientStop -Color Red -Offset 1.0 }) # -TextDecorations ([System.Windows.TextDecorations]::Underline) ## We'll create a timer $window.Tag["Timer"] = DispatcherTimer -Tag $window -Interval "0:0:0.05" -On_Tick { trap { write-host "Error: $_" -fore Red write-host $($_.InvocationInfo.PositionMessage) -fore Red write-host $($_.Exception.StackTrace | Out-String) -fore Red } $remain = 100 if($this.Tag.Tag.TimerEnd -and $this.Tag.Content.Foreground.GradientStops.Count -ge 3) { Write-Verbose $($this.Tag.Tag|Out-String) #-fore magenta $diff = $this.Tag.Tag.TimerEnd - [DateTime]::Now; if($diff.Ticks -ge 0) { $this.Tag.Content.Text = ([DateTime]$diff.Ticks).ToString(" HH:mm.ss") } else { $this.Tag.Content.Text = ([DateTime][Math]::Abs($diff.Ticks)).ToString("-HH:mm.ss") } #Write-Host "Remain: $remain or $($diff.TotalSeconds) of $($this.tag.tag.seconds)" $remain = $diff.TotalSeconds / $this.tag.tag.seconds Write-Verbose "Remain: $remain or $($diff.TotalSeconds) of $($this.tag.tag.seconds)" ## Move the gradient a little bit each time. $this.tag.Content.Foreground.GradientStops[2].Offset = 0.0 # [Math]::Max(0.0, $remain) $this.tag.Content.Foreground.GradientStops[1].Offset = 0.0 # [Math]::Max(0.0, $remain - 0.05) #Write-Host "Gradient: $($this.tag.Content.Foreground.GradientStops[2].Offset) of $($this.tag.Content.Foreground.GradientStops[1].Offset)" } else { Write-Host "Wahat!" } ## When we get to the end ... make a few changes if($remain -le 0) { ## The first time we hit the end, we want to add a mouse click handler... if($this.Interval.Seconds -eq 0) { ## Which will now only fire every few seconds ## So it's easier to close the window ;) $this.Interval = [TimeSpan]"0:0:2" ## If you click on the finished window, it just goes away $this.tag.Add_MouseDown( { $this.tag.Close() } ) ## But if they chose -SingleAlarm, they don't neeto bother if($this.tag.tag["SingleAlarm"]) { $this.tag.Close() } } & { if($this.tag.Tag["Sound"]) { $this.tag.Tag["Sound"].Play() } else { [System.Media.SystemSounds]::Exclamation.Play() } if($this.tag.Tag["EndMessage"]) { $null = $this.tag.Tag["Voice"].Speak( $this.tag.Tag["EndMessage"], 1 ) } } } } ## Now handle starting and stopping the timer # $window.Add_SourceInitialized( { $window.Tag.Timer.Start() } ) $window.Add_Closed( { Write-Host "Shutting Down $this"; $this.Tag.Timer.Stop() } ) } -On_MouseDown { if($_.ChangedButton -eq "Left") { $this.DragMove() } } -Async -Topmost -Background Transparent -ShowInTaskbar:$False -Passthru | ## In lieu of the SourceInitialized event which fires BEFORE we can hook it: ForEach { Invoke-BootsWindow $_ { $BootsWindow.Tag.Timer.Start() } } }