diff --git a/config.example.ini b/config.example.ini index 1e8ecb1..70a22f0 100644 --- a/config.example.ini +++ b/config.example.ini @@ -84,6 +84,19 @@ NotifyOnUpdateComplete = true NotifyOnReboot = true # Reboot required but auto-reboot is disabled NotifyOnRebootRequired = true +# Service Watchdog failed to restart one or more services +NotifyOnServiceRestartFailure = true + +[ServiceWatchdog] +# Enable service watchdog: restart specific services after updates when no reboot is required +Enabled = false +# Comma-separated list of Windows service names to restart (use the service name, not the display name) +# Example: Services = Spooler,W3SVC,MSSQLSERVER +Services = +# Seconds to wait for each service to reach Running state (default: 30) +WaitSeconds = 30 +# Number of restart attempts per service (default: 3) +RetryCount = 3 [Hooks] # Path to PowerShell script to execute BEFORE updates (leave empty to disable) diff --git a/docs/configuration.md b/docs/configuration.md index daa8541..b219cca 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -82,6 +82,16 @@ Steuert, welche Ereignisse eine Benachrichtigung auslösen. Alle Ereignisse sind | `NotifyOnUpdateComplete` | Boolean | `true` | Zusammenfassung nach Update-Installation | | `NotifyOnReboot` | Boolean | `true` | Neustart wird ausgeführt (immediate/delayed/scheduled) | | `NotifyOnRebootRequired` | Boolean | `true` | Neustart nötig, aber Auto-Reboot ist deaktiviert | +| `NotifyOnServiceRestartFailure` | Boolean | `true` | Service-Watchdog konnte einen oder mehrere Dienste nicht starten | + +### [ServiceWatchdog] + +| Option | Typ | Standard | Beschreibung | +|---|---|---|---| +| `Enabled` | Boolean | `false` | Service-Watchdog aktivieren: Dienste nach Updates neu starten, wenn kein Reboot erforderlich ist | +| `Services` | String | *(leer)* | Kommagetrennte Liste von Windows-Dienstnamen (Service Name, nicht Display Name), z.B. `Spooler,W3SVC,MSSQLSERVER` | +| `WaitSeconds` | Integer | `30` | Wartezeit in Sekunden, bis ein Dienst den Status "Running" erreicht hat | +| `RetryCount` | Integer | `3` | Anzahl Neustartversuche pro Dienst | ### [Hooks] @@ -196,6 +206,13 @@ NotifyOnDryRun = true NotifyOnUpdateComplete = true NotifyOnReboot = true NotifyOnRebootRequired = true +NotifyOnServiceRestartFailure = true + +[ServiceWatchdog] +Enabled = true +Services = Spooler,W3SVC +WaitSeconds = 30 +RetryCount = 3 [Hooks] PreUpdateScript = C:\scripts\pre-update.ps1 diff --git a/docs/notifications.md b/docs/notifications.md index 27d1673..757531d 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -166,6 +166,7 @@ Das Script sendet Benachrichtigungen bei folgenden Ereignissen. Jedes Ereignis k | Update-Zusammenfassung | `NotifyOnUpdateComplete` | default/high | `Updates OK - ` oder `Updates (with errors) - ` | | Neustart wird ausgeführt | `NotifyOnReboot` | high | `Reboot - ` / `Reboot in X min - ` / `Reboot scheduled at HH:MM - ` | | Neustart erforderlich | `NotifyOnRebootRequired` | high | `Reboot Required - ` | +| Dienst-Neustart fehlgeschlagen | `NotifyOnServiceRestartFailure` | high | `Service Restart FAILED - ` | ### Details zu jedem Ereignis @@ -225,6 +226,14 @@ Ein Neustart ist nach der Update-Installation nötig, aber der automatische Neus - **Subject:** `Reboot Required - ` - **Inhalt:** Hinweis, dass ein manueller Neustart durchgeführt werden muss +#### Dienst-Neustart fehlgeschlagen (`NotifyOnServiceRestartFailure`) + +Der Service-Watchdog konnte einen oder mehrere konfigurierte Dienste nach den Updates nicht starten. Der Watchdog wird nur ausgeführt, wenn kein Neustart erforderlich ist (`[ServiceWatchdog] Enabled = true`). + +- **Priorität:** high +- **Subject:** `Service Restart FAILED - ` +- **Inhalt:** Liste der betroffenen Dienste mit Fehlerbeschreibung (Dienst nicht gefunden, alle Versuche fehlgeschlagen) + ### Einzelne Ereignisse deaktivieren In der Sektion `[NotificationEvents]` der `config.ini` kann jedes Ereignis individuell gesteuert werden. Beispiel — nur bei Fehlern und Reboot-Bedarf benachrichtigen: @@ -238,6 +247,7 @@ NotifyOnDryRun = false NotifyOnUpdateComplete = true NotifyOnReboot = true NotifyOnRebootRequired = true +NotifyOnServiceRestartFailure = true ``` Wird die Sektion `[NotificationEvents]` nicht angegeben oder ein Schlüssel weggelassen, ist das jeweilige Ereignis standardmäßig **aktiviert** (`true`). diff --git a/windows-updater.ps1 b/windows-updater.ps1 index d62282a..fdcfdfa 100644 --- a/windows-updater.ps1 +++ b/windows-updater.ps1 @@ -9,9 +9,9 @@ Author: Patrick Asmus Web: https://www.cleveradmin.de Repository: https://git.techniverse.net/scriptos/windows-updater.git License: MIT -Version: 2.1.1 +Version: 2.2.0 Datum: 06.07.2026 -Modifiaktione: Notifications können detallierter konfiguriert werden, z.B. nur bei Fehlern oder nur bei Reboot erforderlich. +Modifiaktione: Service-Watchdog: Dienste nach Updates ohne Reboot neu starten und überwachen. ##################################################### #> @@ -549,7 +549,7 @@ function Send-TeamsNotification { function Test-NotificationEvent { param( - [ValidateSet('ModuleError', 'HookFailure', 'NoUpdates', 'DryRun', 'UpdateComplete', 'Reboot', 'RebootRequired')] + [ValidateSet('ModuleError', 'HookFailure', 'NoUpdates', 'DryRun', 'UpdateComplete', 'Reboot', 'RebootRequired', 'ServiceRestartFailure')] [string]$Event ) @@ -1024,6 +1024,89 @@ function Invoke-ScheduledReboot { #endregion +# =================================================== +#region Service Watchdog +# =================================================== + +function Invoke-ServiceWatchdog { + $watchdogEnabled = Get-ConfigValue -Config $script:Config -Section "ServiceWatchdog" -Key "Enabled" -Default $false -Type bool + if (-not $watchdogEnabled) { return } + + $serviceList = Get-ConfigValue -Config $script:Config -Section "ServiceWatchdog" -Key "Services" -Default "" + if ([string]::IsNullOrWhiteSpace($serviceList)) { + Write-Log "ServiceWatchdog enabled but no services configured" -Level WARN + return + } + + $services = $serviceList -split ',' | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } + if ($services.Count -eq 0) { return } + + $waitSeconds = Get-ConfigValue -Config $script:Config -Section "ServiceWatchdog" -Key "WaitSeconds" -Default 30 -Type int + $retryCount = Get-ConfigValue -Config $script:Config -Section "ServiceWatchdog" -Key "RetryCount" -Default 3 -Type int + + Write-Log "Service Watchdog: Restarting $($services.Count) service(s)..." + + $failedServices = [System.Collections.ArrayList]::new() + + foreach ($svcName in $services) { + Write-Log " Restarting service: $svcName" + + $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue + if (-not $svc) { + Write-Log " Service '$svcName' not found on this system" -Level ERROR + $failedServices.Add([PSCustomObject]@{ Name = $svcName; Reason = "Service not found" }) | Out-Null + continue + } + + $started = $false + for ($attempt = 1; $attempt -le $retryCount; $attempt++) { + try { + if ($attempt -eq 1) { + Restart-Service -Name $svcName -Force -ErrorAction Stop + } + else { + Start-Service -Name $svcName -ErrorAction Stop + } + + $svc.WaitForStatus([System.ServiceProcess.ServiceControllerStatus]::Running, [TimeSpan]::FromSeconds($waitSeconds)) + $svc.Refresh() + + if ($svc.Status -eq 'Running') { + Write-Log " Service '$svcName' is running (attempt $attempt/$retryCount)" + $started = $true + break + } + } + catch { + Write-Log " Attempt $attempt/$retryCount for '$svcName' failed: $($_.Exception.Message)" -Level WARN + if ($attempt -lt $retryCount) { + Start-Sleep -Seconds 5 + } + } + } + + if (-not $started) { + $svc.Refresh() + Write-Log " Service '$svcName' could not be started (status: $($svc.Status))" -Level ERROR + $failedServices.Add([PSCustomObject]@{ Name = $svcName; Reason = "Failed after $retryCount attempts (status: $($svc.Status))" }) | Out-Null + $script:HasErrors = $true + } + } + + if ($failedServices.Count -gt 0) { + $failList = ($failedServices | ForEach-Object { " - $($_.Name): $($_.Reason)" }) -join "`n" + $body = "Service Watchdog on $env:COMPUTERNAME failed to restart $($failedServices.Count) service(s):`n$failList" + Write-Log "Service Watchdog: $($failedServices.Count) service(s) failed" -Level ERROR + Send-Notification -Subject "Service Restart FAILED - $env:COMPUTERNAME" ` + -Body $body -Priority "high" -EventName "ServiceRestartFailure" + } + else { + Write-Log "Service Watchdog: All services restarted successfully" + } +} + +#endregion + # =================================================== #region Main Execution # =================================================== @@ -1173,6 +1256,8 @@ function Start-WindowsUpdater { } else { Write-Log "No reboot required" + # --- Service Watchdog (only when no reboot is pending) --- + Invoke-ServiceWatchdog } # --- Finish ---