Files
gott/wwwroot/remote.ps1
T
Meleki a13355d7e9 Remote: Server-seitiger Heartbeat mit Timeout für tote WebSocket-Verbindungen
- Server sendet alle 30s Ping an Client, Client antwortet mit Pong
- LastActivityAt wird bei jeder empfangenen Nachricht aktualisiert
- Nach 90s Inaktivität wird Verbindung als tot erkannt und aufgeräumt
- HeartbeatLoop läuft parallel zum ReceiveLoop
- remote.ps1: ping-Handler hinzugefügt
2026-03-13 15:44:35 +01:00

191 lines
7.1 KiB
PowerShell

# GOTT Remote Agent - Windows PowerShell Client
# Verbindet sich per WebSocket zum GOTT-Server und fuehrt Befehle aus.
#
# Verwendung:
# irm https://gott.example.com/remote.ps1 | iex
# oder:
# .\remote.ps1 -Url "wss://gott.example.com/api/remote"
param(
[string]$Url = "{{WS_URL}}"
)
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host " ============================" -ForegroundColor Cyan
Write-Host " GOTT Remote Agent" -ForegroundColor Cyan
Write-Host " ============================" -ForegroundColor Cyan
Write-Host ""
Write-Host " Verbinde zu: $Url" -ForegroundColor Gray
Write-Host ""
function Send-WsMessage {
param([System.Net.WebSockets.ClientWebSocket]$ws, [string]$msg, [System.Threading.CancellationToken]$ct)
$bytes = [System.Text.Encoding]::UTF8.GetBytes($msg)
$ws.SendAsync((New-Object System.ArraySegment[byte] -ArgumentList @(,$bytes)),
[System.Net.WebSockets.WebSocketMessageType]::Text, $true, $ct).GetAwaiter().GetResult() | Out-Null
}
function Receive-WsMessage {
param([System.Net.WebSockets.ClientWebSocket]$ws, [System.Threading.CancellationToken]$ct)
$buf = New-Object byte[] 65536
$ms = New-Object System.IO.MemoryStream
try {
do {
$seg = New-Object System.ArraySegment[byte] -ArgumentList @(,$buf)
$result = $ws.ReceiveAsync($seg, $ct).GetAwaiter().GetResult()
if ($result.MessageType -eq [System.Net.WebSockets.WebSocketMessageType]::Close) {
return $null
}
$ms.Write($buf, 0, $result.Count)
} while (-not $result.EndOfMessage)
return [System.Text.Encoding]::UTF8.GetString($ms.ToArray())
} finally {
$ms.Dispose()
}
}
try {
$ws = New-Object System.Net.WebSockets.ClientWebSocket
$cts = New-Object System.Threading.CancellationTokenSource
$ct = $cts.Token
# Ctrl+C abfangen → CancellationToken ausloesen
[Console]::TreatControlCAsInput = $false
$cancelHandler = [System.ConsoleCancelEventHandler]{
param($s, $e)
$e.Cancel = $true
$cts.Cancel()
}
[Console]::add_CancelKeyPress($cancelHandler)
# TLS 1.2 erzwingen (fuer aeltere Windows-Versionen)
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
# Verbindung aufbauen
try {
$ws.ConnectAsync([Uri]$Url, $ct).GetAwaiter().GetResult() | Out-Null
} catch {
Write-Host " [FEHLER] Verbindung fehlgeschlagen: $_" -ForegroundColor Red
Write-Host " URL: $Url" -ForegroundColor Gray
return
}
Write-Host " [OK] Verbunden!" -ForegroundColor Green
# Hello-Message senden
$hostname = $env:COMPUTERNAME
$username = $env:USERNAME
$osVersion = [System.Environment]::OSVersion.VersionString
$hello = @{
type = "hello"
hostname = $hostname
username = $username
os = $osVersion
} | ConvertTo-Json -Compress
Send-WsMessage $ws $hello $ct
Write-Host " Host: $username@$hostname ($osVersion)" -ForegroundColor Gray
Write-Host ""
Write-Host " Warte auf Befehle... (Strg+C zum Beenden)" -ForegroundColor Yellow
Write-Host ""
# Synchroner Empfangs-Loop
while ($ws.State -eq [System.Net.WebSockets.WebSocketState]::Open) {
# Blockierend auf naechste Nachricht warten
$json = Receive-WsMessage $ws $ct
if ($null -eq $json) {
Write-Host " Server hat Verbindung geschlossen." -ForegroundColor Yellow
break
}
try {
$msg = $json | ConvertFrom-Json
} catch {
continue
}
switch ($msg.type) {
"execute" {
$command = $msg.command
$timestamp = Get-Date -Format "HH:mm:ss"
Write-Host " [$timestamp] Ausfuehren: " -NoNewline -ForegroundColor Cyan
Write-Host $command -ForegroundColor White
$exitCode = 0
$stdout = ""
$stderr = ""
try {
# EncodedCommand umgeht alle Quoting-Probleme (Anführungszeichen, Sonderzeichen)
$encodedCmd = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
$output = & powershell.exe -NoProfile -NonInteractive -EncodedCommand $encodedCmd 2>&1
$stdoutLines = @($output | Where-Object { $_ -isnot [System.Management.Automation.ErrorRecord] })
$stderrLines = @($output | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }) |
ForEach-Object { $_.ToString() }
$stdout = ($stdoutLines | Out-String).TrimEnd()
$stderr = ($stderrLines | Out-String).TrimEnd()
$exitCode = $LASTEXITCODE
if ($null -eq $exitCode) { $exitCode = 0 }
} catch {
$stderr = $_.Exception.Message
$exitCode = 1
}
# Ergebnis senden
$response = @{
type = "result"
exitCode = $exitCode
output = if ($stdout) { $stdout } else { "" }
error = if ($stderr) { $stderr } else { "" }
} | ConvertTo-Json -Compress -Depth 3
Send-WsMessage $ws $response $ct
# Kurzausgabe lokal
if ($stdout) {
$preview = if ($stdout.Length -gt 200) { $stdout.Substring(0, 200) + "..." } else { $stdout }
Write-Host " -> $preview" -ForegroundColor Gray
}
if ($stderr) {
Write-Host " [stderr] $stderr" -ForegroundColor Red
}
Write-Host " [Exit: $exitCode]" -ForegroundColor $(if ($exitCode -eq 0) { "Green" } else { "Red" })
Write-Host ""
}
"ping" {
# Server-Heartbeat → mit pong antworten
Send-WsMessage $ws '{"type":"pong"}' $ct
}
"pong" {
# Heartbeat-Antwort - ignorieren
}
default {
Write-Host " Unbekannte Nachricht: $($msg.type)" -ForegroundColor Yellow
}
}
}
} catch [System.OperationCanceledException] {
# Ctrl+C
} catch [System.AggregateException] {
# Kann durch CancellationToken in .GetAwaiter().GetResult() kommen
} catch [System.Net.WebSockets.WebSocketException] {
Write-Host " Verbindung verloren." -ForegroundColor Red
} catch {
Write-Host " Fehler: $_" -ForegroundColor Red
} finally {
# Event-Handler entfernen
try { [Console]::remove_CancelKeyPress($cancelHandler) } catch { }
if ($ws -and $ws.State -eq [System.Net.WebSockets.WebSocketState]::Open) {
try {
$closeCt = (New-Object System.Threading.CancellationTokenSource(3000)).Token
$ws.CloseAsync([System.Net.WebSockets.WebSocketCloseStatus]::NormalClosure, "bye", $closeCt).GetAwaiter().GetResult() | Out-Null
} catch { }
}
if ($cts) { $cts.Dispose() }
if ($ws) { $ws.Dispose() }
Write-Host ""
Write-Host " Verbindung beendet." -ForegroundColor Yellow
}