kms-website: diagnose SXSMSI 1603 (Windows Installer state) + fix false reboot msg

Post-restart telemetry: officePFRO cleared (real restart worked) but install still
fails [Failing PreReq=SXSMSI]/1603 with NO pending reboot - so 1603 is not a reboot
issue here.
- Stop claiming "reboot needed" on every 1603; only when a reboot is actually
  pending (Test-OfficeRebootPending = CBS/WU or an Office file-rename).
- Fast-Startup-aware restart hint (use Restart, NOT Shut down) when reboot IS
  pending; gate before the ~3 GB download (this is the user-visible restart notice).
- Capture Windows Installer state in diagnostics (msiserver status/starttype,
  Installer\InProgress, free disk GB) to pinpoint the SXSMSI prereq failure.
- Defensively re-enable msiserver if Disabled (a common SXSMSI cause).
- Get-OdtLogTail also matches prereq/sxsmsi lines.
This commit is contained in:
Viktor Barzin 2026-06-01 21:44:26 +00:00
parent e037caf710
commit 651154218f

View file

@ -271,7 +271,7 @@ function Get-OdtLogTail([int]$lines = 6) {
$logs = Get-ChildItem -Path @($script:OdtLogDir, $env:TEMP) -Filter *.log -ErrorAction SilentlyContinue | $logs = Get-ChildItem -Path @($script:OdtLogDir, $env:TEMP) -Filter *.log -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-30) } | Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-30) } |
Sort-Object LastWriteTime -Descending | Select-Object -First 6 Sort-Object LastWriteTime -Descending | Select-Object -First 6
$rx = 'error code|errorcode|errormessage|we.re sorry|cannot install|already installed|in use|being used|0x[0-9a-fA-F]{8}|\b1603\b|\b17\d{3}\b|\b30\d{3}\b' $rx = 'error code|errorcode|errormessage|we.re sorry|cannot install|already installed|in use|being used|prereq|sxsmsi|0x[0-9a-fA-F]{8}|\b1603\b|\b17\d{3}\b|\b30\d{3}\b'
foreach ($log in $logs) { foreach ($log in $logs) {
$err = Get-Content -LiteralPath $log.FullName -ErrorAction SilentlyContinue | $err = Get-Content -LiteralPath $log.FullName -ErrorAction SilentlyContinue |
Where-Object { $_ -match $rx } | Select-Object -Last $lines Where-Object { $_ -match $rx } | Select-Object -Last $lines
@ -289,8 +289,13 @@ function Get-OfficeState {
$cbs = [bool](Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') $cbs = [bool](Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending')
$wu = [bool](Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') $wu = [bool](Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired')
$pfro = @((Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -ErrorAction SilentlyContinue).PendingFileRenameOperations | Where-Object { $_ -match 'Office|ClickToRun' }) $pfro = @((Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -ErrorAction SilentlyContinue).PendingFileRenameOperations | Where-Object { $_ -match 'Office|ClickToRun' })
# Windows Installer health: the C2R 'SXSMSI' prereq fails (1603) when msiserver
# is disabled, an MSI op is mid-flight (InProgress), or the disk is full.
$msi = Get-Service msiserver -ErrorAction SilentlyContinue
$inprog = [bool](Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\InProgress')
$free = try { [int]((Get-PSDrive C -ErrorAction Stop).Free / 1GB) } catch { -1 }
$svc = "$((Get-Service ClickToRunSvc -ErrorAction SilentlyContinue).Status)" $svc = "$((Get-Service ClickToRunSvc -ErrorAction SilentlyContinue).Status)"
"prids=[$($cfg.ProductReleaseIds)] plat=$($cfg.Platform) roots=$($roots.Count) reboot[cbs=$cbs wu=$wu officePFRO=$($pfro.Count)] c2rsvc=$svc ospp=$([bool](Find-Ospp))" "prids=[$($cfg.ProductReleaseIds)] roots=$($roots.Count) reboot[cbs=$cbs wu=$wu officePFRO=$($pfro.Count)] msi=$($msi.Status)/$($msi.StartType) inprog=$inprog freeGB=$free c2rsvc=$svc ospp=$([bool](Find-Ospp))"
} }
# "A reboot is pending" probe (all signals) - used for advisory messages/telemetry. # "A reboot is pending" probe (all signals) - used for advisory messages/telemetry.
@ -301,12 +306,23 @@ function Test-PendingReboot {
return [bool]$sm.PendingFileRenameOperations return [bool]$sm.PendingFileRenameOperations
} }
# STRONG reboot signals only (CBS / Windows Update) - reliably "a real reboot is # Office-blocking pending reboot: CBS/WU OR an Office-related pending file-rename.
# needed". Used to GATE an install (PendingFileRenameOperations is too noisy to # Office's C2R installer fails its SXSMSI prereq (1603) while a file-rename is
# block on - it is set by lots of unrelated software). # queued, so these specifically must be cleared before an install.
function Test-RebootRequiredHard { function Test-OfficeRebootPending {
(Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') -or if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') { return $true }
(Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') { return $true }
$sm = Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name PendingFileRenameOperations -ErrorAction SilentlyContinue
return [bool](@($sm.PendingFileRenameOperations | Where-Object { $_ -match 'Office|ClickToRun' }))
}
# Restart guidance. The crucial bit: a normal "Shut down" on Win10/11 does NOT
# clear pending file operations (Fast Startup hibernates the kernel) - only a real
# "Restart" runs them. Users routinely hit this.
function Show-RestartHint {
Write-Host " A pending operation must clear first - use Start -> Power -> RESTART" -ForegroundColor Yellow
Write-Host " (NOT 'Shut down': Windows Fast Startup skips pending file operations on shutdown)." -ForegroundColor Yellow
Write-Host " Then re-run the one-liner."
} }
# Poll until the C2R install actually lands. setup.exe /configure can return before # Poll until the C2R install actually lands. setup.exe /configure can return before
@ -344,15 +360,15 @@ function Invoke-Odt([string]$configXml, [string]$stepMsg) {
# 0 = success, 3010 = success/reboot-required. Anything else is a real ODT # 0 = success, 3010 = success/reboot-required. Anything else is a real ODT
# failure - surface the log tail (carries the error code) so we can diagnose. # failure - surface the log tail (carries the error code) so we can diagnose.
if ($code -ne 0 -and $code -ne 3010) { if ($code -ne 0 -and $code -ne 3010) {
$reboot = Test-PendingReboot
Bad "Office Deployment Tool failed (setup.exe exit $code)." Bad "Office Deployment Tool failed (setup.exe exit $code)."
# 1603 = fatal install error; right after removing the bundled consumer # Only blame a reboot when one is ACTUALLY pending (not on every 1603).
# Office it almost always means the old install is still pending a reboot. if (Test-OfficeRebootPending) {
if ($reboot -or $code -eq 1603) { Show-RestartHint
Write-Host " A reboot is needed to finish removing the previous Office. REBOOT, then re-run the one-liner - it installs the Volume License Office directly." -ForegroundColor Yellow } elseif ($code -eq 1603) {
Write-Host " Exit 1603 = a Windows prerequisite failed - commonly Windows Installer disabled, an interrupted MSI (InProgress), or low disk. Diagnostics sent." -ForegroundColor Yellow
} }
Write-Host " (anonymous diagnostics sent to help debug this - no keys/hostname)" Write-Host " (anonymous diagnostics sent to help debug this - no keys/hostname)"
Send-Diag 'odt' 'fail' "exit=$code; reboot=$reboot; $(Get-OfficeState); odt: $(Get-OdtLogTail)" Send-Diag 'odt' 'fail' "exit=$code; $(Get-OfficeState); odt: $(Get-OdtLogTail)"
return $false return $false
} }
if ($code -eq 3010) { Warn "ODT reports a reboot is required to finish." } if ($code -eq 3010) { Warn "ODT reports a reboot is required to finish." }
@ -362,14 +378,21 @@ function Invoke-Odt([string]$configXml, [string]$stepMsg) {
} }
function Reinstall-OfficeVL([string]$product, [string]$channel) { function Reinstall-OfficeVL([string]$product, [string]$channel) {
# A hard pending-reboot (CBS/WU) means a prior Office removal hasn't finished; # An Office-blocking pending reboot (CBS/WU, or an Office file-rename) fails the
# installing now would download ~3 GB only to fail with 1603. Stop early. # C2R 'SXSMSI' prereq with 1603. Stop before the ~3 GB download and tell the user
if (Test-RebootRequiredHard) { # to do a REAL restart (a Fast-Startup "Shut down" does NOT clear it).
Bad "$product install blocked: a reboot is pending (a previous Office removal needs it)." if (Test-OfficeRebootPending) {
Write-Host " REBOOT, then re-run the one-liner - it will install $product directly." -ForegroundColor Yellow Bad "$product install blocked: a pending reboot must clear first."
Send-Diag 'odt' 'reboot-required-before-install' $product Show-RestartHint
Send-Diag 'odt' 'reboot-required-before-install' (Get-OfficeState)
return $false return $false
} }
# Office C2R installs via Windows Installer; a Disabled msiserver is a common
# SXSMSI prereq failure (1603). Make sure it can start.
try {
$msi = Get-Service msiserver -ErrorAction Stop
if ($msi.StartType -eq 'Disabled') { Set-Service msiserver -StartupType Manual -ErrorAction SilentlyContinue; Warn "Windows Installer service was Disabled - re-enabled it (required to install Office)." }
} catch {}
if (-not $channel) { $channel = if ($product -match '2021') { 'PerpetualVL2021' } elseif ($product -match '2019') { 'PerpetualVL2019' } else { 'PerpetualVL2024' } } if (-not $channel) { $channel = if ($product -match '2021') { 'PerpetualVL2021' } elseif ($product -match '2019') { 'PerpetualVL2019' } else { 'PerpetualVL2024' } }
$xml = @" $xml = @"
<Configuration> <Configuration>