Private/GlobalStore.ps1

class GlobalStore
{
    $originalTitle = $null
    $originalPrompt = $null
    $isPromptReplaced = $false
    $originalPSConsoleHostReadLine = $null
    $isReadLineReplaced = $false
    $titleUpdateThread = $null
    $backgroundThreadTimerJobs = @()
    $promptCallbacks = @()
    $commandPreExecutionCallbacks = @()

    [void] Clear()
    {
        $this.ClearTitleUpdateThread()

        foreach ($timerJob in $this.backgroundThreadTimerJobs)
        {
            StopThread $timerJob.Thread
        }
        $this.backgroundThreadTimerJobs = @()

        $this.RestorePrompt()
        $this.RestorePSConsoleHostReadLine()
    }

    [void] ClearTitleUpdateThread()
    {
        if ($this.titleUpdateThread)
        {
            StopThread $this.titleUpdateThread
            $this.titleUpdateThread = $null
        }

        if ($null -ne $this.originalTitle)
        {
            (Get-Host).UI.RawUI.WindowTitle = $this.originalTitle
            $this.originalTitle = $null
        }
    }

    [void] SetTitleUpdateThread($thread, [string]$originalTitle)
    {
        $this.titleUpdateThread = $thread
        $this.originalTitle = $originalTitle
    }

    [void] AddBackgroundThreadTimerJob($job)
    {
        $this.backgroundThreadTimerJobs += $job
    }

    [void] ReplacePrompt()
    {
        if ($this.isPromptReplaced)
        {
            return
        }

        $this.isPromptReplaced = $true
        $this.originalPrompt = $function:global:Prompt
        $function:global:Prompt = {
            $script:globalStore.InvokePrompt()
        }
    }

    [void] RestorePrompt()
    {
        if (-not $this.isPromptReplaced)
        {
            return
        }

        $function:global:Prompt = $this.originalPrompt
        $this.isPromptReplaced = $false

        $this.promptCallbacks = @()
    }

    [void] AddPromptCallback([ScriptBlock]$scriptBlock, $arguments)
    {
        $this.promptCallbacks += ,@($scriptBlock, $arguments)
    }

    [string] InvokePrompt()
    {
        foreach ($private:callback in $this.promptCallbacks)
        {
            $private:scriptBlock = $callback[0]
            $private:arguments = $callback[1]
            $scriptBlock.Invoke($arguments)
        }
        return $this.originalPrompt.Invoke()
    }

    [void] ReplacePSConsoleHostReadLine()
    {
        if ($this.isReadLineReplaced)
        {
            return
        }

        $this.isReadLineReplaced = $true
        $this.originalPSConsoleHostReadLine = $function:global:PSConsoleHostReadLine
        $function:global:PSConsoleHostReadLine = {
            $script:globalStore.InvokePSConsoleHostReadLine()
        }
    }

    [void] RestorePSConsoleHostReadLine()
    {
        if (-not $this.isReadLineReplaced)
        {
            return
        }

        $function:global:PSConsoleHostReadLine = $this.originalPSConsoleHostReadLine
        $this.isReadLineReplaced = $false

        $this.commandPreExecutionCallbacks = @()
    }

    [void] AddCommandPreExecutionCallback([ScriptBlock]$scriptBlock, $arguments)
    {
        $this.commandPreExecutionCallbacks += ,@($scriptBlock, $arguments)
    }

    [Object] InvokePSConsoleHostReadLine()
    {
        $private:command = $this.originalPSConsoleHostReadLine.Invoke()
        foreach ($private:callback in $this.commandPreExecutionCallbacks)
        {
            $private:scriptBlock = $callback[0]
            $private:arguments = $callback[1]
            $arguments.command = $command
            $scriptBlock.Invoke($arguments)
        }
        return $command
    }
}

$script:globalStore = [GlobalStore]::new()