AIGC标识 Office2024_0-2060_MSI_Advertised_排查记录

Microsoft Office 2024 安装失败 0-2060 (0) 排查记录

1. 问题概述

安装 Microsoft Office 2024 64 位版时,安装程序最初报错:

Couldn't install

Error Code: 0-2060 (0)

界面给出的提示主要是检查网络连接、磁盘空间并重试,但最终排查证明,这些都不是实际原因。

安装环境:

Windows 11
版本:10.0.26100.3194
安装介质:ISO
Office:Office LTSC Professional Plus 2024 Volume
架构:x64
Channel:PerpetualVL2024

最终根因是:

系统中残留了一个损坏的旧版 Office MSI Advertised(已播发)产品注册
Office 2024 安装器能够检测到这个 32 位 MSI 产品,因此拒绝安装 64 位 Office;但 Windows Installer 又无法正常卸载它,从而产生 1605,最终被 Office 安装程序包装成通用错误 0-2060 (0)

涉及的 ProductCode:

{90120000-0017-0000-0000-0000000FF1CE}

对应 Windows Installer packed GUID:

00002109710000000000000000F01FEC

2. 初期现象

最开始检查 Click-to-Run 服务:

sc query ClickToRunSvc

得到:

[SC] EnumQueryServicesStatus:OpenService 失败 1060:
指定的服务未安装。

同时:

dir "C:\Program Files\Common Files\Microsoft Shared\ClickToRun"

没有 Click-to-Run 文件;

reg query "HKLM\SOFTWARE\Microsoft\Office\ClickToRun"

也不存在对应注册表项。

这些结果只能说明当前系统没有正常部署 Click-to-Run Office,不能直接解释安装失败的具体原因

系统中还存在一些旧 Office 注册表痕迹,例如:

HKLM\SOFTWARE\Microsoft\Office\15.0
HKLM\SOFTWARE\Microsoft\Office\Common
HKLM\SOFTWARE\Microsoft\Office\Delivery
HKLM\SOFTWARE\Microsoft\Office\PowerPoint

以及 32 位视图中的:

HKLM\SOFTWARE\WOW6432Node\Microsoft\Office\12.0
HKLM\SOFTWARE\WOW6432Node\Microsoft\Office\Common
HKLM\SOFTWARE\WOW6432Node\Microsoft\Office\Excel
HKLM\SOFTWARE\WOW6432Node\Microsoft\Office\PowerPoint
HKLM\SOFTWARE\WOW6432Node\Microsoft\Office\Word

但标准卸载列表中没有真正的旧版 Office,只看到例如 VSTO Runtime 等组件。

因此不能简单地把“存在 Office 注册表目录”等同于“Office 仍然完整安装”。

另外,PowerShell 启动时曾出现:

PSSecurityException
因为在此系统上禁止运行脚本

这最终也与 Office ODT 的 .exe 安装失败无关。


3. 找到真正的安装日志

%TEMP% 中找到:

DESKTOP-XZ001-20260907-1913.log

这是整个排查过程的第一个决定性证据。

日志表明安装程序使用:

Channel: PerpetualVL2024
OfficeClientEdition: 64
ProductsToAdd: ProPlus2024Volume

并且配置中启用了:

Data.RemoveAllMSI: True

也就是 XML 中的:

<RemoveMSI />

安装程序随后决定清理这个旧 MSI ProductCode:

{90120000-0017-0000-0000-0000000FF1CE}

日志中明确出现:

Non-standalone MSI product, not part of any Office SKU which will be kept, remove

并将其列入:

Data.MSIProductsToUninstall:
{{90120000-0017-0000-0000-0000000FF1CE}}

4. 0-2060 (0) 实际上只是外层错误

真正失败发生在 <RemoveMSI /> 阶段。

日志:

MSIScrub::MSIUninstaller::UninstallProducts

ErrorCode: 1605
ErrorType: 1605

Failed to configure:
{90120000-0017-0000-0000-0000000FF1CE}

Error: 0x645

Failed to uninstall MSI product

随后:

Failed to uninstall MSI skus as per config file instructions.
Installation failed

最后安装器才显示:

ErrorCode: 0-2060 (0)

因此:

0-2060 (0)

并不是底层真正错误。

真正的因果链是:

