Public/Timers.ps1

<#
.SYNOPSIS
Adds a new Timer with logic to periodically invoke.
 
.DESCRIPTION
Adds a new Timer with logic to periodically invoke, with options to only run a specific number of times.
 
.PARAMETER Name
The Name of the Timer.
 
.PARAMETER Interval
The number of seconds to periodically invoke the Timer's ScriptBlock.
 
.PARAMETER ScriptBlock
The script for the Timer.
 
.PARAMETER Limit
The number of times the Timer should be invoked before being removed. (If 0, it will run indefinitely)
 
.PARAMETER Skip
The number of "invokes" to skip before the Timer actually runs.
 
.PARAMETER ArgumentList
An array of arguments to supply to the Timer's ScriptBlock.
 
.PARAMETER FilePath
A literal, or relative, path to a file containing a ScriptBlock for the Timer's logic.
 
.PARAMETER OnStart
If supplied, the timer will trigger when the server starts.
 
.EXAMPLE
Add-PodeTimer -Name 'Hello' -Interval 10 -ScriptBlock { 'Hello, world!' | Out-Default }
 
.EXAMPLE
Add-PodeTimer -Name 'RunOnce' -Interval 1 -Limit 1 -ScriptBlock { /* logic */ }
 
.EXAMPLE
Add-PodeTimer -Name 'RunAfter60secs' -Interval 10 -Skip 6 -ScriptBlock { /* logic */ }
 
.EXAMPLE
Add-PodeTimer -Name 'Args' -Interval 2 -ScriptBlock { /* logic */ } -ArgumentList 'arg1', 'arg2'
#>

function Add-PodeTimer
{
    [CmdletBinding(DefaultParameterSetName='Script')]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $Name,

        [Parameter(Mandatory=$true)]
        [int]
        $Interval,

        [Parameter(Mandatory=$true, ParameterSetName='Script')]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [int]
        $Limit = 0,

        [Parameter()]
        [int]
        $Skip = 0,

        [Parameter(Mandatory=$true, ParameterSetName='File')]
        [string]
        $FilePath,

        [Parameter()]
        [object[]]
        $ArgumentList,

        [switch]
        $OnStart
    )

    # error if serverless
    Test-PodeIsServerless -FunctionName 'Add-PodeTimer' -ThrowError

    # ensure the timer doesn't already exist
    if ($PodeContext.Timers.ContainsKey($Name)) {
        throw "[Timer] $($Name): Timer already defined"
    }

    # is the interval valid?
    if ($Interval -le 0) {
        throw "[Timer] $($Name): Interval must be greater than 0"
    }

    # is the limit valid?
    if ($Limit -lt 0) {
        throw "[Timer] $($Name): Cannot have a negative limit"
    }

    # is the skip valid?
    if ($Skip -lt 0) {
        throw "[Timer] $($Name): Cannot have a negative skip value"
    }

    # if we have a file path supplied, load that path as a scriptblock
    if ($PSCmdlet.ParameterSetName -ieq 'file') {
        $ScriptBlock = Convert-PodeFileToScriptBlock -FilePath $FilePath
    }

    # check if the scriptblock has any using vars
    $ScriptBlock, $usingVars = Invoke-PodeUsingScriptConversion -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # check for state/session vars
    $ScriptBlock = Invoke-PodeStateScriptConversion -ScriptBlock $ScriptBlock
    $ScriptBlock = Invoke-PodeSessionScriptConversion -ScriptBlock $ScriptBlock

    # calculate the next tick time (based on Skip)
    $NextTriggerTime = [DateTime]::Now.AddSeconds($Interval)
    if ($Skip -gt 1) {
        $NextTriggerTime = $NextTriggerTime.AddSeconds($Interval * $Skip)
    }

    # add the timer
    $PodeContext.Timers[$Name] = @{
        Name = $Name
        Interval = $Interval
        Limit = $Limit
        Count = 0
        Skip = $Skip
        NextTriggerTime = $NextTriggerTime
        LastTriggerTime = $null
        Script = $ScriptBlock
        UsingVariables = $usingVars
        Arguments = $ArgumentList
        OnStart = $OnStart
        Completed = $false
    }
}


<#
.SYNOPSIS
Adhoc invoke a Timer's logic.
 
