Plus/Invoke-ListenTo.ps1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Scope = 'Function', Target = 'Invoke-ListenTo')]
Param()

function Invoke-ListenTo {
    <#
    .SYNOPSIS
    Create an event listener ("subscriber"). Basically a wrapper for Register-EngineEvent.
    .PARAMETER Path
    Path to file or folder that will be watched for changes
    .PARAMETER Exit
    Set event source identifier to PowerShell.Exiting
    .PARAMETER Idle
    Set event source identifier to PowerShell.OnIdle.
    Warning: It is not advised to write to console in callback of -Idle listeners.
    .EXAMPLE
    { Write-Color 'Event triggered' -Red } | on 'SomeEvent'
 
    # Expressive yet terse syntax for easy event-driven design.
    .EXAMPLE
    Invoke-ListenTo -Name 'SomeEvent' -Callback { Write-Color "Event: $($Event.SourceIdentifier)" }
 
    # Callbacks hae access to automatic variables such as $Event
    .EXAMPLE
    $Callback | on 'SomeEvent' -Once
 
    # Create a listener that automatically destroys itself after one event is triggered
    .EXAMPLE
    $Callback = {
        $Data = $args[1]
        "Name ==> $($Data.Name)" | Write-Color -Magenta
        "Event ==> $($Data.ChangeType)" | Write-Color -Green
        "Fullpath ==> $($Data.FullPath)" | Write-Color -Cyan
    }
    $Callback | listenTo -Path .
 
    # Watch files and folders for changes (create, edit, rename, delete)
    .EXAMPLE
    $Answer = 42
 
    # Create a callback
    $Callback = {
        $Function:Text = "{{ Name }} was changed from {{ OldValue }}, to {{ Value }}" | tpl
        Text $Event.MessageData | say
    }
 
    # Start the variable listener
    $Callback | listenTo 'Answer' -Variable
 
    # Change the value of boot and have your computer tell you what changed
    $Answer = 43
    .EXAMPLE
    { 'EVENT - EXIT' | Out-File ~\dev\MyEvents.txt -Append } | on -Exit
 
    # Execute code when you exit the PowerShell terminal
    #>

    [CmdletBinding(DefaultParameterSetName = 'custom')]
    [Alias('on', 'listenTo')]
    Param(
        [Parameter(ParameterSetName = 'custom', Position = 0)]
        [Parameter(ParameterSetName = 'variable', Position = 0)]
        [String] $Name,
        [Parameter(ParameterSetName = 'custom')]
        [Parameter(ParameterSetName = 'variable')]
        [Switch] $Once,
        [Parameter(ParameterSetName = 'custom')]
        [Switch] $Exit,
        [Parameter(ParameterSetName = 'custom')]
        [Switch] $Idle,
        [Parameter(ParameterSetName = 'custom', Mandatory = $True, ValueFromPipeline = $True)]
        [Parameter(ParameterSetName = 'variable', Mandatory = $True, ValueFromPipeline = $True)]
        [Parameter(ParameterSetName = 'filesystem', Mandatory = $True, ValueFromPipeline = $True)]
        [ScriptBlock] $Callback,
        [Parameter(ParameterSetName = 'custom')]
        [Parameter(ParameterSetName = 'filesystem')]
        [Switch] $Forward,
        [Parameter(ParameterSetName = 'filesystem', Mandatory = $True)]
        [String] $Path,
        [Parameter(ParameterSetName = 'filesystem')]
        [Switch] $IncludeSubDirectories,
        [Parameter(ParameterSetName = 'filesystem')]
        [Switch] $Absolute,
        [Parameter(ParameterSetName = 'variable')]
        [Switch] $Variable
    )
    $Action = $Callback
    if ($Path.Length -gt 0) {
        # file system watcher events
        if (-not $Absolute) {
            $Path = Join-Path (Get-Location) $Path -Resolve
        }
        Write-Verbose "==> Creating file system watcher object for `"$Path`""
        $Watcher = New-Object System.IO.FileSystemWatcher
        $Watcher.Path = $Path
        $Watcher.Filter = '*.*'
        $Watcher.EnableRaisingEvents = $True
        $Watcher.IncludeSubdirectories = $IncludeSubDirectories
        Write-Verbose '==> Creating file system watcher events'
        'Created', 'Changed', 'Deleted', 'Renamed' | ForEach-Object {
            Register-ObjectEvent $Watcher $_ -Action $Action
        }
    } elseif ($Variable) {
        # variable change events
        $VariableNamespace = New-Guid | Select-Object -ExpandProperty Guid | ForEach-Object { $_ -replace '-', '_' }
        $Global:__NameVariableValue = $Name
        $Global:__VariableChangeEventLabel = "VariableChangeEvent_$VariableNamespace"
        $Global:__NameVariableLabel = "Name_$VariableNamespace"
        $Global:__OldValueVariableLabel = "OldValue_$VariableNamespace"
        New-Variable -Name $Global:__NameVariableLabel -Value $Name -Scope Global
        Write-Verbose "Variable name = $Global:__NameVariableValue"
        if ((Get-Variable | Select-Object -ExpandProperty Name) -contains $Name) {
            New-Variable -Name $Global:__OldValueVariableLabel -Value (Get-Variable -Name $Name -ValueOnly) -Scope Global
            Write-Verbose "Initial value = $(Get-Variable -Name $Name -ValueOnly)"
        } else {
            Write-Error "Variable not found in current scope ==> `"$Name`""
        }
        $UpdateValue = {
            $Name = Get-Variable -Name $Global:__NameVariableLabel -Scope Global -ValueOnly
            $NewValue = Get-Variable -Name $Global:__NameVariableValue -Scope Global -ValueOnly
            $OldValue = Get-Variable -Name $Global:__OldValueVariableLabel -Scope Global -ValueOnly
            if (-not (Test-Equal $NewValue $OldValue)) {
                Invoke-FireEvent $Global:__VariableChangeEventLabel -Data @{ Name = $Name; Value = $NewValue; OldValue = $OldValue }
                Set-Variable -Name $Global:__OldValueVariableLabel -Value $NewValue -Scope Global
            }
        }
        $UpdateValue | Invoke-ListenTo -Idle | Out-Null
        $Action | Invoke-ListenTo $Global:__VariableChangeEventLabel | Out-Null
    } else {
        # custom and PowerShell engine events
        if ($Exit) {
            $SourceIdentifier = ([System.Management.Automation.PsEngineEvent]::Exiting)
        } elseif ($Idle) {
            $SourceIdentifier = ([System.Management.Automation.PsEngineEvent]::OnIdle)
        } else {
            $SourceIdentifier = $Name
        }
        if ($Once) {
            Write-Verbose "==> Creating one-time event listener for $SourceIdentifier event"
            $_Event = Register-EngineEvent -SourceIdentifier $SourceIdentifier -MaxTriggerCount 1 -Action $Action -Forward:$Forward
        } else {
            Write-Verbose "==> Creating event listener for `"$SourceIdentifier`" event"
            $_Event = Register-EngineEvent -SourceIdentifier $SourceIdentifier -Action $Action -Forward:$Forward
        }
        $_Event
    }
}