# IT Nexus Monitoring Agent v1.3
# Cereda Systems GmbH
# Laedt Systemdaten und sendet sie an IT Nexus

$AgentVersion = "1.3.1"
$ConfigPath = Join-Path $PSScriptRoot "config.json"
$LogPath = Join-Path $PSScriptRoot "agent.log"

function Write-Log($msg) {
    $line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $msg"
    Add-Content -Path $LogPath -Value $line -ErrorAction SilentlyContinue
}

# Config laden
if (-not (Test-Path $ConfigPath)) {
    Write-Log "ERROR: config.json nicht gefunden"
    exit 1
}
$config = Get-Content $ConfigPath | ConvertFrom-Json
$ServerUrl = $config.server_url.TrimEnd('/')
$AgentKey  = $config.agent_key

function Get-CpuUsage {
    try {
        $pc = [System.Diagnostics.PerformanceCounter]::new("Processor", "% Processor Time", "_Total")
        $pc.NextValue() | Out-Null
        Start-Sleep -Milliseconds 800
        return [math]::Round($pc.NextValue(), 1)
    } catch { return $null }
}

function Get-RamInfo {
    try {
        $os = Get-CimInstance Win32_OperatingSystem
        $total = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
        $free  = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
        return @{ total = $total; used = [math]::Round($total - $free, 2) }
    } catch { return @{ total = $null; used = $null } }
}

function Get-DiskInfo {
    try {
        $disk = Get-PSDrive C
        $total = [math]::Round(($disk.Used + $disk.Free) / 1GB, 2)
        $free  = [math]::Round($disk.Free / 1GB, 2)
        return @{ total = $total; free = $free }
    } catch { return @{ total = $null; free = $null } }
}

function Get-CpuInfo {
    try {
        $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
        return @{ model = $cpu.Name.Trim(); cores = $cpu.NumberOfCores }
    } catch { return @{ model = $null; cores = $null } }
}

function Get-MacAddress {
    try {
        $mac = (Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled } | Select-Object -First 1).MACAddress
        return $mac
    } catch { return $null }
}

function Get-InstalledSoftware {
    try {
        $paths = @(
            'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
            'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
        )
        $apps = Get-ItemProperty $paths -ErrorAction SilentlyContinue |
            Where-Object { $_.DisplayName -and $_.DisplayName -notmatch '^KB\d+' } |
            Select-Object -ExpandProperty DisplayName -Unique |
            ForEach-Object { [System.Text.RegularExpressions.Regex]::Replace($_, '[^\x20-\x7E]', '') } |
            Where-Object { $_.Length -gt 2 } |
            Sort-Object |
            Select-Object -First 100
        return $apps
    } catch { return @() }
}

function Get-PendingUpdates {
    try {
        $session = New-Object -ComObject Microsoft.Update.Session
        $searcher = $session.CreateUpdateSearcher()
        $result = $searcher.Search("IsInstalled=0 and Type='Software'")
        return $result.Updates.Count
    } catch { return 0 }
}

function Get-UptimeHours {
    try {
        $uptime = (Get-Date) - (gcim Win32_OperatingSystem).LastBootUpTime
        return [math]::Round($uptime.TotalHours, 1)
    } catch { return $null }
}