<RemoveMSI />
    ↓
检测到旧 MSI ProductCode
    ↓
尝试卸载
    ↓
Windows Installer 返回 1605 / 0x645
    ↓
RemoveMSI 阶段失败
    ↓
Office Bootstrapper 显示通用 0-2060 (0)

日志中还出现了一些:

OpenProcess failed
ErrorCode: 5

但这些记录明确标记:

OException has occured. Skip.

因此它们不是导致安装失败的错误。


5. 暂时关闭 <RemoveMSI />

因为 ISO 挂载盘是只读的,无法直接修改其中的:

configuration.xml

所以将:

G:\Office

完整复制到:

C:\Office2024

保持目录结构不变:

C:\Office2024\
├── install.exe
├── configuration.xml
└── Office\
    └── Data\

然后将:

<RemoveMSI />

删除或注释。

重新执行:

cd /d C:\Office2024
install.exe /configure configuration.xml

6. 出现新的 32/64 位冲突错误

关闭 <RemoveMSI /> 后,不再出现 0-2060 (0),但安装器改为提示:

Microsoft 365 and Office (64-bit) couldn't be installed
because you have these 32-bit programs installed on your computer.

这非常重要,因为它证明:

  1. <RemoveMSI /> 确实是原来 0-2060 的触发点;
  2. 系统中仍存在能够被 Office 检测到的 32 位 MSI Office 产品;
  3. 需要清理这个残留,而不能单纯绕过 <RemoveMSI />

新的日志:

DESKTOP-XZ001-20260907-1928c.log

直接点名:

BlockingApp:
{90120000-0017-0000-0000-0000000FF1CE}

紧接着:

Preexisting 32bit products detected.
32bit MSI Installation found and trying to install 64bit C2R.

所以不是出现了另一个产品。

从始至终阻塞安装的仍然是同一个 ProductCode。


7. 发现残缺的 Windows Installer 注册

ProductCode:

{90120000-0017-0000-0000-0000000FF1CE}

转换为 Windows Installer packed GUID:

00002109710000000000000000F01FEC

最初检查:

reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\00002109710000000000000000F01FEC" /s

确实找到残留:

...\Features

...\InstallProperties
    LocalPackage       REG_SZ       C:\Windows\Installer\5ab4b.msi
    WindowsInstaller   REG_DWORD    0x1

但:

reg query "HKLM\SOFTWARE\Classes\Installer\Products\00002109710000000000000000F01FEC" /s

没有正常对应项。

这说明 MSI 注册已经处于不一致状态。

备份后删除了该 S-1-5-18\UserData 孤立产品键。

但重新运行 Office 后,新的日志仍然点名同一个 ProductCode

因此该 UserData 项并不是唯一残留。


8. 若干注册表定位尝试

为寻找剩余 MSI 注册,曾依次检查:

  • Office\12.0\Registration
  • 当前 Administrator SID 下的 Installer\UserData
  • Installer\UserData 全树中对应 packed GUID
  • HKCU\Software\Microsoft\Installer
  • Installer\Managed
  • HKCU\Software\Classes\Installer

这些位置都没有找到能解释当前状态的完整产品注册。

此时继续猜注册表路径已经没有效率,因此改为直接询问 Windows Installer API 自己如何看待该 ProductCode。


9. MsiEnumProductsExW:产品未被正常枚举

调用:

MsiEnumProductsExW()

按全部 MSI 安装上下文枚举:

{90120000-0017-0000-0000-0000000FF1CE}

结果:

NOT ENUMERATED BY WINDOWS INSTALLER

这个结果一开始容易被理解为“产品完全不存在”,但后续证明这是不充分的。

某些仅处于 Advertisement 状态的异常产品,并不一定会以预期方式出现在这样的产品枚举结果中。

因此需要继续直接查询产品状态。


10. MsiQueryProductStateW 找到关键状态

直接执行:

MsiQueryProductStateW(
    "{90120000-0017-0000-0000-0000000FF1CE}"
)

返回:

1

即:

INSTALLSTATE_ADVERTISED

这是整个排查的关键突破。

它表示:

Windows Installer 仍然认为该 ProductCode 是一个 Advertised(已播发)产品,但产品并没有处于完整的正常安装状态。

这正好解释此前的矛盾:

