functions/ConsoleTimer.ps1

#functions for displaying a standard timer in a PowerShell console session

#region Exported functions

function Start-ConsoleTimer {
    [cmdletbinding(SupportsShouldProcess)]
    [OutputType('none')]
    [alias('stc')]
    param(
        [Parameter(HelpMessage = 'Specify an ANSI style or console color for the clock display. This setting will have no effect if you are resuming a stopped timer.')]
        [PSDefaultValue(Help = 'Yellow')]
        [ValidateNotNullOrEmpty()]
        [string]$DisplayColor = 'Yellow',

        [Parameter(HelpMessage = 'Add a line border to the display. This setting will have no effect if you are resuming a stopped timer.')]
        [switch]$Border,

        [Parameter(HelpMessage = 'Specify an ANSI or console color for the border. This parameter has no effect unless used with -Border. This setting will have no effect if you are resuming a stopped timer.')]
        [ValidateNotNullOrEmpty()]
        [string]$BorderColor = 'Green',

        [Parameter(HelpMessage = 'Reset and start the console timer beginning at 0 seconds.')]
        [switch]$Reset
    )

    begin {
        _verbose ($strings.Starting -f $MyInvocation.MyCommand)
        if ($MyInvocation.CommandOrigin -eq 'Runspace') {
            _verbose ($strings.Running -f $PSVersionTable.PSVersion)
            _verbose ($strings.UsingModule -f $modVersion)
            _verbose ($strings.Detected -f $Host.Name)
        }
        Write-Information $PSBoundParameters -Tags runtime

        #the action scriptblock needs to be self-contained or able to reference globally scoped items
        $action = {
            #dot source the helper functions from the PSClock module if required
            $modPath = (Get-Module PSTimers).Path | Split-Path -Parent
            $funPath = Join-Path -Path $modPath -ChildPath 'functions\ConsoleTimerHelpers.ps1'
            . $funPath

            if ($global:consoleTimerSettings.Border) {
                #add an offset if there is a Border
                $offSet = 5
            }
            else {
                $offset = 1
            }

            [string]$runTime = if ( $global:consoleTimerSettings.Timer.TotalDays -ge 1 ) {
                $global:consoleTimerSettings.Timer.Elapsed.ToString("dd\.hh\:mm\:ss")
            }  else {
                $global:consoleTimerSettings.Timer.Elapsed.ToString("hh\:mm\:ss")
            }

            $fmtTimer = "{0}$runTime{1}" -f (ConvertTo-AnsiColor $global:consoleTimerSettings.DisplayColor), "$([char]27)[0m"
            #define the cursor position in console
            $here = $host.ui.RawUI.CursorPosition
            $x = $Host.UI.RawUI.WindowSize.Width - $runTime.length - $offSet
            $consolePosition = [System.Management.Automation.Host.Coordinates]::new($x, 0)
            $host.ui.RawUI.CursorPosition = $consolePosition
            if ($global:consoleTimerSettings.Border) {
                Format-BorderBox -Text $fmtTimer -BorderColor (ConvertTo-AnsiColor $global:consoleTimerSettings.BorderColor) -Position $consolePosition
            }
            else {
                Write-Host $fmtTimer -NoNewline
            }
            #reset the cursor position
            $host.ui.RawUI.CursorPosition = $here
            if ($global:consoleTimerSettings.IsRunning) {
                $global:consoleTimerSettings.Timer.Start()
            }

        } #close action

        Write-Information $action
    } #begin

    process {
        if ($host.name -eq 'ConsoleHost') {
            Write-Information -MessageData $myInvocation -Tags process
            Write-Information -MessageData $PSBoundParameters -Tags process

            if ($global:consoleTimerSettings.IsRunning) {
                Write-Warning $strings.ExistingConsoleTimer
                return
            }
            elseif (-not $global:consoleTimerSettings) {
                #create $global:consoleTimerSettings
                _verbose $strings.InitializingConsoleTimer
                $ConsoleTimerParams = @{
                    Name        = 'consoleTimerSettings'
                    Scope       = 'global'
                    Description = 'This is used by the PSTimers module. Do not manually remove.'
                    Value       = [ordered]@{
                        Timer        = [System.Diagnostics.Stopwatch]::new()
                        DisplayColor = ConvertTo-AnsiColor $DisplayColor
                        Border       = $Border
                        BorderColor  = ConvertTo-AnsiColor $BorderColor
                        IsRunning    = $False
                        Started      = Get-Date
                        Note         = 'This is used by the PSTimers module. Do not manually remove.'
                    }
                }
                Set-Variable @ConsoleTimerParams
                Write-Information $global:consoleTimerSettings -Tags runtime
                _verbose $strings.RegisterConsoleTimer
                $global:consoleTimerSettings.IsRunning = $True
                $global:consoleTimerSettings.Timer.Start()
                #Register-EngineEvent doesn't natively support -WhatIf so I have to do it.
                if ($PSCmdlet.ShouldProcess('Start-ConsoleTimer')) {
                    #hide the subscription
                    Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -Action $action -SupportEvent
                    $script:consoleTimerEvent = Get-EventSubscriber -Force | Sort-Object SubscriptionId | Select-Object -Last 1
                    Write-Information $Script:consoleTimerEvent -Tags process
                    #add to the module array for event subscriptions
                    $subs.Add($script:consoleTimerEvent)
                    _verbose ($strings.UsingSubscription -f $script:consoleTimerEvent.SubscriptionID)
                } #WhatIf
            } #initialize new

            if ($Reset) {
                _verbose ($strings.ResettingTimer -f $script:consoleTimerEvent.SubscriptionID)
                $global:consoleTimerSettings.Timer.Restart()
                $global:consoleTimerSettings.IsRunning = $True
            }
            elseif ((-not $global:consoleTimerSettings.IsRunning) -and $global:consoleTimerSettings.Timer.Elapsed.TotalSeconds -gt 0) {
                _verbose ($strings.RestartingTimer -f $script:consoleTimerEvent.SubscriptionID)
                $global:consoleTimerSettings.Timer.Start()
                $global:consoleTimerSettings.IsRunning = $True
            }
        } #if console host
        else {
            Write-Warning $strings.InvalidHost
        }
    } #process

    end {
        _verbose ($strings.Ending -f $MyInvocation.MyCommand)
    } #end

} #close Start-ConsoleTimer
function Stop-ConsoleTimer {
    [cmdletbinding(SupportsShouldProcess)]
    [OutputType('none')]
    param()

    begin {
        _verbose ($strings.Starting -f $MyInvocation.MyCommand)
        if ($MyInvocation.CommandOrigin -eq 'Runspace') {
            _verbose ($strings.Running -f $PSVersionTable.PSVersion)
            _verbose ($strings.UsingModule -f $modVersion)
            _verbose ($strings.Detected -f $Host.Name)
        }
    } #begin

    process {
        _verbose $strings.StoppingConsoleTimer
        $global:consoleTimerSettings.Timer.Stop()
        $global:consoleTimerSettings.IsRunning = $False
    } #process

    end {
        _verbose ($strings.Ending -f $MyInvocation.MyCommand)
    } #end

} #close Stop-ConsoleTimer

