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