src/events.ps1
function Invoke-FireEvent { <# .SYNOPSIS Create event .EXAMPLE 'eventName' | Invoke-FireEvent #> [CmdletBinding()] [Alias('trigger')] Param( [Parameter(Mandatory=$True, Position=0, ValueFromPipeline=$True)] [String] $Name, [PSObject] $Data ) New-Event -SourceIdentifier $Name -MessageData $Data | Out-Null } 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 # Declare a value for boot $boot = 42 # Create a callback $Callback = { $Data = $Event.MessageData say "$($Data.Name) was changed from $($Data.OldValue), to $($Data.Value)" } # Start the variable listener $Callback | listenTo 'boot' -Variable # Change the value of boot and have your computer tell you what changed $boot = 43 .EXAMPLE { 'EVENT - EXIT' | Out-File ~\dev\MyEvents.txt -Append } | on -Exit Execute code when you exit the powershell terminal #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Scope='Function')] [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 } } function Invoke-StopListen { <# .SYNOPSIS Remove event subscriber(s) .EXAMPLE $Callback | on 'SomeEvent' 'SomeEvent' | Invoke-StopListen Remove events using the event "source identifier" (Name) .EXAMPLE $Callback | on -Name 'Namespace:foo' $Callback | on -Name 'Namespace:bar' 'Namespace:' | Invoke-StopListen Remove multiple events using an event namespace .EXAMPLE $Listener = $Callback | on 'SomeEvent' Invoke-StopListen -EventData $Listener Selectively remove a single event by passing its event data #> [CmdletBinding()] Param( [Parameter(ValueFromPipeline=$True)] [String] $Name, [PSObject] $EventData ) if ($EventData) { Unregister-Event -SubscriptionId $EventData.Id } else { if ($Name) { $Events = Get-EventSubscriber | Where-Object { $_.SourceIdentifier -match "^$Name" } } else { $Events = Get-EventSubscriber } $Events | ForEach-Object { Unregister-Event -SubscriptionId $_.SubscriptionId } } } |