Office 安装器:
“我能看到这个 32 位 MSI 产品。”

而正常卸载路径却:

Windows Installer:
“无法按正常已安装产品处理。”

因此才能同时出现:

Office SXS 检测 → 产品存在

和:

RemoveMSI / 卸载 → Error 1605

11. 正常 MSI 配置/卸载 API 同样失败

在确认产品状态为:

INSTALLSTATE_ADVERTISED

以后,尝试通过:

MsiConfigureProductExW(
    ProductCode,
    ...,
    INSTALLSTATE_ABSENT,
    ...
)

让 Windows Installer 自己把产品设置为卸载状态。

返回:

1605

于是形成了异常组合:

MsiQueryProductStateW → 1
MsiConfigureProductExW → 1605

即:

状态查询:它还存在(Advertised)
正常配置/卸载:却认为无法正常处理该产品

这说明 Advertisement 注册仍存在,但完整产品注册已经损坏。


12. 使用 Process Monitor 直接观察 MSI 实际读取位置

此时不再猜注册表。

使用 Microsoft Sysinternals:

Process Monitor / Procmon64.exe

只监控:

powershell.exe

然后再次调用:

MsiQueryProductStateW()

并将捕获结果导出:

Logfile.CSV

ProcMon 记录显示,Windows Installer 在查询该 ProductCode 时访问了多个 MSI 注册位置。

其中部分路径返回:

NAME NOT FOUND

但以下路径能够被成功打开:

HKCR\Installer\Products\
00002109710000000000000000F01FEC

结果:

SUCCESS

这成为最终定位依据。

前面已经删除的 S-1-5-18\UserData 项并不足以解除该 Advertisement 状态;ProcMon 证明 MSI 查询时仍然能通过 HKCR\Installer\Products 看到该 packed GUID。


13. 最终清理

针对 ProcMon 已经确认存在的单独产品键进行备份和删除:

HKCR\Installer\Products\
00002109710000000000000000F01FEC

注意这里只处理这个精确 ProductCode 对应的孤立产品项。

没有删除整个:

Installer
Products
Microsoft\Office

等注册表树。

删除后再次调用:

MsiQueryProductStateW(
    "{90120000-0017-0000-0000-0000000FF1CE}"
)

返回:

-1

即:

INSTALLSTATE_UNKNOWN

这意味着 Windows Installer 已经不再认为该 ProductCode:

  • 已安装;
  • 已 Advertise;
  • 或处于其他有效 MSI 产品状态。

14. 再次安装 Office

保持:

<RemoveMSI />

处于删除/注释状态。

执行:

cd /d C:\Office2024
install.exe /configure configuration.xml

结果:

Office 2024 安装成功

问题解决。


最终根因

整个故障的本质不是网络、磁盘空间、PowerShell ExecutionPolicy,也不是 Click-to-Run 服务本身损坏。

真正的问题是一个损坏的旧 Office 32 位 MSI Advertised 产品注册

ProductCode:
{90120000-0017-0000-0000-0000000FF1CE}

Packed GUID:
00002109710000000000000000F01FEC

其状态异常:

MsiQueryProductStateW → INSTALLSTATE_ADVERTISED (1)

但:

MsiConfigureProductExW / Office RemoveMSI
→ ERROR_UNKNOWN_PRODUCT / 1605

因果链:

旧 MSI Advertised 残留
        ↓
Office 能检测到 32 位产品
        ↓
<RemoveMSI /> 尝试清理
        ↓
Windows Installer 返回 1605
        ↓
Office 显示 0-2060 (0)

关闭 <RemoveMSI /> 后则变成:

旧 MSI Advertised 残留
        ↓
不执行自动删除
        ↓
Office x64 前置检查发现 32 位 MSI
        ↓
64-bit / 32-bit conflict

最终通过 ProcMon 找到实际仍可访问的 Advertisement 注册:

HKCR\Installer\Products\
00002109710000000000000000F01FEC

清除该孤立产品项后:

MsiQueryProductStateW: 1 → -1

Office 2024 随后正常安装。


排查过程中确认不是根因的项目

