Public/Start-DTJobBackgroundThreadTimer.ps1

<#
.SYNOPSIS
Starts a background thread and calls a function periodically on the thread.
 
.DESCRIPTION
Starts a background thread and calls a function periodically on the thread.
The specified ScriptBlock is called periodically at the specified interval on a background thread. It can be used to get the information that takes time to process.
 
.PARAMETER ScriptBlock
ScriptBlock that is called periodically.
 
.PARAMETER ArgumentList
The arguments passed to the ScriptBlock. The ScriptBlock runs on another thread (Runspace) so variables on the main thread need to be passed as this ArgumentList.
 
.PARAMETER InitializationScript
ScriptBlock that is called at the start of the new thread. It runs in the global scope of the thread.
 
.PARAMETER InitializationArgumentList
The arguments passed to the InitializationScript.
 
.PARAMETER IntervalMilliseconds
The ScriptBlock is called at this interval milliseconds.
 
.INPUTS
None.
 
.OUTPUTS
PSCustomObject that represents a job object.
 
.EXAMPLE
$job = Start-DTJobBackgroundThreadTimer -ScriptBlock {Invoke-RestMethod https://wttr.in/?format="%c%t\n"} -IntervalMilliseconds 60000
$weather = Get-DTJobLatestOutput $job
 
#>

function Start-DTJobBackgroundThreadTimer
{
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [OutputType([PSCustomObject])]
    param
    (
        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [ScriptBlock]$ScriptBlock,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [Object[]]$ArgumentList,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [ScriptBlock]$InitializationScript,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [Object[]]$InitializationArgumentList,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [Int]$IntervalMilliseconds = 500
    )

    process
    {
        $sync = [System.Collections.Hashtable]::Synchronized(@{})

        $arguments = @{
            host = $host
            sync = $sync
            scriptBlock = $ScriptBlock.Ast.GetScriptBlock()
            argumentList = $ArgumentList
            intervalMilliseconds = $IntervalMilliseconds
            scriptRoot = $PSScriptRoot
        }

        if ($InitializationScript)
        {
            $arguments.initializationScript = $InitializationScript.Ast.GetScriptBlock()
            $arguments.initializationArgumentList = $InitializationArgumentList
        }

        $threadFunc = {
            $private:dynamicTitleBackgroundThreadTimerJob = New-Module -ScriptBlock {
                . $args
            } -ArgumentList (Join-Path $args.scriptRoot '..\Private\_BackgroundJobHelper.ps1') -AsCustomObject
            $private:dynamicTitleBackgroundThreadTimerJob.Init($args)

            if ($args.initializationScript)
            {
                $private:dynamicTitleErrorVariable = $null
                Invoke-Command $args.initializationScript -NoNewScope -ArgumentList $args.initializationArgumentList -ErrorVariable dynamicTitleErrorVariable
                if ($dynamicTitleErrorVariable)
                {
                    $args.host.UI.WriteErrorLine($dynamicTitleErrorVariable)
                }
            }

            while ($dynamicTitleBackgroundThreadTimerJob.Tick())
            {
                $private:dynamicTitleErrorVariable = $null
                $args.sync.output = Invoke-Command $args.scriptBlock -ArgumentList $args.argumentList -ErrorVariable dynamicTitleErrorVariable
                if ($dynamicTitleErrorVariable)
                {
                    $args.host.UI.WriteErrorLine($dynamicTitleErrorVariable)
                }
            }
        }

        $thread = StartThread $threadFunc $arguments

        $job = [PSCustomObject]@{
            Sync = $sync
            Thread = $thread
        }
        $script:globalStore.AddBackgroundThreadTimerJob($job)

        $job
    }
}