function Remove-ConsoleTimer {
    [cmdletBinding()]
    [OutputType('none')]
    param(
        [Parameter(HelpMessage = 'Clear the console host after removing the console timer.')]
        [Alias('cls')]
        [switch]$ClearHost
    )

    begin {
        _verbose ($strings.Starting -f $MyInvocation.MyCommand)
        if ($MyInvocation.CommandOrigin -eq 'Runspace') {
            _verbose ($strings.Running -f $PSVersionTable.PSVersion)
            _verbose ($strings.UsingModule -f $modVersion)
            _verbose ($strings.Detected -f $Host.Name)
        }
    } #begin

    process {
        if ($script:consoleTimerEvent) {
            _verbose ($strings.Unregister -f $($script:consoleTimerEvent.SubscriptionId))
            Unregister-Event -SubscriptionId $script:consoleTimerEvent.SubscriptionId -Force
            Remove-Variable -Name consoleTimerEvent -Scope Script -Force -ErrorAction SilentlyContinue
            Remove-Variable -Name consoleTimerSettings -Scope global -Force -ErrorAction SilentlyContinue
            $removed = $true
        }
        else {
            Write-Warning $strings.NoConsoleTimer
        }
    } #process
    end {
        _verbose ($strings.Ending -f $MyInvocation.MyCommand)
        if ($ClearHost -and $removed) {
            Clear-Host
        }
    } #end
}

#endregion