kms-website: reboot-aware ODT failure handling (exit 1603) + richer C2R log capture

setup.exe exit 1603 right after removing the bundled consumer Office almost
always means the old install is pending a reboot. The script handed users a bare
1603; now it explains it. Changes:
- Test-RebootRequiredHard (CBS/WU only) GATES Reinstall-OfficeVL before the ~3 GB
  download, so a pending reboot stops early with reboot+re-run guidance instead
  of failing with 1603.
- Invoke-Odt failure path detects pending-reboot / 1603 and tells the user to
  reboot + re-run; ships reboot status in telemetry.
- Get-OdtLogTail also searches %TEMP% (where the C2R client logs the real error)
  and prefers error-bearing lines, so a failure no longer reports an empty log.
This commit is contained in:
Viktor Barzin 2026-06-01 20:52:19 +00:00
parent 3d31a39099
commit 07f88b8f1f

View file

@ -259,17 +259,24 @@ $script:ODT_URL = $(if ($env:KMS_ODT_URL) { $env:KMS_ODT_URL } else { 'https:/
# of a bare "ospp.vbs not found". Cleared at the start of every Invoke-Odt run. # of a bare "ospp.vbs not found". Cleared at the start of every Invoke-Odt run.
$script:OdtLogDir = Join-Path $env:TEMP 'kms-odt-logs' $script:OdtLogDir = Join-Path $env:TEMP 'kms-odt-logs'
# Tail of the newest ODT log - the setup.exe error code lives here on failure. # Tail of the most relevant ODT / Click-to-Run log. ODT's own log goes to
function Get-OdtLogTail([int]$lines = 20) { # OdtLogDir, but the DETAILED failure behind a 1603 is written by the C2R client
$log = Get-ChildItem -Path $script:OdtLogDir -Filter *.log -ErrorAction SilentlyContinue | # to %TEMP%. Search both, newest first, and prefer error-bearing lines so the tail
Sort-Object LastWriteTime -Descending | Select-Object -First 1 # actually explains the failure instead of being empty.
if (-not $log) { return '' } function Get-OdtLogTail([int]$lines = 25) {
return ((Get-Content -LiteralPath $log.FullName -Tail $lines -ErrorAction SilentlyContinue) -join ' | ') $logs = Get-ChildItem -Path @($script:OdtLogDir, $env:TEMP) -Filter *.log -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddMinutes(-30) } |
Sort-Object LastWriteTime -Descending | Select-Object -First 5
foreach ($log in $logs) {
$err = Get-Content -LiteralPath $log.FullName -ErrorAction SilentlyContinue |
Where-Object { $_ -match 'error|fail|1603|0x8|cannot|denied|reboot|in progress' } | Select-Object -Last $lines
if ($err) { return ($log.Name + ': ' + ($err -join ' | ')) }
}
if ($logs) { return ($logs[0].Name + ': ' + ((Get-Content -LiteralPath $logs[0].FullName -Tail $lines -ErrorAction SilentlyContinue) -join ' | ')) }
return ''
} }
# "A reboot is pending" probe. C2R half-completes an install when a prior removal # "A reboot is pending" probe (all signals) - used for advisory messages/telemetry.
# left the servicing stack pending - the usual cause of an install run right after
# uninstalling the bundled consumer Office.
function Test-PendingReboot { function Test-PendingReboot {
if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') { return $true } if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') { return $true }
if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') { return $true } if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') { return $true }
@ -277,6 +284,14 @@ function Test-PendingReboot {
return [bool]$sm.PendingFileRenameOperations return [bool]$sm.PendingFileRenameOperations
} }
# STRONG reboot signals only (CBS / Windows Update) - reliably "a real reboot is
# needed". Used to GATE an install (PendingFileRenameOperations is too noisy to
# block on - it is set by lots of unrelated software).
function Test-RebootRequiredHard {
(Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') -or
(Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired')
}
# 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
# the Click-to-Run service finishes the multi-GB install, so trust on-disk state # the Click-to-Run service finishes the multi-GB install, so trust on-disk state
# (ospp.vbs + the product in ProductReleaseIds), not setup.exe's return. Heartbeat # (ospp.vbs + the product in ProductReleaseIds), not setup.exe's return. Heartbeat
@ -313,9 +328,15 @@ function Invoke-Odt([string]$configXml, [string]$stepMsg) {
# 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) {
$tail = Get-OdtLogTail $tail = Get-OdtLogTail
$reboot = Test-PendingReboot
Bad "Office Deployment Tool failed (setup.exe exit $code)." Bad "Office Deployment Tool failed (setup.exe exit $code)."
if ($tail) { Write-Host " ODT log: $tail" } # 1603 = fatal install error; right after removing the bundled consumer
Send-Diag 'odt' 'fail' "exit=$code; $tail" # Office it almost always means the old install is still pending a reboot.
if ($reboot -or $code -eq 1603) {
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
}
if ($tail) { Write-Host " ODT/C2R log: $tail" }
Send-Diag 'odt' 'fail' "exit=$code; reboot=$reboot; $tail"
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." }
@ -325,6 +346,14 @@ 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;
# installing now would download ~3 GB only to fail with 1603. Stop early.
if (Test-RebootRequiredHard) {
Bad "$product install blocked: a reboot is pending (a previous Office removal needs it)."
Write-Host " REBOOT, then re-run the one-liner - it will install $product directly." -ForegroundColor Yellow
Send-Diag 'odt' 'reboot-required-before-install' $product
return $false
}
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>