using System.Diagnostics; using System.Net.Http.Json; using System.Net.WebSockets; using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace ITNexusAgent.Services; public class ShellService { private readonly string _serverUrl; private readonly string _agentKey; private readonly string _hostname; public ShellService(string serverUrl, string agentKey, string hostname) { _serverUrl = serverUrl; _agentKey = agentKey; _hostname = hostname; } public async Task RunAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { try { await ConnectAsync(ct); } catch (Exception ex) { AgentWorker.Log($"SHELL: {ex.Message}"); } if (!ct.IsCancellationRequested) await Task.Delay(TimeSpan.FromSeconds(15), ct).ContinueWith(_ => { }); } } private async Task ConnectAsync(CancellationToken ct) { var wsUrl = _serverUrl .Replace("https://", "wss://") .Replace("http://", "ws://") + $"/ws?type=agent&key={Uri.EscapeDataString(_agentKey)}&hostname={Uri.EscapeDataString(_hostname)}"; using var ws = new ClientWebSocket(); ws.Options.SetRequestHeader("User-Agent", "IT-Nexus-Agent/2.1.2"); await ws.ConnectAsync(new Uri(wsUrl), ct); AgentWorker.Log("SHELL: WebSocket verbunden"); Process? shell = null; CancellationTokenSource? shellCts = null; async Task SendText(string text) { if (ws.State != WebSocketState.Open) return; var bytes = Encoding.UTF8.GetBytes(text); await ws.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, CancellationToken.None); } void StartShell() { try { shellCts?.Cancel(); shell?.Kill(); } catch { } shellCts = CancellationTokenSource.CreateLinkedTokenSource(ct); var token = shellCts.Token; var psi = new ProcessStartInfo("powershell.exe", "-NoExit -NoLogo -NoProfile") { CreateNoWindow = true, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8, }; shell = Process.Start(psi)!; AgentWorker.Log("SHELL: PowerShell gestartet"); // stdout streamen _ = Task.Run(async () => { var buf = new char[1024]; while (!token.IsCancellationRequested) { try { int n = await shell.StandardOutput.ReadAsync(buf, 0, buf.Length); if (n == 0) break; await SendText(new string(buf, 0, n)); } catch { break; } } }, token); // stderr streamen (rot markiert) _ = Task.Run(async () => { var buf = new char[1024]; while (!token.IsCancellationRequested) { try { int n = await shell.StandardError.ReadAsync(buf, 0, buf.Length); if (n == 0) break; await SendText("\x1b[31m" + new string(buf, 0, n) + "\x1b[0m"); } catch { break; } } }, token); } void StopShell() { shellCts?.Cancel(); try { shell?.Kill(); } catch { } shell = null; AgentWorker.Log("SHELL: PowerShell gestoppt"); } try { var buffer = new byte[8192]; while (ws.State == WebSocketState.Open && !ct.IsCancellationRequested) { WebSocketReceiveResult result; try { result = await ws.ReceiveAsync(new ArraySegment(buffer), ct); } catch { break; } if (result.MessageType == WebSocketMessageType.Close) break; var msg = Encoding.UTF8.GetString(buffer, 0, result.Count); JObject? obj; try { obj = JObject.Parse(msg); } catch { continue; } switch (obj["type"]?.ToString()) { case "start_shell": StartShell(); break; case "input": if (shell != null && !shell.HasExited) { var data = obj["data"]?.ToString() ?? ""; await shell.StandardInput.WriteAsync(data); await shell.StandardInput.FlushAsync(); } break; case "stop_shell": StopShell(); break; case "announcements": // Sofort-Push von Ankündigungen (ohne Warten auf Check-in) var anns = obj["announcements"]?.ToObject>(); if (anns != null && anns.Count > 0) { var notifier = new NotificationService( System.Diagnostics.Process.GetCurrentProcess().MainModule!.FileName, @"C:\ProgramData\IT Nexus Agent"); notifier.ShowAnnouncements(anns); AgentWorker.Log($"SHELL: {anns.Count} Ankündigung(en) via WebSocket empfangen"); // ACK senden + shown_announcements.json bereinigen damit gelöschte+neu erstellte // Ankündigungen mit gleicher ID nicht geblockt werden foreach (var ann in anns) { try { await AckAnnouncementAsync(ann.Id); notifier.RemoveShownId(ann.Id); } catch (Exception ex) { AgentWorker.Log($"SHELL: ACK Fehler für ID {ann.Id}: {ex.Message}"); } } } break; } } } finally { StopShell(); } AgentWorker.Log("SHELL: WebSocket getrennt"); } private async Task AckAnnouncementAsync(int announcementId) { using var http = new HttpClient(); http.DefaultRequestHeaders.Add("x-agent-key", _agentKey); var body = JsonContent.Create(new { hostname = _hostname }); await http.PostAsync($"{_serverUrl}/api/announcements/{announcementId}/ack-agent", body); } }