Files
gott/wwwroot/remote.ps1
T
Meleki 10da2bd964 feat: Remote-Verbindungen – Windows-Workstations per WebSocket steuern
- RemoteConnectionManager: Singleton-Service verwaltet WebSocket-Verbindungen
  mit Command-Execution (Send/Receive mit Timeout + SemaphoreSlim-Locking)
- RemoteEndpoint: WebSocket-Endpoint /api/remote (auth-frei), REST-APIs für
  Connections/Assign/Unassign, PowerShell-Script-Auslieferung mit dynamischer URL
- remote.ps1: PowerShell-Client für Windows – verbindet per WebSocket,
  sendet Hostname/User/OS, empfängt und führt Befehle aus, Heartbeat
- remote_execute Tool: Neues Agent-Tool, nur verfügbar wenn Session eine
  zugeordnete Remote-Verbindung hat, führt PowerShell-Befehle remote aus
- Frontend: Menüpunkt 'Remote-Verbindungen' im Chat-Dropdown, Modal zeigt
  alle aktiven WebSocket-Verbindungen (Hostname, User, OS, Verbindungszeit),
  Verbinden/Trennen-Buttons pro Verbindung und Session
- Auth: /api/remote und /remote.ps1 von Authentifizierung ausgenommen

Architektur: Windows-Client → WebSocket → GOTT-Server → Agent-Tool
Session-gebunden: Eine Verbindung wird einer Session zugeordnet
2026-03-11 07:04:24 +01:00

165 lines
6.4 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 ""
try {
$ws = New-Object System.Net.WebSockets.ClientWebSocket
$ct = New-Object System.Threading.CancellationToken
# Verbindung aufbauen
$ws.ConnectAsync([Uri]$Url, $ct).GetAwaiter().GetResult()
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
$helloBytes = [System.Text.Encoding]::UTF8.GetBytes($hello)
$segment = New-Object System.ArraySegment[byte] -ArgumentList @(,$helloBytes)
$ws.SendAsync($segment, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $ct).GetAwaiter().GetResult()
Write-Host " Host: $username@$hostname ($osVersion)" -ForegroundColor Gray
Write-Host ""
Write-Host " Warte auf Befehle... (Strg+C zum Beenden)" -ForegroundColor Yellow
Write-Host ""
# Empfangs-Loop
$buffer = New-Object byte[] 65536
$messageBuffer = New-Object System.IO.MemoryStream
# Heartbeat-Timer (alle 30 Sekunden)
$lastPing = [DateTime]::UtcNow
while ($ws.State -eq [System.Net.WebSockets.WebSocketState]::Open) {
# Heartbeat senden
if (([DateTime]::UtcNow - $lastPing).TotalSeconds -gt 30) {
$pingBytes = [System.Text.Encoding]::UTF8.GetBytes('{"type":"ping"}')
$pingSegment = New-Object System.ArraySegment[byte] -ArgumentList @(,$pingBytes)
try {
$ws.SendAsync($pingSegment, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $ct).GetAwaiter().GetResult()
} catch {
break
}
$lastPing = [DateTime]::UtcNow
}
# Auf Nachricht warten (mit Timeout fuer Heartbeat)
$receiveTask = $ws.ReceiveAsync((New-Object System.ArraySegment[byte] -ArgumentList @(,$buffer)), $ct)
if (-not $receiveTask.Wait(5000)) {
continue
}
$result = $receiveTask.Result
if ($result.MessageType -eq [System.Net.WebSockets.WebSocketMessageType]::Close) {
Write-Host " Server hat Verbindung geschlossen." -ForegroundColor Yellow
break
}
$messageBuffer.SetLength(0)
$messageBuffer.Write($buffer, 0, $result.Count)
# Mehrteilige Nachrichten zusammenbauen
while (-not $result.EndOfMessage) {
$result = $ws.ReceiveAsync((New-Object System.ArraySegment[byte] -ArgumentList @(,$buffer)), $ct).GetAwaiter().GetResult()
$messageBuffer.Write($buffer, 0, $result.Count)
}
$json = [System.Text.Encoding]::UTF8.GetString($messageBuffer.ToArray())
$msg = $json | ConvertFrom-Json
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 {
# PowerShell-Befehl ausfuehren
$output = & powershell.exe -NoProfile -NonInteractive -Command $command 2>&1
$stdout = ($output | Where-Object { $_ -isnot [System.Management.Automation.ErrorRecord] }) -join "`n"
$stderrLines = ($output | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }) | ForEach-Object { $_.ToString() }
$stderr = $stderrLines -join "`n"
$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
$responseBytes = [System.Text.Encoding]::UTF8.GetBytes($response)
$responseSegment = New-Object System.ArraySegment[byte] -ArgumentList @(,$responseBytes)
$ws.SendAsync($responseSegment, [System.Net.WebSockets.WebSocketMessageType]::Text, $true, $ct).GetAwaiter().GetResult()
# Kurzausgabe
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 ""
}
"pong" {
# Heartbeat-Antwort – ignorieren
}
default {
Write-Host " Unbekannte Nachricht: $($msg.type)" -ForegroundColor Yellow
}
}
}
} catch [System.Net.WebSockets.WebSocketException] {
Write-Host " Verbindung verloren." -ForegroundColor Red
} catch {
Write-Host " Fehler: $_" -ForegroundColor Red
} finally {
if ($ws -and $ws.State -eq [System.Net.WebSockets.WebSocketState]::Open) {
try {
$ws.CloseAsync([System.Net.WebSockets.WebSocketCloseStatus]::NormalClosure, "bye", $ct).GetAwaiter().GetResult()
} catch { }
}
if ($ws) { $ws.Dispose() }
Write-Host ""
Write-Host " Verbindung beendet." -ForegroundColor Yellow
}