From 4a5854fac44761990aa6660abec4c26a9c0e1af9 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Tue, 24 Dec 2024 20:20:58 -0800 Subject: [PATCH 001/123] fix: Get-WebSocket quieting previous job check ( Fixes #43 ) --- Commands/Get-WebSocket.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index a2635c8..1fbf313 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -325,7 +325,7 @@ function Get-WebSocket { $Name = $WebSocketUri } - $existingJob = foreach ($jobWithThisName in (Get-Job -Name $Name)) { + $existingJob = foreach ($jobWithThisName in (Get-Job -Name $Name -ErrorAction Ignore)) { if ( $jobWithThisName.State -in 'Running','NotStarted' -and $jobWithThisName.WebSocket -is [Net.WebSockets.ClientWebSocket] From de52d35cc247b095217c5e159d2b5da2ff590942 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Wed, 25 Dec 2024 04:22:45 +0000 Subject: [PATCH 002/123] fix: Get-WebSocket quieting previous job check ( Fixes #43 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index a7d8667..9f22b2e 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2024-12-20" \ No newline at end of file +"2024-12-25" \ No newline at end of file From c5175af655bb874e38751940d38c4b38b056c2e9 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Tue, 24 Dec 2024 20:29:08 -0800 Subject: [PATCH 003/123] feat: Get-WebSocket -SubProtocol ( Fixes #46 ) --- Commands/Get-WebSocket.ps1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 1fbf313..1ae19bd 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -165,6 +165,10 @@ function Get-WebSocket { [switch] $Binary, + # The subprotocol used by the websocket. If not provided, this will default to `json`. + [string] + $SubProtocol, + # If set, will watch the output of a WebSocket job for one or more conditions. # The conditions are the keys of the dictionary, and can be a regex, a string, or a scriptblock. # The values of the dictionary are what will happen when a match is found. @@ -242,6 +246,11 @@ function Get-WebSocket { if (-not $webSocket) { $ws = [Net.WebSockets.ClientWebSocket]::new() + if ($SubProtocol) { + $ws.Options.AddSubProtocol($SubProtocol) + } else { + $ws.Options.AddSubProtocol('json') + } $null = $ws.ConnectAsync($WebSocketUri, $CT).Wait() } else { $ws = $WebSocket From ab00d9dd4e691d2e6f91b259d951762c9f729e48 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Wed, 25 Dec 2024 04:30:30 +0000 Subject: [PATCH 004/123] feat: Get-WebSocket -SubProtocol ( Fixes #46 ) --- docs/Get-WebSocket.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index f252bef..1cf8c56 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -234,6 +234,13 @@ If set, will output the raw bytes that come out of the WebSocket. |----------|--------|--------|-------------|---------------------------------------| |`[Switch]`|false |named |false |RawByte
RawBytes
Bytes
Byte| +#### **SubProtocol** +The subprotocol used by the websocket. If not provided, this will default to `json`. + +|Type |Required|Position|PipelineInput| +|----------|--------|--------|-------------| +|`[String]`|false |named |false | + #### **WatchFor** If set, will watch the output of a WebSocket job for one or more conditions. The conditions are the keys of the dictionary, and can be a regex, a string, or a scriptblock. @@ -293,5 +300,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [] +Get-WebSocket [[-WebSocketUri] ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [] ``` From 335d7e96fb436145bebedee4feb0cfb3bc9b2b88 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 4 Jan 2025 17:09:25 -0800 Subject: [PATCH 005/123] feat: Get-WebSocket -Debug ( Fixes #45 ) --- Commands/Get-WebSocket.ps1 | 62 +++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 1ae19bd..babde38 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -178,7 +178,7 @@ function Get-WebSocket { foreach ($key in $keys) { if ($key -isnot [scriptblock]) { throw "Keys '$key' must be a scriptblock" - } + } } foreach ($value in $values) { if ($value -isnot [scriptblock] -and $value -isnot [string]) { @@ -224,13 +224,18 @@ function Get-WebSocket { begin { $SocketJob = { - param([Collections.IDictionary]$Variable) + param( + # By accepting a single parameter containing variables, + # we can avoid the need to pass in a large number of parameters. + [Collections.IDictionary]$Variable + ) + # Take every every `-Variable` passed in and define it within the job foreach ($keyValue in $variable.GetEnumerator()) { $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) } - if ((-not $WebSocketUri) -or $webSocket) { + if ((-not $WebSocketUri)) { throw "No WebSocketUri" } @@ -244,7 +249,7 @@ function Get-WebSocket { $CT = [Threading.CancellationToken]::None - if (-not $webSocket) { + if ($webSocket -isnot [Net.WebSockets.ClientWebSocket]) { $ws = [Net.WebSockets.ClientWebSocket]::new() if ($SubProtocol) { $ws.Options.AddSubProtocol($SubProtocol) @@ -328,6 +333,10 @@ function Get-WebSocket { foreach ($keyValuePair in $PSBoundParameters.GetEnumerator()) { $Variable[$keyValuePair.Key] = $keyValuePair.Value } + if ($DebugPreference -notin 'SilentlyContinue','Ignore') { + . $SocketJob -Variable $Variable + return + } $webSocketJob = if ($WebSocketUri) { if (-not $name) { @@ -362,32 +371,37 @@ function Get-WebSocket { SupportEvent = $true } $eventSubscriptions = @( - if ($OnOutput) { - Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Output -Action $OnOutput - } - if ($OnError) { - Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Error -Action $OnError - } - if ($OnWarning) { - Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Warning -Action $OnWarning - } + if ($webSocketJob) { + if ($OnOutput) { + Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Output -Action $OnOutput + } + if ($OnError) { + Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Error -Action $OnError + } + if ($OnWarning) { + Register-ObjectEvent @subscriptionSplat -InputObject $webSocketJob.Warning -Action $OnWarning + } + } ) if ($eventSubscriptions) { $variable['EventSubscriptions'] = $eventSubscriptions } - $webSocketConnectTimeout = [DateTime]::Now + $ConnectionTimeout - while (-not $variable['WebSocket'] -and - ([DateTime]::Now -lt $webSocketConnectTimeout)) { - Start-Sleep -Milliseconds 0 - } + if ($webSocketJob) { + $webSocketConnectTimeout = [DateTime]::Now + $ConnectionTimeout + while (-not $variable['WebSocket'] -and + ([DateTime]::Now -lt $webSocketConnectTimeout)) { + Start-Sleep -Milliseconds 0 + } + + foreach ($keyValuePair in $Variable.GetEnumerator()) { + $webSocketJob.psobject.properties.add( + [psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true + ) + } + $webSocketJob.pstypenames.insert(0, 'WebSocketJob') + } - foreach ($keyValuePair in $Variable.GetEnumerator()) { - $webSocketJob.psobject.properties.add( - [psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true - ) - } - $webSocketJob.pstypenames.insert(0, 'WebSocketJob') if ($Watch) { do { $webSocketJob | Receive-Job From 17e9dcb500b4ea3202001995f0e1243cd212f027 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Sun, 5 Jan 2025 01:10:33 +0000 Subject: [PATCH 006/123] feat: Get-WebSocket -Debug ( Fixes #45 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 9f22b2e..22cbded 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2024-12-25" \ No newline at end of file +"2025-01-05" \ No newline at end of file From 830be61fcdabfbf8aa45b458749cd89951cf2761 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 4 Jan 2025 17:11:15 -0800 Subject: [PATCH 007/123] feat: Get-WebSocket -BufferSize 64kb ( Fixes #52 ) --- Commands/Get-WebSocket.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index babde38..df476bf 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -131,7 +131,7 @@ function Get-WebSocket { # The buffer size. Defaults to 16kb. [int] - $BufferSize = 16kb, + $BufferSize = 64kb, # The ScriptBlock to run after connection to a websocket. # This can be useful for making any initial requests. @@ -244,7 +244,7 @@ function Get-WebSocket { } if (-not $BufferSize) { - $BufferSize = 16kb + $BufferSize = 64kb } $CT = [Threading.CancellationToken]::None From 03010ea2bf5c17f0a0f5a37acb4a249ddeb348db Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 5 Jan 2025 13:29:48 -0800 Subject: [PATCH 008/123] feat: Get-WebSocket -QueryParameter ( Fixes #41 ) --- Commands/Get-WebSocket.ps1 | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index df476bf..d57e305 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -30,6 +30,8 @@ function Get-WebSocket { % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} .EXAMPLE websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post + .EXAMPLE + websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug .EXAMPLE # Watch BlueSky, but just the emoji websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail | @@ -112,6 +114,11 @@ function Get-WebSocket { [Alias('Url','Uri')] [uri]$WebSocketUri, + # A collection of query parameters. + # These will be appended onto the `-WebSocketUri`. + [Collections.IDictionary] + $QueryParameter, + # A ScriptBlock that will handle the output of the WebSocket. [ScriptBlock] $Handler, @@ -227,6 +234,7 @@ function Get-WebSocket { param( # By accepting a single parameter containing variables, # we can avoid the need to pass in a large number of parameters. + # we can also modify this dictionary, to provide a way to pass information back. [Collections.IDictionary]$Variable ) @@ -241,7 +249,20 @@ function Get-WebSocket { if (-not $WebSocketUri.Scheme) { $WebSocketUri = [uri]"wss://$WebSocketUri" - } + } + + if ($QueryParameter) { + $WebSocketUri = [uri]"$($webSocketUri)$($WebSocketUri.Query ? '&' : '?')$(@( + foreach ($keyValuePair in $QueryParameter.GetEnumerator()) { + if ($keyValuePair.Value -is [Collections.IList]) { + foreach ($value in $keyValuePair.Value) { + "$($keyValuePair.Key)=$([Web.HttpUtility]::UrlEncode($value).Replace('+', '%20'))" + } + } else { + "$($keyValuePair.Key)=$([Web.HttpUtility]::UrlEncode($keyValuePair.Value).Replace('+', '%20'))" + } + }) -join '&')" + } if (-not $BufferSize) { $BufferSize = 64kb From 7b04e23670939af4f9fcc61166d1c32a26e7a6b5 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 5 Jan 2025 21:30:41 +0000 Subject: [PATCH 009/123] feat: Get-WebSocket -QueryParameter ( Fixes #41 ) --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3270710..9f2d4e2 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,11 @@ websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app. ~~~ #### Get-WebSocket Example 5 +~~~powershell +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug +~~~ + #### Get-WebSocket Example 6 + ~~~powershell # Watch BlueSky, but just the emoji websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail | @@ -88,7 +93,7 @@ websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.f } } ~~~ - #### Get-WebSocket Example 6 + #### Get-WebSocket Example 7 ~~~powershell $emojiPattern = '[\p{IsHighSurrogates}\p{IsLowSurrogates}\p{IsVariationSelectors}\p{IsCombiningHalfMarks}]+)' @@ -102,7 +107,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ~~~ - #### Get-WebSocket Example 7 + #### Get-WebSocket Example 8 ~~~powershell websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch | @@ -113,7 +118,7 @@ websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app. $_.commit.record.embed.external.uri } ~~~ - #### Get-WebSocket Example 8 + #### Get-WebSocket Example 9 ~~~powershell # BlueSky, but just the hashtags @@ -123,7 +128,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ~~~ - #### Get-WebSocket Example 9 + #### Get-WebSocket Example 10 ~~~powershell # BlueSky, but just the hashtags (as links) @@ -137,7 +142,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ~~~ - #### Get-WebSocket Example 10 + #### Get-WebSocket Example 11 ~~~powershell websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{ @@ -149,7 +154,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ~~~ - #### Get-WebSocket Example 11 + #### Get-WebSocket Example 12 ~~~powershell # We can decorate a type returned from a WebSocket, allowing us to add additional properties. From 4bb7a398ac0c0e3483360b716d36f4deb3bae920 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 5 Jan 2025 21:30:49 +0000 Subject: [PATCH 010/123] feat: Get-WebSocket -QueryParameter ( Fixes #41 ) --- docs/Get-WebSocket.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 1cf8c56..c0fd3f7 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -48,6 +48,11 @@ websocket $blueSkySocketUrl -Watch | ```PowerShell websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post ``` +> EXAMPLE 5 + +```PowerShell +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug +``` Watch BlueSky, but just the emoji ```PowerShell @@ -59,7 +64,7 @@ websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.f } } ``` -> EXAMPLE 6 +> EXAMPLE 7 ```PowerShell $emojiPattern = '[\p{IsHighSurrogates}\p{IsLowSurrogates}\p{IsVariationSelectors}\p{IsCombiningHalfMarks}]+)' @@ -73,7 +78,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ``` -> EXAMPLE 7 +> EXAMPLE 8 ```PowerShell websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch | @@ -106,7 +111,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ``` -> EXAMPLE 10 +> EXAMPLE 11 ```PowerShell websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{ @@ -148,6 +153,14 @@ The Uri of the WebSocket to connect to. |-------|--------|--------|---------------------|-----------| |`[Uri]`|false |1 |true (ByPropertyName)|Url
Uri| +#### **QueryParameter** +A collection of query parameters. +These will be appended onto the `-WebSocketUri`. + +|Type |Required|Position|PipelineInput| +|---------------|--------|--------|-------------| +|`[IDictionary]`|false |named |false | + #### **Handler** A ScriptBlock that will handle the output of the WebSocket. @@ -300,5 +313,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [] +Get-WebSocket [[-WebSocketUri] ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [] ``` From c6402f4e4e649127123efe434045ca76b4456d50 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 5 Jan 2025 21:30:49 +0000 Subject: [PATCH 011/123] feat: Get-WebSocket -QueryParameter ( Fixes #41 ) --- docs/_data/Help/Get-WebSocket.json | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/_data/Help/Get-WebSocket.json b/docs/_data/Help/Get-WebSocket.json index 8d58a08..b24a853 100644 --- a/docs/_data/Help/Get-WebSocket.json +++ b/docs/_data/Help/Get-WebSocket.json @@ -52,36 +52,41 @@ }, { "Title": "EXAMPLE 5", + "Markdown": "", + "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug" + }, + { + "Title": "EXAMPLE 6", "Markdown": "Watch BlueSky, but just the emoji", "Code": "websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail |\n Foreach-Object {\n $in = $_\n if ($in.commit.record.text -match '[\\p{IsHighSurrogates}\\p{IsLowSurrogates}]+') {\n Write-Host $matches.0 -NoNewline\n }\n }" }, { - "Title": "EXAMPLE 6", + "Title": "EXAMPLE 7", "Markdown": "", "Code": "$emojiPattern = '[\\p{IsHighSurrogates}\\p{IsLowSurrogates}\\p{IsVariationSelectors}\\p{IsCombiningHalfMarks}]+)'\nwebsocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail |\n Foreach-Object {\n $in = $_\n $spacing = (' ' * (Get-Random -Minimum 0 -Maximum 7))\n if ($in.commit.record.text -match \"(?>(?:$emojiPattern|\\#\\w+)\") {\n $match = $matches.0 \n Write-Host $spacing,$match,$spacing -NoNewline\n }\n }" }, { - "Title": "EXAMPLE 7", + "Title": "EXAMPLE 8", "Markdown": "", "Code": "websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch |\n Where-Object {\n $_.commit.record.embed.'$type' -eq 'app.bsky.embed.external'\n } |\n Foreach-Object {\n $_.commit.record.embed.external.uri\n }" }, { - "Title": "EXAMPLE 8", + "Title": "EXAMPLE 9", "Markdown": "BlueSky, but just the hashtags", "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{\n {$webSocketoutput.commit.record.text -match \"\\#\\w+\"}={\n $matches.0\n } \n}" }, { - "Title": "EXAMPLE 9", + "Title": "EXAMPLE 10", "Markdown": "BlueSky, but just the hashtags (as links)", "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{\n {$webSocketoutput.commit.record.text -match \"\\#\\w+\"}={\n if ($psStyle.FormatHyperlink) {\n $psStyle.FormatHyperlink($matches.0, \"https://bsky.app/search?q=$([Web.HttpUtility]::UrlEncode($matches.0))\")\n } else {\n $matches.0\n }\n }\n}" }, { - "Title": "EXAMPLE 10", + "Title": "EXAMPLE 11", "Markdown": "", "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{\n {$args.commit.record.text -match \"\\#\\w+\"}={\n $matches.0\n }\n {$args.commit.record.text -match '[\\p{IsHighSurrogates}\\p{IsLowSurrogates}]+'}={\n $matches.0\n }\n}" }, { - "Title": "EXAMPLE 11", + "Title": "EXAMPLE 12", "Markdown": "We can decorate a type returned from a WebSocket, allowing us to add additional properties.\nFor example, let's add a `Tags` property to the `app.bsky.feed.post` type.", "Code": "$typeName = 'app.bsky.feed.post'\nUpdate-TypeData -TypeName $typeName -MemberName 'Tags' -MemberType ScriptProperty -Value {\n @($this.commit.record.facets.features.tag)\n} -Force\n\n# Now, let's get 10kb posts ( this should not take too long )\n$somePosts =\n websocket \"wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=$typeName\" -PSTypeName $typeName -Maximum 10kb -Watch\n$somePosts |\n ? Tags |\n Select -ExpandProperty Tags |\n Group |\n Sort Count -Descending |\n Select -First 10" } From f2c26f0873ea6fc8d944dd523f7e646a18ff0f2a Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 5 Jan 2025 21:30:50 +0000 Subject: [PATCH 012/123] feat: Get-WebSocket -QueryParameter ( Fixes #41 ) --- docs/README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index dce8d2f..7c727fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -78,6 +78,11 @@ websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app. ~~~ #### Get-WebSocket Example 5 +~~~powershell +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug +~~~ + #### Get-WebSocket Example 6 + ~~~powershell # Watch BlueSky, but just the emoji websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail | @@ -88,7 +93,7 @@ websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.f } } ~~~ - #### Get-WebSocket Example 6 + #### Get-WebSocket Example 7 ~~~powershell $emojiPattern = '[\p{IsHighSurrogates}\p{IsLowSurrogates}\p{IsVariationSelectors}\p{IsCombiningHalfMarks}]+)' @@ -102,7 +107,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ~~~ - #### Get-WebSocket Example 7 + #### Get-WebSocket Example 8 ~~~powershell websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch | @@ -113,7 +118,7 @@ websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app. $_.commit.record.embed.external.uri } ~~~ - #### Get-WebSocket Example 8 + #### Get-WebSocket Example 9 ~~~powershell # BlueSky, but just the hashtags @@ -123,7 +128,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ~~~ - #### Get-WebSocket Example 9 + #### Get-WebSocket Example 10 ~~~powershell # BlueSky, but just the hashtags (as links) @@ -137,7 +142,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ~~~ - #### Get-WebSocket Example 10 + #### Get-WebSocket Example 11 ~~~powershell websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{ @@ -149,7 +154,7 @@ websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app. } } ~~~ - #### Get-WebSocket Example 11 + #### Get-WebSocket Example 12 ~~~powershell # We can decorate a type returned from a WebSocket, allowing us to add additional properties. From 8e1be05fa922b9a0db60492a3e70097de5af6093 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 5 Jan 2025 14:00:13 -0800 Subject: [PATCH 013/123] feat: Get-WebSocket -Filter ( Fixes #42 ) --- Commands/Get-WebSocket.ps1 | 56 ++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index d57e305..90e00a6 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -165,7 +165,7 @@ function Get-WebSocket { # If set, will output the raw text that comes out of the WebSocket. [Alias('Raw')] [switch] - $RawText, + $RawText, # If set, will output the raw bytes that come out of the WebSocket. [Alias('RawByte','RawBytes','Bytes','Byte')] @@ -175,6 +175,14 @@ function Get-WebSocket { # The subprotocol used by the websocket. If not provided, this will default to `json`. [string] $SubProtocol, + + # One or more filters to apply to the output of the WebSocket. + # These can be strings, regexes, scriptblocks, or commands. + # If they are strings or regexes, they will be applied to the raw text. + # If they are scriptblocks, they will be applied to the deserialized JSON. + # These filters will be run within the WebSocket job. + [PSObject[]] + $Filter, # If set, will watch the output of a WebSocket job for one or more conditions. # The conditions are the keys of the dictionary, and can be a regex, a string, or a scriptblock. @@ -286,8 +294,9 @@ function Get-WebSocket { $Variable.WebSocket = $ws $MessageCount = [long]0 - - while ($true) { + $FilteredCount = [long]0 + + :WebSocketMessageLoop while ($true) { if ($ws.State -ne 'Open') {break } if ($TimeOut -and ([DateTime]::Now - $webSocketStartTime) -gt $TimeOut) { $ws.CloseAsync([Net.WebSockets.WebSocketCloseStatus]::NormalClosure, 'Timeout', $CT).Wait() @@ -308,12 +317,38 @@ function Get-WebSocket { $webSocketMessage = if ($Binary) { $Buf -gt 0 - } elseif ($RawText) { - $OutputEncoding.GetString($Buf, 0, $Buf.Count) } else { - $JS = $OutputEncoding.GetString($Buf, 0, $Buf.Count) - if ([string]::IsNullOrWhitespace($JS)) { continue } - ConvertFrom-Json $JS + $messageString = $OutputEncoding.GetString($Buf, 0, $Buf.Count) + if ($Filter) { + foreach ($fil in $filter) { + if ($fil -is [string] -and $messageString -like "*$fil*") { + $FilteredCount++ + continue WebSocketMessageLoop + } + if ($fil -is [regex] -and $fil.IsMatch($messageString)) { + $FilteredCount++ + continue WebSocketMessageLoop + } + } + } + if ($RawText) { + $messageString + } else { + $MessageObject = ConvertFrom-Json -InputObject $messageString + if ($filter) { + foreach ($fil in $Filter) { + if ($fil -is [ScriptBlock] -or + $fil -is [Management.Automation.CommandInfo] + ) { + if (& $fil $MessageObject) { + $FilteredCount++ + continue WebSocketMessageLoop + } + } + } + } + $MessageObject + } } if ($PSTypeName) { $webSocketMessage.pstypenames.clear() @@ -351,14 +386,17 @@ function Get-WebSocket { } process { + # First, let's pack all of the parameters into a dictionary of variables. foreach ($keyValuePair in $PSBoundParameters.GetEnumerator()) { $Variable[$keyValuePair.Key] = $keyValuePair.Value } + # If `-Debug` was passed, if ($DebugPreference -notin 'SilentlyContinue','Ignore') { + # run the job in the current scope (so we can debug it). . $SocketJob -Variable $Variable return } - $webSocketJob = + $webSocketJob = if ($WebSocketUri) { if (-not $name) { $Name = $WebSocketUri From d7954e2de11d833cee612bdc06004e8aaec1c99d Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 5 Jan 2025 22:01:26 +0000 Subject: [PATCH 014/123] feat: Get-WebSocket -Filter ( Fixes #42 ) --- docs/Get-WebSocket.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index c0fd3f7..219cc08 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -254,6 +254,17 @@ The subprotocol used by the websocket. If not provided, this will default to `j |----------|--------|--------|-------------| |`[String]`|false |named |false | +#### **Filter** +One or more filters to apply to the output of the WebSocket. +These can be strings, regexes, scriptblocks, or commands. +If they are strings or regexes, they will be applied to the raw text. +If they are scriptblocks, they will be applied to the deserialized JSON. +These filters will be run within the WebSocket job. + +|Type |Required|Position|PipelineInput| +|--------------|--------|--------|-------------| +|`[PSObject[]]`|false |named |false | + #### **WatchFor** If set, will watch the output of a WebSocket job for one or more conditions. The conditions are the keys of the dictionary, and can be a regex, a string, or a scriptblock. @@ -313,5 +324,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [] +Get-WebSocket [[-WebSocketUri] ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [] ``` From 305649e793a2ec09aca33b8b850dd7af2349994e Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 5 Jan 2025 15:57:04 -0800 Subject: [PATCH 015/123] feat: Get-WebSocket SupportsPaging ( Fixes #55 ) --- Commands/Get-WebSocket.ps1 | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 90e00a6..0af6835 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -106,7 +106,7 @@ function Get-WebSocket { Sort Count -Descending | Select -First 10 #> - [CmdletBinding(PositionalBinding=$false)] + [CmdletBinding(PositionalBinding=$false,SupportsPaging)] [Alias('WebSocket')] param( # The Uri of the WebSocket to connect to. @@ -295,6 +295,7 @@ function Get-WebSocket { $MessageCount = [long]0 $FilteredCount = [long]0 + $SkipCount = [long]0 :WebSocketMessageLoop while ($true) { if ($ws.State -ne 'Open') {break } @@ -303,7 +304,9 @@ function Get-WebSocket { break } - if ($Maximum -and $MessageCount -ge $Maximum) { + if ($Maximum -and ( + ($MessageCount - $FilteredCount) -ge $Maximum + )) { $ws.CloseAsync([Net.WebSockets.WebSocketCloseStatus]::NormalClosure, 'Maximum messages reached', $CT).Wait() break } @@ -347,7 +350,16 @@ function Get-WebSocket { } } } + if ($Skip -and ($SkipCount -le $Skip)) { + $SkipCount++ + continue WebSocketMessageLoop + } + + $MessageObject + if ($First -and ($MessageCount - $FilteredCount - $SkipCount) -ge $First) { + $Maximum = $first + } } } if ($PSTypeName) { From 17b61d3eb7bbd69eb5b27e1cf4717bf0e9b895de Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 5 Jan 2025 23:58:11 +0000 Subject: [PATCH 016/123] feat: Get-WebSocket SupportsPaging ( Fixes #55 ) --- docs/Get-WebSocket.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 219cc08..13f4348 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -320,9 +320,27 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces |----------------|--------|--------|-------------|-------| |`[RunspacePool]`|false |named |false |Pool | +#### **IncludeTotalCount** + +|Type |Required|Position|PipelineInput| +|----------|--------|--------|-------------| +|`[Switch]`|false |named |false | + +#### **Skip** + +|Type |Required|Position|PipelineInput| +|----------|--------|--------|-------------| +|`[UInt64]`|false |named |false | + +#### **First** + +|Type |Required|Position|PipelineInput| +|----------|--------|--------|-------------| +|`[UInt64]`|false |named |false | + --- ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [] +Get-WebSocket [[-WebSocketUri] ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From b6a90c75c3a5b373e43f7d5327b0caefe4d833fa Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Wed, 8 Jan 2025 23:20:49 -0800 Subject: [PATCH 017/123] feat: Get-WebSocket is aliased to ws and wss ( Fixes #57 ) --- Commands/Get-WebSocket.ps1 | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 0af6835..9433021 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -107,13 +107,20 @@ function Get-WebSocket { Select -First 10 #> [CmdletBinding(PositionalBinding=$false,SupportsPaging)] - [Alias('WebSocket')] + [Alias('WebSocket','ws','wss')] param( - # The Uri of the WebSocket to connect to. + # The WebSocket Uri. [Parameter(Position=0,ValueFromPipelineByPropertyName)] [Alias('Url','Uri')] [uri]$WebSocketUri, + # One or more root urls. + # If these are provided, a WebSocket server will be created with these listener prefixes. + [Parameter(Position=1,ValueFromPipelineByPropertyName)] + [Alias('HostHeader','Host','ServerURL','ListenerPrefix','ListenerPrefixes','ListenerUrl')] + [string[]] + $RootUrl, + # A collection of query parameters. # These will be appended onto the `-WebSocketUri`. [Collections.IDictionary] @@ -238,7 +245,7 @@ function Get-WebSocket { ) begin { - $SocketJob = { + $SocketClientJob = { param( # By accepting a single parameter containing variables, # we can avoid the need to pass in a large number of parameters. @@ -251,6 +258,8 @@ function Get-WebSocket { $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) } + $Variable.JobRunspace = [Runspace]::DefaultRunspace + if ((-not $WebSocketUri)) { throw "No WebSocketUri" } @@ -288,7 +297,7 @@ function Get-WebSocket { $null = $ws.ConnectAsync($WebSocketUri, $CT).Wait() } else { $ws = $WebSocket - } + } $webSocketStartTime = $Variable.WebSocketStartTime = [DateTime]::Now $Variable.WebSocket = $ws @@ -394,7 +403,7 @@ function Get-WebSocket { Write-Error $_ } } - } + } } process { @@ -405,7 +414,7 @@ function Get-WebSocket { # If `-Debug` was passed, if ($DebugPreference -notin 'SilentlyContinue','Ignore') { # run the job in the current scope (so we can debug it). - . $SocketJob -Variable $Variable + . $SocketClientJob -Variable $Variable return } $webSocketJob = @@ -427,13 +436,8 @@ function Get-WebSocket { if ($existingJob) { $existingJob } else { - Start-ThreadJob -ScriptBlock $SocketJob -Name $Name -InitializationScript $InitializationScript -ArgumentList $Variable + Start-ThreadJob -ScriptBlock $SocketClientJob -Name $Name -InitializationScript $InitializationScript -ArgumentList $Variable } - } elseif ($WebSocket) { - if (-not $name) { - $name = "websocket" - } - Start-ThreadJob -ScriptBlock $SocketJob -Name $Name -InitializationScript $InitializationScript -ArgumentList $Variable } $subscriptionSplat = @{ @@ -470,7 +474,7 @@ function Get-WebSocket { [psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true ) } - $webSocketJob.pstypenames.insert(0, 'WebSocketJob') + $webSocketJob.pstypenames.insert(0, 'WebSocket.ThreadJob') } if ($Watch) { From 2ce08cf72f931ea97223f0cf8dc9f5d83231294a Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Thu, 9 Jan 2025 07:21:53 +0000 Subject: [PATCH 018/123] feat: Get-WebSocket is aliased to ws and wss ( Fixes #57 ) --- docs/Get-WebSocket.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 13f4348..5a7a05c 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -147,12 +147,20 @@ $somePosts | ### Parameters #### **WebSocketUri** -The Uri of the WebSocket to connect to. +The WebSocket Uri. |Type |Required|Position|PipelineInput |Aliases | |-------|--------|--------|---------------------|-----------| |`[Uri]`|false |1 |true (ByPropertyName)|Url
Uri| +#### **RootUrl** +One or more root urls. +If these are provided, a WebSocket server will be created with these listener prefixes. + +|Type |Required|Position|PipelineInput |Aliases | +|------------|--------|--------|---------------------|-----------------------------------------------------------------------------------------| +|`[String[]]`|false |2 |true (ByPropertyName)|HostHeader
Host
ServerURL
ListenerPrefix
ListenerPrefixes
ListenerUrl| + #### **QueryParameter** A collection of query parameters. These will be appended onto the `-WebSocketUri`. @@ -342,5 +350,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From 322445974d595dfe0d25458393d02911bf50f09b Mon Sep 17 00:00:00 2001 From: James Brundage Date: Thu, 9 Jan 2025 07:21:56 +0000 Subject: [PATCH 019/123] feat: Get-WebSocket is aliased to ws and wss ( Fixes #57 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 22cbded..3af8533 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-05" \ No newline at end of file +"2025-01-09" \ No newline at end of file From 0a31f20356805e879cc0aadca9b9c30ffc7cb143 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Wed, 8 Jan 2025 23:32:37 -0800 Subject: [PATCH 020/123] feat: Get-WebSocket is aliased to ws and wss ( Fixes #57 ) Exporting aliases --- WebSocket.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebSocket.psd1 b/WebSocket.psd1 index 4eb77b6..fe667db 100644 --- a/WebSocket.psd1 +++ b/WebSocket.psd1 @@ -7,7 +7,7 @@ Copyright = '2024 StartAutomating' Description = 'Work with WebSockets in PowerShell' FunctionsToExport = @('Get-WebSocket') - AliasesToExport = @('WebSocket') + AliasesToExport = @('WebSocket','ws','wss') PrivateData = @{ PSData = @{ Tags = @('WebSocket', 'WebSockets', 'Networking', 'Web') From 86434eba11f99b0c42ee3a25618b97c09a65d29c Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Fri, 10 Jan 2025 17:54:56 -0800 Subject: [PATCH 021/123] feat: Get-WebSocket -RootURL/-HostHeader ( Fixes #47 ) --- Commands/Get-WebSocket.ps1 | 137 ++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 3 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 9433021..9ba4738 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -403,6 +403,109 @@ function Get-WebSocket { Write-Error $_ } } + } + $SocketServerJob = { + <# + .SYNOPSIS + A fairly simple WebSocket server + .DESCRIPTION + A fairly simple WebSocket server + #> + param( + # By accepting a single parameter containing variables, + # we can avoid the need to pass in a large number of parameters. + # we can also modify this dictionary, to provide a way to pass information back. + [Collections.IDictionary]$Variable + ) + + # Take every every `-Variable` passed in and define it within the job + foreach ($keyValue in $variable.GetEnumerator()) { + $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) + } + + # If there's no listener, create one. + if (-not $httpListener) { + $httpListener = $variable['HttpListener'] = [Net.HttpListener]::new() + } + + # If the listener doesn't have a lookup table for SocketRequests, create one. + if (-not $httpListener.SocketRequests) { + $httpListener.psobject.properties.add( + [psnoteproperty]::new('SocketRequests', [Ordered]@{}), $true) + } + + # If the listener isn't listening, start it. + if (-not $httpListener.IsListening) { $httpListener.Start() } + + # While the listener is listening, + while ($httpListener.IsListening) { + # get the context asynchronously. + $contextAsync = $httpListener.GetContextAsync() + # and wait for it to complete. + while (-not ($contextAsync.IsCompleted -or $contextAsync.IsFaulted -or $contextAsync.IsCanceled)) { + # while this is going on, other events can be processed, and CTRL-C can exit. + } + # If async method fails, + if ($contextAsync.IsFaulted) { + # write an error and continue. + Write-Error -Exception $contextAsync.Exception -Category ProtocolError + continue + } + # Get the context async result. + # The context is basically the next request and response in the queue. + $context = $(try { $contextAsync.Result } catch { $_ }) + $RequestedUrl = $context.Request.Url + # Favicons are literally outdated, but they're still requested. + if ($RequestedUrl -match '/favicon.ico$') { + # by returning a 404 for them, we can make the browser stop asking. + $context.Response.StatusCode = 404 + $context.Response.Close() + continue + } + # Now, for the fun part. + # We turn request into a PowerShell events. + # Each event will have the source identifier of the request scheme + $eventIdentifier = "$($context.Request.Url.Scheme)://" + # and by default it will pass a message containing the context. + $messageData = [Ordered]@{Url = $context.Request.Url;Context = $context} + + # HttpListeners are quite nice, especially when it comes to websocket upgrades. + # If the request is a websocket request + if ($context.Request.IsWebSocketRequest) { + # we will change the event identifier to a websocket scheme. + $eventIdentifier = $eventIdentifier -replace '^http', 'ws' + # and call the `AcceptWebSocketAsync` method to upgrade the connection. + $acceptWebSocket = $context.AcceptWebSocketAsync('json') + # Once again, we'll use a tight loop to wait for the upgrade to complete or fail. + while (-not ($acceptWebSocket.IsCompleted -or $acceptWebSocket.IsFaulted -or $acceptWebSocket.IsCanceled)) { } + # and if it fails, + if ($acceptWebSocket.IsFaulted) { + # we will write an error and continue. + Write-Error -Exception $acceptWebSocket.Exception -Category ProtocolError + continue + } + # If it succeeds, capture the result. + $webSocketResult = try { $acceptWebSocket.Result } catch { $_ } + # and add it to the SocketRequests lookup table, using the request trace identifier as the key. + $httpListener.SocketRequests[$context.Request.RequestTraceIdentifier] = $webSocketResult + # and add the websocketcontext result to the message data. + $messageData["WebSocketContext"] = $webSocketResult + # also add the websocket result to the message data, since many might not exactly know what a "WebSocketContext" is. + $messageData["WebSocket"] = $webSocketResult.WebSocket + } + + # Now, we generate the event. + $generateEventArguments = @( + $eventIdentifier, + $httpListener, + @($context) + $messageData + ) + # Get a pointer to the GenerateEvent method (we'll want this later) + if ($MainRunspace.Events.GenerateEvent) { + $MainRunspace.Events.GenerateEvent.Invoke($generateEventArguments) + } + } } } @@ -411,6 +514,34 @@ function Get-WebSocket { foreach ($keyValuePair in $PSBoundParameters.GetEnumerator()) { $Variable[$keyValuePair.Key] = $keyValuePair.Value } + + $Variable['MainRunspace'] = [Runspace]::DefaultRunspace + + # If we're going to be listening for HTTP requests, run a thread job for the server. + if ($RootUrl) { + + $httpListener = $variable['HttpListener'] = [Net.HttpListener]::new() + foreach ($rootUrl in $RootUrl) { + if ($rootUrl -match '^https?://') { + $httpListener.Prefixes.Add($rootUrl) + } else { + $httpListener.Prefixes.Add("http://$rootUrl/") + $httpListener.Prefixes.Add("https://$rootUrl/") + } + } + $httpListener.Start() + $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -InitializationScript $InitializationScript -ArgumentList $Variable + + if ($httpListenerJob) { + foreach ($keyValuePair in $Variable.GetEnumerator()) { + $httpListenerJob.psobject.properties.add( + [psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true + ) + } + $httpListenerJob + } + } + # If `-Debug` was passed, if ($DebugPreference -notin 'SilentlyContinue','Ignore') { # run the job in the current scope (so we can debug it). @@ -477,7 +608,7 @@ function Get-WebSocket { $webSocketJob.pstypenames.insert(0, 'WebSocket.ThreadJob') } - if ($Watch) { + if ($Watch -and $webSocketJob) { do { $webSocketJob | Receive-Job Start-Sleep -Milliseconds ( @@ -485,7 +616,7 @@ function Get-WebSocket { ) } while ($webSocketJob.State -in 'Running','NotStarted') } - elseif ($WatchFor) { + elseif ($WatchFor -and $webSocketJob) { . { do { $webSocketJob | Receive-Job @@ -512,7 +643,7 @@ function Get-WebSocket { } } } - else { + elseif ($webSocketJob) { $webSocketJob } } From 8a20eca1209ef5e0b04d166a1ba706f6a14d4071 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Sat, 11 Jan 2025 01:56:26 +0000 Subject: [PATCH 022/123] feat: Get-WebSocket -RootURL/-HostHeader ( Fixes #47 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 3af8533..33eeb6f 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-09" \ No newline at end of file +"2025-01-11" \ No newline at end of file From 87b29a5eb0e36b07e2fb1b1fa7bd3e5b15f73d03 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 11 Jan 2025 23:47:01 -0800 Subject: [PATCH 023/123] feat: Get-WebSocket returns socket or listener jobs ( Fixes #68 ) Also, adding some aliases --- Commands/Get-WebSocket.ps1 | 272 +++++++++++++++++++++++++++++++++---- 1 file changed, 245 insertions(+), 27 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 9ba4738..23a8f2f 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -111,16 +111,30 @@ function Get-WebSocket { param( # The WebSocket Uri. [Parameter(Position=0,ValueFromPipelineByPropertyName)] - [Alias('Url','Uri')] - [uri]$WebSocketUri, + [Alias('Url','Uri','WebSocketUrl','WebSocketUri')] + [uri] + $WebSocketUri, # One or more root urls. # If these are provided, a WebSocket server will be created with these listener prefixes. [Parameter(Position=1,ValueFromPipelineByPropertyName)] - [Alias('HostHeader','Host','ServerURL','ListenerPrefix','ListenerPrefixes','ListenerUrl')] + [Alias('HostHeader','Host','CNAME','ServerURL','ListenerPrefix','ListenerPrefixes','ListenerUrl')] [string[]] $RootUrl, + # A route table for all requests. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Routes','RouteTable','WebHook','WebHooks')] + [Collections.IDictionary] + $Route, + + # The Default HTML. + # This will be displayed when visiting the root url. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('DefaultHTML','Home','Index','IndexHTML','DefaultPage')] + [string] + $HTML, + # A collection of query parameters. # These will be appended onto the `-WebSocketUri`. [Collections.IDictionary] @@ -421,7 +435,33 @@ function Get-WebSocket { # Take every every `-Variable` passed in and define it within the job foreach ($keyValue in $variable.GetEnumerator()) { $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) - } + } + + # If we have routes, we will cache all of their possible parameters now + if ($route.Count) { + # We want to keep the parameter sets + $routeParameterSets = [Ordered]@{} + # and the metadata about parameters. + $routeParameters = [Ordered]@{} + + # For each key and value in the route table, we will try to get the command info for the value. + foreach ($routePair in $route.GetEnumerator()) { + $routeToCmd = + # If the value is a scriptblock + if ($routePair.Value -is [ScriptBlock]) { + # we have to create a temporary function + $function:TempFunction = $routePair.Value + # and get that function. + $ExecutionContext.SessionState.InvokeCommand.GetCommand('TempFunction', 'Function') + } elseif ($routePair.Value -is [Management.Automation.CommandInfo]) { + $routePair.Value + } + if ($routeToCmd) { + $routeParameterSets[$routePair.Name] = $routeToCmd.ParametersSets + $routeParameters[$routePair.Name] = $routeToCmd.Parameters + } + } + } # If there's no listener, create one. if (-not $httpListener) { @@ -454,7 +494,12 @@ function Get-WebSocket { # Get the context async result. # The context is basically the next request and response in the queue. $context = $(try { $contextAsync.Result } catch { $_ }) - $RequestedUrl = $context.Request.Url + + # yield the context immediately, in case anything is watching the output of this job + $context + + $Request, $response = $context.Request, $context.Response + $RequestedUrl = $Request.Url # Favicons are literally outdated, but they're still requested. if ($RequestedUrl -match '/favicon.ico$') { # by returning a 404 for them, we can make the browser stop asking. @@ -464,16 +509,18 @@ function Get-WebSocket { } # Now, for the fun part. # We turn request into a PowerShell events. - # Each event will have the source identifier of the request scheme - $eventIdentifier = "$($context.Request.Url.Scheme)://" + # The protocol is the scheme of the request url. + $Protocol = $RequestedUrl.Scheme + # Each event will have the source identifier of the protocol, followed by :// + $eventIdentifier = "$($Protocol)://" # and by default it will pass a message containing the context. - $messageData = [Ordered]@{Url = $context.Request.Url;Context = $context} + $messageData = [Ordered]@{Protocol = $protocol; Url = $context.Request.Url;Context = $context} # HttpListeners are quite nice, especially when it comes to websocket upgrades. # If the request is a websocket request - if ($context.Request.IsWebSocketRequest) { + if ($Request.IsWebSocketRequest) { # we will change the event identifier to a websocket scheme. - $eventIdentifier = $eventIdentifier -replace '^http', 'ws' + $eventIdentifier = $eventIdentifier -replace '^http', 'ws' # and call the `AcceptWebSocketAsync` method to upgrade the connection. $acceptWebSocket = $context.AcceptWebSocketAsync('json') # Once again, we'll use a tight loop to wait for the upgrade to complete or fail. @@ -486,18 +533,26 @@ function Get-WebSocket { } # If it succeeds, capture the result. $webSocketResult = try { $acceptWebSocket.Result } catch { $_ } - # and add it to the SocketRequests lookup table, using the request trace identifier as the key. - $httpListener.SocketRequests[$context.Request.RequestTraceIdentifier] = $webSocketResult - # and add the websocketcontext result to the message data. - $messageData["WebSocketContext"] = $webSocketResult - # also add the websocket result to the message data, since many might not exactly know what a "WebSocketContext" is. - $messageData["WebSocket"] = $webSocketResult.WebSocket + + # If the websocket is open + if ($webSocketResult.WebSocket.State -eq 'open') { + # we have switched protocols! + $Protocol = $requestedUrl.Scheme -replace '^http', 'ws' + + # Now add the result it to the SocketRequests lookup table, using the request trace identifier as the key. + $httpListener.SocketRequests[$context.Request.RequestTraceIdentifier] = $webSocketResult + # and add the websocketcontext result to the message data. + $messageData["WebSocketContext"] = $webSocketResult + # also add the websocket result to the message data, + # since many might not exactly know what a "WebSocketContext" is. + $messageData["WebSocket"] = $webSocketResult.WebSocket + } } # Now, we generate the event. $generateEventArguments = @( - $eventIdentifier, - $httpListener, + $eventIdentifier, + $httpListener, @($context) $messageData ) @@ -505,11 +560,169 @@ function Get-WebSocket { if ($MainRunspace.Events.GenerateEvent) { $MainRunspace.Events.GenerateEvent.Invoke($generateEventArguments) } + + # Everything below this point is for HTTP requests. + if ($protocol -notmatch '^http') { + continue # so if we're already a websocket, we will skip the rest of this code. + } + + $routedTo = $null + $routeKey = $null + # If we have routes, we will try to find a route that matches the request. + if ($route.Count) { + $routeTable = $route + $potentialRouteKeys = @( + $request.Url.AbsolutePath, + ($request.Url.AbsolutePath -replace '/$'), + "$($request.HttpMethod) $($request.Url.AbsolutePath)", + "$($request.HttpMethod) $($request.Url.AbsolutePath -replace '/$')" + "$($request.HttpMethod) $($request.Url.LocalPath)", + "$($request.HttpMethod) $($request.Url.LocalPath -replace '/$')" + ) + $routedTo = foreach ($potentialKey in $potentialRouteKeys) { + if ($routeTable[$potentialKey]) { + $routeTable[$potentialKey] + $routeKey = $potentialKey + break + } + } + } + + if (-not $routedTo -and $html) { + $routedTo = + # If the content is already html, we will use it as is. + if ($html -match '\" + "" + # and apply the site header. + $SiteHeader + "" + "" + $html + "" + "" + ) -join [Environment]::NewLine + } + } + + # If we routed to a string, we will close the response with the string. + if ($routedTo -is [string]) { + $response.Close($OutputEncoding.GetBytes($routedTo), $true) + continue + } + + # If we've routed to is a byte array, we will close the response with the byte array. + if ($routedTo -is [byte[]]) { + $response.Close($routedTo, $true) + continue + } + + # If we routed to a script block or command, we will try to execute it. + if ($routedTo -is [ScriptBlock] -or + $routedTo -is [Management.Automation.CommandInfo]) { + $routeSplat = [Ordered]@{} + + # If the command had a `-Request` parameter, we will pass the request object. + if ($routeParameters -and $routeParameters[$routeKey].Request) { + $routeSplat['Request'] = $request + } + # If the command had a `-Response` parameter, we will pass the response object. + if ($routeParameters -and $routeParameters[$routeKey].Response) { + $routeSplat['Response'] = $response + } + + # If the request has a query string, we will parse it and pass the values to the command. + if ($request.Url.QueryString) { + $parsedQuery = [Web.HttpUtility]::ParseQueryString($request.Url.QueryString) + foreach ($parsedQueryKey in $parsedQuery.Keys) { + if ($routeParameters[$routeKey][$parsedQueryKey]) { + $routeSplat[$parsedQueryKey] = $parsedQuery[$parsedQueryKey] + } + } + } + # If the request has a content type of json, we will parse the json and pass the values to the command. + if ($request.ContentType -match '^(?>application|text)/json') { + $streamReader = [IO.StreamReader]::new($request.InputStream) + $json = $streamReader.ReadToEnd() + $jsonHashtable = ConvertFrom-Json -InputObject $json -AsHashtable + foreach ($keyValuePair in $jsonHashtable.GetEnumerator()) { + if ($routeParameters[$routeKey][$keyValuePair.Key]) { + $routeSplat[$keyValuePair.Key] = $keyValuePair.Value + } + } + $streamReader.Close() + $streamReader.Dispose() + } + + # If the request has a content type of form-urlencoded, we will parse the form and pass the values to the command. + if ($request.ContentType -eq 'application/x-www-form-urlencoded') { + $streamReader = [IO.StreamReader]::new($request.InputStream) + $formData = [Web.HttpUtility]::ParseQueryString($streamReader.ReadToEnd()) + foreach ($formKey in $formData.Keys) { + if ($routeParameters[$routeKey][$formKey]) { + $routeSplat[$formKey] = $form[$formKey] + } + } + $streamReader.Close() + $streamReader.Dispose() + } + + # We will execute the command and get the output. + $routeOutput = . $routedTo @routeSplat + + # If the output is a string, we will close the response with the string. + if ($routeOutput -is [string]) + { + $response.Close($OutputEncoding.GetBytes($routeOutput), $true) + continue + } + # If the output is a byte array, we will close the response with the byte array. + elseif ($routeOutput -is [byte[]]) + { + $response.Close($routeOutput, $true) + continue + } + # If the response is an array, write the responses out one at a time. + # (note: this will likely be changed in the future) + elseif ($routeOutput -is [object[]]) { + foreach ($routeOut in $routeOutput) { + if ($routeOut -is [string]) { + $routeOut = $OutputEncoding.GetBytes($routeOut) + } + if ($routeOut -is [byte[]]) { + $response.OutputStream.Write($routeOut, 0, $routeOut.Length) + } + } + $response.Close() + } + else { + # If the response was an object, we will convert it to json and close the response with the json. + $responseJson = ConvertTo-Json -InputObject $routeOutput -Depth 3 + $response.ContentType = 'application/json' + $response.Close($OutputEncoding.GetBytes($responseJson), $true) + } + } } } } - process { + process { + if ((-not $WebSocketUri) -and (-not $RootUrl)) { + $socketAndListenerJobs = + foreach ($job in Get-Job) { + if ( + $Job.WebSocket -is [Net.WebSockets.ClientWebSocket] -or + $Job.HttpListener -is [Net.HttpListener] + ) { + $job + } + } + $socketAndListenerJobs + } # First, let's pack all of the parameters into a dictionary of variables. foreach ($keyValuePair in $PSBoundParameters.GetEnumerator()) { $Variable[$keyValuePair.Key] = $keyValuePair.Value @@ -520,17 +733,22 @@ function Get-WebSocket { # If we're going to be listening for HTTP requests, run a thread job for the server. if ($RootUrl) { - $httpListener = $variable['HttpListener'] = [Net.HttpListener]::new() - foreach ($rootUrl in $RootUrl) { - if ($rootUrl -match '^https?://') { - $httpListener.Prefixes.Add($rootUrl) + $variable['HttpListener'] = $httpListener = [Net.HttpListener]::new() + foreach ($potentialPrefix in $RootUrl) { + if ($potentialPrefix -match '^https?://') { + $httpListener.Prefixes.Add($potentialPrefix) } else { - $httpListener.Prefixes.Add("http://$rootUrl/") - $httpListener.Prefixes.Add("https://$rootUrl/") + $httpListener.Prefixes.Add("http://$potentialPrefix/") + $httpListener.Prefixes.Add("https://$potentialPrefix/") } } $httpListener.Start() - $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -InitializationScript $InitializationScript -ArgumentList $Variable + + if ($DebugPreference -notin 'SilentlyContinue','Ignore') { + . $SocketServerJob -Variable $Variable + } else { + $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -InitializationScript $InitializationScript -ArgumentList $Variable + } if ($httpListenerJob) { foreach ($keyValuePair in $Variable.GetEnumerator()) { @@ -593,7 +811,7 @@ function Get-WebSocket { $variable['EventSubscriptions'] = $eventSubscriptions } - if ($webSocketJob) { + if ($webSocketJob -and -not $webSocketJob.WebSocket) { $webSocketConnectTimeout = [DateTime]::Now + $ConnectionTimeout while (-not $variable['WebSocket'] -and ([DateTime]::Now -lt $webSocketConnectTimeout)) { From 3351f6148d72333c41c1a62a085d963b73dc97cc Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 12 Jan 2025 13:37:25 -0800 Subject: [PATCH 024/123] fix: Get-WebSocket alias fix Removing duplicate --- Commands/Get-WebSocket.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 23a8f2f..50d1c30 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -111,7 +111,7 @@ function Get-WebSocket { param( # The WebSocket Uri. [Parameter(Position=0,ValueFromPipelineByPropertyName)] - [Alias('Url','Uri','WebSocketUrl','WebSocketUri')] + [Alias('Url','Uri','WebSocketUrl')] [uri] $WebSocketUri, From 1f06a7d78016b368c6a6812fcc5eb2ccb049bf70 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 12 Jan 2025 21:38:42 +0000 Subject: [PATCH 025/123] fix: Get-WebSocket alias fix Removing duplicate --- docs/Get-WebSocket.md | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 5a7a05c..3e1dd45 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -149,17 +149,32 @@ $somePosts | #### **WebSocketUri** The WebSocket Uri. -|Type |Required|Position|PipelineInput |Aliases | -|-------|--------|--------|---------------------|-----------| -|`[Uri]`|false |1 |true (ByPropertyName)|Url
Uri| +|Type |Required|Position|PipelineInput |Aliases | +|-------|--------|--------|---------------------|----------------------------| +|`[Uri]`|false |1 |true (ByPropertyName)|Url
Uri
WebSocketUrl| #### **RootUrl** One or more root urls. If these are provided, a WebSocket server will be created with these listener prefixes. -|Type |Required|Position|PipelineInput |Aliases | -|------------|--------|--------|---------------------|-----------------------------------------------------------------------------------------| -|`[String[]]`|false |2 |true (ByPropertyName)|HostHeader
Host
ServerURL
ListenerPrefix
ListenerPrefixes
ListenerUrl| +|Type |Required|Position|PipelineInput |Aliases | +|------------|--------|--------|---------------------|---------------------------------------------------------------------------------------------------| +|`[String[]]`|false |2 |true (ByPropertyName)|HostHeader
Host
CNAME
ServerURL
ListenerPrefix
ListenerPrefixes
ListenerUrl| + +#### **Route** +A route table for all requests. + +|Type |Required|Position|PipelineInput |Aliases | +|---------------|--------|--------|---------------------|----------------------------------------------| +|`[IDictionary]`|false |named |true (ByPropertyName)|Routes
RouteTable
WebHook
WebHooks| + +#### **HTML** +The Default HTML. +This will be displayed when visiting the root url. + +|Type |Required|Position|PipelineInput |Aliases | +|----------|--------|--------|---------------------|------------------------------------------------------------| +|`[String]`|false |named |true (ByPropertyName)|DefaultHTML
Home
Index
IndexHTML
DefaultPage| #### **QueryParameter** A collection of query parameters. @@ -350,5 +365,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From a88cb3bab63c5d87d5c182d6092b17cca03ef88e Mon Sep 17 00:00:00 2001 From: James Brundage Date: Sun, 12 Jan 2025 21:38:46 +0000 Subject: [PATCH 026/123] fix: Get-WebSocket alias fix Removing duplicate --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 33eeb6f..2921e09 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-11" \ No newline at end of file +"2025-01-12" \ No newline at end of file From c54b4d624eead831767908cf57e35aada192e5a6 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 12 Jan 2025 13:40:43 -0800 Subject: [PATCH 027/123] feat: Get-WebSocket -Force ( Fixes #58 ) --- Commands/Get-WebSocket.ps1 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 50d1c30..ef710be 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -193,6 +193,10 @@ function Get-WebSocket { [switch] $Binary, + # If set, will force a new job to be created, rather than reusing an existing job. + [switch] + $Force, + # The subprotocol used by the websocket. If not provided, this will default to `json`. [string] $SubProtocol, @@ -437,6 +441,8 @@ function Get-WebSocket { $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) } + $Variable.JobRunspace = [Runspace]::DefaultRunspace + # If we have routes, we will cache all of their possible parameters now if ($route.Count) { # We want to keep the parameter sets @@ -782,7 +788,7 @@ function Get-WebSocket { } } - if ($existingJob) { + if ($existingJob -and -not $Force) { $existingJob } else { Start-ThreadJob -ScriptBlock $SocketClientJob -Name $Name -InitializationScript $InitializationScript -ArgumentList $Variable From f216e421968c23d80d7ca58b0bd1e23b9548c01f Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 12 Jan 2025 21:41:52 +0000 Subject: [PATCH 028/123] feat: Get-WebSocket -Force ( Fixes #58 ) --- docs/Get-WebSocket.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 3e1dd45..9fe7103 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -270,6 +270,13 @@ If set, will output the raw bytes that come out of the WebSocket. |----------|--------|--------|-------------|---------------------------------------| |`[Switch]`|false |named |false |RawByte
RawBytes
Bytes
Byte| +#### **Force** +If set, will force a new job to be created, rather than reusing an existing job. + +|Type |Required|Position|PipelineInput| +|----------|--------|--------|-------------| +|`[Switch]`|false |named |false | + #### **SubProtocol** The subprotocol used by the websocket. If not provided, this will default to `json`. @@ -365,5 +372,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From 4926a8338760b66a3808d49dba2c78f362a9993e Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 12 Jan 2025 13:57:35 -0800 Subject: [PATCH 029/123] feat: Get-WebSocket -Force ( Fixes #58 ) Allowing -Force on HttpListener jobs --- Commands/Get-WebSocket.ps1 | 48 +++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index ef710be..0dd7ac5 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -191,7 +191,7 @@ function Get-WebSocket { # If set, will output the raw bytes that come out of the WebSocket. [Alias('RawByte','RawBytes','Bytes','Byte')] [switch] - $Binary, + $Binary, # If set, will force a new job to be created, rather than reusing an existing job. [switch] @@ -383,7 +383,7 @@ function Get-WebSocket { } - $MessageObject + $MessageObject if ($First -and ($MessageCount - $FilteredCount - $SkipCount) -ge $First) { $Maximum = $first } @@ -739,22 +739,42 @@ function Get-WebSocket { # If we're going to be listening for HTTP requests, run a thread job for the server. if ($RootUrl) { - $variable['HttpListener'] = $httpListener = [Net.HttpListener]::new() - foreach ($potentialPrefix in $RootUrl) { - if ($potentialPrefix -match '^https?://') { - $httpListener.Prefixes.Add($potentialPrefix) - } else { - $httpListener.Prefixes.Add("http://$potentialPrefix/") - $httpListener.Prefixes.Add("https://$potentialPrefix/") + if (-not $Name) { + $Name = "$($RootUrl -join '|')" + } + + $existingJob = foreach ($jobWithThisName in (Get-Job -Name $Name -ErrorAction Ignore)) { + if ( + $jobWithThisName.State -in 'Running','NotStarted' -and + $jobWithThisName.HttpListener -is [Net.HttpListener] + ) { + $jobWithThisName + break } } - $httpListener.Start() + + if ((-not $existingJob) -or $Force) { + $variable['HttpListener'] = $httpListener = [Net.HttpListener]::new() + foreach ($potentialPrefix in $RootUrl) { + if ($potentialPrefix -match '^https?://') { + $httpListener.Prefixes.Add($potentialPrefix) + } else { + $httpListener.Prefixes.Add("http://$potentialPrefix/") + $httpListener.Prefixes.Add("https://$potentialPrefix/") + } + } + $httpListener.Start() + } if ($DebugPreference -notin 'SilentlyContinue','Ignore') { . $SocketServerJob -Variable $Variable - } else { - $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -InitializationScript $InitializationScript -ArgumentList $Variable - } + } else { + if ($existingJob -and -not $Force) { + $httpListenerJob = $existingJob + } else { + $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -InitializationScript $InitializationScript -ArgumentList $Variable + } + } if ($httpListenerJob) { foreach ($keyValuePair in $Variable.GetEnumerator()) { @@ -763,7 +783,7 @@ function Get-WebSocket { ) } $httpListenerJob - } + } } # If `-Debug` was passed, From df13f4ef07a177b138a14f7bbbdce9a8d0157045 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 12 Jan 2025 14:12:44 -0800 Subject: [PATCH 030/123] feat: Get-WebSocket -Broadcast ( Fixes #39 ) --- Commands/Get-WebSocket.ps1 | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 0dd7ac5..557de17 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -161,6 +161,9 @@ function Get-WebSocket { [int] $BufferSize = 64kb, + [PSObject] + $Broadcast, + # The ScriptBlock to run after connection to a websocket. # This can be useful for making any initial requests. [ScriptBlock] @@ -771,6 +774,7 @@ function Get-WebSocket { } else { if ($existingJob -and -not $Force) { $httpListenerJob = $existingJob + $httpListener = $existingJob.HttpListener } else { $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -InitializationScript $InitializationScript -ArgumentList $Variable } @@ -783,7 +787,33 @@ function Get-WebSocket { ) } $httpListenerJob - } + } + + if ($Broadcast) { + if (-not $httpListener.SocketRequests) { + Write-Warning "No WebSocket connections to broadcast to." + } else { + if ($broadcast -is [byte[]]) { + $broadcast = [ArraySegment[byte]]::new($broadcast) + } + if ($broadcast -is [System.ArraySegment[byte]]) { + foreach ($socketRequest in $httpListener.SocketRequests.Values) { + $socketRequest.WebSocket.SendAsync($broadcast, 'Binary', 'EndOfMessage', [Threading.CancellationToken]::None) + } + } + else { + foreach ($broadcastItem in $Broadcast) { + $broadcastJson = ConvertTo-Json -InputObject $broadcastItem + $broadcastJsonBytes = $OutputEncoding.GetBytes($broadcastJson) + $broadcastSegment = [ArraySegment[byte]]::new($broadcastJsonBytes) + foreach ($socketRequest in $httpListener.SocketRequests.Values) { + $socketRequest.WebSocket.SendAsync($broadcastSegment, 'Text', 'EndOfMessage', [Threading.CancellationToken]::None) + } + } + } + } + + } } # If `-Debug` was passed, From d88e12b0f1edcdc4d802aaee2e8576a67ac8e803 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 12 Jan 2025 22:13:56 +0000 Subject: [PATCH 031/123] feat: Get-WebSocket -Broadcast ( Fixes #39 ) --- docs/Get-WebSocket.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 9fe7103..bb6441e 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -220,6 +220,12 @@ The buffer size. Defaults to 16kb. |---------|--------|--------|-------------| |`[Int32]`|false |named |false | +#### **Broadcast** + +|Type |Required|Position|PipelineInput| +|------------|--------|--------|-------------| +|`[PSObject]`|false |named |false | + #### **OnConnect** The ScriptBlock to run after connection to a websocket. This can be useful for making any initial requests. @@ -372,5 +378,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From 9e5b80b7da8a11e67ad61c94a2e92c600d6b1e73 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 12 Jan 2025 16:57:37 -0800 Subject: [PATCH 032/123] feat: Get-WebSocket -ThrottleLimit ( Fixes #63 ) --- Commands/Get-WebSocket.ps1 | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 557de17..2ce006d 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -248,6 +248,10 @@ function Get-WebSocket { [long] $Maximum, + # The throttle limit used when creating background jobs. + [int] + $ThrottleLimit = 64, + # The maximum time to wait for a connection to be established. # By default, this is 7 seconds. [TimeSpan] @@ -274,12 +278,12 @@ function Get-WebSocket { [Collections.IDictionary]$Variable ) + $Variable.JobRunspace = [Runspace]::DefaultRunspace + # Take every every `-Variable` passed in and define it within the job foreach ($keyValue in $variable.GetEnumerator()) { $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) - } - - $Variable.JobRunspace = [Runspace]::DefaultRunspace + } if ((-not $WebSocketUri)) { throw "No WebSocketUri" @@ -384,8 +388,7 @@ function Get-WebSocket { $SkipCount++ continue WebSocketMessageLoop } - - + $MessageObject if ($First -and ($MessageCount - $FilteredCount - $SkipCount) -ge $First) { $Maximum = $first @@ -444,7 +447,7 @@ function Get-WebSocket { $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) } - $Variable.JobRunspace = [Runspace]::DefaultRunspace + $Variable['JobRunspace'] = [Runspace]::DefaultRunspace # If we have routes, we will cache all of their possible parameters now if ($route.Count) { @@ -485,6 +488,7 @@ function Get-WebSocket { # If the listener isn't listening, start it. if (-not $httpListener.IsListening) { $httpListener.Start() } + $httpListener.psobject.properties.add([psnoteproperty]::new('JobVariable',$Variable), $true) # While the listener is listening, while ($httpListener.IsListening) { @@ -738,6 +742,10 @@ function Get-WebSocket { } $Variable['MainRunspace'] = [Runspace]::DefaultRunspace + $StartThreadJobSplat = [Ordered]@{ + InitializationScript = $InitializationScript + ThrottleLimit = $ThrottleLimit + } # If we're going to be listening for HTTP requests, run a thread job for the server. if ($RootUrl) { @@ -776,11 +784,17 @@ function Get-WebSocket { $httpListenerJob = $existingJob $httpListener = $existingJob.HttpListener } else { - $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -InitializationScript $InitializationScript -ArgumentList $Variable - } + $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -ArgumentList $Variable @StartThreadJobSplat + } } + # If we have a listener job if ($httpListenerJob) { + # and the job has not started + if ($httpListenerJob.JobStateInfo.State -eq 'NotStarted') { + # sleep for no time (this will allow the job to start) + Start-Sleep -Milliseconds 0 + } foreach ($keyValuePair in $Variable.GetEnumerator()) { $httpListenerJob.psobject.properties.add( [psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true @@ -841,8 +855,8 @@ function Get-WebSocket { if ($existingJob -and -not $Force) { $existingJob } else { - Start-ThreadJob -ScriptBlock $SocketClientJob -Name $Name -InitializationScript $InitializationScript -ArgumentList $Variable - } + Start-ThreadJob -ScriptBlock $SocketClientJob -Name $Name -ArgumentList $Variable @StartThreadJobSplat + } } $subscriptionSplat = @{ From 3a30fe8c44f93984fec9ed6a948ec4b61d6916e5 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Mon, 13 Jan 2025 00:58:40 +0000 Subject: [PATCH 033/123] feat: Get-WebSocket -ThrottleLimit ( Fixes #63 ) --- docs/Get-WebSocket.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index bb6441e..e8e3a80 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -332,6 +332,13 @@ The maximum number of messages to receive before closing the WebSocket. |---------|--------|--------|-------------| |`[Int64]`|false |named |false | +#### **ThrottleLimit** +The throttle limit used when creating background jobs. + +|Type |Required|Position|PipelineInput| +|---------|--------|--------|-------------| +|`[Int32]`|false |named |false | + #### **ConnectionTimeout** The maximum time to wait for a connection to be established. By default, this is 7 seconds. @@ -378,5 +385,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From 7b25694712340a9d209d750b44777986c7dcf2c7 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Mon, 13 Jan 2025 00:58:43 +0000 Subject: [PATCH 034/123] feat: Get-WebSocket -ThrottleLimit ( Fixes #63 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 2921e09..7448705 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-12" \ No newline at end of file +"2025-01-13" \ No newline at end of file From 82ec3a526b1233c2cf588bfed860a84cf011d232 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Thu, 23 Jan 2025 19:04:23 -0800 Subject: [PATCH 035/123] feat: WebSocket Server Support ( Fixes #64, Fixes #65, Fixes #66, Fixes #71, Fixes #72, Fixes #75 ) --- Commands/Get-WebSocket.ps1 | 133 ++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 2ce006d..b63b544 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -135,6 +135,46 @@ function Get-WebSocket { [string] $HTML, + # The name of the palette to use. This will include the [4bitcss](https://4bitcss.com) stylesheet. + [Alias('Palette','ColorScheme','ColorPalette')] + [ArgumentCompleter({ + param ($commandName,$parameterName,$wordToComplete,$commandAst,$fakeBoundParameters ) + if (-not $script:4bitcssPaletteList) { + $script:4bitcssPaletteList = Invoke-RestMethod -Uri https://cdn.jsdelivr.net/gh/2bitdesigns/4bitcss@latest/docs/Palette-List.json + } + if ($wordToComplete) { + $script:4bitcssPaletteList -match "$([Regex]::Escape($wordToComplete) -replace '\\\*', '.{0,}')" + } else { + $script:4bitcssPaletteList + } + })] + [string] + $PaletteName, + + # The [Google Font](https://fonts.google.com/) name. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('FontName')] + [string] + $GoogleFont, + + # The Google Font name to use for code blocks. + # (this should be a [monospace font](https://fonts.google.com/?classification=Monospace)) + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('PreFont','CodeFontName','PreFontName')] + [string] + $CodeFont, + + # A list of javascript files or urls to include in the content. + [Parameter(ValueFromPipelineByPropertyName)] + [string[]] + $JavaScript, + + # A javascript import map. This allows you to import javascript modules. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('ImportsJavaScript','JavaScriptImports','JavaScriptImportMap')] + [Collections.IDictionary] + $ImportMap, + # A collection of query parameters. # These will be appended onto the `-WebSocketUri`. [Collections.IDictionary] @@ -488,6 +528,63 @@ function Get-WebSocket { # If the listener isn't listening, start it. if (-not $httpListener.IsListening) { $httpListener.Start() } + + $variable['SiteHeader'] = $siteHeader = @( + + if ($Javascript) { + # as well as any javascript files provided. + foreach ($js in $Javascript) { + if ($js -match '.js$') { + "" + } else { + "" + } + } + } + + # If an import map was provided, we will include it. + if ($ImportMap) { + $variable['ImportMap'] = @( + "" + ) -join [Environment]::NewLine + } + + # If a palette name was provided, we will include the 4bitcss stylesheet. + if ($PaletteName) { + if ($PaletteName -match '/.+?\.css$') { + "" + + } else { + '' -replace '\.css', "$PaletteName.css" + } + } + + # If a font name was provided, we will include the font stylesheet. + if ($GoogleFont) { + "" + "" + } + + # If a code font was provided, we will include the code font stylesheet. + if ($CodeFont) { + "" + "" + } + + # and if any stylesheets were provided, we will include them. + foreach ($css in $variable.StyleSheet) { + if ($css -match '.css$') { + "" + } else { + "" + } + } + ) + $httpListener.psobject.properties.add([psnoteproperty]::new('JobVariable',$Variable), $true) # While the listener is listening, @@ -497,6 +594,24 @@ function Get-WebSocket { # and wait for it to complete. while (-not ($contextAsync.IsCompleted -or $contextAsync.IsFaulted -or $contextAsync.IsCanceled)) { # while this is going on, other events can be processed, and CTRL-C can exit. + # also, we can go ahead and check for any socket requests, and get ready for the next one if we find one. + foreach ($socketRequest in @($httpListener.SocketRequests.GetEnumerator())) { + if ($socketRequest.Value.Receiving.IsCompleted) { + $socketRequest.Value.MessageCount++ + $jsonMessage = ConvertFrom-Json -InputObject ($OutputEncoding.GetString($socketRequest.Value.ClientBuffer -gt 0)) + $socketRequest.Value.ClientBuffer.Clear() + if ($MainRunspace.Events.GenerateEvent) { + $MainRunspace.Events.GenerateEvent.Invoke(@( + "$($request.Url.Scheme -replace '^http', 'ws')://", + $httpListener, + @($socketRequest.Value.Context, $socketRequest.Value.WebSocketContet, $socketRequest.Key, $socketRequest.Value), + $jsonMessage + )) + } + $socketRequest.Value.Receiving = + $socketRequest.Value.WebSocket.ReceiveAsync($socketRequest.Value.ClientBuffer, [Threading.CancellationToken]::None) + } + } } # If async method fails, if ($contextAsync.IsFaulted) { @@ -553,7 +668,18 @@ function Get-WebSocket { $Protocol = $requestedUrl.Scheme -replace '^http', 'ws' # Now add the result it to the SocketRequests lookup table, using the request trace identifier as the key. - $httpListener.SocketRequests[$context.Request.RequestTraceIdentifier] = $webSocketResult + $clientBuffer = $webSocketResult.WebSocket::CreateClientBuffer($BufferSize, $BufferSize) + $httpListener.SocketRequests[$context.Request.RequestTraceIdentifier] = [Ordered]@{ + Context = $context + WebSocketContext = $webSocketResult + WebSocket = $webSocketResult.WebSocket + ClientBuffer = $clientBuffer + Created = [DateTime]::UtcNow + LastMessageTime = $null + Receiving = $webSocketResult.WebSocket.ReceiveAsync($clientBuffer, [Threading.CancellationToken]::None) + MessageQueue = [Collections.Queue]::new() + MessageCount = [long]0 + } # and add the websocketcontext result to the message data. $messageData["WebSocketContext"] = $webSocketResult # also add the websocket result to the message data, @@ -612,7 +738,7 @@ function Get-WebSocket { "" "" # and apply the site header. - $SiteHeader + $SiteHeader -join [Environment]::NewLine "" "" $html @@ -742,6 +868,9 @@ function Get-WebSocket { } $Variable['MainRunspace'] = [Runspace]::DefaultRunspace + if (-not $variable['BufferSize']) { + $variable['BufferSize'] = $BufferSize + } $StartThreadJobSplat = [Ordered]@{ InitializationScript = $InitializationScript ThrottleLimit = $ThrottleLimit From 8ec2acbfb97de7649457f3741e0b07fe1811b71b Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 24 Jan 2025 03:05:47 +0000 Subject: [PATCH 036/123] feat: WebSocket Server Support ( Fixes #64, Fixes #65, Fixes #66, Fixes #71, Fixes #72, Fixes #75 ) --- docs/Get-WebSocket.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index e8e3a80..e9b5c3f 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -176,6 +176,42 @@ This will be displayed when visiting the root url. |----------|--------|--------|---------------------|------------------------------------------------------------| |`[String]`|false |named |true (ByPropertyName)|DefaultHTML
Home
Index
IndexHTML
DefaultPage| +#### **PaletteName** +The name of the palette to use. This will include the [4bitcss](https://4bitcss.com) stylesheet. + +|Type |Required|Position|PipelineInput|Aliases | +|----------|--------|--------|-------------|----------------------------------------| +|`[String]`|false |named |false |Palette
ColorScheme
ColorPalette| + +#### **GoogleFont** +The [Google Font](https://fonts.google.com/) name. + +|Type |Required|Position|PipelineInput |Aliases | +|----------|--------|--------|---------------------|--------| +|`[String]`|false |named |true (ByPropertyName)|FontName| + +#### **CodeFont** +The Google Font name to use for code blocks. +(this should be a [monospace font](https://fonts.google.com/?classification=Monospace)) + +|Type |Required|Position|PipelineInput |Aliases | +|----------|--------|--------|---------------------|----------------------------------------| +|`[String]`|false |named |true (ByPropertyName)|PreFont
CodeFontName
PreFontName| + +#### **JavaScript** +A list of javascript files or urls to include in the content. + +|Type |Required|Position|PipelineInput | +|------------|--------|--------|---------------------| +|`[String[]]`|false |named |true (ByPropertyName)| + +#### **ImportMap** +A javascript import map. This allows you to import javascript modules. + +|Type |Required|Position|PipelineInput |Aliases | +|---------------|--------|--------|---------------------|---------------------------------------------------------------| +|`[IDictionary]`|false |named |true (ByPropertyName)|ImportsJavaScript
JavaScriptImports
JavaScriptImportMap| + #### **QueryParameter** A collection of query parameters. These will be appended onto the `-WebSocketUri`. @@ -385,5 +421,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From 952518c5c3c7771947ea4d2d603a3a94487845f1 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Fri, 24 Jan 2025 03:05:51 +0000 Subject: [PATCH 037/123] feat: WebSocket Server Support ( Fixes #64, Fixes #65, Fixes #66, Fixes #71, Fixes #72, Fixes #75 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 7448705..012f58e 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-13" \ No newline at end of file +"2025-01-24" \ No newline at end of file From 28094e4c1606d15032236b05e01148b1768e7dd7 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Thu, 23 Jan 2025 19:54:38 -0800 Subject: [PATCH 038/123] feat: WebSocket Parameter Sets ( Fixes #73, Fixes #74, Fixes #76 ) --- Commands/Get-WebSocket.ps1 | 71 +++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index b63b544..7a2bf42 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -10,7 +10,7 @@ function Get-WebSocket { If the `-Watch` parameter is provided, will output a continous stream of objects. .EXAMPLE # Create a WebSocket job that connects to a WebSocket and outputs the results. - Get-WebSocket -WebSocketUri "wss://localhost:9669/" + Get-WebSocket -SocketUrl "wss://localhost:9669/" .EXAMPLE # Get is the default verb, so we can just say WebSocket. # `-Watch` will output a continous stream of objects from the websocket. @@ -106,36 +106,41 @@ function Get-WebSocket { Sort Count -Descending | Select -First 10 #> - [CmdletBinding(PositionalBinding=$false,SupportsPaging)] + [CmdletBinding( + PositionalBinding=$false, + SupportsPaging, + DefaultParameterSetName='WebSocketClient' + )] [Alias('WebSocket','ws','wss')] param( # The WebSocket Uri. - [Parameter(Position=0,ValueFromPipelineByPropertyName)] - [Alias('Url','Uri','WebSocketUrl')] + [Parameter(Position=0,ParameterSetName='WebSocketClient',ValueFromPipelineByPropertyName)] + [Alias('Url','Uri','WebSocketUri','WebSocketUrl')] [uri] - $WebSocketUri, + $SocketUrl, # One or more root urls. # If these are provided, a WebSocket server will be created with these listener prefixes. - [Parameter(Position=1,ValueFromPipelineByPropertyName)] - [Alias('HostHeader','Host','CNAME','ServerURL','ListenerPrefix','ListenerPrefixes','ListenerUrl')] + [Parameter(Mandatory,Position=0,ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] + [Alias('HostHeader','Host','CNAME','ListenerPrefix','ListenerPrefixes','ListenerUrl')] [string[]] $RootUrl, # A route table for all requests. - [Parameter(ValueFromPipelineByPropertyName)] + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] [Alias('Routes','RouteTable','WebHook','WebHooks')] [Collections.IDictionary] $Route, # The Default HTML. # This will be displayed when visiting the root url. - [Parameter(ValueFromPipelineByPropertyName)] + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] [Alias('DefaultHTML','Home','Index','IndexHTML','DefaultPage')] [string] $HTML, # The name of the palette to use. This will include the [4bitcss](https://4bitcss.com) stylesheet. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] [Alias('Palette','ColorScheme','ColorPalette')] [ArgumentCompleter({ param ($commandName,$parameterName,$wordToComplete,$commandAst,$fakeBoundParameters ) @@ -152,35 +157,37 @@ function Get-WebSocket { $PaletteName, # The [Google Font](https://fonts.google.com/) name. - [Parameter(ValueFromPipelineByPropertyName)] + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] [Alias('FontName')] [string] $GoogleFont, # The Google Font name to use for code blocks. # (this should be a [monospace font](https://fonts.google.com/?classification=Monospace)) - [Parameter(ValueFromPipelineByPropertyName)] + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] [Alias('PreFont','CodeFontName','PreFontName')] [string] $CodeFont, # A list of javascript files or urls to include in the content. - [Parameter(ValueFromPipelineByPropertyName)] + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] [string[]] $JavaScript, # A javascript import map. This allows you to import javascript modules. - [Parameter(ValueFromPipelineByPropertyName)] + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] [Alias('ImportsJavaScript','JavaScriptImports','JavaScriptImportMap')] [Collections.IDictionary] $ImportMap, # A collection of query parameters. - # These will be appended onto the `-WebSocketUri`. + # These will be appended onto the `-SocketUrl`. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [Collections.IDictionary] $QueryParameter, # A ScriptBlock that will handle the output of the WebSocket. + [Parameter(ParameterSetName='WebSocketServer')] [ScriptBlock] $Handler, @@ -206,32 +213,39 @@ function Get-WebSocket { # The ScriptBlock to run after connection to a websocket. # This can be useful for making any initial requests. + [Parameter(ParameterSetName='WebSocketClient')] [ScriptBlock] $OnConnect, # The ScriptBlock to run when an error occurs. + [Parameter(ParameterSetName='WebSocketClient')] [ScriptBlock] $OnError, # The ScriptBlock to run when the WebSocket job outputs an object. + [Parameter(ParameterSetName='WebSocketClient')] [ScriptBlock] $OnOutput, # The Scriptblock to run when the WebSocket job produces a warning. + [Parameter(ParameterSetName='WebSocketClient')] [ScriptBlock] $OnWarning, # If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. + [Parameter(ParameterSetName='WebSocketClient')] [Alias('Tail')] [switch] $Watch, # If set, will output the raw text that comes out of the WebSocket. + [Parameter(ParameterSetName='WebSocketClient')] [Alias('Raw')] [switch] $RawText, # If set, will output the raw bytes that come out of the WebSocket. + [Parameter(ParameterSetName='WebSocketClient')] [Alias('RawByte','RawBytes','Bytes','Byte')] [switch] $Binary, @@ -241,6 +255,7 @@ function Get-WebSocket { $Force, # The subprotocol used by the websocket. If not provided, this will default to `json`. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [string] $SubProtocol, @@ -249,12 +264,14 @@ function Get-WebSocket { # If they are strings or regexes, they will be applied to the raw text. # If they are scriptblocks, they will be applied to the deserialized JSON. # These filters will be run within the WebSocket job. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [PSObject[]] $Filter, # If set, will watch the output of a WebSocket job for one or more conditions. # The conditions are the keys of the dictionary, and can be a regex, a string, or a scriptblock. # The values of the dictionary are what will happen when a match is found. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [ValidateScript({ $keys = $_.Keys $values = $_.values @@ -280,6 +297,7 @@ function Get-WebSocket { # If provided, will decorate the objects outputted from a websocket job. # This will only decorate objects converted from JSON. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [Alias('PSTypeNames','Decorate','Decoration')] [string[]] $PSTypeName, @@ -293,17 +311,20 @@ function Get-WebSocket { $ThrottleLimit = 64, # The maximum time to wait for a connection to be established. - # By default, this is 7 seconds. + # By default, this is 7 seconds. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [TimeSpan] $ConnectionTimeout = '00:00:07', # The Runspace where the handler should run. # Runspaces allow you to limit the scope of the handler. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [Runspace] $Runspace, # The RunspacePool where the handler should run. # RunspacePools allow you to limit the scope of the handler to a pool of runspaces. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [Management.Automation.Runspaces.RunspacePool] [Alias('Pool')] $RunspacePool @@ -325,16 +346,18 @@ function Get-WebSocket { $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) } - if ((-not $WebSocketUri)) { - throw "No WebSocketUri" + if ((-not $SocketUrl)) { + throw "No SocketUrl" } - if (-not $WebSocketUri.Scheme) { - $WebSocketUri = [uri]"wss://$WebSocketUri" + if (-not $SocketUrl.Scheme) { + $SocketUrl = [uri]"wss://$SocketUrl" + } elseif ($SocketUrl.Scheme -match '^https?') { + $SocketUrl = $SocketUrl -replace '^http', 'ws' } if ($QueryParameter) { - $WebSocketUri = [uri]"$($webSocketUri)$($WebSocketUri.Query ? '&' : '?')$(@( + $SocketUrl = [uri]"$($SocketUrl)$($SocketUrl.Query ? '&' : '?')$(@( foreach ($keyValuePair in $QueryParameter.GetEnumerator()) { if ($keyValuePair.Value -is [Collections.IList]) { foreach ($value in $keyValuePair.Value) { @@ -359,7 +382,7 @@ function Get-WebSocket { } else { $ws.Options.AddSubProtocol('json') } - $null = $ws.ConnectAsync($WebSocketUri, $CT).Wait() + $null = $ws.ConnectAsync($SocketUrl, $CT).Wait() } else { $ws = $WebSocket } @@ -850,7 +873,7 @@ function Get-WebSocket { } process { - if ((-not $WebSocketUri) -and (-not $RootUrl)) { + if ((-not $SocketUrl) -and (-not $RootUrl)) { $socketAndListenerJobs = foreach ($job in Get-Job) { if ( @@ -966,9 +989,9 @@ function Get-WebSocket { return } $webSocketJob = - if ($WebSocketUri) { + if ($SocketUrl) { if (-not $name) { - $Name = $WebSocketUri + $Name = $SocketUrl } $existingJob = foreach ($jobWithThisName in (Get-Job -Name $Name -ErrorAction Ignore)) { From b091c47db808f4099e034a77adc67b5c8f1b42b5 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 24 Jan 2025 03:56:06 +0000 Subject: [PATCH 039/123] feat: WebSocket Parameter Sets ( Fixes #73, Fixes #74, Fixes #76 ) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f2d4e2..1ff022d 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ To stop watching a websocket, simply stop the background job. ~~~powershell # Create a WebSocket job that connects to a WebSocket and outputs the results. -Get-WebSocket -WebSocketUri "wss://localhost:9669/" +Get-WebSocket -SocketUrl "wss://localhost:9669/" ~~~ #### Get-WebSocket Example 2 From 75172ec4f3c4d03c7dab57b2b45eea727aa344a4 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 24 Jan 2025 03:56:14 +0000 Subject: [PATCH 040/123] feat: WebSocket Parameter Sets ( Fixes #73, Fixes #74, Fixes #76 ) --- docs/Get-WebSocket.md | 77 ++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index e9b5c3f..7de7e2e 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -20,7 +20,7 @@ If the `-Watch` parameter is provided, will output a continous stream of objects Create a WebSocket job that connects to a WebSocket and outputs the results. ```PowerShell -Get-WebSocket -WebSocketUri "wss://localhost:9669/" +Get-WebSocket -SocketUrl "wss://localhost:9669/" ``` Get is the default verb, so we can just say WebSocket. `-Watch` will output a continous stream of objects from the websocket. @@ -146,20 +146,20 @@ $somePosts | --- ### Parameters -#### **WebSocketUri** +#### **SocketUrl** The WebSocket Uri. -|Type |Required|Position|PipelineInput |Aliases | -|-------|--------|--------|---------------------|----------------------------| -|`[Uri]`|false |1 |true (ByPropertyName)|Url
Uri
WebSocketUrl| +|Type |Required|Position|PipelineInput |Aliases | +|-------|--------|--------|---------------------|---------------------------------------------| +|`[Uri]`|false |1 |true (ByPropertyName)|Url
Uri
WebSocketUri
WebSocketUrl| #### **RootUrl** One or more root urls. If these are provided, a WebSocket server will be created with these listener prefixes. -|Type |Required|Position|PipelineInput |Aliases | -|------------|--------|--------|---------------------|---------------------------------------------------------------------------------------------------| -|`[String[]]`|false |2 |true (ByPropertyName)|HostHeader
Host
CNAME
ServerURL
ListenerPrefix
ListenerPrefixes
ListenerUrl| +|Type |Required|Position|PipelineInput |Aliases | +|------------|--------|--------|---------------------|-------------------------------------------------------------------------------------| +|`[String[]]`|true |1 |true (ByPropertyName)|HostHeader
Host
CNAME
ListenerPrefix
ListenerPrefixes
ListenerUrl| #### **Route** A route table for all requests. @@ -179,9 +179,9 @@ This will be displayed when visiting the root url. #### **PaletteName** The name of the palette to use. This will include the [4bitcss](https://4bitcss.com) stylesheet. -|Type |Required|Position|PipelineInput|Aliases | -|----------|--------|--------|-------------|----------------------------------------| -|`[String]`|false |named |false |Palette
ColorScheme
ColorPalette| +|Type |Required|Position|PipelineInput |Aliases | +|----------|--------|--------|---------------------|----------------------------------------| +|`[String]`|false |named |true (ByPropertyName)|Palette
ColorScheme
ColorPalette| #### **GoogleFont** The [Google Font](https://fonts.google.com/) name. @@ -214,11 +214,11 @@ A javascript import map. This allows you to import javascript modules. #### **QueryParameter** A collection of query parameters. -These will be appended onto the `-WebSocketUri`. +These will be appended onto the `-SocketUrl`. -|Type |Required|Position|PipelineInput| -|---------------|--------|--------|-------------| -|`[IDictionary]`|false |named |false | +|Type |Required|Position|PipelineInput | +|---------------|--------|--------|---------------------| +|`[IDictionary]`|false |named |true (ByPropertyName)| #### **Handler** A ScriptBlock that will handle the output of the WebSocket. @@ -322,9 +322,9 @@ If set, will force a new job to be created, rather than reusing an existing job. #### **SubProtocol** The subprotocol used by the websocket. If not provided, this will default to `json`. -|Type |Required|Position|PipelineInput| -|----------|--------|--------|-------------| -|`[String]`|false |named |false | +|Type |Required|Position|PipelineInput | +|----------|--------|--------|---------------------| +|`[String]`|false |named |true (ByPropertyName)| #### **Filter** One or more filters to apply to the output of the WebSocket. @@ -333,18 +333,18 @@ If they are strings or regexes, they will be applied to the raw text. If they are scriptblocks, they will be applied to the deserialized JSON. These filters will be run within the WebSocket job. -|Type |Required|Position|PipelineInput| -|--------------|--------|--------|-------------| -|`[PSObject[]]`|false |named |false | +|Type |Required|Position|PipelineInput | +|--------------|--------|--------|---------------------| +|`[PSObject[]]`|false |named |true (ByPropertyName)| #### **WatchFor** If set, will watch the output of a WebSocket job for one or more conditions. The conditions are the keys of the dictionary, and can be a regex, a string, or a scriptblock. The values of the dictionary are what will happen when a match is found. -|Type |Required|Position|PipelineInput|Aliases | -|---------------|--------|--------|-------------|----------------------| -|`[IDictionary]`|false |named |false |WhereFor
Wherefore| +|Type |Required|Position|PipelineInput |Aliases | +|---------------|--------|--------|---------------------|----------------------| +|`[IDictionary]`|false |named |true (ByPropertyName)|WhereFor
Wherefore| #### **TimeOut** The timeout for the WebSocket connection. If this is provided, after the timeout elapsed, the WebSocket will be closed. @@ -357,9 +357,9 @@ The timeout for the WebSocket connection. If this is provided, after the timeou If provided, will decorate the objects outputted from a websocket job. This will only decorate objects converted from JSON. -|Type |Required|Position|PipelineInput|Aliases | -|------------|--------|--------|-------------|---------------------------------------| -|`[String[]]`|false |named |false |PSTypeNames
Decorate
Decoration| +|Type |Required|Position|PipelineInput |Aliases | +|------------|--------|--------|---------------------|---------------------------------------| +|`[String[]]`|false |named |true (ByPropertyName)|PSTypeNames
Decorate
Decoration| #### **Maximum** The maximum number of messages to receive before closing the WebSocket. @@ -379,25 +379,25 @@ The throttle limit used when creating background jobs. The maximum time to wait for a connection to be established. By default, this is 7 seconds. -|Type |Required|Position|PipelineInput| -|------------|--------|--------|-------------| -|`[TimeSpan]`|false |named |false | +|Type |Required|Position|PipelineInput | +|------------|--------|--------|---------------------| +|`[TimeSpan]`|false |named |true (ByPropertyName)| #### **Runspace** The Runspace where the handler should run. Runspaces allow you to limit the scope of the handler. -|Type |Required|Position|PipelineInput| -|------------|--------|--------|-------------| -|`[Runspace]`|false |named |false | +|Type |Required|Position|PipelineInput | +|------------|--------|--------|---------------------| +|`[Runspace]`|false |named |true (ByPropertyName)| #### **RunspacePool** The RunspacePool where the handler should run. RunspacePools allow you to limit the scope of the handler to a pool of runspaces. -|Type |Required|Position|PipelineInput|Aliases| -|----------------|--------|--------|-------------|-------| -|`[RunspacePool]`|false |named |false |Pool | +|Type |Required|Position|PipelineInput |Aliases| +|----------------|--------|--------|---------------------|-------| +|`[RunspacePool]`|false |named |true (ByPropertyName)|Pool | #### **IncludeTotalCount** @@ -421,5 +421,8 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-WebSocketUri] ] [[-RootUrl] ] [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-QueryParameter ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +``` +```PowerShell +Get-WebSocket [-RootUrl] [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From 3dbaeccc346a5932c4e00761a46f2190b9c05130 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 24 Jan 2025 03:56:14 +0000 Subject: [PATCH 041/123] feat: WebSocket Parameter Sets ( Fixes #73, Fixes #74, Fixes #76 ) --- docs/_data/Help/Get-WebSocket.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/Help/Get-WebSocket.json b/docs/_data/Help/Get-WebSocket.json index b24a853..c6b964e 100644 --- a/docs/_data/Help/Get-WebSocket.json +++ b/docs/_data/Help/Get-WebSocket.json @@ -33,7 +33,7 @@ { "Title": "EXAMPLE 1", "Markdown": "Create a WebSocket job that connects to a WebSocket and outputs the results.", - "Code": "Get-WebSocket -WebSocketUri \"wss://localhost:9669/\"" + "Code": "Get-WebSocket -SocketUrl \"wss://localhost:9669/\"" }, { "Title": "EXAMPLE 2", From 10bd595a52ce78a0800ac01179c0d7c522159b12 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 24 Jan 2025 03:56:15 +0000 Subject: [PATCH 042/123] feat: WebSocket Parameter Sets ( Fixes #73, Fixes #74, Fixes #76 ) --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 7c727fb..811d474 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,7 +46,7 @@ To stop watching a websocket, simply stop the background job. ~~~powershell # Create a WebSocket job that connects to a WebSocket and outputs the results. -Get-WebSocket -WebSocketUri "wss://localhost:9669/" +Get-WebSocket -SocketUrl "wss://localhost:9669/" ~~~ #### Get-WebSocket Example 2 From be9f910305d69f90a40400db1176b2bf4f04b479 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 10:32:16 -0800 Subject: [PATCH 043/123] feat: Get-WebSocket -ForwardEvent ( Fixes #56 ) --- Commands/Get-WebSocket.ps1 | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 7a2bf42..f2d590f 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -62,11 +62,13 @@ function Get-WebSocket { } .EXAMPLE # BlueSky, but just the hashtags - websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{ + websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ + wantedCollections = 'app.bsky.feed.post' + } -WatchFor @{ {$webSocketoutput.commit.record.text -match "\#\w+"}={ $matches.0 } - } + } -Maximum 1kb .EXAMPLE # BlueSky, but just the hashtags (as links) websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{ @@ -191,6 +193,13 @@ function Get-WebSocket { [ScriptBlock] $Handler, + # If set, will forward websocket messages as events. + # Only events that match -Filter will be forwarded. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] + [Alias('Forward')] + [switch] + $ForwardEvent, + # Any variables to declare in the WebSocket job. # These variables will also be added to the job as properties. [Collections.IDictionary] @@ -434,7 +443,7 @@ function Get-WebSocket { if ($RawText) { $messageString } else { - $MessageObject = ConvertFrom-Json -InputObject $messageString + $MessageObject = ConvertFrom-Json -InputObject $messageString if ($filter) { foreach ($fil in $Filter) { if ($fil -is [ScriptBlock] -or @@ -452,7 +461,19 @@ function Get-WebSocket { continue WebSocketMessageLoop } - $MessageObject + $MessageObject + + # If we are forwarding events + if ($ForwardEvent -and $MainRunspace.Events.GenerateEvent) { + # generate an event in the main runspace + $null = $MainRunspace.Events.GenerateEvent( + "$SocketUrl", + $ws, + @($MessageObject), + $MessageObject + ) + } + if ($First -and ($MessageCount - $FilteredCount - $SkipCount) -ge $First) { $Maximum = $first } From 935b26eb761ec4bcae8afa2c55b8a84bea4c784b Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 25 Jan 2025 18:33:41 +0000 Subject: [PATCH 044/123] feat: Get-WebSocket -ForwardEvent ( Fixes #56 ) --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ff022d..4f856aa 100644 --- a/README.md +++ b/README.md @@ -122,11 +122,13 @@ websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app. ~~~powershell # BlueSky, but just the hashtags -websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{ +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ + wantedCollections = 'app.bsky.feed.post' +} -WatchFor @{ {$webSocketoutput.commit.record.text -match "\#\w+"}={ $matches.0 } -} +} -Maximum 1kb ~~~ #### Get-WebSocket Example 10 From 2c64adf0875f886f7a64f389d561873b2a8d5776 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 25 Jan 2025 18:33:49 +0000 Subject: [PATCH 045/123] feat: Get-WebSocket -ForwardEvent ( Fixes #56 ) --- docs/Get-WebSocket.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 7de7e2e..7615c4f 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -92,11 +92,13 @@ websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app. BlueSky, but just the hashtags ```PowerShell -websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{ +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ + wantedCollections = 'app.bsky.feed.post' +} -WatchFor @{ {$webSocketoutput.commit.record.text -match "\#\w+"}={ $matches.0 } -} +} -Maximum 1kb ``` BlueSky, but just the hashtags (as links) @@ -227,6 +229,14 @@ A ScriptBlock that will handle the output of the WebSocket. |---------------|--------|--------|-------------| |`[ScriptBlock]`|false |named |false | +#### **ForwardEvent** +If set, will forward websocket messages as events. +Only events that match -Filter will be forwarded. + +|Type |Required|Position|PipelineInput |Aliases| +|----------|--------|--------|---------------------|-------| +|`[Switch]`|false |named |true (ByPropertyName)|Forward| + #### **Variable** Any variables to declare in the WebSocket job. These variables will also be added to the job as properties. @@ -421,7 +431,7 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` ```PowerShell Get-WebSocket [-RootUrl] [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] From af994d24007b7c418b1f5f507ae8e16c419e8d2d Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 25 Jan 2025 18:33:49 +0000 Subject: [PATCH 046/123] feat: Get-WebSocket -ForwardEvent ( Fixes #56 ) --- docs/_data/Help/Get-WebSocket.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/Help/Get-WebSocket.json b/docs/_data/Help/Get-WebSocket.json index c6b964e..3d0ed00 100644 --- a/docs/_data/Help/Get-WebSocket.json +++ b/docs/_data/Help/Get-WebSocket.json @@ -73,7 +73,7 @@ { "Title": "EXAMPLE 9", "Markdown": "BlueSky, but just the hashtags", - "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{\n {$webSocketoutput.commit.record.text -match \"\\#\\w+\"}={\n $matches.0\n } \n}" + "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{\n wantedCollections = 'app.bsky.feed.post'\n} -WatchFor @{\n {$webSocketoutput.commit.record.text -match \"\\#\\w+\"}={\n $matches.0\n } \n} -Maximum 1kb" }, { "Title": "EXAMPLE 10", From be4eeb065be7388280a9c83a01de0120fe7da741 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 25 Jan 2025 18:33:50 +0000 Subject: [PATCH 047/123] feat: Get-WebSocket -ForwardEvent ( Fixes #56 ) --- docs/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 811d474..71aa8f6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -122,11 +122,13 @@ websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app. ~~~powershell # BlueSky, but just the hashtags -websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -WatchFor @{ +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ + wantedCollections = 'app.bsky.feed.post' +} -WatchFor @{ {$webSocketoutput.commit.record.text -match "\#\w+"}={ $matches.0 } -} +} -Maximum 1kb ~~~ #### Get-WebSocket Example 10 From 20ea848b74dd77dceaedbedbf80d3eec6ea4468c Mon Sep 17 00:00:00 2001 From: James Brundage Date: Sat, 25 Jan 2025 18:33:52 +0000 Subject: [PATCH 048/123] feat: Get-WebSocket -ForwardEvent ( Fixes #56 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 012f58e..7c7a54a 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-24" \ No newline at end of file +"2025-01-25" \ No newline at end of file From c9f97b4f8640382885159d8f11e3d9eefadf9d92 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 10:49:26 -0800 Subject: [PATCH 049/123] feat: Get-WebSocket -Header ( Fixes #77 ) --- Commands/Get-WebSocket.ps1 | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index f2d590f..aa2b02e 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -18,7 +18,7 @@ function Get-WebSocket { websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch | % { $_.commit.record.text - } + } .EXAMPLE # Watch BlueSky, but just the text and spacing $blueSkySocketUrl = "wss://jetstream2.us-$( @@ -202,9 +202,14 @@ function Get-WebSocket { # Any variables to declare in the WebSocket job. # These variables will also be added to the job as properties. - [Collections.IDictionary] + [Collections.IDictionary] $Variable = @{}, + # Any Http Headers to include in the WebSocket request or server response. + [Collections.IDictionary] + [Alias('Headers')] + $Header, + # The name of the WebSocket job. [string] $Name, @@ -391,6 +396,11 @@ function Get-WebSocket { } else { $ws.Options.AddSubProtocol('json') } + if ($Header) { + foreach ($headerKeyValue in $header.GetEnumerator()) { + $ws.Options.SetRequestHeader($headerKeyValue.Key, $headerKeyValue.Value) + } + } $null = $ws.ConnectAsync($SocketUrl, $CT).Wait() } else { $ws = $WebSocket @@ -688,6 +698,16 @@ function Get-WebSocket { # and by default it will pass a message containing the context. $messageData = [Ordered]@{Protocol = $protocol; Url = $context.Request.Url;Context = $context} + if ($Header -and $response) { + foreach ($headerKeyValue in $Header.GetEnumerator()) { + try { + $response.Headers.Add($headerKeyValue.Key, $headerKeyValue.Value) + } catch { + Write-Warning "Cannot add header '$($headerKeyValue.Key)': $_" + } + } + } + # HttpListeners are quite nice, especially when it comes to websocket upgrades. # If the request is a websocket request if ($Request.IsWebSocketRequest) { From 9c755ca5bbe412f471c1bd5901274b861f98eeb7 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 25 Jan 2025 18:50:39 +0000 Subject: [PATCH 050/123] feat: Get-WebSocket -Header ( Fixes #77 ) --- docs/Get-WebSocket.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 7615c4f..b0cdb12 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -245,6 +245,13 @@ These variables will also be added to the job as properties. |---------------|--------|--------|-------------| |`[IDictionary]`|false |named |false | +#### **Header** +Any Http Headers to include in the WebSocket request or server response. + +|Type |Required|Position|PipelineInput|Aliases| +|---------------|--------|--------|-------------|-------| +|`[IDictionary]`|false |named |false |Headers| + #### **Name** The name of the WebSocket job. @@ -431,8 +438,8 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` ```PowerShell -Get-WebSocket [-RootUrl] [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [-RootUrl] [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From dcd8bbd6d0f158513e421ef3b61d9c7f5165a765 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 11:02:09 -0800 Subject: [PATCH 051/123] feat: Get-WebSocket -Authenticate ( Fixes #69 ) --- Commands/Get-WebSocket.ps1 | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index aa2b02e..96a8226 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -246,6 +246,16 @@ function Get-WebSocket { [ScriptBlock] $OnWarning, + # If provided, will authenticate the WebSocket. + # Many websockets require an initial authentication handshake + # after an initial message is received. + # This parameter can be either a ScriptBlock or any other object. + # If it is a ScriptBlock, it will be run with the output of the WebSocket passed as the first argument. + [Parameter(ParameterSetName='WebSocketClient')] + [Alias('Authorize','Identify')] + [PSObject] + $Authenticate, + # If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. [Parameter(ParameterSetName='WebSocketClient')] [Alias('Tail')] @@ -256,7 +266,7 @@ function Get-WebSocket { [Parameter(ParameterSetName='WebSocketClient')] [Alias('Raw')] [switch] - $RawText, + $RawText, # If set, will output the raw bytes that come out of the WebSocket. [Parameter(ParameterSetName='WebSocketClient')] @@ -466,6 +476,25 @@ function Get-WebSocket { } } } + + if ($Authenticate) { + # a number of websockets require some handshaking to authenticate + $authenticationMessage = + if ($Authenticate -is [ScriptBlock]) { + & $Authenticate $MessageObject + } else { + $authenticate + } + + if ($authenticationMessage) { + if ($authenticationMessage -isnot [string]) { + $ws.SendAsync([ArraySegment[byte]]::new( + $OutputEncoding.GetBytes((ConvertTo-Json -InputObject $authenticationMessage -Depth 10)) + ), 'Text', $true, $CT) + } + } + } + if ($Skip -and ($SkipCount -le $Skip)) { $SkipCount++ continue WebSocketMessageLoop @@ -482,7 +511,7 @@ function Get-WebSocket { @($MessageObject), $MessageObject ) - } + } if ($First -and ($MessageCount - $FilteredCount - $SkipCount) -ge $First) { $Maximum = $first From 17f1625e03ab3225c9ab1e2278b943b7ec171703 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 25 Jan 2025 19:03:33 +0000 Subject: [PATCH 052/123] feat: Get-WebSocket -Authenticate ( Fixes #69 ) --- docs/Get-WebSocket.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index b0cdb12..9ef2a65 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -308,6 +308,17 @@ The Scriptblock to run when the WebSocket job produces a warning. |---------------|--------|--------|-------------| |`[ScriptBlock]`|false |named |false | +#### **Authenticate** +If provided, will authenticate the WebSocket. +Many websockets require an initial authentication handshake +after an initial message is received. +This parameter can be either a ScriptBlock or any other object. +If it is a ScriptBlock, it will be run with the output of the WebSocket passed as the first argument. + +|Type |Required|Position|PipelineInput|Aliases | +|------------|--------|--------|-------------|----------------------| +|`[PSObject]`|false |named |false |Authorize
Identify| + #### **Watch** If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. @@ -438,7 +449,7 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` ```PowerShell Get-WebSocket [-RootUrl] [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] From d3033f5fc0945b0afd6bc8613b79807af70fd49e Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 15:02:48 -0800 Subject: [PATCH 053/123] feat: Get-WebSocket Parameter Sets ( Fixes #73, Fixes #74 ) RootURL is no longer positionally bound --- Commands/Get-WebSocket.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 96a8226..b32ed85 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -123,7 +123,7 @@ function Get-WebSocket { # One or more root urls. # If these are provided, a WebSocket server will be created with these listener prefixes. - [Parameter(Mandatory,Position=0,ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] + [Parameter(Mandatory,ValueFromPipelineByPropertyName,ParameterSetName='WebSocketServer')] [Alias('HostHeader','Host','CNAME','ListenerPrefix','ListenerPrefixes','ListenerUrl')] [string[]] $RootUrl, @@ -633,7 +633,7 @@ function Get-WebSocket { imports = $ImportMap } | ConvertTo-Json -Depth 3 "" - ) -join [Environment]::NewLine + ) -join [Environment]::NewLine } # If a palette name was provided, we will include the 4bitcss stylesheet. From 5855eb7eb5f47f9938a7606e73c60c8acfb87b60 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 25 Jan 2025 23:04:22 +0000 Subject: [PATCH 054/123] feat: Get-WebSocket Parameter Sets ( Fixes #73, Fixes #74 ) RootURL is no longer positionally bound --- docs/Get-WebSocket.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 9ef2a65..6f913cc 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -161,7 +161,7 @@ If these are provided, a WebSocket server will be created with these listener pr |Type |Required|Position|PipelineInput |Aliases | |------------|--------|--------|---------------------|-------------------------------------------------------------------------------------| -|`[String[]]`|true |1 |true (ByPropertyName)|HostHeader
Host
CNAME
ListenerPrefix
ListenerPrefixes
ListenerUrl| +|`[String[]]`|true |named |true (ByPropertyName)|HostHeader
Host
CNAME
ListenerPrefix
ListenerPrefixes
ListenerUrl| #### **Route** A route table for all requests. @@ -452,5 +452,5 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` ```PowerShell -Get-WebSocket [-RootUrl] [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket -RootUrl [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From 2beade27050a0acc178636d880258f468ee77502 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 15:38:00 -0800 Subject: [PATCH 055/123] feat: Get-WebSocket Authentication ( Fixes #69 ) Authenticating prior to first Receive --- Commands/Get-WebSocket.ps1 | 69 +++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index b32ed85..3af9686 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -251,10 +251,11 @@ function Get-WebSocket { # after an initial message is received. # This parameter can be either a ScriptBlock or any other object. # If it is a ScriptBlock, it will be run with the output of the WebSocket passed as the first argument. + # This will run after the socket is connected but before any messages are received. [Parameter(ParameterSetName='WebSocketClient')] - [Alias('Authorize','Identify')] + [Alias('Authorize','HelloMessage')] [PSObject] - $Authenticate, + $Authenticate, # If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. [Parameter(ParameterSetName='WebSocketClient')] @@ -382,14 +383,15 @@ function Get-WebSocket { if ($QueryParameter) { $SocketUrl = [uri]"$($SocketUrl)$($SocketUrl.Query ? '&' : '?')$(@( - foreach ($keyValuePair in $QueryParameter.GetEnumerator()) { - if ($keyValuePair.Value -is [Collections.IList]) { - foreach ($value in $keyValuePair.Value) { - "$($keyValuePair.Key)=$([Web.HttpUtility]::UrlEncode($value).Replace('+', '%20'))" + foreach ($keyValuePair in $QueryParameter.GetEnumerator()) { + foreach ($value in $keyValuePair.Value) { + $valueString = if ($value -is [bool] -or $value -is [switch]) { + ($value -as [bool] -as [string]).ToLower() + } else { + $value } - } else { - "$($keyValuePair.Key)=$([Web.HttpUtility]::UrlEncode($keyValuePair.Value).Replace('+', '%20'))" - } + "$($keyValuePair.Key)=$([Web.HttpUtility]::UrlEncode($valueString).Replace('+', '%20'))" + } }) -join '&')" } @@ -421,7 +423,9 @@ function Get-WebSocket { $MessageCount = [long]0 $FilteredCount = [long]0 - $SkipCount = [long]0 + $SkipCount = [long]0 + + $saidHello = $null :WebSocketMessageLoop while ($true) { if ($ws.State -ne 'Open') {break } @@ -437,9 +441,30 @@ function Get-WebSocket { break } + if ($Authenticate -and -not $SaidHello) { + # a number of websockets require some handshaking to authenticate + $authenticationMessage = + if ($Authenticate -is [ScriptBlock]) { + & $Authenticate $MessageObject + } else { + $authenticate + } + + if ($authenticationMessage) { + if ($authenticationMessage -isnot [string]) { + $saidHello = $ws.SendAsync([ArraySegment[byte]]::new( + $OutputEncoding.GetBytes((ConvertTo-Json -InputObject $authenticationMessage -Depth 10)) + ), 'Text', $true, $CT) + } + } + } + $Buf = [byte[]]::new($BufferSize) $Seg = [ArraySegment[byte]]::new($Buf) - $null = $ws.ReceiveAsync($Seg, $CT).Wait() + $receivingWebSocket = $ws.ReceiveAsync($Seg, $CT) + while (-not ($receivingWebSocket.IsCompleted -or $receivingWebSocket.IsFaulted -or $receivingWebSocket.IsCanceled)) { + + } $MessageCount++ try { @@ -475,25 +500,7 @@ function Get-WebSocket { } } } - } - - if ($Authenticate) { - # a number of websockets require some handshaking to authenticate - $authenticationMessage = - if ($Authenticate -is [ScriptBlock]) { - & $Authenticate $MessageObject - } else { - $authenticate - } - - if ($authenticationMessage) { - if ($authenticationMessage -isnot [string]) { - $ws.SendAsync([ArraySegment[byte]]::new( - $OutputEncoding.GetBytes((ConvertTo-Json -InputObject $authenticationMessage -Depth 10)) - ), 'Text', $true, $CT) - } - } - } + } if ($Skip -and ($SkipCount -le $Skip)) { $SkipCount++ @@ -1110,7 +1117,7 @@ function Get-WebSocket { Start-Sleep -Milliseconds 0 } - foreach ($keyValuePair in $Variable.GetEnumerator()) { + foreach ($keyValuePair in $Variable.GetEnumerator()) { $webSocketJob.psobject.properties.add( [psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true ) From f078e7d2500584ff4ca116f33479efbe39d2a8bb Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 25 Jan 2025 23:39:26 +0000 Subject: [PATCH 056/123] feat: Get-WebSocket Authentication ( Fixes #69 ) Authenticating prior to first Receive --- docs/Get-WebSocket.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 6f913cc..e178609 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -314,10 +314,11 @@ Many websockets require an initial authentication handshake after an initial message is received. This parameter can be either a ScriptBlock or any other object. If it is a ScriptBlock, it will be run with the output of the WebSocket passed as the first argument. +This will run after the socket is connected but before any messages are received. -|Type |Required|Position|PipelineInput|Aliases | -|------------|--------|--------|-------------|----------------------| -|`[PSObject]`|false |named |false |Authorize
Identify| +|Type |Required|Position|PipelineInput|Aliases | +|------------|--------|--------|-------------|--------------------------| +|`[PSObject]`|false |named |false |Authorize
HelloMessage| #### **Watch** If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. From 8f767b33179e197faa1adcc14f3d961fe3c744ea Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 16:55:36 -0800 Subject: [PATCH 057/123] feat: Get-WebSocket honoring CloseStatusDescription ( Fixes #80 ) --- Commands/Get-WebSocket.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 3af9686..023243e 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -255,7 +255,11 @@ function Get-WebSocket { [Parameter(ParameterSetName='WebSocketClient')] [Alias('Authorize','HelloMessage')] [PSObject] - $Authenticate, + $Authenticate, + + [Alias('Identify','AutoReply')] + [PSObject] + $Reply, # If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. [Parameter(ParameterSetName='WebSocketClient')] @@ -428,7 +432,12 @@ function Get-WebSocket { $saidHello = $null :WebSocketMessageLoop while ($true) { - if ($ws.State -ne 'Open') {break } + if ($ws.State -ne 'Open') { + if ($ws.CloseStatusDescription) { + Write-Error $ws.CloseStatusDescription -TargetObject $ws + } + break + } if ($TimeOut -and ([DateTime]::Now - $webSocketStartTime) -gt $TimeOut) { $ws.CloseAsync([Net.WebSockets.WebSocketCloseStatus]::NormalClosure, 'Timeout', $CT).Wait() break From 3e3716a1175ada042d3f13e0cf0d7c6e5071e64e Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 26 Jan 2025 00:57:11 +0000 Subject: [PATCH 058/123] feat: Get-WebSocket honoring CloseStatusDescription ( Fixes #80 ) --- docs/Get-WebSocket.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index e178609..1983dfa 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -320,6 +320,12 @@ This will run after the socket is connected but before any messages are received |------------|--------|--------|-------------|--------------------------| |`[PSObject]`|false |named |false |Authorize
HelloMessage| +#### **Reply** + +|Type |Required|Position|PipelineInput|Aliases | +|------------|--------|--------|-------------|----------------------| +|`[PSObject]`|false |named |false |Identify
AutoReply| + #### **Watch** If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. @@ -450,8 +456,8 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Reply ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` ```PowerShell -Get-WebSocket -RootUrl [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket -RootUrl [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Reply ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From a45590df3cbf475cb3cffb84ff87326194c3fba5 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Sun, 26 Jan 2025 00:57:14 +0000 Subject: [PATCH 059/123] feat: Get-WebSocket honoring CloseStatusDescription ( Fixes #80 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 7c7a54a..fe49969 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-25" \ No newline at end of file +"2025-01-26" \ No newline at end of file From 4ef241e9ea0dae784186fd6dbd15d9a13fdeb31a Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 17:36:48 -0800 Subject: [PATCH 060/123] feat: Get-WebSocket -Handshake ( Fixes #81 ) --- Commands/Get-WebSocket.ps1 | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 023243e..391efdf 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -256,10 +256,15 @@ function Get-WebSocket { [Alias('Authorize','HelloMessage')] [PSObject] $Authenticate, - - [Alias('Identify','AutoReply')] + + # If provided, will shake hands after the first websocket message is received. + # This parameter can be either a ScriptBlock or any other object. + # If it is a ScriptBlock, it will be run with the output of the WebSocket passed as the first argument. + # This will run after the socket is connected and the first message is received. + [Parameter(ParameterSetName='WebSocketClient')] + [Alias('Identify','Handshake')] [PSObject] - $Reply, + $Handshake, # If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. [Parameter(ParameterSetName='WebSocketClient')] @@ -430,6 +435,7 @@ function Get-WebSocket { $SkipCount = [long]0 $saidHello = $null + $shookHands = $null :WebSocketMessageLoop while ($true) { if ($ws.State -ne 'Open') { @@ -477,6 +483,24 @@ function Get-WebSocket { $MessageCount++ try { + if ($Handshake -and -not $shookHands) { + # a number of websockets require some handshaking + $handShakeMessage = + if ($Handshake -is [ScriptBlock]) { + & $Handshake $MessageObject + } else { + $Handshake + } + + if ($handShakeMessage) { + if ($handShakeMessage -isnot [string]) { + $saidHello = $ws.SendAsync([ArraySegment[byte]]::new( + $OutputEncoding.GetBytes((ConvertTo-Json -InputObject $handShakeMessage -Depth 10)) + ), 'Text', $true, $CT) + } + } + } + $webSocketMessage = if ($Binary) { $Buf -gt 0 From 91dc2860282193fd3c877a06f50a31872a02608c Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 17:49:52 -0800 Subject: [PATCH 061/123] feat: Get-WebSocket -Handshake ( Fixes #81 ) Also, improving internal docs --- Commands/Get-WebSocket.ps1 | 76 +++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 391efdf..9ccc783 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -262,7 +262,7 @@ function Get-WebSocket { # If it is a ScriptBlock, it will be run with the output of the WebSocket passed as the first argument. # This will run after the socket is connected and the first message is received. [Parameter(ParameterSetName='WebSocketClient')] - [Alias('Identify','Handshake')] + [Alias('Identify')] [PSObject] $Handshake, @@ -380,78 +380,106 @@ function Get-WebSocket { $ExecutionContext.SessionState.PSVariable.Set($keyValue.Key, $keyValue.Value) } + # If we have no socket url, if ((-not $SocketUrl)) { + # throw up an error. throw "No SocketUrl" } + # If the socket url does not have a scheme if (-not $SocketUrl.Scheme) { + # assume `wss` $SocketUrl = [uri]"wss://$SocketUrl" - } elseif ($SocketUrl.Scheme -match '^https?') { + } elseif ( + # otherwise, if the scheme is http or https + $SocketUrl.Scheme -match '^https?' + ) { + # replace it with `ws` or `wss` $SocketUrl = $SocketUrl -replace '^http', 'ws' } + # If any query parameters were provided if ($QueryParameter) { + # add them to the socket url $SocketUrl = [uri]"$($SocketUrl)$($SocketUrl.Query ? '&' : '?')$(@( - foreach ($keyValuePair in $QueryParameter.GetEnumerator()) { + foreach ($keyValuePair in $QueryParameter.GetEnumerator()) { + # cannocially, each key value pair should be url encoded, + # and multiple values should be passed multiple times. foreach ($value in $keyValuePair.Value) { - $valueString = if ($value -is [bool] -or $value -is [switch]) { - ($value -as [bool] -as [string]).ToLower() - } else { - $value - } + $valueString = + # If the value is a boolean or a switch, + if ($value -is [bool] -or $value -is [switch]) { + # convert it to a string and make it lowercase. + ($value -as [bool] -as [string]).ToLower() + } else { + # Otherwise, just stringify. + "$value" + } "$($keyValuePair.Key)=$([Web.HttpUtility]::UrlEncode($valueString).Replace('+', '%20'))" } }) -join '&')" } + # If we had not set a -BufferSize, if (-not $BufferSize) { - $BufferSize = 64kb + $BufferSize = 64kb # default to 64kb. } + # Create a cancellation token, as this will save syntax space $CT = [Threading.CancellationToken]::None + # If `$WebSocket `is not already a websocket if ($webSocket -isnot [Net.WebSockets.ClientWebSocket]) { + # create a new socket $ws = [Net.WebSockets.ClientWebSocket]::new() if ($SubProtocol) { + # and add the subprotocol $ws.Options.AddSubProtocol($SubProtocol) } else { $ws.Options.AddSubProtocol('json') } + # If there are headers if ($Header) { + # add them to the initial socket request. foreach ($headerKeyValue in $header.GetEnumerator()) { $ws.Options.SetRequestHeader($headerKeyValue.Key, $headerKeyValue.Value) } } + # Now, let's try to connect to the WebSocket. $null = $ws.ConnectAsync($SocketUrl, $CT).Wait() } else { $ws = $WebSocket } + # Keep track of the time $webSocketStartTime = $Variable.WebSocketStartTime = [DateTime]::Now + # and add the WebSocket to the variable dictionary, so we can access it later. $Variable.WebSocket = $ws - $MessageCount = [long]0 - $FilteredCount = [long]0 - $SkipCount = [long]0 + # Initialize some counters: + $MessageCount = [long]0 # * The number of messages received + $FilteredCount = [long]0 # * The number of messages filtered out + $SkipCount = [long]0 # * The number of messages skipped - $saidHello = $null - $shookHands = $null - - :WebSocketMessageLoop while ($true) { - if ($ws.State -ne 'Open') { - if ($ws.CloseStatusDescription) { - Write-Error $ws.CloseStatusDescription -TargetObject $ws - } - break - } + # Initialize variables related to handshaking + $saidHello = $null # * Whether we have said hello + $shookHands = $null # * Whether we have shaken hands + + # This loop will run as long as the websocket is open. + :WebSocketMessageLoop while ($ws.State -eq 'Open') { + # If we've given a timeout for the websocket, + # and the websocket has been open for longer than the timeout, if ($TimeOut -and ([DateTime]::Now - $webSocketStartTime) -gt $TimeOut) { + # then it's closing time (you don't have to go home but you can't stay here). $ws.CloseAsync([Net.WebSockets.WebSocketCloseStatus]::NormalClosure, 'Timeout', $CT).Wait() break } + # If we've gotten the maximum number of messages, if ($Maximum -and ( ($MessageCount - $FilteredCount) -ge $Maximum )) { + # then I can't even take any more responses. $ws.CloseAsync([Net.WebSockets.WebSocketCloseStatus]::NormalClosure, 'Maximum messages reached', $CT).Wait() break } @@ -590,6 +618,10 @@ function Get-WebSocket { Write-Error $_ } } + + if ($ws.CloseStatusDescription) { + Write-Error $ws.CloseStatusDescription -TargetObject $ws + } } $SocketServerJob = { <# From 803b11ed2b7518485588e01edcfd14e7e908726c Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 26 Jan 2025 01:51:14 +0000 Subject: [PATCH 062/123] feat: Get-WebSocket -Handshake ( Fixes #81 ) Also, improving internal docs --- docs/Get-WebSocket.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 1983dfa..831e188 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -320,11 +320,15 @@ This will run after the socket is connected but before any messages are received |------------|--------|--------|-------------|--------------------------| |`[PSObject]`|false |named |false |Authorize
HelloMessage| -#### **Reply** +#### **Handshake** +If provided, will shake hands after the first websocket message is received. +This parameter can be either a ScriptBlock or any other object. +If it is a ScriptBlock, it will be run with the output of the WebSocket passed as the first argument. +This will run after the socket is connected and the first message is received. -|Type |Required|Position|PipelineInput|Aliases | -|------------|--------|--------|-------------|----------------------| -|`[PSObject]`|false |named |false |Identify
AutoReply| +|Type |Required|Position|PipelineInput|Aliases | +|------------|--------|--------|-------------|--------| +|`[PSObject]`|false |named |false |Identify| #### **Watch** If set, will watch the output of the WebSocket job, outputting results continuously instead of outputting a websocket job. @@ -456,8 +460,8 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Reply ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Handshake ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` ```PowerShell -Get-WebSocket -RootUrl [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Reply ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket -RootUrl [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From 922f57d468b8b486e1376390190f697e2b585538 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 18:55:55 -0800 Subject: [PATCH 063/123] docs: Get-WebSocket docs ( Fixes #82 ) Documenting WebSocketclient --- Commands/Get-WebSocket.ps1 | 141 +++++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 39 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 9ccc783..64e8dfe 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -484,17 +484,23 @@ function Get-WebSocket { break } + # If we're authenticating, and haven't yet said hello if ($Authenticate -and -not $SaidHello) { - # a number of websockets require some handshaking to authenticate - $authenticationMessage = + # then we should say hello. + # Determine the authentication message + $authenticationMessage = + # If the authentication message is a scriptblock, if ($Authenticate -is [ScriptBlock]) { - & $Authenticate $MessageObject + & $Authenticate # run it } else { - $authenticate + $authenticate # otherwise, use it as-is. } + # If we have an authentication message if ($authenticationMessage) { + # and it's not a string if ($authenticationMessage -isnot [string]) { + # then we should send it as JSON and mark that we've said hello. $saidHello = $ws.SendAsync([ArraySegment[byte]]::new( $OutputEncoding.GetBytes((ConvertTo-Json -InputObject $authenticationMessage -Depth 10)) ), 'Text', $true, $CT) @@ -502,26 +508,43 @@ function Get-WebSocket { } } + # Ok, let's get the next message. $Buf = [byte[]]::new($BufferSize) $Seg = [ArraySegment[byte]]::new($Buf) $receivingWebSocket = $ws.ReceiveAsync($Seg, $CT) + # use this tight loop to let us cancel the await if we need to. while (-not ($receivingWebSocket.IsCompleted -or $receivingWebSocket.IsFaulted -or $receivingWebSocket.IsCanceled)) { + } + # If we had a problem, write an error. + if ($receivingWebSocket.Exception) { + Write-Error -Exception $receivingWebSocket.Exception -Category ProtocolError + continue } $MessageCount++ try { + # If we have a handshake and we haven't yet shaken hands if ($Handshake -and -not $shookHands) { - # a number of websockets require some handshaking - $handShakeMessage = + # then we should shake hands. + # Get the message string + $messageString = $OutputEncoding.GetString($Buf, 0, $Buf.Count) + # and try to convert it from JSON. + $messageObject = ConvertFrom-Json -InputObject $messageString *>&1 + # Determine the handshake message + $handShakeMessage = + # If the handshake message is a scriptblock, if ($Handshake -is [ScriptBlock]) { - & $Handshake $MessageObject + & $Handshake $MessageObject # run it and pass the message } else { - $Handshake + $Handshake # otherwise, use it as-is. } + # If we have a handshake message if ($handShakeMessage) { + # and it's not a string if ($handShakeMessage -isnot [string]) { + # then we should send it as JSON and mark that we've shaken hands. $saidHello = $ws.SendAsync([ArraySegment[byte]]::new( $OutputEncoding.GetBytes((ConvertTo-Json -InputObject $handShakeMessage -Depth 10)) ), 'Text', $true, $CT) @@ -529,97 +552,137 @@ function Get-WebSocket { } } + # Get the message from the websocket $webSocketMessage = - if ($Binary) { - $Buf -gt 0 + if ($Binary) { # If we wanted binary + $Buf -gt 0 -as [byte[]] # then return non-null bytes } else { + # otherwise, get the message as a string $messageString = $OutputEncoding.GetString($Buf, 0, $Buf.Count) + # if we have any filters if ($Filter) { + # then we see if we can apply them now. foreach ($fil in $filter) { + # Wilcard filters can be applied to the raw text if ($fil -is [string] -and $messageString -like "*$fil*") { $FilteredCount++ continue WebSocketMessageLoop } + # and so can regex filters. if ($fil -is [regex] -and $fil.IsMatch($messageString)) { $FilteredCount++ continue WebSocketMessageLoop } } } + # If we have asked for -RawText if ($RawText) { - $messageString + $messageString # then return the raw text } else { - $MessageObject = ConvertFrom-Json -InputObject $messageString + # Otherwise, try to convert the message from JSON. + $MessageObject = ConvertFrom-Json -InputObject $messageString + + # Now we can run any filters that are scriptblocks or commands. if ($filter) { foreach ($fil in $Filter) { - if ($fil -is [ScriptBlock] -or + if ($fil -is [ScriptBlock] -or $fil -is [Management.Automation.CommandInfo] ) { - if (& $fil $MessageObject) { - $FilteredCount++ + # Capture the output of the filter + $filterOutput = $MessageObject | & $fil $MessageObject + # if the output was falsy, + if (-not $filterOutput) { + $FilteredCount++ # filter out the message. continue WebSocketMessageLoop } } } - } + } + # If -Skip was provided and we haven't skipped enough messages if ($Skip -and ($SkipCount -le $Skip)) { + # then skip this message. $SkipCount++ continue WebSocketMessageLoop } - - $MessageObject - - # If we are forwarding events - if ($ForwardEvent -and $MainRunspace.Events.GenerateEvent) { - # generate an event in the main runspace - $null = $MainRunspace.Events.GenerateEvent( - "$SocketUrl", - $ws, - @($MessageObject), - $MessageObject - ) - } + + # Now, emit the message object. + # (expressions that are not assigned will be outputted) + $MessageObject + # If we have a -First parameter, and we have not yet reached the maximum + # (after accounting for skips and filters) if ($First -and ($MessageCount - $FilteredCount - $SkipCount) -ge $First) { + # then set the maximum to first (which will cancel this after the next loop) $Maximum = $first } } } + + # If we want to decorate the output if ($PSTypeName) { + # clear it's typenames $webSocketMessage.pstypenames.clear() - [Array]::Reverse($PSTypeName) - foreach ($psType in $psTypeName) { - $webSocketMessage.pstypenames.add($psType) - } + for ($typeNameIndex = $PSTypeName.Length - 1; $typeNameIndex -ge 0; $typeNameIndex--) { + # and add each type name in reverse order + $webSocketMessage.pstypenames.add($PSTypeName[$typeNameIndex]) + } + } + + # If we are forwarding events + if ($ForwardEvent -and $MainRunspace.Events.GenerateEvent) { + # generate an event in the main runspace + $null = $MainRunspace.Events.GenerateEvent( + "$SocketUrl", + $ws, + @($webSocketMessage), + $webSocketMessage + ) } - if ($handler) { - $psCmd = + + # If we have an output handler, try to run it and get the output + $handledResponse = if ($handler) { + # We may need to run the handler in a `[PowerShell]` command. + $psCmd = + # This is true if we want `NoLanguage` mode. if ($runspace.LanguageMode -eq 'NoLanguage' -or $runspacePool.InitialSessionState.LanguageMode -eq 'NoLanguage') { + # (in which case we'll call .GetPowerShell()) $handler.GetPowerShell() - } elseif ($Runspace -or $RunspacePool) { - [PowerShell]::Create().AddScript($handler) + } elseif ( + # or if we have a runspace or runspace pool + $Runspace -or $RunspacePool + ) { + # (in which case we'll `.Create()` and `.AddScript()`) + [PowerShell]::Create().AddScript($handler, $true) } if ($psCmd) { + # If we have a runspace, we'll use that. if ($Runspace) { $psCmd.Runspace = $Runspace } elseif ($RunspacePool) { + # or, alternatively, we can use a runspace pool. $psCmd.RunspacePool = $RunspacePool } + # Now, we can invoke the command. + $psCmd.Invoke(@($webSocketMessage)) } else { + # Otherwise, we'll just run the handler. $webSocketMessage | . $handler } - } else { $webSocketMessage - } + } } catch { Write-Error $_ } } + # Now that the socket is closed, + # check for a status description. + # If there is one, if ($ws.CloseStatusDescription) { + # write an error. Write-Error $ws.CloseStatusDescription -TargetObject $ws } } From a03570b0971ff8c0032383d928fb20371f562bec Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 25 Jan 2025 19:21:19 -0800 Subject: [PATCH 064/123] fix: Get-WebSocket output docs ( Fixes #82 ) Documenting and fixing output --- Commands/Get-WebSocket.ps1 | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 64e8dfe..b8ac84a 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -670,9 +670,14 @@ function Get-WebSocket { # Otherwise, we'll just run the handler. $webSocketMessage | . $handler } - } else { - $webSocketMessage } + + # If we have a response from the handler, + if ($handledResponse) { + $handledResponse # emit that response. + } else { + $webSocketMessage # otherwise, emit the message. + } } catch { Write-Error $_ } @@ -897,7 +902,7 @@ function Get-WebSocket { # Now add the result it to the SocketRequests lookup table, using the request trace identifier as the key. $clientBuffer = $webSocketResult.WebSocket::CreateClientBuffer($BufferSize, $BufferSize) - $httpListener.SocketRequests[$context.Request.RequestTraceIdentifier] = [Ordered]@{ + $socketObject = [PSCustomObject][Ordered]@{ Context = $context WebSocketContext = $webSocketResult WebSocket = $webSocketResult.WebSocket @@ -908,6 +913,10 @@ function Get-WebSocket { MessageQueue = [Collections.Queue]::new() MessageCount = [long]0 } + if (-not $httpListener.SocketRequests["$($webSocketResult.RequestUri)"]) { + $httpListener.SocketRequests["$($webSocketResult.RequestUri)"] = [Collections.Queue]::new() + } + $httpListener.SocketRequests["$($webSocketResult.RequestUri)"].Enqueue($socketObject) # and add the websocketcontext result to the message data. $messageData["WebSocketContext"] = $webSocketResult # also add the websocket result to the message data, @@ -1157,7 +1166,10 @@ function Get-WebSocket { [psnoteproperty]::new($keyValuePair.Key, $keyValuePair.Value), $true ) } - $httpListenerJob + + if (-not $Broadcast) { + $httpListenerJob + } } if ($Broadcast) { @@ -1168,7 +1180,7 @@ function Get-WebSocket { $broadcast = [ArraySegment[byte]]::new($broadcast) } if ($broadcast -is [System.ArraySegment[byte]]) { - foreach ($socketRequest in $httpListener.SocketRequests.Values) { + foreach ($socketRequest in @($httpListener.SocketRequests.Values)) { $socketRequest.WebSocket.SendAsync($broadcast, 'Binary', 'EndOfMessage', [Threading.CancellationToken]::None) } } @@ -1177,7 +1189,7 @@ function Get-WebSocket { $broadcastJson = ConvertTo-Json -InputObject $broadcastItem $broadcastJsonBytes = $OutputEncoding.GetBytes($broadcastJson) $broadcastSegment = [ArraySegment[byte]]::new($broadcastJsonBytes) - foreach ($socketRequest in $httpListener.SocketRequests.Values) { + foreach ($socketRequest in @($httpListener.SocketRequests.Values | . { process { $_ } })) { $socketRequest.WebSocket.SendAsync($broadcastSegment, 'Text', 'EndOfMessage', [Threading.CancellationToken]::None) } } From 965f4166f9e7037f067bd7d0f335b1d90afd3e86 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 26 Jan 2025 12:11:34 -0800 Subject: [PATCH 065/123] feat: Get-WebSocket Fixing WebSocketClient job name default ( Fixes #76 ) Changing prefix to ws/wss --- Commands/Get-WebSocket.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index b8ac84a..d47257e 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -1205,10 +1205,14 @@ function Get-WebSocket { . $SocketClientJob -Variable $Variable return } + + # If -Debug was not passed, we're running in a background thread job. $webSocketJob = if ($SocketUrl) { + # If we had no name, we will use the SocketUrl as the name. if (-not $name) { - $Name = $SocketUrl + # and we will ensure that it starts with `ws://` or `wss://` + $Name = $SocketUrl -replace '^http', 'ws' } $existingJob = foreach ($jobWithThisName in (Get-Job -Name $Name -ErrorAction Ignore)) { From 68bc8e00a25be7f6770638d3588b5ae16c52ff92 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 26 Jan 2025 15:02:52 -0800 Subject: [PATCH 066/123] feat: Get-WebSocket -Broadcast and -Handler Improvement ( Fixes #84, Fixes #39 ) Making -Broadcast and -Handler work in both client and server scenarios --- Commands/Get-WebSocket.ps1 | 132 +++++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 34 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index d47257e..4c0c06d 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -188,8 +188,9 @@ function Get-WebSocket { [Collections.IDictionary] $QueryParameter, - # A ScriptBlock that will handle the output of the WebSocket. - [Parameter(ParameterSetName='WebSocketServer')] + # A ScriptBlock that can handle the output of the WebSocket or the Http Request. + # This may be run in a separate `-Runspace` or `-RunspacePool`. + # The output of the WebSocket or the Context will be passed as an object. [ScriptBlock] $Handler, @@ -222,6 +223,9 @@ function Get-WebSocket { [int] $BufferSize = 64kb, + # If provided, will send an object. + # If this is a scriptblock, it will be run and the output will be sent. + [Alias('Send')] [PSObject] $Broadcast, @@ -352,13 +356,13 @@ function Get-WebSocket { # The Runspace where the handler should run. # Runspaces allow you to limit the scope of the handler. - [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] + [Parameter(ValueFromPipelineByPropertyName)] [Runspace] $Runspace, # The RunspacePool where the handler should run. # RunspacePools allow you to limit the scope of the handler to a pool of runspaces. - [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] + [Parameter(ValueFromPipelineByPropertyName)] [Management.Automation.Runspaces.RunspacePool] [Alias('Pool')] $RunspacePool @@ -913,6 +917,7 @@ function Get-WebSocket { MessageQueue = [Collections.Queue]::new() MessageCount = [long]0 } + if (-not $httpListener.SocketRequests["$($webSocketResult.RequestUri)"]) { $httpListener.SocketRequests["$($webSocketResult.RequestUri)"] = [Collections.Queue]::new() } @@ -964,6 +969,40 @@ function Get-WebSocket { } } + if (-not $routedTo -and $handler) { + # If we have an output handler, try to run it and get the output + $routedTo = if ($handler) { + # We may need to run the handler in a `[PowerShell]` command. + $psCmd = + # This is true if we want `NoLanguage` mode. + if ($runspace.LanguageMode -eq 'NoLanguage' -or + $runspacePool.InitialSessionState.LanguageMode -eq 'NoLanguage') { + # (in which case we'll call .GetPowerShell()) + $handler.GetPowerShell() + } elseif ( + # or if we have a runspace or runspace pool + $Runspace -or $RunspacePool + ) { + # (in which case we'll `.Create()` and `.AddScript()`) + [PowerShell]::Create().AddScript($handler, $true) + } + if ($psCmd) { + # If we have a runspace, we'll use that. + if ($Runspace) { + $psCmd.Runspace = $Runspace + } elseif ($RunspacePool) { + # or, alternatively, we can use a runspace pool. + $psCmd.RunspacePool = $RunspacePool + } + # Now, we can invoke the command. + $psCmd.Invoke(@($context)) + } else { + # Otherwise, we'll just run the handler. + $context | . $handler + } + } + } + if (-not $routedTo -and $html) { $routedTo = # If the content is already html, we will use it as is. @@ -1170,33 +1209,7 @@ function Get-WebSocket { if (-not $Broadcast) { $httpListenerJob } - } - - if ($Broadcast) { - if (-not $httpListener.SocketRequests) { - Write-Warning "No WebSocket connections to broadcast to." - } else { - if ($broadcast -is [byte[]]) { - $broadcast = [ArraySegment[byte]]::new($broadcast) - } - if ($broadcast -is [System.ArraySegment[byte]]) { - foreach ($socketRequest in @($httpListener.SocketRequests.Values)) { - $socketRequest.WebSocket.SendAsync($broadcast, 'Binary', 'EndOfMessage', [Threading.CancellationToken]::None) - } - } - else { - foreach ($broadcastItem in $Broadcast) { - $broadcastJson = ConvertTo-Json -InputObject $broadcastItem - $broadcastJsonBytes = $OutputEncoding.GetBytes($broadcastJson) - $broadcastSegment = [ArraySegment[byte]]::new($broadcastJsonBytes) - foreach ($socketRequest in @($httpListener.SocketRequests.Values | . { process { $_ } })) { - $socketRequest.WebSocket.SendAsync($broadcastSegment, 'Text', 'EndOfMessage', [Threading.CancellationToken]::None) - } - } - } - } - - } + } } # If `-Debug` was passed, @@ -1267,7 +1280,58 @@ function Get-WebSocket { ) } $webSocketJob.pstypenames.insert(0, 'WebSocket.ThreadJob') - } + } + + # If we're broadcasting a message + if ($Broadcast) { + # find out who is listening. + $socketList = @( + if ($httpListener.SocketRequests) { + @(foreach ($queue in $httpListener.SocketRequests.Values) { + foreach ($socket in $queue) { + if ($socket.WebSocket.State -eq 'Open') { + $socket.WebSocket + } + } + }) + } + if ($webSocketJob.WebSocket) { + $webSocketJob.WebSocket + } + ) + + # If no one is listening, write a warning. + if (-not $socketList) { + Write-Warning "No one is listening" + } + + # If the broadcast is a scriptblock or command, run it. + if ($Broadcast -is [ScriptBlock] -or + $Broadcast -is [Management.Automation.CommandInfo]) { + $Broadcast = & $Broadcast + } + # If the broadcast is a byte array, convert it to an array segment. + if ($broadcast -is [byte[]]) { + $broadcast = [ArraySegment[byte]]::new($broadcast) + } + + # If the broadcast is an array segment, send it as binary. + if ($broadcast -is [ArraySegment[byte]]) { + foreach ($socket in $socketList) { + $null = $socket.SendAsync($broadcast, 'Binary', 'EndOfMessage', [Threading.CancellationToken]::None) + } + } + else { + # Otherwise, convert the broadcast to JSON. + $broadcastJson = ConvertTo-Json -InputObject $Broadcast + $broadcastJsonBytes = $OutputEncoding.GetBytes($broadcastJson) + $broadcastSegment = [ArraySegment[byte]]::new($broadcastJsonBytes) + foreach ($socket in $socketList) { + $null = $socket.SendAsync($broadcastSegment, 'Text', 'EndOfMessage', [Threading.CancellationToken]::None) + } + } + $Broadcast # emit the broadcast. + } if ($Watch -and $webSocketJob) { do { @@ -1303,8 +1367,8 @@ function Get-WebSocket { } } } - } - elseif ($webSocketJob) { + } + elseif ($webSocketJob -and -not $broadcast) { $webSocketJob } } From c81e6b96b33086ff41b8e54ce92ffe5b8db69285 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sun, 26 Jan 2025 23:03:54 +0000 Subject: [PATCH 067/123] feat: Get-WebSocket -Broadcast and -Handler Improvement ( Fixes #84, Fixes #39 ) Making -Broadcast and -Handler work in both client and server scenarios --- docs/Get-WebSocket.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 831e188..a2ebe75 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -223,7 +223,9 @@ These will be appended onto the `-SocketUrl`. |`[IDictionary]`|false |named |true (ByPropertyName)| #### **Handler** -A ScriptBlock that will handle the output of the WebSocket. +A ScriptBlock that can handle the output of the WebSocket or the Http Request. +This may be run in a separate `-Runspace` or `-RunspacePool`. +The output of the WebSocket or the Context will be passed as an object. |Type |Required|Position|PipelineInput| |---------------|--------|--------|-------------| @@ -274,10 +276,12 @@ The buffer size. Defaults to 16kb. |`[Int32]`|false |named |false | #### **Broadcast** +If provided, will send an object. +If this is a scriptblock, it will be run and the output will be sent. -|Type |Required|Position|PipelineInput| -|------------|--------|--------|-------------| -|`[PSObject]`|false |named |false | +|Type |Required|Position|PipelineInput|Aliases| +|------------|--------|--------|-------------|-------| +|`[PSObject]`|false |named |false |Send | #### **OnConnect** The ScriptBlock to run after connection to a websocket. @@ -460,8 +464,8 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Handshake ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-Handler ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Handshake ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` ```PowerShell -Get-WebSocket -RootUrl [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket -RootUrl [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` From e2f05ee629073b7770c808b88da548f4132b62a2 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 26 Jan 2025 16:16:35 -0800 Subject: [PATCH 068/123] feat: Get-WebSocket -Timeout/-LifeSpan server support ( Fixes #85 ) --- Commands/Get-WebSocket.ps1 | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 4c0c06d..e0993ed 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -184,7 +184,9 @@ function Get-WebSocket { # A collection of query parameters. # These will be appended onto the `-SocketUrl`. + # Multiple values for a single parameter will be passed as multiple parameters. [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] + [Alias('QueryParameters','Query')] [Collections.IDictionary] $QueryParameter, @@ -315,7 +317,7 @@ function Get-WebSocket { $values = $_.values foreach ($key in $keys) { if ($key -isnot [scriptblock]) { - throw "Keys '$key' must be a scriptblock" + throw "Key '$key' must be a scriptblock" } } foreach ($value in $values) { @@ -329,7 +331,10 @@ function Get-WebSocket { [Collections.IDictionary] $WatchFor, - # The timeout for the WebSocket connection. If this is provided, after the timeout elapsed, the WebSocket will be closed. + # The timeout for the WebSocket connection. + # If this is provided, after the timeout elapsed, the WebSocket will be closed. + [Parameter(ValueFromPipelineByPropertyName)] + [Alias('Lifespan')] [TimeSpan] $TimeOut, @@ -810,12 +815,20 @@ function Get-WebSocket { "" } } - ) + ) $httpListener.psobject.properties.add([psnoteproperty]::new('JobVariable',$Variable), $true) + $listenerStartTime = [DateTime]::Now # While the listener is listening, while ($httpListener.IsListening) { + # If we've given a timeout for the listener, + # and the listener has been open for longer than the timeout, + if ($Timeout -and ([DateTime]::Now - $listenerStartTime) -gt $TimeOut) { + # then it's closing time (you don't have to go home but you can't stay here). + $httpListener.Stop() + break + } # get the context asynchronously. $contextAsync = $httpListener.GetContextAsync() # and wait for it to complete. From 7ccaa7f66cece07245325940d0c6dfea482e7ca6 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Mon, 27 Jan 2025 00:17:44 +0000 Subject: [PATCH 069/123] feat: Get-WebSocket -Timeout/-LifeSpan server support ( Fixes #85 ) --- docs/Get-WebSocket.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index a2ebe75..c9d09b5 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -217,10 +217,11 @@ A javascript import map. This allows you to import javascript modules. #### **QueryParameter** A collection of query parameters. These will be appended onto the `-SocketUrl`. +Multiple values for a single parameter will be passed as multiple parameters. -|Type |Required|Position|PipelineInput | -|---------------|--------|--------|---------------------| -|`[IDictionary]`|false |named |true (ByPropertyName)| +|Type |Required|Position|PipelineInput |Aliases | +|---------------|--------|--------|---------------------|-------------------------| +|`[IDictionary]`|false |named |true (ByPropertyName)|QueryParameters
Query| #### **Handler** A ScriptBlock that can handle the output of the WebSocket or the Http Request. @@ -390,11 +391,12 @@ The values of the dictionary are what will happen when a match is found. |`[IDictionary]`|false |named |true (ByPropertyName)|WhereFor
Wherefore| #### **TimeOut** -The timeout for the WebSocket connection. If this is provided, after the timeout elapsed, the WebSocket will be closed. +The timeout for the WebSocket connection. +If this is provided, after the timeout elapsed, the WebSocket will be closed. -|Type |Required|Position|PipelineInput| -|------------|--------|--------|-------------| -|`[TimeSpan]`|false |named |false | +|Type |Required|Position|PipelineInput |Aliases | +|------------|--------|--------|---------------------|--------| +|`[TimeSpan]`|false |named |true (ByPropertyName)|Lifespan| #### **PSTypeName** If provided, will decorate the objects outputted from a websocket job. From 9867f07cac03dea4badf5af8518d6945d33b9975 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Mon, 27 Jan 2025 00:17:47 +0000 Subject: [PATCH 070/123] feat: Get-WebSocket -Timeout/-LifeSpan server support ( Fixes #85 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index fe49969..06892f6 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-26" \ No newline at end of file +"2025-01-27" \ No newline at end of file From df6708aa06c2ca9dcaa85dc8856ffa47d56729a6 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sun, 26 Jan 2025 16:34:58 -0800 Subject: [PATCH 071/123] feat: Get-WebSocket improving piping ( Fixes #87 ) --- Commands/Get-WebSocket.ps1 | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index e0993ed..7e39ac4 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -213,7 +213,7 @@ function Get-WebSocket { [Alias('Headers')] $Header, - # The name of the WebSocket job. + # The name of the WebSocket job. [string] $Name, @@ -222,6 +222,7 @@ function Get-WebSocket { $InitializationScript = {}, # The buffer size. Defaults to 16kb. + [Parameter(ValueFromPipelineByPropertyName)] [int] $BufferSize = 64kb, @@ -346,10 +347,12 @@ function Get-WebSocket { $PSTypeName, # The maximum number of messages to receive before closing the WebSocket. + [Parameter(ValueFromPipelineByPropertyName)] [long] $Maximum, # The throttle limit used when creating background jobs. + [Parameter(ValueFromPipelineByPropertyName)] [int] $ThrottleLimit = 64, @@ -1138,7 +1141,21 @@ function Get-WebSocket { } } - process { + process { + # Sometimes we want to customize the behavior of a command based off of the input object + # So, start off by capturing $_ + $inputObject = $_ + # If the input was a job, we might remap a parameter + if ($inputObject -is [ThreadJob.ThreadJob]) { + if ($inputObject.WebSocket -is [Net.WebSockets.ClientWebSocket] -and + $inputObject.SocketUrl) { + $SocketUrl = $inputObject.SocketUrl + } + if ($inputObject.HttpListener -is [Net.HttpListener] -and + $inputObject.RootUrl) { + $RootUrl = $inputObject.RootUrl + } + } if ((-not $SocketUrl) -and (-not $RootUrl)) { $socketAndListenerJobs = foreach ($job in Get-Job) { From ed9f4e1594f974e23783d1594ee365f0271f6100 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Mon, 27 Jan 2025 00:36:14 +0000 Subject: [PATCH 072/123] feat: Get-WebSocket improving piping ( Fixes #87 ) --- docs/Get-WebSocket.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index c9d09b5..51f8cf3 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -272,9 +272,9 @@ The script to run when the WebSocket job starts. #### **BufferSize** The buffer size. Defaults to 16kb. -|Type |Required|Position|PipelineInput| -|---------|--------|--------|-------------| -|`[Int32]`|false |named |false | +|Type |Required|Position|PipelineInput | +|---------|--------|--------|---------------------| +|`[Int32]`|false |named |true (ByPropertyName)| #### **Broadcast** If provided, will send an object. @@ -409,16 +409,16 @@ This will only decorate objects converted from JSON. #### **Maximum** The maximum number of messages to receive before closing the WebSocket. -|Type |Required|Position|PipelineInput| -|---------|--------|--------|-------------| -|`[Int64]`|false |named |false | +|Type |Required|Position|PipelineInput | +|---------|--------|--------|---------------------| +|`[Int64]`|false |named |true (ByPropertyName)| #### **ThrottleLimit** The throttle limit used when creating background jobs. -|Type |Required|Position|PipelineInput| -|---------|--------|--------|-------------| -|`[Int32]`|false |named |false | +|Type |Required|Position|PipelineInput | +|---------|--------|--------|---------------------| +|`[Int32]`|false |named |true (ByPropertyName)| #### **ConnectionTimeout** The maximum time to wait for a connection to be established. From 4362a3cf6c23148e84397a17de19088c5f2a7e6a Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Mon, 27 Jan 2025 23:05:59 -0800 Subject: [PATCH 073/123] feat: Get-WebSocket improved piping ( Fixes #87 ) Updating test for ThreadJob type --- Commands/Get-WebSocket.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 7e39ac4..7267601 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -1146,7 +1146,7 @@ function Get-WebSocket { # So, start off by capturing $_ $inputObject = $_ # If the input was a job, we might remap a parameter - if ($inputObject -is [ThreadJob.ThreadJob]) { + if ($inputObject -is 'ThreadJob.ThreadJob') { if ($inputObject.WebSocket -is [Net.WebSockets.ClientWebSocket] -and $inputObject.SocketUrl) { $SocketUrl = $inputObject.SocketUrl From 23dd7b445cc523127f48139c3f36bd40c5f4892c Mon Sep 17 00:00:00 2001 From: James Brundage Date: Tue, 28 Jan 2025 07:07:11 +0000 Subject: [PATCH 074/123] feat: Get-WebSocket improved piping ( Fixes #87 ) Updating test for ThreadJob type --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 06892f6..23f03a9 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-27" \ No newline at end of file +"2025-01-28" \ No newline at end of file From 8dddd8625408949a43a42aa4a1cae5ed76cf0065 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Mon, 27 Jan 2025 23:44:05 -0800 Subject: [PATCH 075/123] feat: Get-WebSocket improved piping ( Fixes #87 ) Updating test for Job type --- Commands/Get-WebSocket.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 7267601..0831858 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -1146,7 +1146,7 @@ function Get-WebSocket { # So, start off by capturing $_ $inputObject = $_ # If the input was a job, we might remap a parameter - if ($inputObject -is 'ThreadJob.ThreadJob') { + if ($inputObject -is 'Management.Automation.Job') { if ($inputObject.WebSocket -is [Net.WebSockets.ClientWebSocket] -and $inputObject.SocketUrl) { $SocketUrl = $inputObject.SocketUrl From e931b53280bc8f4f67acf5aa669cae3b720e3d34 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Mon, 27 Jan 2025 23:50:48 -0800 Subject: [PATCH 076/123] feat: Get-WebSocket -NoSubprotocol ( Fixes #83 ) --- Commands/Get-WebSocket.ps1 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 0831858..d6fd3be 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -299,6 +299,11 @@ function Get-WebSocket { [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] [string] $SubProtocol, + + # If set, will not set a subprotocol. This will only work with certain websocket servers, but will not work with an HTTP Listener WebSocket. + [Parameter(ValueFromPipelineByPropertyName,ParameterSetName='WebSocketClient')] + [switch] + $NoSubProtocol, # One or more filters to apply to the output of the WebSocket. # These can be strings, regexes, scriptblocks, or commands. @@ -447,7 +452,7 @@ function Get-WebSocket { if ($SubProtocol) { # and add the subprotocol $ws.Options.AddSubProtocol($SubProtocol) - } else { + } elseif (-not $NoSubProtocol) { $ws.Options.AddSubProtocol('json') } # If there are headers From 843df9e0329fe4b0008dfafcc37dbea62414e5ab Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Tue, 28 Jan 2025 07:51:41 +0000 Subject: [PATCH 077/123] feat: Get-WebSocket -NoSubprotocol ( Fixes #83 ) --- docs/Get-WebSocket.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 51f8cf3..f162d03 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -370,6 +370,13 @@ The subprotocol used by the websocket. If not provided, this will default to `j |----------|--------|--------|---------------------| |`[String]`|false |named |true (ByPropertyName)| +#### **NoSubProtocol** +If set, will not set a subprotocol. This will only work with certain websocket servers, but will not work with an HTTP Listener WebSocket. + +|Type |Required|Position|PipelineInput | +|----------|--------|--------|---------------------| +|`[Switch]`|false |named |true (ByPropertyName)| + #### **Filter** One or more filters to apply to the output of the WebSocket. These can be strings, regexes, scriptblocks, or commands. @@ -466,7 +473,7 @@ RunspacePools allow you to limit the scope of the handler to a pool of runspaces ### Syntax ```PowerShell -Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-Handler ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Handshake ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] +Get-WebSocket [[-SocketUrl] ] [-QueryParameter ] [-Handler ] [-ForwardEvent] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-OnConnect ] [-OnError ] [-OnOutput ] [-OnWarning ] [-Authenticate ] [-Handshake ] [-Watch] [-RawText] [-Binary] [-Force] [-SubProtocol ] [-NoSubProtocol] [-Filter ] [-WatchFor ] [-TimeOut ] [-PSTypeName ] [-Maximum ] [-ThrottleLimit ] [-ConnectionTimeout ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] ``` ```PowerShell Get-WebSocket -RootUrl [-Route ] [-HTML ] [-PaletteName ] [-GoogleFont ] [-CodeFont ] [-JavaScript ] [-ImportMap ] [-Handler ] [-Variable ] [-Header ] [-Name ] [-InitializationScript ] [-BufferSize ] [-Broadcast ] [-Force] [-TimeOut ] [-Maximum ] [-ThrottleLimit ] [-Runspace ] [-RunspacePool ] [-IncludeTotalCount] [-Skip ] [-First ] [] From fa41114d6d9de45d25189bee71b86c7d90ec3649 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Mon, 27 Jan 2025 23:56:30 -0800 Subject: [PATCH 078/123] docs: Get-WebSocket Links ( Fixes #88 ) --- Commands/Get-WebSocket.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index d6fd3be..404eb42 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -8,6 +8,12 @@ function Get-WebSocket { This will create a job that connects to a WebSocket and outputs the results. If the `-Watch` parameter is provided, will output a continous stream of objects. + .LINK + https://websocket.powershellweb.com/Get-WebSocket/ + .LINK + https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket?wt.mc_id=MVP_321542 + .LINK + https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?wt.mc_id=MVP_321542 .EXAMPLE # Create a WebSocket job that connects to a WebSocket and outputs the results. Get-WebSocket -SocketUrl "wss://localhost:9669/" @@ -107,6 +113,7 @@ function Get-WebSocket { Group | Sort Count -Descending | Select -First 10 + #> [CmdletBinding( PositionalBinding=$false, From d6927804b4bf6308ba18ac453e2012073cae9a20 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Tue, 28 Jan 2025 07:57:27 +0000 Subject: [PATCH 079/123] docs: Get-WebSocket Links ( Fixes #88 ) --- docs/Get-WebSocket.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index f162d03..6fa6a24 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -16,6 +16,15 @@ If the `-Watch` parameter is provided, will output a continous stream of objects --- +### Related Links +* [https://websocket.powershellweb.com/Get-WebSocket/](https://websocket.powershellweb.com/Get-WebSocket/) + +* [https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket?wt.mc_id=MVP_321542](https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket?wt.mc_id=MVP_321542) + +* [https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?wt.mc_id=MVP_321542](https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?wt.mc_id=MVP_321542) + +--- + ### Examples Create a WebSocket job that connects to a WebSocket and outputs the results. From 2db937fbe34c4e7226d72e358879d98f731cc9ab Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Tue, 28 Jan 2025 07:57:28 +0000 Subject: [PATCH 080/123] docs: Get-WebSocket Links ( Fixes #88 ) --- docs/_data/Help/Get-WebSocket.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/_data/Help/Get-WebSocket.json b/docs/_data/Help/Get-WebSocket.json index 3d0ed00..93f3110 100644 --- a/docs/_data/Help/Get-WebSocket.json +++ b/docs/_data/Help/Get-WebSocket.json @@ -28,7 +28,11 @@ "Outputs": [ null ], - "Links": [], + "Links": [ + "https://websocket.powershellweb.com/Get-WebSocket/", + "https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket?wt.mc_id=MVP_321542", + "https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?wt.mc_id=MVP_321542" + ], "Examples": [ { "Title": "EXAMPLE 1", From b477d4c61bc72a389ff0fdc3b16de3ed8e57222d Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Tue, 28 Jan 2025 21:10:50 -0800 Subject: [PATCH 081/123] feat: WebSocket.ThreadJob formatting ( Fixes #91 ) --- Types/WebSocket.ThreadJob/WebSocket.ThreadJob.format.ps1 | 1 + 1 file changed, 1 insertion(+) create mode 100644 Types/WebSocket.ThreadJob/WebSocket.ThreadJob.format.ps1 diff --git a/Types/WebSocket.ThreadJob/WebSocket.ThreadJob.format.ps1 b/Types/WebSocket.ThreadJob/WebSocket.ThreadJob.format.ps1 new file mode 100644 index 0000000..22e7f8e --- /dev/null +++ b/Types/WebSocket.ThreadJob/WebSocket.ThreadJob.format.ps1 @@ -0,0 +1 @@ +Write-FormatView -TypeName WebSocket.ThreadJob -Property Id, Name, State -AutoSize From 70508d2118e5e2f065e44a96a65b3fb5007ec025 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Wed, 29 Jan 2025 05:11:51 +0000 Subject: [PATCH 082/123] feat: WebSocket.ThreadJob formatting ( Fixes #91 ) --- WebSocket.format.ps1xml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 WebSocket.format.ps1xml diff --git a/WebSocket.format.ps1xml b/WebSocket.format.ps1xml new file mode 100644 index 0000000..9c8c52b --- /dev/null +++ b/WebSocket.format.ps1xml @@ -0,0 +1,37 @@ + + + + + WebSocket.ThreadJob + + WebSocket.ThreadJob + + + + + + + + + + + + + + + + Id + + + Name + + + State + + + + + + + + \ No newline at end of file From 9da136c9add1e433695d4dec738a5ef6eaea8878 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Wed, 29 Jan 2025 05:11:51 +0000 Subject: [PATCH 083/123] feat: WebSocket.ThreadJob formatting ( Fixes #91 ) --- WebSocket.types.ps1xml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 WebSocket.types.ps1xml diff --git a/WebSocket.types.ps1xml b/WebSocket.types.ps1xml new file mode 100644 index 0000000..d548790 --- /dev/null +++ b/WebSocket.types.ps1xml @@ -0,0 +1,8 @@ + + + + WebSocket.ThreadJob + + + + \ No newline at end of file From 627dccafa1ae352a0968fcd181bde065df89f00f Mon Sep 17 00:00:00 2001 From: James Brundage Date: Wed, 29 Jan 2025 05:11:58 +0000 Subject: [PATCH 084/123] feat: WebSocket.ThreadJob formatting ( Fixes #91 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 23f03a9..4e07e16 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-28" \ No newline at end of file +"2025-01-29" \ No newline at end of file From 43810e4587d482036429f543a64b6f7c80c38994 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Tue, 28 Jan 2025 21:14:00 -0800 Subject: [PATCH 085/123] feat: WebSocket.ThreadJob formatting ( Fixes #91 ) Including formatting in module --- WebSocket.psd1 | 1 + 1 file changed, 1 insertion(+) diff --git a/WebSocket.psd1 b/WebSocket.psd1 index fe667db..b7c2160 100644 --- a/WebSocket.psd1 +++ b/WebSocket.psd1 @@ -8,6 +8,7 @@ Description = 'Work with WebSockets in PowerShell' FunctionsToExport = @('Get-WebSocket') AliasesToExport = @('WebSocket','ws','wss') + FormatsToProcess = @('WebSocket.format.ps1') PrivateData = @{ PSData = @{ Tags = @('WebSocket', 'WebSockets', 'Networking', 'Web') From c2a0ec01ce6edd59bc0131e1b9587e51af9538ad Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Tue, 28 Jan 2025 21:16:53 -0800 Subject: [PATCH 086/123] feat: WebSocket.ThreadJob formatting ( Fixes #91 ) Including formatting in module (fixing extension typo) --- WebSocket.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebSocket.psd1 b/WebSocket.psd1 index b7c2160..87c6895 100644 --- a/WebSocket.psd1 +++ b/WebSocket.psd1 @@ -8,7 +8,7 @@ Description = 'Work with WebSockets in PowerShell' FunctionsToExport = @('Get-WebSocket') AliasesToExport = @('WebSocket','ws','wss') - FormatsToProcess = @('WebSocket.format.ps1') + FormatsToProcess = @('WebSocket.format.ps1xml') PrivateData = @{ PSData = @{ Tags = @('WebSocket', 'WebSockets', 'Networking', 'Web') From 7ab7f2fa514f8da0850753b9a4d0c18ce020093f Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Mon, 10 Feb 2025 23:48:36 -0800 Subject: [PATCH 087/123] fix: WebSocket Container fix ( Fixes #96 ) --- Container.init.ps1 | 15 ++++++--------- Container.start.ps1 | 5 +++-- Container.stop.ps1 | 2 +- Dockerfile | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Container.init.ps1 b/Container.init.ps1 index 352fe81..6d0c8cf 100644 --- a/Container.init.ps1 +++ b/Container.init.ps1 @@ -10,19 +10,16 @@ # Thank you Microsoft! Thank you PowerShell! Thank you Docker! FROM mcr.microsoft.com/powershell # Set the shell to PowerShell (thanks again, Docker!) - SHELL ["/bin/pwsh", "-nologo", "-command"] - # Run the initialization script. This will do all remaining initialization in a single layer. - RUN --mount=type=bind,src=./,target=/Initialize ./Initialize/Container.init.ps1 + # Copy the module into the container + RUN --mount=type=bind,src=./,target=/Initialize /bin/pwsh -nologo -command /Initialize/Container.init.ps1 + # Set the entrypoint to the script we just created. + ENTRYPOINT [ "/bin/pwsh","-nologo","-noexit","-file","/Container.start.ps1" ] ~~~ The scripts arguments can be provided with either an `ARG` or `ENV` instruction in the Dockerfile. -.NOTES - Did you know that in PowerShell you can 'use' namespaces that do not really exist? - This seems like a nice way to describe a relationship to a container image. - That is why this file is using the namespace 'mcr.microsoft.com/powershell'. - (this does nothing, but most likely will be used in the future) #> -using namespace 'mcr.microsoft.com/powershell AS powerShell' + +#use container mcr.microsoft.com/powershell param( # The name of the module to be installed. diff --git a/Container.start.ps1 b/Container.start.ps1 index 1f83e5c..bcde545 100644 --- a/Container.start.ps1 +++ b/Container.start.ps1 @@ -24,7 +24,8 @@ That is why this file is using the namespace 'mcr.microsoft.com/powershell'. (this does nothing, but most likely will be used in the future) #> -using namespace 'ghcr.io/powershellweb/websocket' + +#use container ghcr.io/powershellweb/websocket param() @@ -62,7 +63,7 @@ if ($args) { # If a single drive is mounted, start the socket files. $webSocketFiles = $mountedFolders | Get-ChildItem -Filter *.WebSocket.ps1 foreach ($webSocketFile in $webSocketFiles) { - Start-ThreadJob -Name $webSocketFile.Name -ScriptBlock {param($webSocketFile) . $using:webSocketFile.FullName } -ArgumentList $webSocketFile + Start-ThreadJob -Name $webSocketFile.Name -ScriptBlock { . $using:webSocketFile.FullName } . $webSocketFile.FullName } } diff --git a/Container.stop.ps1 b/Container.stop.ps1 index edb66db..bb6dfe6 100644 --- a/Container.stop.ps1 +++ b/Container.stop.ps1 @@ -6,4 +6,4 @@ It can be used to perform any necessary cleanup before the container is stopped. #> -"Container now exiting, thank you for using WebSocket!" | Out-Host +"Container now exiting, thank you for using $env:ModuleName!" | Out-Host diff --git a/Dockerfile b/Dockerfile index 6df5630..be769c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Thank you Microsoft! Thank you PowerShell! Thank you Docker! -FROM mcr.microsoft.com/powershell AS powershell +FROM mcr.microsoft.com/powershell # Set the module name to the name of the module we are building ENV ModuleName=WebSocket From 1f1ae446c611165f739b79589ef085fc48c8b855 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Tue, 11 Feb 2025 07:51:04 +0000 Subject: [PATCH 088/123] fix: WebSocket Container fix ( Fixes #96 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 4e07e16..ed8864e 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-01-29" \ No newline at end of file +"2025-02-11" \ No newline at end of file From 3e9ff6641e18324c8f72c0decf81f1aa7f28e889 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Mon, 17 Feb 2025 19:13:50 -0800 Subject: [PATCH 089/123] fix: Adding WebSocket pseudotypes ( Fixes #92, Fixes #93 ) --- Commands/Get-WebSocket.ps1 | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index 404eb42..db8f396 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -16,12 +16,13 @@ function Get-WebSocket { https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistener?wt.mc_id=MVP_321542 .EXAMPLE # Create a WebSocket job that connects to a WebSocket and outputs the results. - Get-WebSocket -SocketUrl "wss://localhost:9669/" + $socketServer = Get-WebSocket -RootUrl "http://localhost:8387/" -HTML "

WebSocket Server

" + $socketClient = Get-WebSocket -SocketUrl "ws://localhost:8387/" .EXAMPLE # Get is the default verb, so we can just say WebSocket. # `-Watch` will output a continous stream of objects from the websocket. - # For example, let's Watch BlueSky, but just the text. - websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch | + # For example, let's Watch BlueSky, but just the text + websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch -Maximum 1kb | % { $_.commit.record.text } @@ -33,14 +34,19 @@ function Get-WebSocket { "wantedCollections=app.bsky.feed.post" ) -join '&')" websocket $blueSkySocketUrl -Watch | - % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} - .EXAMPLE + % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} -Max 1kb + .EXAMPLE + # Watch continuously in a background job. websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post .EXAMPLE - websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug + # Watch the first message in -Debug mode. + # This allows you to literally debug the WebSocket messages as they are encountered. + websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ + wantedCollections = 'app.bsky.feed.post' + } -Max 1 -Debug .EXAMPLE # Watch BlueSky, but just the emoji - websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail | + websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail -Max 1kb | Foreach-Object { $in = $_ if ($in.commit.record.text -match '[\p{IsHighSurrogates}\p{IsLowSurrogates}]+') { @@ -113,7 +119,6 @@ function Get-WebSocket { Group | Sort Count -Descending | Select -First 10 - #> [CmdletBinding( PositionalBinding=$false, @@ -1231,7 +1236,9 @@ function Get-WebSocket { $httpListenerJob = $existingJob $httpListener = $existingJob.HttpListener } else { - $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -ArgumentList $Variable @StartThreadJobSplat + $httpListenerJob = Start-ThreadJob -ScriptBlock $SocketServerJob -Name "$RootUrl" -ArgumentList $Variable @StartThreadJobSplat + $httpListenerJob.pstypenames.insert(0, 'WebSocket.ThreadJob') + $httpListenerJob.pstypenames.insert(0, 'WebSocket.Server.ThreadJob') } } @@ -1322,6 +1329,7 @@ function Get-WebSocket { ) } $webSocketJob.pstypenames.insert(0, 'WebSocket.ThreadJob') + $webSocketJob.pstypenames.insert(0, 'WebSocket.Client.ThreadJob') } # If we're broadcasting a message From 717eb2048be5dfcbea2d25198b3fcbb9b80629ac Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Tue, 18 Feb 2025 03:14:46 +0000 Subject: [PATCH 090/123] fix: Adding WebSocket pseudotypes ( Fixes #92, Fixes #93 ) --- README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4f856aa..b70c164 100644 --- a/README.md +++ b/README.md @@ -46,15 +46,16 @@ To stop watching a websocket, simply stop the background job. ~~~powershell # Create a WebSocket job that connects to a WebSocket and outputs the results. -Get-WebSocket -SocketUrl "wss://localhost:9669/" +$socketServer = Get-WebSocket -RootUrl "http://localhost:8387/" -HTML "

WebSocket Server

" +$socketClient = Get-WebSocket -SocketUrl "ws://localhost:8387/" ~~~ #### Get-WebSocket Example 2 ~~~powershell # Get is the default verb, so we can just say WebSocket. # `-Watch` will output a continous stream of objects from the websocket. -# For example, let's Watch BlueSky, but just the text. -websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch | +# For example, let's Watch BlueSky, but just the text +websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch -Maximum 1kb | % { $_.commit.record.text } @@ -69,23 +70,28 @@ $blueSkySocketUrl = "wss://jetstream2.us-$( "wantedCollections=app.bsky.feed.post" ) -join '&')" websocket $blueSkySocketUrl -Watch | - % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} + % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} -Max 1kb ~~~ #### Get-WebSocket Example 4 ~~~powershell +# Watch continuously in a background job. websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post ~~~ #### Get-WebSocket Example 5 ~~~powershell -websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug +# Watch the first message in -Debug mode. +# This allows you to literally debug the WebSocket messages as they are encountered. +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ + wantedCollections = 'app.bsky.feed.post' +} -Max 1 -Debug ~~~ #### Get-WebSocket Example 6 ~~~powershell # Watch BlueSky, but just the emoji -websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail | +websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail -Max 1kb | Foreach-Object { $in = $_ if ($in.commit.record.text -match '[\p{IsHighSurrogates}\p{IsLowSurrogates}]+') { From ff020774d2d3810fbef445b60dee229f86a19698 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Tue, 18 Feb 2025 03:14:54 +0000 Subject: [PATCH 091/123] fix: Adding WebSocket pseudotypes ( Fixes #92, Fixes #93 ) --- docs/Get-WebSocket.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 6fa6a24..4169655 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -29,14 +29,15 @@ If the `-Watch` parameter is provided, will output a continous stream of objects Create a WebSocket job that connects to a WebSocket and outputs the results. ```PowerShell -Get-WebSocket -SocketUrl "wss://localhost:9669/" +$socketServer = Get-WebSocket -RootUrl "http://localhost:8387/" -HTML "

WebSocket Server

" +$socketClient = Get-WebSocket -SocketUrl "ws://localhost:8387/" ``` Get is the default verb, so we can just say WebSocket. `-Watch` will output a continous stream of objects from the websocket. -For example, let's Watch BlueSky, but just the text. +For example, let's Watch BlueSky, but just the text ```PowerShell -websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch | +websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch -Maximum 1kb | % { $_.commit.record.text } @@ -50,22 +51,25 @@ $blueSkySocketUrl = "wss://jetstream2.us-$( "wantedCollections=app.bsky.feed.post" ) -join '&')" websocket $blueSkySocketUrl -Watch | - % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} + % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} -Max 1kb ``` -> EXAMPLE 4 +Watch continuously in a background job. ```PowerShell websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post ``` -> EXAMPLE 5 +Watch the first message in -Debug mode. +This allows you to literally debug the WebSocket messages as they are encountered. ```PowerShell -websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ + wantedCollections = 'app.bsky.feed.post' +} -Max 1 -Debug ``` Watch BlueSky, but just the emoji ```PowerShell -websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail | +websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail -Max 1kb | Foreach-Object { $in = $_ if ($in.commit.record.text -match '[\p{IsHighSurrogates}\p{IsLowSurrogates}]+') { From 268ee36db1636da66dd6cef44601b6dc0bc57b67 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Tue, 18 Feb 2025 03:14:54 +0000 Subject: [PATCH 092/123] fix: Adding WebSocket pseudotypes ( Fixes #92, Fixes #93 ) --- docs/_data/Help/Get-WebSocket.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_data/Help/Get-WebSocket.json b/docs/_data/Help/Get-WebSocket.json index 93f3110..17427ba 100644 --- a/docs/_data/Help/Get-WebSocket.json +++ b/docs/_data/Help/Get-WebSocket.json @@ -37,32 +37,32 @@ { "Title": "EXAMPLE 1", "Markdown": "Create a WebSocket job that connects to a WebSocket and outputs the results.", - "Code": "Get-WebSocket -SocketUrl \"wss://localhost:9669/\"" + "Code": "$socketServer = Get-WebSocket -RootUrl \"http://localhost:8387/\" -HTML \"

WebSocket Server

\"\n$socketClient = Get-WebSocket -SocketUrl \"ws://localhost:8387/\"" }, { "Title": "EXAMPLE 2", - "Markdown": "Get is the default verb, so we can just say WebSocket.\n`-Watch` will output a continous stream of objects from the websocket.\nFor example, let's Watch BlueSky, but just the text. ", - "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch |\n % { \n $_.commit.record.text\n }" + "Markdown": "Get is the default verb, so we can just say WebSocket.\n`-Watch` will output a continous stream of objects from the websocket.\nFor example, let's Watch BlueSky, but just the text ", + "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch -Maximum 1kb |\n % { \n $_.commit.record.text\n }" }, { "Title": "EXAMPLE 3", "Markdown": "Watch BlueSky, but just the text and spacing", - "Code": "$blueSkySocketUrl = \"wss://jetstream2.us-$(\n 'east','west'|Get-Random\n).bsky.network/subscribe?$(@(\n \"wantedCollections=app.bsky.feed.post\"\n) -join '&')\"\nwebsocket $blueSkySocketUrl -Watch | \n % { Write-Host \"$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))\"}" + "Code": "$blueSkySocketUrl = \"wss://jetstream2.us-$(\n 'east','west'|Get-Random\n).bsky.network/subscribe?$(@(\n \"wantedCollections=app.bsky.feed.post\"\n) -join '&')\"\nwebsocket $blueSkySocketUrl -Watch | \n % { Write-Host \"$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))\"} -Max 1kb" }, { "Title": "EXAMPLE 4", - "Markdown": "", + "Markdown": "Watch continuously in a background job.", "Code": "websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post" }, { "Title": "EXAMPLE 5", - "Markdown": "", - "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug" + "Markdown": "Watch the first message in -Debug mode. \nThis allows you to literally debug the WebSocket messages as they are encountered.", + "Code": "websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{\n wantedCollections = 'app.bsky.feed.post'\n} -Max 1 -Debug" }, { "Title": "EXAMPLE 6", "Markdown": "Watch BlueSky, but just the emoji", - "Code": "websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail |\n Foreach-Object {\n $in = $_\n if ($in.commit.record.text -match '[\\p{IsHighSurrogates}\\p{IsLowSurrogates}]+') {\n Write-Host $matches.0 -NoNewline\n }\n }" + "Code": "websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail -Max 1kb |\n Foreach-Object {\n $in = $_\n if ($in.commit.record.text -match '[\\p{IsHighSurrogates}\\p{IsLowSurrogates}]+') {\n Write-Host $matches.0 -NoNewline\n }\n }" }, { "Title": "EXAMPLE 7", From 69c4ec0931e4d653a9eaad643eb2242fdd88b6b1 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Tue, 18 Feb 2025 03:14:55 +0000 Subject: [PATCH 093/123] fix: Adding WebSocket pseudotypes ( Fixes #92, Fixes #93 ) --- docs/README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index 71aa8f6..34b79a7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,15 +46,16 @@ To stop watching a websocket, simply stop the background job. ~~~powershell # Create a WebSocket job that connects to a WebSocket and outputs the results. -Get-WebSocket -SocketUrl "wss://localhost:9669/" +$socketServer = Get-WebSocket -RootUrl "http://localhost:8387/" -HTML "

WebSocket Server

" +$socketClient = Get-WebSocket -SocketUrl "ws://localhost:8387/" ~~~ #### Get-WebSocket Example 2 ~~~powershell # Get is the default verb, so we can just say WebSocket. # `-Watch` will output a continous stream of objects from the websocket. -# For example, let's Watch BlueSky, but just the text. -websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch | +# For example, let's Watch BlueSky, but just the text +websocket wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Watch -Maximum 1kb | % { $_.commit.record.text } @@ -69,23 +70,28 @@ $blueSkySocketUrl = "wss://jetstream2.us-$( "wantedCollections=app.bsky.feed.post" ) -join '&')" websocket $blueSkySocketUrl -Watch | - % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} + % { Write-Host "$(' ' * (Get-Random -Max 10))$($_.commit.record.text)$($(' ' * (Get-Random -Max 10)))"} -Max 1kb ~~~ #### Get-WebSocket Example 4 ~~~powershell +# Watch continuously in a background job. websocket wss://jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post ~~~ #### Get-WebSocket Example 5 ~~~powershell -websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ wantedCollections = 'app.bsky.feed.post' } -Max 1 -Debug +# Watch the first message in -Debug mode. +# This allows you to literally debug the WebSocket messages as they are encountered. +websocket wss://jetstream2.us-west.bsky.network/subscribe -QueryParameter @{ + wantedCollections = 'app.bsky.feed.post' +} -Max 1 -Debug ~~~ #### Get-WebSocket Example 6 ~~~powershell # Watch BlueSky, but just the emoji -websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail | +websocket jetstream2.us-east.bsky.network/subscribe?wantedCollections=app.bsky.feed.post -Tail -Max 1kb | Foreach-Object { $in = $_ if ($in.commit.record.text -match '[\p{IsHighSurrogates}\p{IsLowSurrogates}]+') { From 6ace3f1a4101abe67e545159aaf5033447971d41 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Tue, 18 Feb 2025 03:14:57 +0000 Subject: [PATCH 094/123] fix: Adding WebSocket pseudotypes ( Fixes #92, Fixes #93 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index ed8864e..6f42cc9 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-02-11" \ No newline at end of file +"2025-02-18" \ No newline at end of file From 70a4504d74f2046f6c3a0f78f3d32c9f955a5022 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Tue, 18 Feb 2025 16:08:34 -0800 Subject: [PATCH 095/123] fix: PublishTestResults workflow step version bump ( Fixes #97 ) Switching to @main instead of a pinned version --- .github/workflows/BuildWebSocket.yml | 2 +- Build/GitHub/Steps/PublishTestResults.psd1 | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Build/GitHub/Steps/PublishTestResults.psd1 diff --git a/.github/workflows/BuildWebSocket.yml b/.github/workflows/BuildWebSocket.yml index 2f67fd1..dd87ef2 100644 --- a/.github/workflows/BuildWebSocket.yml +++ b/.github/workflows/BuildWebSocket.yml @@ -103,7 +103,7 @@ jobs: } } @Parameters - name: PublishTestResults - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@main with: name: PesterResults path: '**.TestResults.xml' diff --git a/Build/GitHub/Steps/PublishTestResults.psd1 b/Build/GitHub/Steps/PublishTestResults.psd1 new file mode 100644 index 0000000..e8111e8 --- /dev/null +++ b/Build/GitHub/Steps/PublishTestResults.psd1 @@ -0,0 +1,10 @@ +@{ + name = 'PublishTestResults' + uses = 'actions/upload-artifact@main' + with = @{ + name = 'PesterResults' + path = '**.TestResults.xml' + } + if = '${{always()}}' +} + From 4c044c5ee53d1047eb1830fc260baff2f0f4dfeb Mon Sep 17 00:00:00 2001 From: James Brundage Date: Wed, 19 Feb 2025 00:09:49 +0000 Subject: [PATCH 096/123] fix: PublishTestResults workflow step version bump ( Fixes #97 ) Switching to @main instead of a pinned version --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 6f42cc9..f88ea75 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-02-18" \ No newline at end of file +"2025-02-19" \ No newline at end of file From 47db49d4de121b02d11e4406627cc8f421a295fa Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Tue, 18 Feb 2025 16:45:48 -0800 Subject: [PATCH 097/123] docs: Adding PowerShell Gallery badge to WebSocket --- README.ps.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.ps.md b/README.ps.md index c3a7d5f..397739e 100644 --- a/README.ps.md +++ b/README.ps.md @@ -1,5 +1,9 @@
WebSocket Logo (Animated) +
+ + +
# WebSocket From 2dce5fbc6b1ae555be0538680f52eca0327855c7 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Wed, 19 Feb 2025 00:46:55 +0000 Subject: [PATCH 098/123] docs: Adding PowerShell Gallery badge to WebSocket --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b70c164..a9d86d3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@
WebSocket Logo (Animated) +
+ + +
# WebSocket From e05d5c976bbdd7719bb1189f47ad6b04b742ed08 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Wed, 19 Feb 2025 00:47:04 +0000 Subject: [PATCH 099/123] docs: Adding PowerShell Gallery badge to WebSocket --- docs/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/README.md b/docs/README.md index 34b79a7..7a79a38 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,9 @@
WebSocket Logo (Animated) +
+ + +
# WebSocket From 9bb9763b8973704545d79795d86620311e876a41 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Thu, 6 Mar 2025 19:27:55 -0800 Subject: [PATCH 100/123] feat: WebSocket.Client.ThreadJob.Send ( Fixes #89 ) --- .../WebSocket.Client.ThreadJob/Send.ps1 | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Types/WebSocket.ThreadJob/WebSocket.Client.ThreadJob/Send.ps1 diff --git a/Types/WebSocket.ThreadJob/WebSocket.Client.ThreadJob/Send.ps1 b/Types/WebSocket.ThreadJob/WebSocket.Client.ThreadJob/Send.ps1 new file mode 100644 index 0000000..facfe86 --- /dev/null +++ b/Types/WebSocket.ThreadJob/WebSocket.Client.ThreadJob/Send.ps1 @@ -0,0 +1,38 @@ +<# +.SYNOPSIS + Sends a WebSocket message. +.DESCRIPTION + Sends a message to a WebSocket server. +#> +param( +[PSObject] +$Message +) + +function sendMessage { + param([Parameter(ValueFromPipeline)]$msg) + process { + if ($msg -is [byte[]]) { + [ArraySegment[byte]]$messageSegment = [ArraySegment[byte]]::new($msg) + if ($null -ne $messageSegment -and $this.WebSocket.SendAsync) { + $this.WebSocket.SendAsync($messageSegment, 'Binary', 'EndOfMessage',[Threading.Cancellationtoken]::None) + } + } else { + $jsonMessage = ConvertTo-Json -InputObject $msg + $messageSegment = [ArraySegment[byte]]::new($OutputEncoding.GetBytes($jsonMessage)) + if ($null -ne $jsonMessage -and $this.WebSocket.SendAsync) { + $this.WebSocket.SendAsync($messageSegment, 'Text', 'EndOfMessage', [Threading.Cancellationtoken]::None) + } + } + } +} + +if ($message -is [Collections.IList] -and $message -isnot [byte[]]) { + $Message | sendMessage +} else { + sendMessage -msg $Message +} + + + + From 07850e05c2747df6aa80685c77bc4825c33b6c15 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 7 Mar 2025 03:29:00 +0000 Subject: [PATCH 101/123] feat: WebSocket.Client.ThreadJob.Send ( Fixes #89 ) --- WebSocket.types.ps1xml | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/WebSocket.types.ps1xml b/WebSocket.types.ps1xml index d548790..e1f5fb5 100644 --- a/WebSocket.types.ps1xml +++ b/WebSocket.types.ps1xml @@ -1,5 +1,54 @@ + + WebSocket.Client.ThreadJob + + + Send + + + + WebSocket.ThreadJob From 98bb7c0c5f1312b87f61c2a7500a28c7f1d76c65 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Fri, 7 Mar 2025 03:29:10 +0000 Subject: [PATCH 102/123] feat: WebSocket.Client.ThreadJob.Send ( Fixes #89 ) --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index f88ea75..00b8070 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-02-19" \ No newline at end of file +"2025-03-07" \ No newline at end of file From c9a4eb85795e85c52b18969e02f43f4eb2eb87b2 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Fri, 7 Mar 2025 11:50:43 -0800 Subject: [PATCH 103/123] feat: WebSocket.ThreadJob.Pop ( Fixes #98 ) --- Types/WebSocket.ThreadJob/Pop.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Types/WebSocket.ThreadJob/Pop.ps1 diff --git a/Types/WebSocket.ThreadJob/Pop.ps1 b/Types/WebSocket.ThreadJob/Pop.ps1 new file mode 100644 index 0000000..5b64908 --- /dev/null +++ b/Types/WebSocket.ThreadJob/Pop.ps1 @@ -0,0 +1,6 @@ +param() + +if ($this.Output.Count -gt 0) { + $this.Output[0] + $this.Output.RemoveAt(0) +} \ No newline at end of file From e71b4f51cf6158394f98898e2dd331ef9514f1d3 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 7 Mar 2025 19:51:49 +0000 Subject: [PATCH 104/123] feat: WebSocket.ThreadJob.Pop ( Fixes #98 ) --- WebSocket.types.ps1xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/WebSocket.types.ps1xml b/WebSocket.types.ps1xml index e1f5fb5..0282123 100644 --- a/WebSocket.types.ps1xml +++ b/WebSocket.types.ps1xml @@ -3,6 +3,17 @@ WebSocket.Client.ThreadJob + + Pop + + Send + \ No newline at end of file From 5b1fb5cc6460cfb6a7847b38455e2138ce5b0661 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Fri, 7 Mar 2025 11:54:33 -0800 Subject: [PATCH 105/123] feat: WebSocket.Server.ThreadJob.Send ( Fixes #94 ) --- .../WebSocket.Server.ThreadJob/Send.ps1 | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Types/WebSocket.ThreadJob/WebSocket.Server.ThreadJob/Send.ps1 diff --git a/Types/WebSocket.ThreadJob/WebSocket.Server.ThreadJob/Send.ps1 b/Types/WebSocket.ThreadJob/WebSocket.Server.ThreadJob/Send.ps1 new file mode 100644 index 0000000..9782265 --- /dev/null +++ b/Types/WebSocket.ThreadJob/WebSocket.Server.ThreadJob/Send.ps1 @@ -0,0 +1,69 @@ +<# +.SYNOPSIS + Sends a WebSocket message. +.DESCRIPTION + Sends a message from a WebSocket server. +#> +param( +[PSObject] +$Message, + +[string] +$Pattern +) + +function sendMessage { + param([Parameter(ValueFromPipeline)]$msg, [PSObject[]]$Sockets) + process { + if ($msg -is [byte[]]) { + $messageSegment = [ArraySegment[byte]]::new($msg) + foreach ($socket in $sockets) { + if ($null -ne $messageSegment -and $socket.SendAsync) { + $null = $socket.SendAsync($messageSegment, 'Binary', 'EndOfMessage',[Threading.Cancellationtoken]::None) + } + } + + } else { + $jsonMessage = ConvertTo-Json -InputObject $msg + $messageSegment = [ArraySegment[byte]]::new($OutputEncoding.GetBytes($jsonMessage)) + foreach ($socket in $sockets) { + if ($null -ne $messageSegment -and $socket.SendAsync) { + $null = $socket.SendAsync($messageSegment, 'Binary', 'EndOfMessage',[Threading.Cancellationtoken]::None) + } + } + } + $msg + } +} + +$patternAsRegex = $pattern -as [regex] +$socketList = @( + foreach ($socketConnection in $this.HttpListener.SocketRequests.Values) { + if ( + $patternAsRegex -and + $socketConnection.WebSocketContext.RequestUri -match $pattern + ) { + $socketConnection.WebSocket + } + elseif ( + $pattern -and + $socketConnection.WebSocketContext.RequestUri -like $pattern + ) { + $socketConnection.WebSocket + } + else { + $socketConnection.WebSocket + } + } +) + + +if ($message -is [Collections.IList] -and $message -isnot [byte[]]) { + $Message | sendMessage -Sockets $socketList +} else { + sendMessage -msg $Message -Sockets $socketList +} + + + + From a1f46968e53016f54d3147f0731d45a26546d2df Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 7 Mar 2025 19:55:28 +0000 Subject: [PATCH 106/123] feat: WebSocket.Server.ThreadJob.Send ( Fixes #94 ) --- WebSocket.types.ps1xml | 91 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/WebSocket.types.ps1xml b/WebSocket.types.ps1xml index 0282123..1315d63 100644 --- a/WebSocket.types.ps1xml +++ b/WebSocket.types.ps1xml @@ -56,6 +56,97 @@ if ($message -is [Collections.IList] -and $message -isnot [byte[]]) { + + + + + + WebSocket.Server.ThreadJob + + + Pop + + + + Send + From ec7e5d0f5fb48ed7dc8249dffa1b3b1322a48d92 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Fri, 7 Mar 2025 13:54:38 -0800 Subject: [PATCH 107/123] feat: WebSocket.Server.ThreadJob.Clear ( Fixes #99 ) --- Types/WebSocket.ThreadJob/Clear.ps1 | 1 + 1 file changed, 1 insertion(+) create mode 100644 Types/WebSocket.ThreadJob/Clear.ps1 diff --git a/Types/WebSocket.ThreadJob/Clear.ps1 b/Types/WebSocket.ThreadJob/Clear.ps1 new file mode 100644 index 0000000..ed0c6e2 --- /dev/null +++ b/Types/WebSocket.ThreadJob/Clear.ps1 @@ -0,0 +1 @@ +$this.Output.Clear() \ No newline at end of file From 0232f2eb3725cf2a6abf1c1e708dd913d183b56e Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 7 Mar 2025 21:55:49 +0000 Subject: [PATCH 108/123] feat: WebSocket.Server.ThreadJob.Clear ( Fixes #99 ) --- WebSocket.types.ps1xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/WebSocket.types.ps1xml b/WebSocket.types.ps1xml index 1315d63..db9dfb3 100644 --- a/WebSocket.types.ps1xml +++ b/WebSocket.types.ps1xml @@ -3,6 +3,12 @@ WebSocket.Client.ThreadJob + + Clear + + Pop + Pop + Pop + + Receive + + Send + + Receive + + Send + + Receive + + \ No newline at end of file From 950ae7f91a0039c3e38211d0ac1179f158b1c6a3 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Fri, 7 Mar 2025 14:01:38 -0800 Subject: [PATCH 111/123] feat: Adding WebSocket.types.ps1xml to manifest ( Fixes #90 ) --- WebSocket.psd1 | 1 + 1 file changed, 1 insertion(+) diff --git a/WebSocket.psd1 b/WebSocket.psd1 index 87c6895..9a140de 100644 --- a/WebSocket.psd1 +++ b/WebSocket.psd1 @@ -9,6 +9,7 @@ FunctionsToExport = @('Get-WebSocket') AliasesToExport = @('WebSocket','ws','wss') FormatsToProcess = @('WebSocket.format.ps1xml') + TypesToProcess = @('WebSocket.types.ps1xml') PrivateData = @{ PSData = @{ Tags = @('WebSocket', 'WebSockets', 'Networking', 'Web') From 7d22a4075400ac60464b9578549bb271c2d644b2 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 7 Mar 2025 22:03:49 +0000 Subject: [PATCH 112/123] feat: Adding WebSocket.types.ps1xml to manifest ( Fixes #90 ) --- docs/WebSocket/Client/ThreadJob/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/WebSocket/Client/ThreadJob/README.md diff --git a/docs/WebSocket/Client/ThreadJob/README.md b/docs/WebSocket/Client/ThreadJob/README.md new file mode 100644 index 0000000..a15b69a --- /dev/null +++ b/docs/WebSocket/Client/ThreadJob/README.md @@ -0,0 +1,7 @@ +## WebSocket.Client.ThreadJob + + +### Script Methods + + +* [Send()](Send.md) From bc82912cd9b3e270ca44f3f1a82c8f80c310418b Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 7 Mar 2025 22:03:49 +0000 Subject: [PATCH 113/123] feat: Adding WebSocket.types.ps1xml to manifest ( Fixes #90 ) --- docs/WebSocket/Client/ThreadJob/Send.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/WebSocket/Client/ThreadJob/Send.md diff --git a/docs/WebSocket/Client/ThreadJob/Send.md b/docs/WebSocket/Client/ThreadJob/Send.md new file mode 100644 index 0000000..c18eca5 --- /dev/null +++ b/docs/WebSocket/Client/ThreadJob/Send.md @@ -0,0 +1,22 @@ +WebSocket.Client.ThreadJob.Send() +--------------------------------- + +### Synopsis +Sends a WebSocket message. + +--- + +### Description + +Sends a message to a WebSocket server. + +--- + +### Parameters +#### **Message** + +|Type |Required|Position|PipelineInput| +|------------|--------|--------|-------------| +|`[PSObject]`|false |1 |false | + +--- From 997f5a6f25c9283ddd0b873871ebf2f167df569e Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 7 Mar 2025 22:03:49 +0000 Subject: [PATCH 114/123] feat: Adding WebSocket.types.ps1xml to manifest ( Fixes #90 ) --- docs/WebSocket/Server/ThreadJob/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/WebSocket/Server/ThreadJob/README.md diff --git a/docs/WebSocket/Server/ThreadJob/README.md b/docs/WebSocket/Server/ThreadJob/README.md new file mode 100644 index 0000000..b98bd30 --- /dev/null +++ b/docs/WebSocket/Server/ThreadJob/README.md @@ -0,0 +1,7 @@ +## WebSocket.Server.ThreadJob + + +### Script Methods + + +* [Send()](Send.md) From 3ffca356b7915c8fe5796819d242d55a280c313c Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Fri, 7 Mar 2025 22:03:49 +0000 Subject: [PATCH 115/123] feat: Adding WebSocket.types.ps1xml to manifest ( Fixes #90 ) --- docs/WebSocket/Server/ThreadJob/Send.md | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/WebSocket/Server/ThreadJob/Send.md diff --git a/docs/WebSocket/Server/ThreadJob/Send.md b/docs/WebSocket/Server/ThreadJob/Send.md new file mode 100644 index 0000000..b7272b0 --- /dev/null +++ b/docs/WebSocket/Server/ThreadJob/Send.md @@ -0,0 +1,28 @@ +WebSocket.Server.ThreadJob.Send() +--------------------------------- + +### Synopsis +Sends a WebSocket message. + +--- + +### Description + +Sends a message from a WebSocket server. + +--- + +### Parameters +#### **Message** + +|Type |Required|Position|PipelineInput| +|------------|--------|--------|-------------| +|`[PSObject]`|false |1 |false | + +#### **Pattern** + +|Type |Required|Position|PipelineInput| +|----------|--------|--------|-------------| +|`[String]`|false |2 |false | + +--- From 22aaf8991b80c7f4db3b0ad9de2dd03e0e54ea96 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 22 Mar 2025 08:26:16 -0700 Subject: [PATCH 116/123] docs: Get-WebSocket examples --- Commands/Get-WebSocket.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Commands/Get-WebSocket.ps1 b/Commands/Get-WebSocket.ps1 index db8f396..8216fd9 100644 --- a/Commands/Get-WebSocket.ps1 +++ b/Commands/Get-WebSocket.ps1 @@ -18,6 +18,8 @@ function Get-WebSocket { # Create a WebSocket job that connects to a WebSocket and outputs the results. $socketServer = Get-WebSocket -RootUrl "http://localhost:8387/" -HTML "

WebSocket Server

" $socketClient = Get-WebSocket -SocketUrl "ws://localhost:8387/" + foreach ($n in 1..10) { $socketServer.Send(@{n=Get-Random}) } + $socketClient | Receive-Job -Keep .EXAMPLE # Get is the default verb, so we can just say WebSocket. # `-Watch` will output a continous stream of objects from the websocket. @@ -918,7 +920,7 @@ function Get-WebSocket { # If the request is a websocket request if ($Request.IsWebSocketRequest) { # we will change the event identifier to a websocket scheme. - $eventIdentifier = $eventIdentifier -replace '^http', 'ws' + $eventIdentifier = $eventIdentifier -replace '^http', 'ws' # and call the `AcceptWebSocketAsync` method to upgrade the connection. $acceptWebSocket = $context.AcceptWebSocketAsync('json') # Once again, we'll use a tight loop to wait for the upgrade to complete or fail. From 9bdfdc3f41cff89d3e6dd8483c18a3142d5bb668 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 22 Mar 2025 15:27:19 +0000 Subject: [PATCH 117/123] docs: Get-WebSocket examples --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a9d86d3..84f64b3 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ To stop watching a websocket, simply stop the background job. # Create a WebSocket job that connects to a WebSocket and outputs the results. $socketServer = Get-WebSocket -RootUrl "http://localhost:8387/" -HTML "

WebSocket Server

" $socketClient = Get-WebSocket -SocketUrl "ws://localhost:8387/" +foreach ($n in 1..10) { $socketServer.Send(@{n=Get-Random}) } +$socketClient | Receive-Job -Keep ~~~ #### Get-WebSocket Example 2 From 31df074aead3b0239f62bd261efec87e007f46a4 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 22 Mar 2025 15:27:27 +0000 Subject: [PATCH 118/123] docs: Get-WebSocket examples --- docs/Get-WebSocket.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Get-WebSocket.md b/docs/Get-WebSocket.md index 4169655..0e76005 100644 --- a/docs/Get-WebSocket.md +++ b/docs/Get-WebSocket.md @@ -31,6 +31,8 @@ Create a WebSocket job that connects to a WebSocket and outputs the results. ```PowerShell $socketServer = Get-WebSocket -RootUrl "http://localhost:8387/" -HTML "

WebSocket Server

" $socketClient = Get-WebSocket -SocketUrl "ws://localhost:8387/" +foreach ($n in 1..10) { $socketServer.Send(@{n=Get-Random}) } +$socketClient | Receive-Job -Keep ``` Get is the default verb, so we can just say WebSocket. `-Watch` will output a continous stream of objects from the websocket. From 1715d05fb8d4cf4cba9bf6cb5641ef46c076278d Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 22 Mar 2025 15:27:27 +0000 Subject: [PATCH 119/123] docs: Get-WebSocket examples --- docs/_data/Help/Get-WebSocket.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/Help/Get-WebSocket.json b/docs/_data/Help/Get-WebSocket.json index 17427ba..0d88615 100644 --- a/docs/_data/Help/Get-WebSocket.json +++ b/docs/_data/Help/Get-WebSocket.json @@ -37,7 +37,7 @@ { "Title": "EXAMPLE 1", "Markdown": "Create a WebSocket job that connects to a WebSocket and outputs the results.", - "Code": "$socketServer = Get-WebSocket -RootUrl \"http://localhost:8387/\" -HTML \"

WebSocket Server

\"\n$socketClient = Get-WebSocket -SocketUrl \"ws://localhost:8387/\"" + "Code": "$socketServer = Get-WebSocket -RootUrl \"http://localhost:8387/\" -HTML \"

WebSocket Server

\"\n$socketClient = Get-WebSocket -SocketUrl \"ws://localhost:8387/\"\nforeach ($n in 1..10) { $socketServer.Send(@{n=Get-Random}) }\n$socketClient | Receive-Job -Keep" }, { "Title": "EXAMPLE 2", From 102eba2c3baaf65f4c84197dff2272c7de26f425 Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 22 Mar 2025 15:27:28 +0000 Subject: [PATCH 120/123] docs: Get-WebSocket examples --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index 7a79a38..a4c7c52 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,6 +52,8 @@ To stop watching a websocket, simply stop the background job. # Create a WebSocket job that connects to a WebSocket and outputs the results. $socketServer = Get-WebSocket -RootUrl "http://localhost:8387/" -HTML "

WebSocket Server

" $socketClient = Get-WebSocket -SocketUrl "ws://localhost:8387/" +foreach ($n in 1..10) { $socketServer.Send(@{n=Get-Random}) } +$socketClient | Receive-Job -Keep ~~~ #### Get-WebSocket Example 2 From bc3a562c6d21d91af572c26c0d54b17bc02b76a3 Mon Sep 17 00:00:00 2001 From: James Brundage Date: Sat, 22 Mar 2025 15:27:31 +0000 Subject: [PATCH 121/123] docs: Get-WebSocket examples --- docs/_data/LastDateBuilt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/LastDateBuilt.json b/docs/_data/LastDateBuilt.json index 00b8070..b0f9097 100644 --- a/docs/_data/LastDateBuilt.json +++ b/docs/_data/LastDateBuilt.json @@ -1 +1 @@ -"2025-03-07" \ No newline at end of file +"2025-03-22" \ No newline at end of file From 20bdf0d75f2c89e44d6fafbf26e89082a9cadc21 Mon Sep 17 00:00:00 2001 From: James Brundage <+@noreply.github.com> Date: Sat, 22 Mar 2025 09:07:00 -0700 Subject: [PATCH 122/123] release: WebSocket 0.1.3 Updating Module Version and release notes --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ WebSocket.psd1 | 44 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eacffa..5403c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,41 @@ > Like It? [Star It](https://github.com/PowerShellWeb/WebSocket) > Love It? [Support It](https://github.com/sponsors/StartAutomating) +## WebSocket 0.1.3 + +WebSocket server support! + +### Server Features + +For consistency, capabilities, and aesthetics, +this is a fairly fully features HTTP server that happens to support websockets + +* `Get-WebSocket` `-RootURL/-HostHeader` ( #47 ) +* `-StyleSheet` lets you include stylesheets ( #64 ) +* `-JavaScript` lets you include javascript ( #65 ) +* `-Timeout/-LifeSpan` server support ( #85 ) + +### Client Improvements + +* `Get-WebSocket -QueryParameter` lets you specify query parameters ( #41 ) +* `Get-WebSocket -Debug` lets you debug the websocketjob. ( #45 ) +* `Get-WebSocket -SubProtocol` lets you specify a subprotocol (defaults to JSON) ( #46 ) +* `Get-WebSocket -Authenticate` allows sends pre-connection authentication ( #69 ) +* `Get-WebSocket -Handshake` allows post-connection authentication ( #81 ) +* `Get-WebSocket -Force` allows the creation of multiple clients ( #58 ) + + +### General Improvements + +* `Get-WebSocket -SupportsPaging` ( #55 ) +* `Get-WebSocket -BufferSize 64kb` ( #52 ) +* `Get-WebSocket -Force` ( #58 ) +* `Get-WebSocket -Filter` ( #42 ) +* `Get-WebSocket -ForwardEvent` ( #56 ) +* `Get-WebSocket` Parameter Sets ( #73, #74 ) +* `-Broadcast` lets you broadcast to clients and servers ( #39 ) +* `Get-WebSocket` quieting previous job check ( #43 ) + ## WebSocket 0.1.2 * WebSocket now decorates (#34) diff --git a/WebSocket.psd1 b/WebSocket.psd1 index 9a140de..8b6598c 100644 --- a/WebSocket.psd1 +++ b/WebSocket.psd1 @@ -1,5 +1,5 @@ @{ - ModuleVersion = '0.1.2' + ModuleVersion = '0.1.3' RootModule = 'WebSocket.psm1' Guid = '75c70c8b-e5eb-4a60-982e-a19110a1185d' Author = 'James Brundage' @@ -19,18 +19,44 @@ > Like It? [Star It](https://github.com/PowerShellWeb/WebSocket) > Love It? [Support It](https://github.com/sponsors/StartAutomating) -## WebSocket 0.1.2 +## WebSocket 0.1.3 -* WebSocket now decorates (#34) - * Added a -PSTypeName(s) parameter to Get-WebSocket, so we can extend the output. -* Reusing WebSockets (#35) - * If a WebSocketUri is already open, we will reuse it. -* Explicitly exporting commands (#38) - * This should enable automatic import and enable Find-Command +WebSocket server support! + +### Server Features + +For consistency, capabilities, and aesthetics, +this is a fairly fully features HTTP server that happens to support websockets + +* `Get-WebSocket` `-RootURL/-HostHeader` ( #47 ) +* `-StyleSheet` lets you include stylesheets ( #64 ) +* `-JavaScript` lets you include javascript ( #65 ) +* `-Timeout/-LifeSpan` server support ( #85 ) + +### Client Improvements + +* `Get-WebSocket -QueryParameter` lets you specify query parameters ( #41 ) +* `Get-WebSocket -Debug` lets you debug the websocketjob. ( #45 ) +* `Get-WebSocket -SubProtocol` lets you specify a subprotocol (defaults to JSON) ( #46 ) +* `Get-WebSocket -Authenticate` allows sends pre-connection authentication ( #69 ) +* `Get-WebSocket -Handshake` allows post-connection authentication ( #81 ) +* `Get-WebSocket -Force` allows the creation of multiple clients ( #58 ) + + +### General Improvements + +* `Get-WebSocket -SupportsPaging` ( #55 ) +* `Get-WebSocket -BufferSize 64kb` ( #52 ) +* `Get-WebSocket -Force` ( #58 ) +* `Get-WebSocket -Filter` ( #42 ) +* `Get-WebSocket -ForwardEvent` ( #56 ) +* `Get-WebSocket` Parameter Sets ( #73, #74 ) +* `-Broadcast` lets you broadcast to clients and servers ( #39 ) +* `Get-WebSocket` quieting previous job check ( #43 ) --- -Additional details available in the [CHANGELOG](CHANGELOG.md) +Additional details available in the [CHANGELOG](https://github.com/PowerShellWeb/WebSocket/blob/main/CHANGELOG.md) '@ } } From c8c4a9a03d32316aa7548e3c35595c07662e9c3b Mon Sep 17 00:00:00 2001 From: StartAutomating Date: Sat, 22 Mar 2025 16:08:34 +0000 Subject: [PATCH 123/123] release: WebSocket 0.1.3 Updating Module Version and release notes --- docs/CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a2ac339..3314467 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,41 @@ > Like It? [Star It](https://github.com/PowerShellWeb/WebSocket) > Love It? [Support It](https://github.com/sponsors/StartAutomating) +## WebSocket 0.1.3 + +WebSocket server support! + +### Server Features + +For consistency, capabilities, and aesthetics, +this is a fairly fully features HTTP server that happens to support websockets + +* `Get-WebSocket` `-RootURL/-HostHeader` ( #47 ) +* `-StyleSheet` lets you include stylesheets ( #64 ) +* `-JavaScript` lets you include javascript ( #65 ) +* `-Timeout/-LifeSpan` server support ( #85 ) + +### Client Improvements + +* `Get-WebSocket -QueryParameter` lets you specify query parameters ( #41 ) +* `Get-WebSocket -Debug` lets you debug the websocketjob. ( #45 ) +* `Get-WebSocket -SubProtocol` lets you specify a subprotocol (defaults to JSON) ( #46 ) +* `Get-WebSocket -Authenticate` allows sends pre-connection authentication ( #69 ) +* `Get-WebSocket -Handshake` allows post-connection authentication ( #81 ) +* `Get-WebSocket -Force` allows the creation of multiple clients ( #58 ) + + +### General Improvements + +* `Get-WebSocket -SupportsPaging` ( #55 ) +* `Get-WebSocket -BufferSize 64kb` ( #52 ) +* `Get-WebSocket -Force` ( #58 ) +* `Get-WebSocket -Filter` ( #42 ) +* `Get-WebSocket -ForwardEvent` ( #56 ) +* `Get-WebSocket` Parameter Sets ( #73, #74 ) +* `-Broadcast` lets you broadcast to clients and servers ( #39 ) +* `Get-WebSocket` quieting previous job check ( #43 ) + ## WebSocket 0.1.2 * WebSocket now decorates (#34)