项目 结论
ClickToRunSvc 不存在 只能说明当前未部署 C2R Office,不是本次错误根因
0-2060 (0) Office Bootstrapper 的外层通用错误
网络 / 磁盘空间提示 通用 UI 文案,与实际日志不符
PowerShell ExecutionPolicy 报错 与 ODT .exe 安装过程无直接关系
OpenProcess Error 5 日志明确标记为 Skip
v32.cab not reachable 在检测出 32 位 MSI 之后才发生,不是触发 32/64 冲突的原因
VSTO Runtime 不是被日志点名的 ProductCode
Office\12.0 注册表目录本身 只能说明有旧 Office 痕迹,不能直接说明是哪一项阻塞
标准“程序和功能”卸载列表 无法可靠显示这种损坏的 Advertised MSI 残留

本次排查中走过的弯路

本次排查前半段做过一些价值较低或结论不足的检查,包括:

  • 反复寻找不同 Office 注册表目录;
  • 先根据普通卸载列表判断是否存在 Office;
  • 分别猜测 HKCUHKLM、不同 SID 下的 Installer 路径;
  • 一度把 MsiEnumProductsExW 的“不枚举”理解得过强;
  • 一度根据 HKCR 合并视图猜测底层 hive,而没有第一时间直接观察 API 的注册表访问。

后期更有效的方法是:

安装日志
    ↓
锁定具体 ProductCode
    ↓
Windows Installer API 查询实际产品状态
    ↓
ProcMon 观察 API 实际访问位置
    ↓
只清理已确认的孤立注册项

相比“大范围搜索 Office 注册表”,这个流程更快,也更安全。


推荐的类似问题排查顺序

以后如果再次遇到 Office 安装出现:

0-xxxx

或 32/64 位冲突,但“程序和功能”中又没有对应 Office,可以优先:

  1. 先看 ODT / Click-to-Run 日志,不以安装窗口的通用错误作为根因。
  2. 找:
    ErrorCode
    MSIProductsToUninstall
    BlockingApp
    SXSMSIValidator
    
  3. 如果已经获得具体 ProductCode,不再大范围清 Office 注册表。
  4. 调用:
    MsiQueryProductStateW
    
    查看 MSI 自己认为什么状态。
  5. 如果出现:
    INSTALLSTATE_ADVERTISED
    
    且正常卸载返回:
    1605
    
    则高度怀疑是损坏的 Advertisement 注册。
  6. 使用 ProcMon 捕获调用 MsiQueryProductStateW 时的实际注册表访问。
  7. 只处理 ProcMon 确认的孤立 ProductCode 项。
  8. 删除后再次确认:
    MsiQueryProductStateW → -1
    
  9. 再重新安装 Office。

关键文件

本次排查涉及:

DESKTOP-XZ001-20260907-1913.log

第一次安装失败日志,确认:

RemoveMSI
→ ProductCode {90120000-0017-...}
→ 1605
→ 0-2060 (0)

以及:

DESKTOP-XZ001-20260907-1928c.log

关闭 <RemoveMSI /> 后的日志,确认:

BlockingApp:
{90120000-0017-0000-0000-0000000FF1CE}

Preexisting 32bit products detected.

最后:

Logfile.CSV

为 Process Monitor 捕获结果,用于定位使 MSI 返回 INSTALLSTATE_ADVERTISED 的实际注册表来源。


备注

安装日志中包含 Office 产品密钥等敏感信息,因此本记录不保存 PIDKEY、MachineId、SessionID 等不必要的敏感字段

注册表操作也只记录最终确定的 ProductCode 和对应孤立 MSI 项,不建议将“删除整个 Office/Installer 注册表树”作为通用处理办法。


附录 A:Windows Installer API PowerShell 代码

本次排查过程中,注册表检查只能看到部分 MSI 残留,最终是通过直接调用 Windows Installer 的 msi.dll API 才确认产品处于异常的 Advertised 状态。

目标 ProductCode:

{90120000-0017-0000-0000-0000000FF1CE}

Windows Installer packed GUID:

00002109710000000000000000F01FEC

以下代码均在 Windows PowerShell 中执行。


A.1 查询产品状态:MsiQueryProductStateW

这是本次排查中最关键的 API。

常用返回状态:

INSTALLSTATE_UNKNOWN     = -1
INSTALLSTATE_ADVERTISED  = 1
INSTALLSTATE_ABSENT      = 2
INSTALLSTATE_DEFAULT     = 5