.DESCRIPTION
Adhoc invoke a Timer's logic outside of its defined interval. This invocation doesn't count towards the Timer's limit.
 
.PARAMETER Name
The Name of the Timer.
 
.EXAMPLE
Invoke-PodeTimer -Name 'timer-name'
#>

function Invoke-PodeTimer
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [string]
        $Name
    )

    # ensure the timer exists
    if (!$PodeContext.Timers.ContainsKey($Name)) {
        throw "Timer '$($Name)' does not exist"
    }

    # run timer logic
    Invoke-PodeInternalTimer -Timer ($PodeContext.Timers[$Name])
}

<#
.SYNOPSIS
Removes a specific Timer.
 
.DESCRIPTION
Removes a specific Timer.
 
.PARAMETER Name
The Name of Timer to be removed.
 
.EXAMPLE
Remove-PodeTimer -Name 'SaveState'
#>

function Remove-PodeTimer
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [string]
        $Name
    )

    $null = $PodeContext.Timers.Remove($Name)
}

<#
.SYNOPSIS
Removes all Timers.
 
.DESCRIPTION
Removes all Timers.
 
.EXAMPLE
Clear-PodeTimers
#>

function Clear-PodeTimers
{
    [CmdletBinding()]
    param()

    $PodeContext.Timers.Clear()
}

<#
.SYNOPSIS
Edits an existing Timer.
 
.DESCRIPTION
Edits an existing Timer's properties, such as interval or scriptblock.
 
.PARAMETER Name
The Name of the Timer.
 
.PARAMETER Interval
The new Interval for the Timer in seconds.
 
.PARAMETER ScriptBlock
The new ScriptBlock for the Timer.
 
.PARAMETER ArgumentList
Any new Arguments for the Timer.
 
.EXAMPLE
Edit-PodeTimer -Name 'Hello' -Interval 10
#>

function Edit-PodeTimer
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [string]
        $Name,

        [Parameter()]
        [int]
        $Interval = 0,

        [Parameter()]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $ArgumentList
    )

    # ensure the timer exists
    if (!$PodeContext.Timers.ContainsKey($Name)) {
        throw "Timer '$($Name)' does not exist"
    }

    $_timer = $PodeContext.Timers[$Name]

    # edit interval if supplied
    if ($Interval -gt 0) {
        $_timer.Interval = $Interval
    }

    # edit scriptblock if supplied
    if (!(Test-PodeIsEmpty $ScriptBlock)) {
        $ScriptBlock, $usingVars = Invoke-PodeUsingScriptConversion -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
        $ScriptBlock = Invoke-PodeStateScriptConversion -ScriptBlock $ScriptBlock
        $ScriptBlock = Invoke-PodeSessionScriptConversion -ScriptBlock $ScriptBlock
        $_timer.Script = $ScriptBlock
        $_timer.UsingVariables = $usingVars
    }

    # edit arguments if supplied
    if (!(Test-PodeIsEmpty $ArgumentList)) {
        $_timer.Arguments = $ArgumentList
    }
}

<#
.SYNOPSIS
Returns any defined timers.
 
.DESCRIPTION
Returns any defined timers, with support for filtering.
 
.PARAMETER Name
Any timer Names to filter the timers.
 
.EXAMPLE
Get-PodeTimer
 
.EXAMPLE
Get-PodeTimer -Name Name1, Name2
#>

function Get-PodeTimer
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]]
        $Name
    )

    $timers = $PodeContext.Timers.Values

    # further filter by timer names
    if (($null -ne $Name) -and ($Name.Length -gt 0)) {
        $timers = @(foreach ($_name in $Name) {
            foreach ($timer in $timers) {
                if ($timer.Name -ine $_name) {
                    continue
                }

                $timer
            }
        })
    }

    # return
    return $timers
}

<#
.SYNOPSIS
Automatically loads timer ps1 files
 
.DESCRIPTION
Automatically loads timer ps1 files from either a /timers folder, or a custom folder. Saves space dot-sourcing them all one-by-one.
 
.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.
 
.EXAMPLE
Use-PodeTimers
 
.EXAMPLE
Use-PodeTimers -Path './my-timers'
#>

function Use-PodeTimers
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'timers'
}