# ITHN Monitoring - VM Auto-Install Bootstrap v5 (Auto-Detect) # Generated: 2026-08-10 16:28:28 # Parent host: ATL-SNAI-TS-01 (id=82) # # Als ADMINISTRATOR auf dem Hyper-V-Host ausfuehren. # # v3: Erkennt Rollen automatisch per PowerShell Direct (Veeam, Printserver, # DC, DNS, DHCP, SQL, Fileserver, Lexware, DATEV, Exchange). # Installiert auch den Parent-Agent selbst, falls noch nicht vorhanden. # Interaktiv nur noch: Credentials, SNMP-Community, eine Bestaetigung. # SQL/Lexware/DATEV/DC/etc. ueberwacht der Agent selbst (auto_detect_services). # v4: Parent-Agent bekommt redfish_auto (BMC-Selbst-Discovery): findet das eigene # iDRAC/iLO im lokalen Netz per Service-Tag-Abgleich (read-only User Monitor). # Voraussetzung: read-only User im BMC anlegen (Name/Passwort = Standard-Konvention). # v5: Gastdienstschnittstelle wird vor Copy-VMFile automatisch aktiviert (kein # manueller Handgriff mehr). Bei bereits vorhandener Parent-agent.yml wird der # veeam-Status neu abgeglichen (Host, der spaeter Veeam bekommt, wird nachgezogen). $ErrorActionPreference = 'Stop' # Admin-Check $wi = [Security.Principal.WindowsIdentity]::GetCurrent() $wp = New-Object Security.Principal.WindowsPrincipal($wi) if (-not $wp.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host "FEHLER: Bitte in einer Administrator-PowerShell ausfuehren." -ForegroundColor Red Read-Host "ENTER zum Beenden" exit 1 } $ServerUrl = 'https://monitoring.services-ithn.de' $ParentToken = 'p0PYnVLrgx1ARJ6CLMO9x7z5cFXuhBxg2YdmuBw8UUJ44O80' $HostAgentDir = 'C:\_ITHN\agents\bin' $VmAgentDir = 'C:\_ITHN\agents\bin' Write-Host "" Write-Host "=== ITHN Monitoring - Bootstrap v3 (Auto-Detect) ===" -ForegroundColor Cyan Write-Host "Parent: ATL-SNAI-TS-01" -ForegroundColor Gray Write-Host "" # Pruefungen if (-not (Get-Command Get-VM -ErrorAction SilentlyContinue)) { Write-Host "FEHLER: Hyper-V PowerShell-Modul nicht installiert." -ForegroundColor Red exit 1 } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 # ============================ # Agent auf Parent installieren (falls noch nicht vorhanden) # ============================ if (-not (Test-Path $HostAgentDir)) { New-Item -Path $HostAgentDir -ItemType Directory -Force | Out-Null } if (-not (Test-Path "$HostAgentDir\agent.exe")) { Write-Host "agent.exe wird heruntergeladen (agent-v0.20.0.exe)..." -ForegroundColor Cyan Invoke-WebRequest -Uri "$ServerUrl/agent/agent-v0.20.0.exe" -OutFile "$HostAgentDir\agent.exe" -UseBasicParsing -TimeoutSec 120 | Out-Null Write-Host "agent.exe installiert." -ForegroundColor Green } # ============================ # curl.exe bereitstellen — der Updater braucht es, Windows Server 2016 bringt keins mit. # In den Agent-Ordner laden (fuer VM-Kopie) + nach System32, falls auf dem Host nicht vorhanden. # ============================ $curlPath = Join-Path $HostAgentDir 'curl.exe' try { Invoke-WebRequest -Uri "$ServerUrl/agent/curl.exe" -OutFile $curlPath -UseBasicParsing -TimeoutSec 90 | Out-Null } catch { Write-Host "WARNUNG: curl.exe-Download fehlgeschlagen: $($_.Exception.Message)" -ForegroundColor Yellow } if ((Test-Path $curlPath) -and -not (Get-Command curl.exe -ErrorAction SilentlyContinue)) { Copy-Item $curlPath "$env:WINDIR\System32\curl.exe" -Force -ErrorAction SilentlyContinue Write-Host "curl.exe nach System32 installiert (fehlte - z.B. Server 2016)." -ForegroundColor Green } # ============================ # Updater auf Parent installieren (Auto-Update fuer Agent) # ============================ $updaterPath = Join-Path $HostAgentDir 'updater.ps1' try { Write-Host "Updater wird heruntergeladen..." -ForegroundColor Cyan Invoke-WebRequest -Uri "$ServerUrl/agent/updater.ps1" -OutFile $updaterPath -UseBasicParsing -TimeoutSec 30 | Out-Null } catch { Write-Host "WARNUNG: Updater-Download fehlgeschlagen: $($_.Exception.Message)" -ForegroundColor Yellow } if (Test-Path $updaterPath) { cmd /c "schtasks /Query /TN ITHN-Monitoring-Updater >nul 2>&1" if ($LASTEXITCODE -eq 0) { cmd /c "schtasks /End /TN ITHN-Monitoring-Updater >nul 2>&1" cmd /c "schtasks /Delete /TN ITHN-Monitoring-Updater /F >nul 2>&1" } $cmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$updaterPath`"" schtasks /Create /TN ITHN-Monitoring-Updater /TR $cmd /SC MINUTE /MO 5 /RU SYSTEM /F | Out-Null Write-Host "Updater-Task auf Parent registriert (alle 5 Min)." -ForegroundColor Green } $vms = Get-VM | Where-Object { $_.State -eq 'Running' } if (-not $vms) { Write-Host "Keine laufenden VMs gefunden." -ForegroundColor Yellow exit 0 } # ============================ # VM-Liste anzeigen # ============================ Write-Host "" Write-Host "Gefundene VMs:" -ForegroundColor Cyan $vmList = @() foreach ($vm in $vms) { Write-Host " - $($vm.Name)" $vmList += $vm } Write-Host "" # ============================ # Credentials (einmal, gilt fuer Erkennung UND Installation) # ============================ $cred = Get-Credential -Message "Lokaler Admin (oder Domain-Admin) der Ziel-VMs" if ($null -eq $cred) { Write-Host "Keine Credentials angegeben - Bootstrap abgebrochen." -ForegroundColor Red exit 1 } # ============================ # Phase 1: Rollen-Erkennung pro VM (PowerShell Direct) # ============================ $detectBlock = { $ErrorActionPreference = 'SilentlyContinue' $svc = Get-Service $printers = @(Get-Printer | Where-Object { $_.Shared }) $shares = @(Get-SmbShare | Where-Object { -not $_.Special -and $_.Name -ne 'NETLOGON' -and $_.Name -ne 'SYSVOL' -and $_.Name -ne 'print$' }) [pscustomobject]@{ Veeam = [bool]($svc | Where-Object { $_.Name -eq 'VeeamBackupSvc' }) SharedPrinters = $printers.Count Shares = $shares.Count DC = [bool]($svc | Where-Object { $_.Name -eq 'NTDS' }) Dns = [bool]($svc | Where-Object { $_.Name -eq 'DNS' }) Dhcp = [bool]($svc | Where-Object { $_.Name -eq 'DHCPServer' }) Exchange = [bool]($svc | Where-Object { $_.Name -eq 'MSExchangeIS' }) Sql = @($svc | Where-Object { $_.Name -like 'MSSQL$*' -or $_.Name -eq 'MSSQLSERVER' }).Count Firebird = @($svc | Where-Object { $_.Name -like 'Firebird*' }).Count Datev = @($svc | Where-Object { $_.Name -like '*datev*' }).Count } } $detect = @{} $vmCreds = @{} $skippedVms = @() foreach ($vm in $vmList) { $vmName = $vm.Name Write-Host "[$vmName] Rollen werden erkannt..." -ForegroundColor Cyan $tryCred = $cred $ok = $false for ($attempt = 1; $attempt -le 3 -and -not $ok; $attempt++) { try { $info = Invoke-Command -VMName $vmName -Credential $tryCred -ScriptBlock $detectBlock $detect[$vmName] = $info $vmCreds[$vmName] = $tryCred $cred = $tryCred # Funktionierende Creds als Default fuer naechste VM merken $ok = $true } catch { Write-Host "[$vmName] Zugriff fehlgeschlagen ($attempt/3): $($_.Exception.Message)" -ForegroundColor Yellow if ($attempt -lt 3) { $newCred = Get-Credential -Message "Credentials fuer $vmName (Cancel = VM ueberspringen)" if ($null -eq $newCred) { break } $tryCred = $newCred } } } if (-not $ok) { $skippedVms += $vmName Write-Host "[$vmName] WIRD UEBERSPRUNGEN (kein Zugriff)" -ForegroundColor Red } } if ($vmCreds.Count -eq 0) { Write-Host "Keine VM erreichbar - Bootstrap abgebrochen." -ForegroundColor Red exit 1 } function Get-RoleString($d) { $roles = @() if ($d.DC) { $roles += 'DC' } if ($d.Dns) { $roles += 'DNS' } if ($d.Dhcp) { $roles += 'DHCP' } if ($d.Veeam) { $roles += 'VEEAM-BACKUP' } if ($d.SharedPrinters -gt 0) { $roles += "PRINT ($($d.SharedPrinters) Drucker)" } if ($d.Shares -gt 0) { $roles += "FILE ($($d.Shares) Shares)" } if ($d.Sql -gt 0) { $roles += "SQL ($($d.Sql) Instanz/en)" } if ($d.Firebird -gt 0) { $roles += 'Lexware/Firebird' } if ($d.Datev -gt 0) { $roles += 'DATEV' } if ($d.Exchange) { $roles += 'EXCHANGE' } if ($roles.Count -eq 0) { return '-' } return ($roles -join ', ') } # ============================ # SNMP NAS (fuer Parent-Agent) - einzige Frage, ist nicht auto-erkennbar # ============================ Write-Host "" Write-Host "--- SNMP NAS-Monitoring ---" -ForegroundColor Yellow # Community kommt aus dem Onboarding-Formular (ins Skript gebacken); Env-Variable als Fallback. # Leer = kein NAS -> SNMP wird uebersprungen. KEIN Read-Host -> Skript haengt nie. $snmpCommunity = '' if (-not $snmpCommunity) { $snmpCommunity = $env:ITHN_SNMP_COMMUNITY } if ($snmpCommunity) { Write-Host " SNMP-Community gesetzt: $snmpCommunity" -ForegroundColor Green } else { Write-Host " uebersprungen (kein NAS / keine Community)." -ForegroundColor Gray } # ============================ # Zusammenfassung + Bestaetigung # ============================ Write-Host "" Write-Host "=== Erkannte Rollen ===" -ForegroundColor Cyan foreach ($vm in $vmList) { $vmName = $vm.Name if ($detect.ContainsKey($vmName)) { Write-Host (" {0,-25} {1}" -f $vmName, (Get-RoleString $detect[$vmName])) -ForegroundColor Green } else { Write-Host (" {0,-25} UEBERSPRUNGEN (kein Zugriff)" -f $vmName) -ForegroundColor Red } } if ($snmpCommunity) { Write-Host " SNMP: community=$snmpCommunity" -ForegroundColor Green } Write-Host "" Write-Host "Hinweis: SQL/Lexware/DATEV/DC/DNS/DHCP/Exchange ueberwacht der Agent" -ForegroundColor Gray Write-Host "automatisch (auto_detect_services) - keine manuelle Konfiguration noetig." -ForegroundColor Gray Write-Host "" Write-Host "Installation laeuft automatisch (nicht-interaktiv) ..." -ForegroundColor Cyan # ============================ # Parent-Agent einrichten (agent.yml + Task), falls Neuinstallation # ============================ $parentYmlPath = "$HostAgentDir\agent.yml" $snmpAppended = $false $parentYmlChanged = $false if (-not (Test-Path $parentYmlPath)) { Write-Host "" Write-Host "Parent-Agent wird eingerichtet..." -ForegroundColor Cyan $parentVeeam = 'false' if (Get-Service -Name VeeamBackupSvc -ErrorAction SilentlyContinue) { $parentVeeam = 'true' } $pLines = @( "server_url: `"$ServerUrl`"" "token: `"$ParentToken`"" "host_name: `"ATL-SNAI-TS-01`"" "host_type: `"vm`"" "interval_seconds: 60" "modules:" " hyperv: true" " veeam: $parentVeeam" " auto_detect_services: true" " redfish_auto:" " user: Monitor" " pass: `"Benutzer@2026!?!`"" ) if ($snmpCommunity) { $pLines += " snmp:" $pLines += " community: `"$snmpCommunity`"" } (($pLines -join "`n") + "`n") | Out-File -FilePath $parentYmlPath -Encoding ascii -NoNewline # Agent-Task auf Parent registrieren cmd /c "schtasks /Query /TN ITHN-Monitoring-Agent >nul 2>&1" if ($LASTEXITCODE -eq 0) { cmd /c "schtasks /End /TN ITHN-Monitoring-Agent >nul 2>&1" cmd /c "schtasks /Delete /TN ITHN-Monitoring-Agent /F >nul 2>&1" } schtasks /Create /TN ITHN-Monitoring-Agent /TR "$HostAgentDir\agent.exe" /SC ONSTART /RU SYSTEM /F | Out-Null $xml = [xml](schtasks /Query /TN ITHN-Monitoring-Agent /XML ONE) $ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable) $ns.AddNamespace('t', 'http://schemas.microsoft.com/windows/2004/02/mit/task') $limit = $xml.SelectSingleNode('//t:ExecutionTimeLimit', $ns) if ($limit) { $limit.InnerText = 'PT0S' } $tempXml = Join-Path $env:TEMP 'ithn-task.xml' $xml.Save($tempXml) schtasks /Create /TN ITHN-Monitoring-Agent /XML $tempXml /F | Out-Null Remove-Item $tempXml -ErrorAction SilentlyContinue schtasks /Run /TN ITHN-Monitoring-Agent | Out-Null # Agent-Watchdog (5-Min, startet agent.exe nach Absturz selbst neu) $wdScript = @" `$ErrorActionPreference = 'SilentlyContinue' `$bin = '$HostAgentDir' if (-not (Get-Process agent -ErrorAction SilentlyContinue)) { Start-Process -FilePath "`$bin\agent.exe" -WorkingDirectory `$bin -WindowStyle Hidden "`$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') watchdog: agent neu gestartet" | Out-File "`$bin\agent-watchdog.log" -Append -Encoding utf8 } "@ Set-Content -Path "$HostAgentDir\agent-watchdog.ps1" -Value $wdScript -Encoding UTF8 schtasks /Create /TN ITHN-Monitoring-Agent-Watchdog /TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$HostAgentDir\agent-watchdog.ps1`"" /SC MINUTE /MO 5 /RU SYSTEM /F | Out-Null Write-Host "Parent-Agent + Watchdog installiert und gestartet (hyperv=true, veeam=$parentVeeam)." -ForegroundColor Green } else { # Bestehende Parent-agent.yml: veeam-Status neu abgleichen + ggf. SNMP nachtragen Write-Host "" Write-Host "Parent agent.yml wird geprueft (Veeam/SNMP)..." -ForegroundColor Cyan $veeamNow = [bool](Get-Service -Name VeeamBackupSvc -ErrorAction SilentlyContinue) $parentYml = Get-Content $parentYmlPath -Raw if ($veeamNow -and $parentYml -match '(?m)^\s*veeam:\s*false\s*$') { $parentYml = $parentYml -replace '(?m)^(\s*veeam:\s*)false\s*$', '${1}true' Set-Content -Path $parentYmlPath -Value $parentYml -Encoding ascii -NoNewline $parentYmlChanged = $true Write-Host " Veeam auf dem Host erkannt -> veeam: true nachgetragen." -ForegroundColor Green $parentYml = Get-Content $parentYmlPath -Raw } elseif ($veeamNow -and $parentYml -notmatch '(?m)^\s*veeam:\s*') { $parentYml = $parentYml -replace '(?m)^(modules:\s*)$', "`$1`n veeam: true" Set-Content -Path $parentYmlPath -Value $parentYml -Encoding ascii -NoNewline $parentYmlChanged = $true Write-Host " Veeam auf dem Host erkannt -> veeam: true ergaenzt." -ForegroundColor Green $parentYml = Get-Content $parentYmlPath -Raw } else { Write-Host " Veeam-Status unveraendert." -ForegroundColor Gray } if ($snmpCommunity) { if ($parentYml -notmatch 'snmp:') { $parentYml = $parentYml.TrimEnd() + "`n snmp:`n community: `"$snmpCommunity`"`n" Set-Content -Path $parentYmlPath -Value $parentYml -Encoding ascii -NoNewline $snmpAppended = $true $parentYmlChanged = $true Write-Host " SNMP-Config hinzugefuegt." -ForegroundColor Green } else { Write-Host " SNMP bereits konfiguriert, uebersprungen." -ForegroundColor Gray } } } # ============================ # Phase 2: Jede erreichbare VM provisionieren # ============================ foreach ($vm in $vmList) { $vmName = $vm.Name if (-not $vmCreds.ContainsKey($vmName)) { continue } $d = $detect[$vmName] Write-Host "" Write-Host "[$vmName] Provisioning..." -ForegroundColor Cyan # Provision API aufrufen try { $resp = Invoke-RestMethod -Uri "$ServerUrl/api/agent/provision" -Method Post ` -Headers @{ Authorization = "Bearer $ParentToken" } ` -ContentType 'application/json' ` -Body (@{ vm_name = $vmName; hypervisor_id = $vm.Id.ToString() } | ConvertTo-Json) } catch { Write-Host "[$vmName] Provision-API Fehler: $($_.Exception.Message)" -ForegroundColor Red continue } # agent.yml aus erkannten Rollen generieren $ymlLines = @( "server_url: `"$ServerUrl`"" "token: `"$($resp.token)`"" "host_name: `"$vmName`"" "host_type: `"vm`"" "interval_seconds: 60" "modules:" " hyperv: false" ) if ($d.Veeam) { $ymlLines += " veeam: true" } else { $ymlLines += " veeam: false" } if ($d.SharedPrinters -gt 0) { $ymlLines += " printserver: true" } $ymlLines += " auto_detect_services: true" $ymlContent = ($ymlLines -join "`n") + "`n" $ymlPath = Join-Path $env:TEMP "agent-$vmName.yml" $ymlContent | Out-File -FilePath $ymlPath -Encoding ascii -NoNewline # Deploy (Creds wurden in Phase 1 bereits verifiziert, max 3 Versuche) $deploySuccess = $false $vmCred = $vmCreds[$vmName] $maxAttempts = 3 $skipped = $false # Gastdienstschnittstelle sicherstellen (fuer Copy-VMFile noetig) - Host-seitig, idempotent try { Enable-VMIntegrationService -VMName $vmName -Name "Gastdienstschnittstelle" -ErrorAction Stop | Out-Null } catch {} for ($attempt = 1; $attempt -le $maxAttempts -and -not $skipped; $attempt++) { try { Write-Host "[$vmName] Alten Agent stoppen..." Invoke-Command -VMName $vmName -Credential $vmCred -ScriptBlock { param($dir) # Task stoppen cmd /c "schtasks /End /TN ITHN-Monitoring-Agent >nul 2>&1" # Prozess killen falls noch aktiv Get-Process -Name agent -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 # Verzeichnis sicherstellen if (-not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null } } -ArgumentList $VmAgentDir Write-Host "[$vmName] agent.exe kopieren..." Copy-VMFile -VMName $vmName -SourcePath "$HostAgentDir\agent.exe" -DestinationPath "$VmAgentDir\agent.exe" -CreateFullPath -FileSource Host -Force Write-Host "[$vmName] agent.yml kopieren..." Copy-VMFile -VMName $vmName -SourcePath $ymlPath -DestinationPath "$VmAgentDir\agent.yml" -CreateFullPath -FileSource Host -Force Write-Host "[$vmName] Scheduled Task registrieren..." Invoke-Command -VMName $vmName -Credential $vmCred -ScriptBlock { param($dir) cmd /c "schtasks /Query /TN ITHN-Monitoring-Agent >nul 2>&1" if ($LASTEXITCODE -eq 0) { cmd /c "schtasks /End /TN ITHN-Monitoring-Agent >nul 2>&1" cmd /c "schtasks /Delete /TN ITHN-Monitoring-Agent /F >nul 2>&1" } schtasks /Create /TN ITHN-Monitoring-Agent /TR "$dir\agent.exe" /SC ONSTART /RU SYSTEM /F | Out-Null $xml = [xml](schtasks /Query /TN ITHN-Monitoring-Agent /XML ONE) $ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable) $ns.AddNamespace('t', 'http://schemas.microsoft.com/windows/2004/02/mit/task') $limit = $xml.SelectSingleNode('//t:ExecutionTimeLimit', $ns) if ($limit) { $limit.InnerText = 'PT0S' } $tempXml = Join-Path $env:TEMP 'ithn-task.xml' $xml.Save($tempXml) schtasks /Create /TN ITHN-Monitoring-Agent /XML $tempXml /F | Out-Null Remove-Item $tempXml -ErrorAction SilentlyContinue schtasks /Run /TN ITHN-Monitoring-Agent | Out-Null # Agent-Watchdog in der VM (5-Min, Selbstheilung nach Absturz) $wdScript = @" `$ErrorActionPreference = 'SilentlyContinue' `$bin = '$dir' if (-not (Get-Process agent -ErrorAction SilentlyContinue)) { Start-Process -FilePath "`$bin\agent.exe" -WorkingDirectory `$bin -WindowStyle Hidden "`$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') watchdog: agent neu gestartet" | Out-File "`$bin\agent-watchdog.log" -Append -Encoding utf8 } "@ Set-Content -Path "$dir\agent-watchdog.ps1" -Value $wdScript -Encoding UTF8 schtasks /Create /TN ITHN-Monitoring-Agent-Watchdog /TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$dir\agent-watchdog.ps1`"" /SC MINUTE /MO 5 /RU SYSTEM /F | Out-Null } -ArgumentList $VmAgentDir # Updater in die VM kopieren + Task registrieren if (Test-Path $updaterPath) { Write-Host "[$vmName] updater.ps1 kopieren..." Copy-VMFile -VMName $vmName -SourcePath $updaterPath -DestinationPath "$VmAgentDir\updater.ps1" -CreateFullPath -FileSource Host -Force # curl.exe in die VM kopieren (Updater braucht es; Server 2016 hat keins) if (Test-Path $curlPath) { try { Copy-VMFile -VMName $vmName -SourcePath $curlPath -DestinationPath "$VmAgentDir\curl.exe" -CreateFullPath -FileSource Host -Force } catch {} } Write-Host "[$vmName] Updater-Task registrieren..." Invoke-Command -VMName $vmName -Credential $vmCred -ScriptBlock { param($dir) # curl.exe sicherstellen (Server 2016 bringt keins mit) if ((Test-Path "$dir\curl.exe") -and -not (Get-Command curl.exe -ErrorAction SilentlyContinue)) { Copy-Item "$dir\curl.exe" "$env:WINDIR\System32\curl.exe" -Force -ErrorAction SilentlyContinue } cmd /c "schtasks /Query /TN ITHN-Monitoring-Updater >nul 2>&1" if ($LASTEXITCODE -eq 0) { cmd /c "schtasks /End /TN ITHN-Monitoring-Updater >nul 2>&1" cmd /c "schtasks /Delete /TN ITHN-Monitoring-Updater /F >nul 2>&1" } $cmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"$dir\updater.ps1`"" schtasks /Create /TN ITHN-Monitoring-Updater /TR $cmd /SC MINUTE /MO 5 /RU SYSTEM /F | Out-Null } -ArgumentList $VmAgentDir } $deploySuccess = $true break } catch { $errMsg = $_.Exception.Message Write-Host "[$vmName] Versuch $attempt/$maxAttempts fehlgeschlagen: $errMsg" -ForegroundColor Yellow if ($attempt -lt $maxAttempts) { $newCred = Get-Credential -Message "Credentials fuer $vmName (Versuch $($attempt+1)/$maxAttempts) - Cancel = VM ueberspringen" if ($null -eq $newCred) { Write-Host "[$vmName] Abgebrochen, ueberspringe." -ForegroundColor Red $skipped = $true } else { $vmCred = $newCred } } else { Write-Host "[$vmName] $maxAttempts Versuche fehlgeschlagen, ueberspringe." -ForegroundColor Red $skippedVms += $vmName } } } if ($deploySuccess) { Write-Host "[$vmName] FERTIG (host_id=$($resp.host_id)) [$(Get-RoleString $d)]" -ForegroundColor Green } Remove-Item $ymlPath -ErrorAction SilentlyContinue } Write-Host "" Write-Host "=== Bootstrap abgeschlossen ===" -ForegroundColor Cyan if ($skippedVms.Count -gt 0) { Write-Host "Uebersprungene VMs: $($skippedVms -join ', ')" -ForegroundColor Yellow } if ($parentYmlChanged) { Write-Host "Parent-agent.yml geaendert (Veeam/SNMP) - Agent wird neu gestartet..." -ForegroundColor Yellow Stop-ScheduledTask -TaskName 'ITHN-Monitoring-Agent' -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 Start-ScheduledTask -TaskName 'ITHN-Monitoring-Agent' -ErrorAction SilentlyContinue Write-Host " Agent neu gestartet." -ForegroundColor Green }