diff --git a/CHANGELOG.md b/CHANGELOG.md index f07c472..0a21215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- A deferred automatic reboot that was already performed **out-of-band** (a manual restart or a Windows Update reboot) is now reconciled instead of restarting the server again: when a `.pending` reboot request predates the server's last boot time, `Invoke-SPSAutomaticReboot` marks the Reboot phase `Done` ("Reboot already completed out-of-band"), clears the pending marker and skips the restart. A fresh reboot required by an install performed in the same run is unaffected. ([#68](https://github.com/luigilink/SPSUpdate/issues/68)) - `-Action ProductUpdate` now refreshes the installed server's live **Patch Status** on the Binaries Installation card after the cumulative update is installed, so the column reflects the real post-install state (typically `Upgrade Required` until the Configuration Wizard runs) instead of keeping the pre-install value (`No Action Required`) until the next `Default` run. It reuses the same master-side patch-status read as the `Default` baseline (no remoting) and is status-only — the Content Databases and Configuration Wizard cards still evolve only during a `Default` run. A failed read preserves the last-known-good value. ([#59](https://github.com/luigilink/SPSUpdate/issues/59)) - The near real-time dashboard now refreshes the live farm status at key transitions (the pre-patch baseline, after the Configuration Wizard completes, and the final render) instead of only at the pre-patch baseline. The master re-reads each server's patch status and re-publishes the content-database inventory with fresh upgrade status, so a successful campaign no longer shows a stale `Upgrade Required` Patch Status or a lingering pre-patch inconsistency banner once the farm is back to `No Action Required`. The Content Databases card also shows a database that SPSUpdate has already upgraded (State `Done`) as `No update pending` even before the next refresh. Server patch status and role are shown with short readable labels (for example `Upgrade Required`, `Application with Search`) via the new public helpers `ConvertTo-SPSPatchStatusLabel` and `ConvertTo-SPSRoleLabel`, instead of the raw enum names. ([#51](https://github.com/luigilink/SPSUpdate/issues/51)) - A standalone, idempotent helper `New-SPSDashboardSite.ps1` (in `src/`) that provisions the dashboard hosting target on an IIS/pull server — folder, SMB share (Modify for the write accounts), NTFS permissions, a static-file `web.config`, and either a dedicated IIS site or a sub-application under an existing site. It has no dependency on the `SPSUpdate.Common` module, so it can be copied on its own to the IIS server. A new wiki page, *Hosting the dashboard on IIS*, documents the validated setup (and the manual equivalent). ([#49](https://github.com/luigilink/SPSUpdate/issues/49)) diff --git a/src/SPSUpdate.ps1 b/src/SPSUpdate.ps1 index d1ff9d6..682f5d9 100644 --- a/src/SPSUpdate.ps1 +++ b/src/SPSUpdate.ps1 @@ -632,6 +632,24 @@ function Invoke-SPSAutomaticReboot { return } + # Reconcile a deferred reboot already performed out-of-band (pending predates last boot), but + # not when this run itself needs a reboot (fresh -RebootRequired, or Force on a successful install). + if ($hasPending -and -not $RebootRequired -and -not ($rebootCfg.Force -and $InstallPerformed)) { + $pendingStamp = $null + try { $pendingStamp = [datetimeoffset]::Parse((Get-Content -Path $rebootPendingMarker -Raw).Trim(), [cultureinfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::RoundtripKind).UtcDateTime } catch { $pendingStamp = $null } + $lastBoot = $null + try { $lastBoot = (Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop).LastBootUpTime.ToUniversalTime() } catch { $lastBoot = $null } + if ($null -ne $pendingStamp -and $null -ne $lastBoot -and $lastBoot -gt $pendingStamp) { + Write-Output "Deferred reboot for $thisServer already satisfied out-of-band (last boot $lastBoot > request $pendingStamp); reconciling." + if ($null -ne $rebootDoneMarker) { Set-Content -Path $rebootDoneMarker -Value (Get-Date -Format o) -Force -ErrorAction SilentlyContinue } + Remove-Item -Path $rebootPendingMarker -Force -ErrorAction SilentlyContinue + Write-SPSStatus -Scope 'Reboot' -Phase 'Reboot' -Server $thisServer -State 'Done' -Detail 'Reboot already completed out-of-band' + Add-SPSUpdateEvent -Message "Deferred reboot on $thisServer reconciled: the server was already rebooted out-of-band." -Source 'Restart-SPSServer' -EntryType 'Information' -EventID 3010 + Write-SPSDashboard + return + } + } + # Decide whether a reboot is warranted. Force reboots even on a successful no-reboot # install (exit 0), but only when an install actually ran this time, so an already-patched # server (null / exit 17025) is never rebooted just because a new campaign started.