其中:

  • -1:既未安装,也未 Advertise
  • 1:已 Advertise,但未处于正常完整安装状态
  • 2:Absent 状态
  • 5:默认/本地安装状态

完整 PowerShell 代码

Add-Type @'
using System.Runtime.InteropServices;

public static class MSIState
{
    [DllImport("msi.dll", CharSet = CharSet.Unicode)]
    public static extern int MsiQueryProductStateW(
        string szProduct
    );
}
'@

$productCode = "{90120000-0017-0000-0000-0000000FF1CE}"

$state = [MSIState]::MsiQueryProductStateW($productCode)

$stateName = switch ($state)
{
    -1 { "INSTALLSTATE_UNKNOWN" }
     1 { "INSTALLSTATE_ADVERTISED" }
     2 { "INSTALLSTATE_ABSENT" }
     5 { "INSTALLSTATE_DEFAULT" }
    default { "UNKNOWN_VALUE" }
}

[PSCustomObject]@{
    ProductCode = $productCode
    State       = $state
    StateName   = $stateName
}

本机异常状态下返回:

ProductCode : {90120000-0017-0000-0000-0000000FF1CE}
State       : 1
StateName   : INSTALLSTATE_ADVERTISED

最终清理完成后再次调用,返回:

State       : -1
StateName   : INSTALLSTATE_UNKNOWN

这也是确认残留已经真正消失的最终判据。


A.2 枚举产品上下文:MsiEnumProductsExW

为了确认该 ProductCode 是否存在于不同 MSI 安装上下文中,调用:

MsiEnumProductsExW

完整 PowerShell 代码

Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;

public static class MSIEnum
{
    [DllImport("msi.dll", CharSet = CharSet.Unicode)]
    public static extern uint MsiEnumProductsExW(
        string szProductCode,
        string szUserSid,
        uint dwContext,
        uint dwIndex,
        StringBuilder szInstalledProductCode,
        out uint pdwInstalledContext,
        StringBuilder szSid,
        ref uint pcchSid
    );
}
'@

$productCode = "{90120000-0017-0000-0000-0000000FF1CE}"

# MSIINSTALLCONTEXT_USERMANAGED   = 1
# MSIINSTALLCONTEXT_USERUNMANAGED = 2
# MSIINSTALLCONTEXT_MACHINE       = 4
# MSIINSTALLCONTEXT_ALL           = 7

$MSIINSTALLCONTEXT_ALL = 7

# S-1-1-0 = Everyone
$userSid = "S-1-1-0"

$index = 0
$found = $false

while ($true)
{
    $installedProductCode =
        New-Object System.Text.StringBuilder 39

    $sidBuffer =
        New-Object System.Text.StringBuilder 256

    [uint32]$sidLength = 256
    [uint32]$installedContext = 0

    $result = [MSIEnum]::MsiEnumProductsExW(
        $productCode,
        $userSid,
        $MSIINSTALLCONTEXT_ALL,
        $index,
        $installedProductCode,
        [ref]$installedContext,
        $sidBuffer,
        [ref]$sidLength
    )

    # ERROR_NO_MORE_ITEMS = 259
    if ($result -eq 259)
    {
        break
    }

    if ($result -ne 0)
    {
        Write-Host "MsiEnumProductsExW failed."
        Write-Host "ErrorCode = $result"
        break
    }

    $found = $true

    $contextName = switch ($installedContext)
    {
        1 { "MSIINSTALLCONTEXT_USERMANAGED" }
        2 { "MSIINSTALLCONTEXT_USERUNMANAGED" }
        4 { "MSIINSTALLCONTEXT_MACHINE" }
        default { "UNKNOWN_CONTEXT" }
    }

    [PSCustomObject]@{
        ProductCode = $installedProductCode.ToString()
        Context     = $contextName
        ContextID   = $installedContext
        SID         = $sidBuffer.ToString()
    }

    $index++
}

if (-not $found)
{
    Write-Host "NOT ENUMERATED BY WINDOWS INSTALLER"
}

本次实际返回:

NOT ENUMERATED BY WINDOWS INSTALLER

但之后:

MsiQueryProductStateW → 1

因此不能把:

MsiEnumProductsExW 没有枚举到

简单等价为:

Windows Installer 完全不知道该产品

实际状态应继续通过:

MsiQueryProductStateW

确认。


A.3 尝试使用 MSI API 正常卸载:MsiConfigureProductExW

在确认产品状态为:

INSTALLSTATE_ADVERTISED

以后,尝试让 Windows Installer 自己将产品设置为:

INSTALLSTATE_ABSENT

完整 PowerShell 代码

Add-Type @'
using System.Runtime.InteropServices;

public static class MSIConfigure
{
    [DllImport("msi.dll", CharSet = CharSet.Unicode)]
    public static extern uint MsiConfigureProductExW(
        string szProduct,
        int iInstallLevel,
        int eInstallState,
        string szCommandLine
    );

    [DllImport("msi.dll", CharSet = CharSet.Unicode)]
    public static extern int MsiQueryProductStateW(
        string szProduct
    );
}
'@

$productCode =
    "{90120000-0017-0000-0000-0000000FF1CE}"

# INSTALLSTATE_ABSENT = 2
$INSTALLSTATE_ABSENT = 2

$result = [MSIConfigure]::MsiConfigureProductExW(
    $productCode,
    0,
    $INSTALLSTATE_ABSENT,
    "REBOOT=ReallySuppress"
)

$stateAfter =
    [MSIConfigure]::MsiQueryProductStateW(
        $productCode
    )

[PSCustomObject]@{
    ProductCode  = $productCode
    RemoveResult = $result
    StateAfter   = $stateAfter
}

理想情况下,如果产品注册完整,正常结果应类似:

RemoveResult = 0
StateAfter   = -1

但本机实际返回:

RemoveResult = 1605

而此前:

MsiQueryProductStateW = 1

因此出现异常组合:

QueryProductState:
INSTALLSTATE_ADVERTISED
        ↓
ConfigureProductEx:
ERROR_UNKNOWN_PRODUCT / 1605

说明 MSI Advertisement 注册仍然存在,但已经不具备一个可正常配置、修复或卸载的完整产品注册。


A.4 将查询与枚举合并成一个只读诊断脚本

下面的脚本:

  1. 查询产品状态;
  2. 枚举产品上下文;
  3. 输出解释。

不会修改系统。

Add-Type @'
using System;
using System.Text;
using System.Runtime.InteropServices;

public static class MSIProbe
{
    [DllImport("msi.dll", CharSet = CharSet.Unicode)]
    public static extern int MsiQueryProductStateW(
        string szProduct
    );

    [DllImport("msi.dll", CharSet = CharSet.Unicode)]
    public static extern uint MsiEnumProductsExW(
        string szProductCode,
        string szUserSid,
        uint dwContext,
        uint dwIndex,
        StringBuilder szInstalledProductCode,
        out uint pdwInstalledContext,
        StringBuilder szSid,
        ref uint pcchSid
    );
}
'@

$productCode =
    "{90120000-0017-0000-0000-0000000FF1CE}"

Write-Host "=== Windows Installer Product Probe ==="
Write-Host "ProductCode: $productCode"
Write-Host ""

# ------------------------------------------------------------
# 1. Query product state
# ------------------------------------------------------------

$state =
    [MSIProbe]::MsiQueryProductStateW(
        $productCode
    )

$stateName = switch ($state)
{
    -1 { "INSTALLSTATE_UNKNOWN" }
     1 { "INSTALLSTATE_ADVERTISED" }
     2 { "INSTALLSTATE_ABSENT" }
     5 { "INSTALLSTATE_DEFAULT" }
    default { "UNKNOWN_VALUE" }
}

Write-Host "MsiQueryProductStateW:"
Write-Host "  State     = $state"
Write-Host "  StateName = $stateName"
Write-Host ""

# ------------------------------------------------------------
# 2. Enumerate MSI contexts
# ------------------------------------------------------------

$contextAll = 7
$everyoneSid = "S-1-1-0"

$index = 0
$found = $false

Write-Host "MsiEnumProductsExW:"

