PlugEvents.psm1
|
function Connect-PlugEvents { <# .SYNOPSIS Connect to Plug.Events .DESCRIPTION Connect to Plug.Events .PARAMETER Endpoint Endpoint to connect to. When not entered it will retrieve the first production endpoint by default. .PARAMETER ConnectionToken Token to use in the connection. When not entered it will retrieve this automatically. .EXAMPLE Connect to Plug-Events PS> Connect-PlugEvents #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Its the name of the product')] [CmdLetBinding()] Param ( [Parameter(Mandatory = $false, Position = 1)] [String] $Endpoint = (Get-PlugEventsEndpoint -Type p -First 1).Types.Endpoint, [Parameter(Mandatory = $false, Position = 2)] [String] $ConnectionToken = (Open-PlugEventsWebsocket -Endpoint $Endpoint).connectionToken ) # Create the websocket client and cancellation token $Script:websocket = [System.Net.WebSockets.ClientWebSocket]::new() $Script:cancellationToken = [System.Threading.CancellationTokenSource]::new() # Add the option for json $Script:websocket.Options.AddSubProtocol('json') # Connect $uriObj = [Uri]"wss://$Endpoint/hub1?id=$ConnectionToken" $null = $Script:websocket.ConnectAsync($uriObj, $Script:cancellationToken.Token).GetAwaiter().GetResult() # Send a message to establish the handshake Send-PlugEventsMessage -Message '{"protocol":"json","version":1}' # Check for a message $message = Receive-PlugEventsMessage -IgnoreKeepAlive if($message -ne "{}") { throw "Plug Events: the connection could not be established. Error message: $message" } } function Disconnect-PlugEvents { <# .SYNOPSIS Disconnect from Plug.Events .DESCRIPTION Disconnect from Plug.Events .EXAMPLE Disconnect PS> Disconnect-PlugEvents #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Its the name of the product')] [CmdLetBinding()] Param () # Close the connection $null = $Script:websocket.CloseAsync( [System.Net.WebSockets.WebSocketCloseStatus]::Empty, "", $Script:cancellationToken.Token ) # Dispose of the objects $Script:websocket.Dispose() $Script:cancellationToken.Dispose() } function Get-PlugEventsCancellationToken { <# .SYNOPSIS Get the CancellationToken for the connection to Plug.Events .DESCRIPTION Get the CancellationToken for the connection to Plug.Events .EXAMPLE Get the token PS> Get-PlugEventsCancellationToken #> [CmdLetBinding()] Param () # Return the cancellationtoken object $Script:cancellationToken } function Get-PlugEventsConnection { <# .SYNOPSIS Get the object that contains the websocketconnection to plug.events .DESCRIPTION Get the object that contains the websocketconnection to plug.events .EXAMPLE Get the connection PS> Get-PlugEventsConnection #> [CmdLetBinding()] Param () # Return the websocket object $Script:websocket } function Get-PlugEventsEndpoint { <# .SYNOPSIS Return the endpoints for plug.events backend servers. .DESCRIPTION Return the endpoints for plug.events backend servers. .PARAMETER Type Type of the server. Can be p or i. .PARAMETER First Amount of entries to return. .EXAMPLE Get all endpoints PS> Get-PlugEventsEndpoint Id Types -- ----- 639009792141241401 {@{Type=p; Endpoint=pi31.plug.events}, @{Type=i; Endpoint=ii31.plug.events}} 639003195191559039 {@{Type=p; Endpoint=pi30.plug.events}, @{Type=i; Endpoint=ii30.plug.events}} .EXAMPLE Get first endpoint PS> Get-PlugEventsEndpoint -First 1 Id Types -- ----- 639009792141241401 {@{Type=p; Endpoint=pi31.plug.events}, @{Type=i; Endpoint=ii31.plug.events}} .EXAMPLE Get first endpoint of type p PS> Get-PlugEventsEndpoint -Type p -First 1 Id Types -- ----- 639009792141241401 {@{Type=p; Endpoint=pi31.plug.events}} #> [CmdLetBinding()] [OutputType([Array])] Param ( [Parameter(Mandatory = $false, Position = 1)] [String] $Type = "none", [Parameter(Mandatory = $false, Position = 2)] [Int] $First = 0 ) # Get all the endpoints from the txt file $nodemap = Invoke-RestMethod -uri "https://www.plug.events/nodemap.txt" -Method GET # Parse the endpoints $endpoints = @() $nodemap | ConvertFrom-Csv -Header "id", "endpoints" | ForEach-Object { # Create an object $node = [PSCustomObject]@{ Id = $_.Id Types = @() } # Split the different types foreach ($endpoint in $_.endpoints.split("|")) { # Check for the type if($Type -ne "none") { if($Type -ne $endpoint[0]) { Continue } } $node.Types += [PSCustomObject]@{ Type = $endpoint[0] Endpoint = $endpoint } } # Add the node to the endpoint array $endpoints += $node } # Filter amount if($First -gt 0) { $endpoints = $endpoints | Select-Object -First $First } $endpoints } function Get-PlugEventsUmbrellaEvent { <# .SYNOPSIS Get all events under a specific umbrella in Plug.Events .DESCRIPTION Get all events under a specific umbrella in Plug.Events .PARAMETER Id Id of the umbrella org .PARAMETER StartDate DateTime object containing the date to start filtering from. Default is 30 days in the past. .PARAMETER EndDate DateTime object containing the date to stop filtering from. Default is today. .PARAMETER Top Maximum amount of items to return. Default is 999. .EXAMPLE Get the events PS> Get-PlugEventsUmbrellaEvent -Id "balfolk-nl" -StartDate (Get-Date "2025-01-01") -EndDate (Get-Date "2025-12-31") -Top 200 #> [CmdLetBinding()] [OutputType([Array])] Param ( [Parameter(Mandatory = $true, Position = 1)] [String] $Id, [Parameter(Mandatory = $false, Position = 2)] [DateTime] $StartDate = (Get-Date).AddDays(-30), [Parameter(Mandatory = $false, Position = 3)] [DateTime] $EndDate = (Get-Date), [Parameter(Mandatory = $false, Position = 4)] [Int] $Top = 999 ) # Set up the message $message = '{"target":"GetNetworkViewPage2","arguments":[{"recKind":1,"slug":"'+$Id+'","slugs":null,"direction":103,"startAt":0,"maxCount":'+$Top+',"nameContains":"","roleSlugFilters":null,"isClaimed":null,"seq1Filter":null,"seq1InverseFilter":null,"minEventTime":"'+($StartDate | Get-Date -Format "yyyyMMdd0000")+'","maxEventTime":"'+($EndDate | Get-Date -Format "yyyyMMdd0000")+'","interestSlug":null,"subinterest":null,"localeSlug":null,"toRoleNameContains":""}],"invocationId":"9","type":1}' # Send the message Send-PlugEventsMessage -Message $message # Receive the response $response = Receive-PlugEventsMessage -Timeout 30 -IgnoreKeepAlive # Convert the response from JSON ($response | ConvertFrom-Json).result.directionSets.items } function Open-PlugEventsWebsocket { <# .SYNOPSIS Open a websocket connection to plug.events and return the connectionToken. .DESCRIPTION Open a websocket connection to plug.events and return the connectionToken. .PARAMETER Endpoint Endpoint to connect to. When not entered it will retrieve the first production endpoint by default. .EXAMPLE Open the connection PS> Open-PlugEventsWebsocket negotiateVersion connectionId connectionToken availableTransports ---------------- ------------ --------------- ------------------- 1 abcdefabcdefabcdefabcd abcdefabcdefabcdefabcd {@{transport=WebSockets; transferFormats=System.Object[]}, @{transport=ServerSentEvents; transferFormats=System… .EXAMPLE Open the connection for a specific endpoint PS> Open-PlugEventsWebsocket -Endpoint "pi31.plug.events" negotiateVersion connectionId connectionToken availableTransports ---------------- ------------ --------------- ------------------- 1 abcdefabcdefabcdefabcd abcdefabcdefabcdefabcd {@{transport=WebSockets; transferFormats=System.Object[]}, @{transport=ServerSentEvents; transferFormats=System… #> [CmdLetBinding()] [OutputType([Object])] Param ( [Parameter(Mandatory = $false, Position = 1)] [String] $Endpoint = (Get-PlugEventsEndpoint -Type p -First 1).Types.Endpoint ) # Open the connection Invoke-RestMethod -Uri "https://$Endpoint/hub1/negotiate?negotiateVersion=1" -Method POST } function Receive-PlugEventsMessage { <# .SYNOPSIS Receive a message from the plug.events back-end. .DESCRIPTION Receive a message from the plug.events back-end. The end-of-message marker will automatically be removed from the output. .PARAMETER Connection Connection object for the websocket connection. Default this will be the connection set up via Connect-PlugEvents .PARAMETER Timeout Maximum time in seconds to wait for a response before cancelling the request (this will close the connection). Default is 21 seconds. .PARAMETER IgnoreKeepAlive Ignore the keepalive messages and only return other types of messages. .EXAMPLE Receive the data PS> Receive-PlugEventsMessage -Timeout 30 -IgnoreKeepAlive #> [CmdLetBinding()] [OutputType([String])] Param ( [Parameter(Mandatory = $false, Position = 1)] [Object] $Connection = $Script:websocket, [Parameter(Mandatory = $false, Position = 2)] [Int] $Timeout = 21, [Parameter(Mandatory = $false, Position = 3)] [Switch] $IgnoreKeepAlive ) # Check if a connection is create if($Connection.State -ne [System.Net.WebSockets.WebSocketState]::Open) { throw "Plug-Events: no open connection was detected. Please run Connect-PlugEvents first." } # Create a buffer to store the message $buffer = New-Object byte[] 8192 $stringbuilder = [System.Text.StringBuilder]::new() # Extra variable to prevent unending loop $time = Get-Date while ($Connection.State -eq [System.Net.WebSockets.WebSocketState]::Open) { # Create an async call $cancellationToken = [System.Threading.CancellationTokenSource]::new($Timeout*1000) $await = $Connection.ReceiveAsync([ArraySegment[byte]]::new($buffer), $cancellationToken.Token) # Wait untill the response is returned while($await.Status -ne "RanToCompletion") { # Check if the timeout has been reached if ($await.Status -eq "Canceled") { throw "Plug-Events: the timeout of $Timeout seconds has been exceeded while waiting for a response from the server." } # Check if not in unending loop if((Get-Date) -gt $time.AddSeconds($Timeout)) { $null = $cancellationToken.Cancel() throw "Plug-Events: waiting for more then $Timeout seconds for a reply of server. request has been cancelled." } } # Get the result from the await $result = $await.GetAwaiter().GetResult() if ($result.MessageType -eq [System.Net.WebSockets.WebSocketMessageType]::Close) { Disconnect-PlugEvents break } # Accumulate chunk(s) until EndOfMessage $null = $stringbuilder.Append([System.Text.Encoding]::UTF8.GetString($buffer, 0, $result.Count)) # If the message is done convert the stringbuilder to a string and stop the loop if ($result.EndOfMessage) { $text = $stringbuilder.ToString() # Check if the message is a keepalive if ($IgnoreKeepAlive -and $text -eq '{"type":6}') { # Continue and clear the stringbuilder $null = $stringbuilder.Clear() } else { # Break the loop break } } } # Check if the end of message character is still added if([int]$text[-1] -eq 30) { $text = $text.TrimEnd([char]0x1e) } # Return the message $text } function Send-PlugEventsMessage { <# .SYNOPSIS Send a message to the plug.events back-end. .DESCRIPTION Send a message to the plug.events back-end. .PARAMETER Message The message to send in string format. If the end-of-message marker is not added this will be added automatically. .PARAMETER Connection Connection object for the websocket connection. Default this will be the connection set up via Connect-PlugEvents .PARAMETER Timeout Maximum time in seconds to wait for a response before cancelling the request (this will close the connection). Default is 5 seconds. .PARAMETER Async Dont wait on confirmation that the message has been send. .EXAMPLE Send the message PS> $Message = '{"target":"GetLocalesBySlug","arguments":[["netherlands"]],"invocationId":"1","type":1}' PS> Send-PlugEventsMessage -Message $Message -Timeout 10 -Async #> [CmdLetBinding()] [OutputType([String])] Param ( [Parameter(Mandatory = $true, Position = 1)] [String] $Message, [Parameter(Mandatory = $false, Position = 2)] [Object] $Connection = $Script:websocket, [Parameter(Mandatory = $false, Position = 3)] [Int] $Timeout = 5, [Parameter(Mandatory = $false, Position = 4)] [Switch] $Async ) # Check if a connection is create if ($Connection.State -ne [System.Net.WebSockets.WebSocketState]::Open) { throw "Plug-Events: no open connection was detected. Please run Connect-PlugEvents first." } # Check if end of message marker is added (0x1e = 30) if ([int]$Message[-1] -ne 30) { $Message += [char]0x1e } # Create byte array $bytes = [System.Text.Encoding]::UTF8.GetBytes($Message) $seg = [ArraySegment[byte]]::new($bytes) # Send the message $cancellationToken = [System.Threading.CancellationTokenSource]::new($Timeout * 1000) $await = $Connection.SendAsync( $seg, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $cancellationToken.Token ) if ( -not $Async) { # Extra variable to prevent unending loop $time = Get-Date # Wait untill the message is semd while ($await.Status -ne "RanToCompletion") { # Check if the timeout has been reached if ($await.Status -eq "Canceled") { throw "Plug-Events: the timeout of $Timeout seconds has been exceeded while waiting for a response from the server." } # Check if not in unending loop if ((Get-Date) -gt $time.AddSeconds($Timeout)) { $null = $cancellationToken.Cancel() throw "Plug-Events: waiting for more then $Timeout seconds for a reply of server. request has been cancelled." } } } } # =================================================================== # ================== WEBSOCKET ====================================== # =================================================================== # Create env variables $Script:websocket = [System.Net.WebSockets.ClientWebSocket]::new() $Script:cancellationToken = [System.Threading.CancellationTokenSource]::new() # =================================================================== # ================== TELEMETRY ====================================== # =================================================================== # Create env variables $Env:PLUGEVENTS_TELEMETRY_OPTIN = (-not $Evn:POWERSHELL_TELEMETRY_OPTOUT) # use the invert of default powershell telemetry setting # Set up the telemetry Initialize-THTelemetry -ModuleName "plugEvents" Set-THTelemetryConfiguration -ModuleName "plugEvents" -OptInVariableName "PLUGEVENTS_TELEMETRY_OPTIN" -StripPersonallyIdentifiableInformation $true -Confirm:$false Add-THAppInsightsConnectionString -ModuleName "plugEvents" -ConnectionString "InstrumentationKey=df9757a1-873b-41c6-b4a2-2b93d15c9fb1;IngestionEndpoint=https://westeurope-5.in.applicationinsights.azure.com/;LiveEndpoint=https://westeurope.livediagnostics.monitor.azure.com/" # Create a message about the telemetry Write-Information ("Telemetry for plugEvents module is $(if([string] $Env:PLUGEVENTS_TELEMETRY_OPTIN -in ("no","false","0")){"NOT "})enabled. Change the behavior by setting the value of " + '$Env:PLUGEVENTS_TELEMETRY_OPTIN') -InformationAction Continue # Send a metric for the installation of the module Send-THEvent -ModuleName "plugEvents" -EventName "Import Module plugEvents" |