# 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 }