while ($true)
{
    $productBuffer =
        New-Object System.Text.StringBuilder 39

    $sidBuffer =
        New-Object System.Text.StringBuilder 256

    [uint32]$sidLength = 256
    [uint32]$context = 0

    $result =
        [MSIProbe]::MsiEnumProductsExW(
            $productCode,
            $everyoneSid,
            $contextAll,
            $index,
            $productBuffer,
            [ref]$context,
            $sidBuffer,
            [ref]$sidLength
        )

    if ($result -eq 259)
    {
        break
    }

    if ($result -ne 0)
    {
        Write-Host "  API Error = $result"
        break
    }

    $found = $true

    $contextName = switch ($context)
    {
        1 { "USER_MANAGED" }
        2 { "USER_UNMANAGED" }
        4 { "MACHINE" }
        default { "UNKNOWN" }
    }

    Write-Host "  Product = $($productBuffer.ToString())"
    Write-Host "  Context = $contextName ($context)"
    Write-Host "  SID     = $($sidBuffer.ToString())"
    Write-Host ""

    $index++
}

if (-not $found)
{
    Write-Host "  No instance enumerated."
}

Write-Host ""
Write-Host "=== Interpretation ==="

switch ($state)
{
    -1 {
        Write-Host "Product is neither installed nor advertised."
    }

     1 {
        Write-Host "Product is ADVERTISED."
        Write-Host "If normal configure/uninstall returns 1605, suspect broken MSI advertisement registration."
    }

     2 {
        Write-Host "Product is in INSTALLSTATE_ABSENT."
    }

     5 {
        Write-Host "Product is in INSTALLSTATE_DEFAULT."
    }

    default {
        Write-Host "Unexpected MSI product state: $state"
    }
}

对于本次故障,这个脚本的核心结果相当于:

=== Windows Installer Product Probe ===

ProductCode:
{90120000-0017-0000-0000-0000000FF1CE}

MsiQueryProductStateW:
  State     = 1
  StateName = INSTALLSTATE_ADVERTISED

MsiEnumProductsExW:
  No instance enumerated.

=== Interpretation ===

Product is ADVERTISED.
If normal configure/uninstall returns 1605,
suspect broken MSI advertisement registration.

A.5 清理完成后的验证

删除 ProcMon 已确认的孤立产品注册后,最终只需要再次执行:

[MSIState]::MsiQueryProductStateW(
    "{90120000-0017-0000-0000-0000000FF1CE}"
)

最终结果:

-1

即:

INSTALLSTATE_UNKNOWN

这是本次问题真正解决的关键验证。

随后重新运行:

cd /d C:\Office2024
install.exe /configure configuration.xml

Office 2024 成功安装。


A.6 API 在本次排查中的作用

三个 API 的作用可以概括为:

MsiEnumProductsExW
    │
    ├─ Windows Installer 能否正常枚举这个产品实例?
    │
    └─ 本次:没有枚举到
              │
              ▼
MsiQueryProductStateW
    │
    ├─ MSI 当前到底认为这个 ProductCode 是什么状态?
    │
    └─ 本次:INSTALLSTATE_ADVERTISED (1)
              │
              ▼
MsiConfigureProductExW
    │
    ├─ 能否让 MSI 自己正常移除它?
    │
    └─ 本次:1605 / ERROR_UNKNOWN_PRODUCT
              │
              ▼
结论:
Advertisement 状态仍存在,
但完整产品注册已经损坏
              │
              ▼
ProcMon 跟踪 MsiQueryProductStateW
              │
              ▼
定位实际 Registry SUCCESS 项
              │
              ▼
精确删除孤立产品注册
              │
              ▼
MsiQueryProductStateW = -1

附录 B:关键命令速查

B.1 复制 ISO 中的 Office 安装目录

xcopy G:\Office C:\Office2024 /E /I /H

B.2 从本地目录安装

cd /d C:\Office2024
install.exe /configure configuration.xml

B.3 查询已知 MSI ProductCode 的状态

[MSIState]::MsiQueryProductStateW(
    "{90120000-0017-0000-0000-0000000FF1CE}"
)

本次关键状态变化:

清理前:1
清理后:-1

B.4 最终定位到的孤立注册项

HKCR\Installer\Products\
00002109710000000000000000F01FEC

对类似问题,不应直接复制本记录中的 GUID 进行删除。

必须先从自己的 Office 日志中确定实际 BlockingApp / MSIProductsToUninstall ProductCode,再通过 MSI API 和 ProcMon 验证。

posted @ 2026-09-09 13:30  Cicecoo  阅读(10)  评论(0)    收藏  举报