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 } } |