function Get-LastUser {
    try {
        $user = (Get-CimInstance Win32_ComputerSystem).UserName
        if ($user) { return $user.Split('\')[-1] }
        $profile = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*' |
            Where-Object { $_.ProfileImagePath -like 'C:\Users\*' -and $_.ProfileImagePath -notlike '*default*' } |
            Sort-Object { $_.PSChildName } | Select-Object -Last 1
        return ($profile.ProfileImagePath -split '\\')[-1]
    } catch { return $null }
}

function Get-TpmInfo {
    try {
        $tpm = Get-WmiObject -Namespace "Root\CIMv2\Security\MicrosoftTpm" -Class Win32_Tpm -ErrorAction Stop
        if ($tpm) {
            $specVer = $tpm.SpecVersion
            $isV2 = $specVer -like "*2.0*"
            return @{ present = $true; version = $specVer; is_v2 = $isV2 }
        }
        return @{ present = $false; version = $null; is_v2 = $false }
    } catch {
        return @{ present = $false; version = $null; is_v2 = $false }
    }
}

function Get-SecureBootStatus {
    try {
        $sb = Confirm-SecureBootUEFI -ErrorAction Stop
        return [bool]$sb
    } catch {
        return $false
    }
}

function Get-Win11Readiness {
    $tpm = Get-TpmInfo
    $sb  = Get-SecureBootStatus
    $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
    $ram = (Get-CimInstance Win32_OperatingSystem).TotalVisibleMemorySize / 1MB

    $cpuOk = $cpu -and $cpu.NumberOfCores -ge 2 -and $cpu.MaxClockSpeed -ge 1000
    $ramOk = $ram -ge 4
    $ready = $tpm.is_v2 -and $sb -and $cpuOk -and $ramOk

    return @{
        tpm_present  = $tpm.present
        tpm_version  = if ($tpm.version) { $tpm.version } else { $null }
        tpm_v2       = $tpm.is_v2
        secure_boot  = $sb
        win11_ready  = $ready
    }
}

# Systemdaten sammeln
Write-Log "Sammle Systemdaten..."

$cpu      = Get-CpuInfo
$ram      = Get-RamInfo
$disk     = Get-DiskInfo
$cpuPct   = Get-CpuUsage
$mac      = Get-MacAddress
$updates  = Get-PendingUpdates
$uptime   = Get-UptimeHours
$lastUser = Get-LastUser
$software = Get-InstalledSoftware
$win11    = Get-Win11Readiness

$ip = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {
    $_.InterfaceAlias -notlike '*Loopback*' -and
    $_.InterfaceAlias -notlike '*vEthernet*' -and
    $_.InterfaceAlias -notlike '*WSL*' -and
    $_.InterfaceAlias -notlike '*Virtual*' -and
    $_.IPAddress -notlike '169.*' -and
    $_.IPAddress -notlike '172.*'
} | Select-Object -First 1).IPAddress
if (-not $ip) {
    $ip = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {
        $_.InterfaceAlias -notlike '*Loopback*' -and
        $_.InterfaceAlias -notlike '*vEthernet*' -and
        $_.IPAddress -notlike '169.*'
    } | Select-Object -First 1).IPAddress
}
$os     = Get-CimInstance Win32_OperatingSystem
$domain = $env:USERDOMAIN

$payload = @{
    hostname                 = $env:COMPUTERNAME
    ip_address               = $ip
    mac_address              = $mac
    os_name                  = $os.Caption
    os_version               = $os.Version
    cpu_model                = $cpu.model
    cpu_cores                = $cpu.cores
    cpu_usage_percent        = $cpuPct
    ram_total_gb             = $ram.total
    ram_used_gb              = $ram.used
    disk_total_gb            = $disk.total
    disk_free_gb             = $disk.free
    last_user                = $lastUser
    uptime_hours             = $uptime
    domain                   = $domain
    agent_version            = $AgentVersion
    installed_software       = $software
    windows_updates_pending  = $updates
    tpm_present              = $win11.tpm_present
    tpm_version              = $win11.tpm_version
    tpm_v2                   = $win11.tpm_v2
    secure_boot              = $win11.secure_boot
    win11_ready              = $win11.win11_ready
} | ConvertTo-Json -Depth 3

