Skip to content

Instantly share code, notes, and snippets.

@Tim-S
Created July 20, 2022 17:15
Show Gist options
  • Save Tim-S/0b09a1ddf57789a5399f734fbf957199 to your computer and use it in GitHub Desktop.
Save Tim-S/0b09a1ddf57789a5399f734fbf957199 to your computer and use it in GitHub Desktop.
Powershell WebSocket Echoserver
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = "ws://localhost:5001/";
var output;
function init() {
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket() {
websocket = new WebSocket(wsUri);
websocket.onopen = function (evt) { onOpen(evt) };
websocket.onclose = function (evt) { onClose(evt) };
websocket.onmessage = function (evt) { onMessage(evt) };
websocket.onerror = function (evt) { onError(evt) };
}
function onOpen(evt) {
writeToScreen("CONNECTED");
doSend("Client says Hello!");
}
function onClose(evt) {
writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
writeToScreen('<span style="color: blue;">Received: ' + evt.data + '</span>');
}
function onError(evt) {
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message) {
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<body>
<h2>WebSocket Test</h2>
<div id="output"></div>
<label for="inputMessage">Message:</label><input id="inputMessage" type="text"/>
<button id="btnSendMessage" onclick="doSend(document.getElementById('inputMessage').value)">Send Message</button>
</body>
write-host "Web Listener: Start"
# maybe need to allow script execution via
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
try {
$listener = New-Object System.Net.HttpListener
# $listener.Prefixes.Add('http://+:81/') # listen on ony interface (matching netsh or running on admin console required)
$listener.Prefixes.Add('http://localhost:5001/')
$listener.Start()
}
catch {
write-error "Unable to open listener. Check Admin permission or NETSH Binding"
exit 1
}
# Helper for awaiting async tasks on powershell
# see https://blog.ironmansoftware.com/powershell-async-method/#:~:text=PowerShell%20does%20not%20provide%20an,when%20calling%20async%20methods%20in%20.
function Wait-Task {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[System.Threading.Tasks.Task[]]$Task
)
Begin {
$Tasks = @()
}
Process {
$Tasks += $Task
}
End {
While (-not [System.Threading.Tasks.Task]::WaitAll($Tasks, 200)) {}
$Tasks.ForEach( { $_.GetAwaiter().GetResult() })
}
}
Set-Alias -Name await -Value Wait-Task -Force
$recv_job = {
param($ws, $client_id, $recv_queue)
$buffer = [Net.WebSockets.WebSocket]::CreateClientBuffer(1024,1024)
$ct = [Threading.CancellationToken]::new($false)
$taskResult = $null
while ($ws.State -eq [Net.WebSockets.WebSocketState]::Open) {
$jsonResult = ""
do {
$taskResult = $ws.ReceiveAsync($buffer, $ct)
while (-not $taskResult.IsCompleted -and $ws.State -eq [Net.WebSockets.WebSocketState]::Open) {
[Threading.Thread]::Sleep(10)
}
$jsonResult += [Text.Encoding]::UTF8.GetString($buffer, 0, $taskResult.Result.Count)
} until (
$ws.State -ne [Net.WebSockets.WebSocketState]::Open -or $taskResult.Result.EndOfMessage
)
if (-not [string]::IsNullOrEmpty($jsonResult)) {
"Received message(s): $jsonResult" | Out-File -FilePath "logs.txt" -Append
$recv_queue.Enqueue($jsonResult)
}
}
}
$send_job = {
param($ws, $client_id, $send_queue)
$ct = New-Object Threading.CancellationToken($false)
$workitem = $null
while ($ws.State -eq [Net.WebSockets.WebSocketState]::Open){
if ($send_queue.TryDequeue([ref] $workitem)) {
"Sending message: $workitem" | Out-File -FilePath "logs.txt" -Append
[ArraySegment[byte]]$msg = [Text.Encoding]::UTF8.GetBytes($workitem)
$ws.SendAsync(
$msg,
[System.Net.WebSockets.WebSocketMessageType]::Text,
$true,
$ct
).GetAwaiter().GetResult() | Out-Null
}
}
}
# Create a simple Websocke server proving a Message including teh current time
Write-host "Web Listener listening"
[console]::TreatControlCAsInput = $true
while (!([console]::KeyAvailable)) {
Write-host "Press any key to Stop (requires client to be connected, yet)"
$context = await $listener.GetContextAsync()
if ($context.Request.IsWebSocketRequest)
{
write-host ("Received Websocket-Request on " + $listener.Prefixes )
$webSocketContext = await $context.AcceptWebSocketAsync(([NullString]::Value)) # # https://docs.microsoft.com/de-de/dotnet/api/system.net.httplistenercontext.acceptwebsocketasync?view=netframework-4.5
$webSocket = $webSocketContext.WebSocket;
$cts = New-Object Threading.CancellationTokenSource
$ct = New-Object Threading.CancellationToken($false)
$recv_queue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[String]'
$send_queue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[String]'
$client_id = [System.GUID]::NewGuid()
Write-Output "Starting recv runspace"
$recv_runspace = [PowerShell]::Create()
$recv_runspace.AddScript($recv_job).
AddParameter("ws", $webSocket).
AddParameter("client_id", $client_id).
AddParameter("recv_queue", $recv_queue).BeginInvoke() | Out-Null
Write-Output "Starting send runspace"
$send_runspace = [PowerShell]::Create()
$send_runspace.AddScript($send_job).
AddParameter("ws", $webSocket).
AddParameter("client_id", $client_id).
AddParameter("send_queue", $send_queue).BeginInvoke() | Out-Null
try {
do {
$msg = $null
while ($recv_queue.TryDequeue([ref] $msg)) {
Write-Output "Processed message: $msg"
$hash = @{
ClientID = $client_id
Date = (get-date -Format yyyy-MM-dd-HH:mm:ss)
Message = $msg
}
$test_payload = New-Object PSObject -Property $hash
$json = ConvertTo-Json $test_payload
$send_queue.Enqueue($json)
}
} until ($webSocket.State -ne [Net.WebSockets.WebSocketState]::Open -or ([console]::KeyAvailable))
}
finally {
Write-Output "Closing WS connection"
$closetask = $webSocket.CloseAsync(
[System.Net.WebSockets.WebSocketCloseStatus]::Empty,
"",
$ct
)
do { Sleep(1) }
until ($closetask.IsCompleted)
$webSocket.Dispose()
Write-Output "Stopping runspaces"
$recv_runspace.Stop()
$recv_runspace.Dispose()
$send_runspace.Stop()
$send_runspace.Dispose()
}
}
$response = $context.Response
$response.OutputStream.close()
}
Write-host "Web Listener Stopping"
$listener.Stop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment