kms-website: detect + remove pre-existing MSI Office before C2R VL install (real SXSMSI cause)
Live debug on a user laptop (direct SSH, verbose MSI log) found the true SXSMSI/1603 root cause: a pre-existing 32-bit MSI Office (Office Standard 2010) blocks the 64-bit C2R VL install. C2R log: SXSMSIValidator "32bit MSI Installation found and trying to install 64bit C2R". MSI Office cannot coexist with C2R and never appears in C2R ProductReleaseIds (the only place the script looked) - so it was invisible. This is why the VM 300 pilot (no old MSI Office) succeeded and DISM/SFC didn't help. - Get-MsiOffice: detect main MSI Office suites via ARP keys OfficeNN.<RELEASE> (Office14.STANDARDR etc.) across both registry hives, with the Office Setup Controller path per version. - Remove-MsiOffice: silent uninstall via the setup controller + Display=none config (Office 2010/2013/2016). - Install-LatestOfficeVL now removes BOTH blockers before the VL install: retail/M365 C2R AND any MSI Office; shows them in the consent prompt; reboot+re-run after. - Get-OfficeState telemetry now includes msiOffice=[...].
This commit is contained in:
parent
6f74565356
commit
a794d1acde
1 changed files with 62 additions and 13 deletions
|
|
@ -283,6 +283,41 @@ function Get-OdtLogTail([int]$lines = 6) {
|
||||||
# Compact, anonymous snapshot of the install-relevant system state. Shipped in
|
# Compact, anonymous snapshot of the install-relevant system state. Shipped in
|
||||||
# diagnostics (before install + on failure) so a stuck install can be debugged
|
# diagnostics (before install + on failure) so a stuck install can be debugged
|
||||||
# server-side without the user pasting anything. Counts only (no paths) - no PII.
|
# server-side without the user pasting anything. Counts only (no paths) - no PII.
|
||||||
|
# Detect a pre-existing MSI-based Office (2010/2013/2016). The main suite product
|
||||||
|
# registers an ARP key named OfficeNN.<RELEASE> (e.g. Office14.STANDARDR = Office
|
||||||
|
# 2010 Standard). An MSI Office BLOCKS a Click-to-Run install - the C2R 'SXSMSI'
|
||||||
|
# prereq fails 1603 ("32bit MSI Installation found and trying to install 64bit
|
||||||
|
# C2R") and MSI Office cannot coexist with C2R - so it must be removed first.
|
||||||
|
# This is the gap that hid a real failure: the script used to look only at C2R
|
||||||
|
# ProductReleaseIds, where an MSI Office never appears.
|
||||||
|
function Get-MsiOffice {
|
||||||
|
$paths = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
||||||
|
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||||
|
Get-ItemProperty $paths -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.PSChildName -match '^Office(\d\d)\.(\w+)$' } |
|
||||||
|
ForEach-Object {
|
||||||
|
[void]($_.PSChildName -match '^Office(\d\d)\.(\w+)$')
|
||||||
|
$ver = $matches[1]; $rel = $matches[2]
|
||||||
|
$ctrl = @("${env:CommonProgramFiles(x86)}\Microsoft Shared\OFFICE$ver\Office Setup Controller\setup.exe",
|
||||||
|
"$env:CommonProgramFiles\Microsoft Shared\OFFICE$ver\Office Setup Controller\setup.exe") |
|
||||||
|
Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||||
|
[pscustomobject]@{ Name = $_.DisplayName; Ver = $ver; Release = $rel; Controller = $ctrl }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Silently uninstall an MSI Office suite via its Office Setup Controller + a
|
||||||
|
# Display=none config (works for Office 2010/2013/2016 MSI).
|
||||||
|
function Remove-MsiOffice($o) {
|
||||||
|
if (-not $o.Controller) { Bad "$($o.Name): Office Setup Controller not found; cannot auto-remove. Uninstall it from Settings > Apps, then re-run."; return $false }
|
||||||
|
$cfg = Join-Path $env:TEMP "kms-msiun-$($o.Release).xml"
|
||||||
|
"<Configuration Product=`"$($o.Release)`"><Display Level=`"none`" CompletionNotice=`"no`" SuppressModal=`"yes`" AcceptEula=`"yes`" /><Setting Id=`"SETUP_REBOOT`" Value=`"Never`" /></Configuration>" |
|
||||||
|
Set-Content -Path $cfg -Encoding ascii
|
||||||
|
Step "Uninstalling $($o.Name) (old MSI Office; several minutes)"
|
||||||
|
$p = Start-Process -FilePath $o.Controller -ArgumentList '/uninstall', $o.Release, '/config', "`"$cfg`"" -Wait -PassThru
|
||||||
|
# 0 = ok, 1605 = already absent, 3010 = ok/reboot-required.
|
||||||
|
return ($p.ExitCode -in 0, 1605, 3010)
|
||||||
|
}
|
||||||
|
|
||||||
function Get-OfficeState {
|
function Get-OfficeState {
|
||||||
$cfg = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' -ErrorAction SilentlyContinue
|
$cfg = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' -ErrorAction SilentlyContinue
|
||||||
$roots = @('C:\Program Files\Microsoft Office\root', 'C:\Program Files (x86)\Microsoft Office\root' | Where-Object { Test-Path $_ })
|
$roots = @('C:\Program Files\Microsoft Office\root', 'C:\Program Files (x86)\Microsoft Office\root' | Where-Object { Test-Path $_ })
|
||||||
|
|
@ -299,7 +334,8 @@ function Get-OfficeState {
|
||||||
$dmsi = (Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Installer' -Name DisableMSI -ErrorAction SilentlyContinue).DisableMSI
|
$dmsi = (Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Installer' -Name DisableMSI -ErrorAction SilentlyContinue).DisableMSI
|
||||||
$free = try { [int]((Get-PSDrive C -ErrorAction Stop).Free / 1GB) } catch { -1 }
|
$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)] roots=$($roots.Count) reboot[cbs=$cbs wu=$wu officePFRO=$($pfro.Count)] msi=$($msi.Status)/$($msi.StartType) evtlog=$($evt.Status) trustedinst=$($ti.StartType) inprog=$inprog disableMSI=$dmsi freeGB=$free c2rsvc=$svc ospp=$([bool](Find-Ospp))"
|
$msiOffice = @(Get-MsiOffice | ForEach-Object { "$($_.Ver).$($_.Release)" })
|
||||||
|
"prids=[$($cfg.ProductReleaseIds)] msiOffice=[$($msiOffice -join ',')] roots=$($roots.Count) reboot[cbs=$cbs wu=$wu officePFRO=$($pfro.Count)] msi=$($msi.Status)/$($msi.StartType) evtlog=$($evt.Status) trustedinst=$($ti.StartType) inprog=$inprog disableMSI=$dmsi 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.
|
||||||
|
|
@ -533,33 +569,46 @@ function Install-LatestOfficeVL([string]$label) {
|
||||||
$disp = if ($best) { "$($best.edition) ($target)" } else { $target }
|
$disp = if ($best) { "$($best.edition) ($target)" } else { $target }
|
||||||
# Retail/M365 Click-to-Run Office (IDs not ending in 'Volume') can't coexist
|
# Retail/M365 Click-to-Run Office (IDs not ending in 'Volume') can't coexist
|
||||||
# with a VL install of the same suite and must be removed first.
|
# with a VL install of the same suite and must be removed first.
|
||||||
|
# Two kinds of blocker must be removed before a VL C2R install:
|
||||||
|
# 1. Retail/M365 Click-to-Run of the same suite (IDs not ending in 'Volume').
|
||||||
|
# 2. A pre-existing MSI-based Office (2010/2013/2016) - it makes the C2R
|
||||||
|
# installer fail its SXSMSI prereq (1603, "32bit MSI Installation found").
|
||||||
$incompat = @(Get-AllOfficeC2R | Where-Object { $_ -notmatch 'Volume$' })
|
$incompat = @(Get-AllOfficeC2R | Where-Object { $_ -notmatch 'Volume$' })
|
||||||
$rm = if ($incompat) { "`n * FIRST UNINSTALLS the incompatible (non-VL) Office found: $($incompat -join ', ')" } else { '' }
|
$msi = @(Get-MsiOffice)
|
||||||
|
$rm = if ($incompat) { "`n * FIRST UNINSTALLS the incompatible (non-VL) Office found: $($incompat -join ', ')" } else { '' }
|
||||||
|
$rmMsi = if ($msi) { "`n * FIRST UNINSTALLS the old MSI Office that blocks this install: $(($msi | ForEach-Object { $_.Name }) -join ', ')" } else { '' }
|
||||||
$text = @"
|
$text = @"
|
||||||
KMS can only activate Volume License $label, and none is installed here.
|
KMS can only activate Volume License $label, and none is installed here.
|
||||||
The Office Deployment Tool can install the LATEST Volume License edition:
|
The Office Deployment Tool can install the LATEST Volume License edition:
|
||||||
|
|
||||||
-> $disp
|
-> $disp
|
||||||
|
|
||||||
Consequences:$rm
|
Consequences:$rm$rmMsi
|
||||||
* Downloads ~3 GB and runs setup.exe /configure
|
* Downloads ~3 GB and runs setup.exe /configure
|
||||||
* CLOSES all running Office apps (Word/Excel/Outlook/...)
|
* CLOSES all running Office apps (Word/Excel/Outlook/...)
|
||||||
* Several minutes
|
* Several minutes
|
||||||
"@
|
"@
|
||||||
if (-not (Approve $text ([bool]$env:KMS_OFFICE_PRODUCT))) { Warn "$label install skipped."; Send-Diag $label.ToLower() 'install-skipped' $target; return $null }
|
if (-not (Approve $text ([bool]$env:KMS_OFFICE_PRODUCT))) { Warn "$label install skipped."; Send-Diag $label.ToLower() 'install-skipped' $target; return $null }
|
||||||
|
$removed = $false
|
||||||
if ($incompat) {
|
if ($incompat) {
|
||||||
if (-not (Uninstall-OfficeC2R $incompat)) { Bad "Uninstall of incompatible Office failed; aborting before VL install."; Send-Diag $label.ToLower() 'uninstall-fail' ($incompat -join ','); return $null }
|
if (-not (Uninstall-OfficeC2R $incompat)) { Bad "Uninstall of incompatible Office failed; aborting before VL install."; Send-Diag $label.ToLower() 'uninstall-fail' ($incompat -join ','); return $null }
|
||||||
Send-Diag $label.ToLower() 'uninstalled-incompatible' ($incompat -join ',')
|
Send-Diag $label.ToLower() 'uninstalled-incompatible' ($incompat -join ','); $removed = $true
|
||||||
# Removing the bundled consumer Office usually leaves the servicing stack
|
}
|
||||||
# pending a reboot; a VL install in the SAME session then half-completes with
|
if ($msi) {
|
||||||
# no ospp.vbs. Stop here and have the user reboot + re-run - the script is
|
foreach ($o in $msi) {
|
||||||
# idempotent and (no incompatible Office left) goes straight to the install.
|
if (-not (Remove-MsiOffice $o)) { Bad "Uninstall of $($o.Name) failed; aborting before VL install."; Send-Diag $label.ToLower() 'msi-uninstall-fail' "$($o.Ver).$($o.Release)"; return $null }
|
||||||
if (Test-PendingReboot) {
|
|
||||||
Warn "Incompatible Office removed, but a reboot is required before the Volume License install."
|
|
||||||
Write-Host " Please REBOOT, then re-run the one-liner - it will skip the uninstall and install $target directly."
|
|
||||||
Send-Diag $label.ToLower() 'reboot-required-after-uninstall' $target
|
|
||||||
return $null
|
|
||||||
}
|
}
|
||||||
|
Send-Diag $label.ToLower() 'uninstalled-msi-office' (($msi | ForEach-Object { "$($_.Ver).$($_.Release)" }) -join ','); $removed = $true
|
||||||
|
}
|
||||||
|
# Any Office removal leaves the servicing stack pending a reboot; a VL install in
|
||||||
|
# the SAME session then half-completes (no ospp.vbs / SXSMSI). Stop and have the
|
||||||
|
# user reboot + re-run - the script is idempotent and (no blocker left) goes
|
||||||
|
# straight to the install.
|
||||||
|
if ($removed -and (Test-PendingReboot)) {
|
||||||
|
Warn "Old Office removed, but a reboot is required before the Volume License install."
|
||||||
|
Write-Host " Please REBOOT, then re-run the one-liner - it will skip the uninstall and install $target directly."
|
||||||
|
Send-Diag $label.ToLower() 'reboot-required-after-uninstall' $target
|
||||||
|
return $null
|
||||||
}
|
}
|
||||||
if (-not (Reinstall-OfficeVL $target $channel)) { return $null }
|
if (-not (Reinstall-OfficeVL $target $channel)) { return $null }
|
||||||
return $target
|
return $target
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue