# 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 { # Process direkt starten um CLIXML-Parsing-Fehler bei stderr zu vermeiden $encodedCmd = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command)) $proc = New-Object System.Diagnostics.Process $proc.StartInfo.FileName = "powershell.exe" $proc.StartInfo.Arguments = "-NoProfile -NonInteractive -EncodedCommand $encodedCmd" $proc.StartInfo.RedirectStandardOutput = $true $proc.StartInfo.RedirectStandardError = $true $proc.StartInfo.UseShellExecute = $false $proc.StartInfo.CreateNoWindow = $true $proc.Start() | Out-Null # stderr async lesen um Deadlocks zu vermeiden $stderrTask = $proc.StandardError.ReadToEndAsync() $stdout = $proc.StandardOutput.ReadToEnd() $stderrTask.Wait() $stderr = $stderrTask.Result $proc.WaitForExit() $exitCode = $proc.ExitCode $proc.Dispose() } catch { $stderr = $_.Exception.Message $exitCode = 1 } # CLIXML-Progress aus stderr filtern (PowerShell schreibt Modul-Lade-Fortschritt als CLIXML) if ($stderr -and $stderr.StartsWith("#< CLIXML")) { $cleanLines = @() foreach ($line in ($stderr -split "`n")) { $trimmed = $line.Trim() if ($trimmed -eq "#< CLIXML") { continue } if ($trimmed -match '^\.*\Completed\') { continue } if ($trimmed -ne "") { $cleanLines += $line } } $stderr = ($cleanLines -join "`n").Trim() } # 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 }