functions/Start-PSClock.ps1
Function Start-PSClock { [CmdletBinding()] [alias("psclock")] [OutputType("None", "PSClock")] Param( [Parameter( Position = 0, HelpMessage = "Specify a .NET format string value like F, or G.", ValueFromPipelineByPropertyName )] [alias("format")] [ValidateNotNullOrEmpty()] [String]$DateFormat = "F", [Parameter(ValueFromPipelineByPropertyName)] [ValidateScript({ $_ -gt 8 })] [alias("size")] [Int]$FontSize = 18, [Parameter( HelpMessage = "Specify a font style.", ValueFromPipelineByPropertyName )] [ValidateSet("Normal", "Italic", "Oblique")] [alias("style")] [String]$FontStyle = "Normal", [Parameter( HelpMessage = "Specify a font weight.", ValueFromPipelineByPropertyName )] [ValidateSet("Normal", "Bold", "Light")] [alias("weight")] [String]$FontWeight = "Normal", [Parameter( HelpMessage = "Specify a font color like Green or an HTML code like '#FF1257EA'", ValueFromPipelineByPropertyName )] [ValidateNotNullOrEmpty()] [String]$Color = "White", [Parameter( HelpMessage = "Specify a font family.", ValueFromPipelineByPropertyName )] [ValidateNotNullOrEmpty()] [alias("family")] [String]$FontFamily = "Segoi UI", [Parameter( HelpMessage = "Do you want the clock to always be on top?", ValueFromPipelineByPropertyName )] [Switch]$OnTop, [Parameter( HelpMessage = "Specify the clock position as an array of left and top values.", ValueFromPipelineByPropertyName )] [ValidateCount(2, 2)] [Int32[]]$Position, [Parameter(HelpMessage = "Force a new PSClock, ignoring any previously saved settings. The saved settings file will remain.")] [Switch]$Force, [Switch]$PassThru ) Begin { Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)" Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Running under PowerShell $($PSVersionTable.PSVersion)" } #begin Process { Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Validating" if ($IsLinux -OR $isMacOS) { Write-Warning "This command requires a Windows platform." return } if ($PSClockSettings.Running) { Write-Warning "You already have a clock running. You can only have one clock running at a time." Return } if (Test-Path $env:temp\psclock-flag.txt) { $msg = @" A running clock has been detected from another PowerShell session: $(Get-Content $env:temp\psclock-flag.txt) If this is incorrect, delete $env:temp\psclock-flag.txt and try again. "@ Write-Warning $msg $r = Read-Host "Do you want to remove the flag file? Y/N" if ($r -eq 'Y') { Remove-Item $env:temp\psclock-flag.txt } else { #bail out Return } } #verify the DateTime format Try { [void](Get-Date -Format $DateFormat -ErrorAction Stop) } Catch { Write-Warning "The DateFormat value $DateFormat is not a valid format string. Try something like F,G, or U which are case-sensitive." Return } #Test if there is a saved settings file and no other parameters have been called # $SavePath is a module-scoped variable set in the psm1 file # $SavePath = Join-Path -Path $home -ChildPath PSClockSettings.xml if ((Test-Path $SavePath)-AND (-not $Force)) { Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Using saved settings" $import = Import-Clixml -Path $SavePath foreach ($prop in $import.PSObject.properties) { Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Using imported value for $($prop.name)" Set-Variable -name $prop.name -value $prop.Value } } Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Building a synchronized hashtable" $global:PSClockSettings = [hashtable]::Synchronized(@{ DateFormat = $DateFormat FontSize = $FontSize FontStyle = $FontStyle FontWeight = $FontWeight Color = $Color FontFamily = $FontFamily OnTop = $OnTop StartingPosition = $Position CurrentPosition = $Null }) Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] $($global:PSClockSettings | Out-String)" #Run the clock in a runspace $rs = [RunspaceFactory]::CreateRunspace() $rs.ApartmentState = "STA" $rs.ThreadOptions = "ReuseThread" $rs.Open() $global:PSClockSettings.add("Runspace", $rs) $rs.SessionStateProxy.SetVariable("PSClockSettings", $global:PSClockSettings) Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Defining the runspace command" $PSCmd = [PowerShell]::Create().AddScript({ Add-Type -AssemblyName PresentationFramework -ErrorAction Stop Add-Type -AssemblyName PresentationCore -ErrorAction Stop Add-Type -AssemblyName WindowsBase -ErrorAction Stop # a private function to stop the clock and clean up Function _QuitClock { $timer.stop() $timer.IsEnabled = $False $form.close() $PSClockSettings.Running = $False #define a thread job to clean up the runspace $cmd = { Param([Int]$ID) $r = Get-Runspace -Id $id $r.close() $r.dispose() } Start-ThreadJob -ScriptBlock $cmd -ArgumentList $PSClockSettings.runspace.id #delete the flag file if (Test-Path $env:temp\psclock-flag.txt) { Remove-Item $env:temp\psclock-flag.txt } } $form = New-Object System.Windows.Window <# some of the form settings are irrelevant because it is transparent but leaving them in the event I need to turn off transparency to debug or troubleshoot #> $form.Title = "PSClock" $form.Height = 200 $form.Width = 400 $form.SizeToContent = "WidthAndHeight" $form.AllowsTransparency = $True $form.Topmost = $PSClockSettings.OnTop $form.Background = "Transparent" $form.BorderThickness = "1,1,1,1" $form.VerticalAlignment = "top" if ($PSClockSettings.StartingPosition) { $form.left = $PSClockSettings.StartingPosition[0] $form.top = $PSClockSettings.StartingPosition[1] } else { $form.WindowStartupLocation = "CenterScreen" } $form.WindowStyle = "None" $form.ShowInTaskbar = $False #define events #call the private function to stop the clock and clean up $form.Add_MouseRightButtonUp({ _QuitClock }) $form.Add_MouseLeftButtonDown({ $form.DragMove() }) #press + to increase the size and - to decrease #the clock needs to refresh to see the result $form.Add_KeyDown({ switch ($_.key) { { 'Add', 'OemPlus' -contains $_ } { If ( $PSClockSettings.fontSize -ge 8) { $PSClockSettings.fontSize++ $form.UpdateLayout() } } { 'Subtract', 'OemMinus' -contains $_ } { If ($PSClockSettings.FontSize -ge 8) { $PSClockSettings.FontSize-- $form.UpdateLayout() } } } }) #fail safe to remove flag file $form.Add_Unloaded({ if (Test-Path $env:temp\psclock-flag.txt) { Remove-Item $env:temp\psclock-flag.txt } }) $stack = New-Object System.Windows.Controls.StackPanel $label = New-Object System.Windows.Controls.label $label.Content = Get-Date -Format $PSClockSettings.DateFormat $label.HorizontalContentAlignment = "Center" $label.Foreground = $PSClockSettings.Color $label.FontStyle = $PSClockSettings.FontStyle $label.FontWeight = $PSClockSettings.FontWeight $label.FontSize = $PSClockSettings.FontSize $label.FontFamily = $PSClockSettings.FontFamily $label.VerticalAlignment = "Top" $stack.AddChild($label) $form.AddChild($stack) $timer = New-Object System.Windows.Threading.DispatcherTimer $timer.Interval = [TimeSpan]"0:0:1.00" $timer.Add_Tick({ if ($PSClockSettings.Running) { $label.Foreground = $PSClockSettings.Color $label.FontStyle = $PSClockSettings.FontStyle $label.FontWeight = $PSClockSettings.FontWeight $label.FontSize = $PSClockSettings.FontSize $label.FontFamily = $PSClockSettings.FontFamily $label.Content = Get-Date -Format $PSClockSettings.DateFormat $form.TopMost = $PSClockSettings.OnTop $form.UpdateLayout() #$PSClockSettings.Window = $Form $PSClockSettings.CurrentPosition = $form.left,$form.top } else { _QuitClock } }) $timer.Start() $PSClockSettings.Running = $True $PSClockSettings.Started = Get-Date #Show the clock form [void]$form.ShowDialog() }) $PSCmd.runspace = $rs Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Launching the runspace" [void]$PSCmd.BeginInvoke() Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Creating the flag file $env:temp\psclock-flag.txt" "[{0}] PSClock started by {1} under PowerShell process id $pid" -f (Get-Date), $env:USERNAME | Out-File -filepath $env:temp\psclock-flag.txt if ($PassThru) { Start-Sleep -Seconds 1 Get-PSClock } } #process End { Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)" } #end } #close function |