# --- An IT Nexus senden ---
function Invoke-PatchCommand($cmd, $cmdId) {
    Write-Log "PATCH: Command empfangen: $($cmd) (ID: $cmdId)"
    $result = "OK"
    try {
        switch ($cmd) {
            'check_updates' {
                UsoClient.exe StartScan 2>$null
                Start-Sleep -Seconds 5
                $result = "Update-Scan gestartet"
                Write-Log "PATCH: Update-Scan gestartet"
            }
            'install_updates' {
                UsoClient.exe StartDownload 2>$null
                Start-Sleep -Seconds 3
                UsoClient.exe StartInstall 2>$null
                $result = "Update-Installation gestartet"
                Write-Log "PATCH: Update-Installation gestartet"
            }
            'update_agent' {
                Write-Log "UPDATE: Manuelles Agent-Update angefordert..."
                try {
                    $scriptPath = $MyInvocation.MyCommand.Path
                    $tempPath   = "$scriptPath.update"
                    $updateHeaders = @{ 'X-Agent-Key' = $AgentKey }
                    Invoke-WebRequest -Uri "$ServerUrl/api/monitoring/agent-script" `
                        -Headers $updateHeaders -OutFile $tempPath -TimeoutSec 30
                    if ((Get-Item $tempPath -ErrorAction SilentlyContinue).Length -gt 1024) {
                        Copy-Item -Path $tempPath -Destination $scriptPath -Force
                        Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
                        $result = "Agent erfolgreich aktualisiert - neue Version aktiv ab naechstem Run"
                        Write-Log "UPDATE: Manuelles Update erfolgreich"
                    } else {
                        Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
                        $result = "Fehler: Heruntergeladene Datei ungueltig"
                        Write-Log "UPDATE ERROR: Datei zu klein"
                    }
                } catch {
                    $result = "Fehler: $($_.Exception.Message)"
                    Write-Log "UPDATE ERROR: $result"
                }
            }
            'upgrade_win11' {
                Write-Log "WIN11: Starte Windows 11 Upgrade-Prozess..."
                $assistantPath = "C:\ProgramData\IT Nexus Agent\Win11Upgrade.exe"
                try {
                    Write-Log "WIN11: Lade Installation Assistant herunter..."
                    Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/?linkid=2171764" -OutFile $assistantPath -TimeoutSec 300
                    if ((Get-Item $assistantPath -ErrorAction SilentlyContinue).Length -gt 1MB) {
                        # Als angemeldeter Benutzer ausfuehren damit UI sichtbar ist
                        $loggedInUser = (Get-CimInstance Win32_ComputerSystem).UserName
                        if ($loggedInUser -and $loggedInUser -ne '') {
                            Unregister-ScheduledTask -TaskName "IT Nexus Win11 Upgrade" -Confirm:$false -ErrorAction SilentlyContinue
                            $action    = New-ScheduledTaskAction -Execute $assistantPath -Argument "/skipeula /auto upgrade"
                            $trigger   = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(20)
                            $principal = New-ScheduledTaskPrincipal -UserId $loggedInUser -LogonType Interactive -RunLevel Highest
                            $settings  = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 3)
                            Register-ScheduledTask -TaskName "IT Nexus Win11 Upgrade" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
                            Start-ScheduledTask -TaskName "IT Nexus Win11 Upgrade"
                            $reportBody = @{ command_id = $cmdId; status = 'running'; result = "Windows 11 Upgrade gestartet fuer Benutzer $loggedInUser - Fortschritt sichtbar auf dem Geraet" } | ConvertTo-Json
                            Invoke-RestMethod -Uri "$ServerUrl/api/patch/commands/result" -Method POST -Body $reportBody -Headers @{ 'Content-Type' = 'application/json'; 'X-Agent-Key' = $AgentKey } -TimeoutSec 10 -ErrorAction SilentlyContinue
                            Write-Log "WIN11: Upgrade als $loggedInUser gestartet (Status: running)"
                            return
                        } else {
                            Start-Process -FilePath $assistantPath -ArgumentList "/quietinstall /skipeula /auto upgrade" -NoNewWindow
                            $reportBody = @{ command_id = $cmdId; status = 'running'; result = "Windows 11 Upgrade gestartet (kein Benutzer angemeldet)" } | ConvertTo-Json
                            Invoke-RestMethod -Uri "$ServerUrl/api/patch/commands/result" -Method POST -Body $reportBody -Headers @{ 'Content-Type' = 'application/json'; 'X-Agent-Key' = $AgentKey } -TimeoutSec 10 -ErrorAction SilentlyContinue
                            Write-Log "WIN11: Upgrade als SYSTEM gestartet (kein User)"
                            return
                        }
                    } else {
                        $result = "Fehler: Download fehlgeschlagen oder Datei zu klein"
                        Write-Log "WIN11 ERROR: Download fehlgeschlagen"
                    }
                } catch {
                    $result = "Fehler: $($_.Exception.Message)"
                    Write-Log "WIN11 ERROR: $result"
                }
            }
            'reboot' {
                $result = "Neustart wird in 60 Sekunden durchgefuehrt"
                Write-Log "PATCH: Neustart geplant"
                $reportBody = @{ command_id = $cmdId; status = 'done'; result = $result } | ConvertTo-Json
                Invoke-RestMethod -Uri "$ServerUrl/api/patch/commands/result" `
                    -Method POST -Body $reportBody `
                    -Headers @{ 'Content-Type' = 'application/json'; 'X-Agent-Key' = $AgentKey } `
                    -TimeoutSec 10 -ErrorAction SilentlyContinue
                Start-Sleep -Seconds 5
                shutdown.exe /r /t 60 /c "IT Nexus Patch Management - Geplanter Neustart"
                return
            }
        }
    } catch {
        $result = "Fehler: $($_.Exception.Message)"
        Write-Log "PATCH ERROR: $result"
    }

    try {
        $reportBody = @{ command_id = $cmdId; status = 'done'; result = $result } | ConvertTo-Json
        Invoke-RestMethod -Uri "$ServerUrl/api/patch/commands/result" `
            -Method POST -Body $reportBody `
            -Headers @{ 'Content-Type' = 'application/json'; 'X-Agent-Key' = $AgentKey } `
            -TimeoutSec 10
    } catch {
        Write-Log "PATCH: Ergebnis-Meldung fehlgeschlagen: $($_.Exception.Message)"
    }
}

try {
    $headers = @{
        'Content-Type' = 'application/json'
        'X-Agent-Key'  = $AgentKey
    }
    $cleanPayload = $payload -replace '[\x00-\x1F\x7F]', ''
    $response = Invoke-RestMethod -Uri "$ServerUrl/api/monitoring/checkin" `
        -Method POST -Body $cleanPayload -Headers $headers -TimeoutSec 30
    Write-Log "OK: Checkin erfolgreich (Hostname: $env:COMPUTERNAME)"

    # Self-Update pruefen
    $serverVersion = $response.agent_version
    if ($serverVersion -and $serverVersion -ne $AgentVersion) {
        Write-Log "UPDATE: Neue Agent-Version verfuegbar: $serverVersion (aktuell: $AgentVersion)"
        try {
            $scriptPath = $MyInvocation.MyCommand.Path
            $tempPath   = "$scriptPath.update"
            $updateHeaders = @{ 'X-Agent-Key' = $AgentKey }
            Invoke-WebRequest -Uri "$ServerUrl/api/monitoring/agent-script" `
                -Headers $updateHeaders -OutFile $tempPath -TimeoutSec 30
            if ((Get-Item $tempPath).Length -gt 1024) {
                Copy-Item -Path $tempPath -Destination $scriptPath -Force
                Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
                Write-Log "UPDATE: Agent erfolgreich auf Version $serverVersion aktualisiert."
            } else {
                Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
                Write-Log "UPDATE: Heruntergeladene Datei zu klein - Update abgebrochen"
            }
        } catch {
            Write-Log "UPDATE ERROR: $($_.Exception.Message)"
        }
    }

    # Laufende Commands pruefen (z.B. Win11 Upgrade)
    if ($response.running_commands -and $response.running_commands.Count -gt 0) {
        foreach ($rc in $response.running_commands) {
            if ($rc.command -eq 'upgrade_win11') {
                $task = Get-ScheduledTask -TaskName "IT Nexus Win11 Upgrade" -ErrorAction SilentlyContinue
                $proc = Get-Process -Name "Win11Upgrade" -ErrorAction SilentlyContinue
                $stillRunning = ($task -and $task.State -eq 'Running') -or ($proc -ne $null)
                if (-not $stillRunning) {
                    $taskInfo = Get-ScheduledTaskInfo -TaskName "IT Nexus Win11 Upgrade" -ErrorAction SilentlyContinue
                    $exitCode = if ($taskInfo) { $taskInfo.LastTaskResult } else { 0 }
                    $doneResult = if ($exitCode -eq 0) { "Windows 11 Upgrade abgeschlossen" } else { "Upgrade beendet (Code: $exitCode)" }
                    $rb = @{ command_id = $rc.id; status = 'done'; result = $doneResult } | ConvertTo-Json
                    Invoke-RestMethod -Uri "$ServerUrl/api/patch/commands/result" -Method POST -Body $rb -Headers @{ 'Content-Type' = 'application/json'; 'X-Agent-Key' = $AgentKey } -TimeoutSec 10 -ErrorAction SilentlyContinue
                    Write-Log "WIN11: Upgrade abgeschlossen (Code: $exitCode)"
                } else {
                    Write-Log "WIN11: Upgrade laeuft noch..."
                }
            }
        }
    }

    # Ankuendigungen anzeigen (als eingeloggter Benutzer via ScheduledTask)
    if ($response.announcements -and $response.announcements.Count -gt 0) {
        foreach ($ann in $response.announcements) {
            try {
                $annId      = $ann.id
                $annTitle   = $ann.title -replace "'", "''"
                $annMessage = $ann.message -replace "'", "''"
                $typeLabel  = switch ($ann.type) {
                    'warning'     { 'WICHTIGE WARNUNG' }
                    'maintenance' { 'WARTUNGSANKUENDIGUNG' }
                    default       { 'INFORMATION' }
                }
                $accentColor = switch ($ann.type) {
                    'warning'     { '220, 50, 50' }
                    'maintenance' { '245, 158, 11' }
                    default       { '99, 102, 241' }
                }

                # PS-Script das im Benutzer-Kontext laeuft und den Dialog zeigt
                $dialogScript = @"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
`$form = New-Object System.Windows.Forms.Form
`$form.Text = 'IT Nexus - $typeLabel'
`$form.Size = New-Object System.Drawing.Size(520, 330)
`$form.StartPosition = 'CenterScreen'
`$form.FormBorderStyle = 'FixedDialog'
`$form.MaximizeBox = `$false
`$form.MinimizeBox = `$false
`$form.TopMost = `$true
`$form.BackColor = [System.Drawing.Color]::FromArgb(24, 24, 37)
`$lTitle = New-Object System.Windows.Forms.Label
`$lTitle.Text = '$annTitle'
`$lTitle.Font = New-Object System.Drawing.Font('Segoe UI', 13, [System.Drawing.FontStyle]::Bold)
`$lTitle.ForeColor = [System.Drawing.Color]::White
`$lTitle.Location = New-Object System.Drawing.Point(20, 20)
`$lTitle.Size = New-Object System.Drawing.Size(460, 36)
`$form.Controls.Add(`$lTitle)
`$lMsg = New-Object System.Windows.Forms.Label
`$lMsg.Text = '$annMessage'
`$lMsg.Font = New-Object System.Drawing.Font('Segoe UI', 10)
`$lMsg.ForeColor = [System.Drawing.Color]::FromArgb(200, 200, 220)
`$lMsg.Location = New-Object System.Drawing.Point(20, 66)
`$lMsg.Size = New-Object System.Drawing.Size(460, 155)
`$lMsg.AutoSize = `$false
`$form.Controls.Add(`$lMsg)
`$lHint = New-Object System.Windows.Forms.Label
`$lHint.Text = 'Bitte lesen und Kenntnisnahme bestaetigen.'
`$lHint.Font = New-Object System.Drawing.Font('Segoe UI', 8, [System.Drawing.FontStyle]::Italic)
`$lHint.ForeColor = [System.Drawing.Color]::FromArgb(130, 130, 150)
`$lHint.Location = New-Object System.Drawing.Point(20, 228)
`$lHint.Size = New-Object System.Drawing.Size(460, 20)
`$form.Controls.Add(`$lHint)
`$btn = New-Object System.Windows.Forms.Button
`$btn.Text = 'Gelesen und bestaetigt'
`$btn.Font = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold)
`$btn.ForeColor = [System.Drawing.Color]::White
`$btn.BackColor = [System.Drawing.Color]::FromArgb($accentColor)
`$btn.FlatStyle = 'Flat'
`$btn.Location = New-Object System.Drawing.Point(20, 255)
`$btn.Size = New-Object System.Drawing.Size(460, 40)
`$btn.DialogResult = [System.Windows.Forms.DialogResult]::OK
`$form.Controls.Add(`$btn)
`$form.AcceptButton = `$btn
`$r = `$form.ShowDialog()
if (`$r -eq [System.Windows.Forms.DialogResult]::OK) {
    `$b = [System.Text.Encoding]::UTF8.GetBytes('{"hostname":"' + `$env:COMPUTERNAME + '"}')
    `$req = [System.Net.WebRequest]::Create('$ServerUrl/api/announcements/$annId/ack-agent')
    `$req.Method = 'POST'
    `$req.ContentType = 'application/json'
    `$req.Headers.Add('X-Agent-Key','$AgentKey')
    `$req.ContentLength = `$b.Length
    `$s = `$req.GetRequestStream(); `$s.Write(`$b,0,`$b.Length); `$s.Close()
    try { `$req.GetResponse().Close() } catch {}
}
"@
                # Dialog-Script temporaer speichern
                $scriptFile = "C:\ProgramData\IT Nexus Agent\ann_$annId.ps1"
                $dialogScript | Out-File -FilePath $scriptFile -Encoding UTF8 -Force

                # Eingeloggten Benutzer ermitteln
                $loggedUser = (Get-CimInstance Win32_ComputerSystem).UserName
                if ($loggedUser -and $loggedUser -ne '') {
                    $taskName = "ITNexus-Ann-$annId"
                    Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
                    $action   = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-ExecutionPolicy Bypass -WindowStyle Normal -File `"$scriptFile`""
                    $trigger  = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(3)
                    $principal = New-ScheduledTaskPrincipal -UserId $loggedUser -LogonType Interactive -RunLevel Limited
                    $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -StartWhenAvailable
                    Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
                    Write-Log "ANNOUNCEMENT: Dialog fuer Benutzer $loggedUser gestartet - ID $annId"
                } else {
                    Write-Log "ANNOUNCEMENT: Kein Benutzer eingeloggt - Dialog wird beim naechsten Check-in versucht"
                }
            } catch {
                Write-Log "ANNOUNCEMENT ERROR: $($_.Exception.Message)"
            }
        }
    }

    # Pending Patch-Commands verarbeiten
    if ($response.commands -and $response.commands.Count -gt 0) {
        Write-Log "PATCH: $($response.commands.Count) Command(s) empfangen"
        foreach ($cmd in $response.commands) {
            Invoke-PatchCommand -cmd $cmd.command -cmdId $cmd.id
        }
    }
} catch {
    $statusCode = $_.Exception.Response.StatusCode.Value__
    Write-Log "ERROR: Checkin fehlgeschlagen - HTTP $statusCode - $($_.Exception.Message)"
    try {
        $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
        Write-Log "ERROR Detail: $($reader.ReadToEnd())"
    } catch {}
}

# ── Schneller Announcement-Poll (alle 15 Sekunden im Hintergrund) ─────────────
function Invoke-AnnouncementPoll {
    try {
        $pollBody = @{ hostname = $env:COMPUTERNAME } | ConvertTo-Json
        $pollResp = Invoke-RestMethod -Uri "$ServerUrl/api/monitoring/announcements-poll" `
            -Method POST -Body $pollBody `
            -Headers @{ 'Content-Type' = 'application/json'; 'X-Agent-Key' = $AgentKey } `
            -TimeoutSec 10
        if ($pollResp.announcements -and $pollResp.announcements.Count -gt 0) {
            foreach ($ann in $pollResp.announcements) {
                $annId      = $ann.id
                $annTitle   = $ann.title -replace "'", "''"
                $annMessage = $ann.message -replace "'", "''"
                $accentColor = switch ($ann.type) {
                    'warning'     { '220, 50, 50' }
                    'maintenance' { '245, 158, 11' }
                    default       { '99, 102, 241' }
                }
                $typeLabel = switch ($ann.type) {
                    'warning'     { 'WICHTIGE WARNUNG' }
                    'maintenance' { 'WARTUNGSANKUENDIGUNG' }
                    default       { 'INFORMATION' }
                }
                $dialogScript = @"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
`$form = New-Object System.Windows.Forms.Form
`$form.Text = 'IT Nexus - $typeLabel'
`$form.Size = New-Object System.Drawing.Size(520, 330)
`$form.StartPosition = 'CenterScreen'
`$form.FormBorderStyle = 'FixedDialog'
`$form.MaximizeBox = `$false
`$form.MinimizeBox = `$false
`$form.TopMost = `$true
`$form.BackColor = [System.Drawing.Color]::FromArgb(24, 24, 37)
`$lTitle = New-Object System.Windows.Forms.Label
`$lTitle.Text = '$annTitle'
`$lTitle.Font = New-Object System.Drawing.Font('Segoe UI', 13, [System.Drawing.FontStyle]::Bold)
`$lTitle.ForeColor = [System.Drawing.Color]::White
`$lTitle.Location = New-Object System.Drawing.Point(20, 20)
`$lTitle.Size = New-Object System.Drawing.Size(460, 36)
`$form.Controls.Add(`$lTitle)
`$lMsg = New-Object System.Windows.Forms.Label
`$lMsg.Text = '$annMessage'
`$lMsg.Font = New-Object System.Drawing.Font('Segoe UI', 10)
`$lMsg.ForeColor = [System.Drawing.Color]::FromArgb(200, 200, 220)
`$lMsg.Location = New-Object System.Drawing.Point(20, 66)
`$lMsg.Size = New-Object System.Drawing.Size(460, 155)
`$lMsg.AutoSize = `$false
`$form.Controls.Add(`$lMsg)
`$lHint = New-Object System.Windows.Forms.Label
`$lHint.Text = 'Bitte lesen und Kenntnisnahme bestaetigen.'
`$lHint.Font = New-Object System.Drawing.Font('Segoe UI', 8, [System.Drawing.FontStyle]::Italic)
`$lHint.ForeColor = [System.Drawing.Color]::FromArgb(130, 130, 150)
`$lHint.Location = New-Object System.Drawing.Point(20, 228)
`$lHint.Size = New-Object System.Drawing.Size(460, 20)
`$form.Controls.Add(`$lHint)
`$btn = New-Object System.Windows.Forms.Button
`$btn.Text = 'Gelesen und bestaetigt'
`$btn.Font = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold)
`$btn.ForeColor = [System.Drawing.Color]::White
`$btn.BackColor = [System.Drawing.Color]::FromArgb($accentColor)
`$btn.FlatStyle = 'Flat'
`$btn.Location = New-Object System.Drawing.Point(20, 255)
`$btn.Size = New-Object System.Drawing.Size(460, 40)
`$btn.DialogResult = [System.Windows.Forms.DialogResult]::OK
`$form.Controls.Add(`$btn)
`$form.AcceptButton = `$btn
`$r = `$form.ShowDialog()
if (`$r -eq [System.Windows.Forms.DialogResult]::OK) {
    `$b = [System.Text.Encoding]::UTF8.GetBytes('{"hostname":"' + `$env:COMPUTERNAME + '"}')
    `$req = [System.Net.WebRequest]::Create('$ServerUrl/api/announcements/$annId/ack-agent')
    `$req.Method = 'POST'; `$req.ContentType = 'application/json'
    `$req.Headers.Add('X-Agent-Key','$AgentKey')
    `$req.ContentLength = `$b.Length
    `$s = `$req.GetRequestStream(); `$s.Write(`$b,0,`$b.Length); `$s.Close()
    try { `$req.GetResponse().Close() } catch {}
}
"@
                $scriptFile = "C:\ProgramData\IT Nexus Agent\ann_$annId.ps1"
                $dialogScript | Out-File -FilePath $scriptFile -Encoding UTF8 -Force
                $loggedUser = (Get-CimInstance Win32_ComputerSystem).UserName
                if ($loggedUser) {
                    $taskName = "ITNexus-Ann-$annId"
                    Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
                    $action    = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-ExecutionPolicy Bypass -WindowStyle Normal -File `"$scriptFile`""
                    $trigger   = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(2)
                    $principal = New-ScheduledTaskPrincipal -UserId $loggedUser -LogonType Interactive -RunLevel Limited
                    $settings  = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 60) -StartWhenAvailable
                    Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
                    Write-Log "ANNOUNCEMENT: Sofort-Dialog gestartet fuer $loggedUser - ID $annId"
                }
            }
        }
    } catch {
        # Stiller Fehler - nicht kritisch
    }
}

