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', 'Medium', 'SemiBold')] [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 CommandPath = $PSScriptRoot }) 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 #dot source the private settings preview function $previewFunction = Join-Path -Path "$($global:PSClockSettings.CommandPath)" -childPath ClockSettingsPreview.ps1 . $previewFunction # 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 the form 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({ #temporarily stop the timer while moving the clock $timer.stop() $form.DragMove() #update the positions $PSClockSettings.CurrentPosition = $form.left, $form.top #restart the timer $timer.start() }) #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() } } { 'P' -contains $_ } { #show the preview settings form Show-PSClockSettingPreview } } }) #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.left = $PSClockSettings.CurrentPosition[0] $form.top = $PSClockSettings.CurrentPosition[1] $form.UpdateLayout() $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 |