# Announcement-Watcher-Task einrichten (alle 15 Sek, bleibt dauerhaft aktiv)
try {
    $watcherName   = 'IT Nexus Announcement Watcher'
    $watcherScript = 'C:\ProgramData\IT Nexus Agent\ann-watcher.ps1'

    # Watcher-Script vom Server laden (immer aktuell)
    Invoke-WebRequest -Uri "$ServerUrl/api/monitoring/ann-watcher" `
        -Headers @{ 'X-Agent-Key' = $AgentKey } `
        -OutFile $watcherScript -TimeoutSec 15 -ErrorAction SilentlyContinue

    # Task registrieren falls noch nicht vorhanden
    if (-not (Get-ScheduledTask -TaskName $watcherName -ErrorAction SilentlyContinue)) {
        $wAct = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-ExecutionPolicy Bypass -NonInteractive -WindowStyle Hidden -File `"$watcherScript`""
        $wTrg = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Seconds 15) -Once -At (Get-Date)
        $wSet = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 14) -MultipleInstances IgnoreNew -StartWhenAvailable
        $wPri = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
        Register-ScheduledTask -TaskName $watcherName -Action $wAct -Trigger $wTrg -Settings $wSet -Principal $wPri -Force | Out-Null
        Write-Log "ANNOUNCEMENT: Watcher-Task registriert (alle 15 Sekunden)"
    }
} catch {
    Write-Log "ANNOUNCEMENT WATCHER ERROR: $($_.Exception.Message)"
}
