Files
navop/script/validate-windows-msi.ps1
T
胡飞 24abe96da8 fix(release): Windows MSI 快捷方式不再引用图标表,图标取自 exe 内嵌资源(issue #325)
issue #325 报告桌面上的 Navop 快捷方式图标退化成通用白纸。仓库里没有任何隐藏快捷
方式箭头或桌面图标的功能,现象落回安装包给出的快捷方式图标上:navop.wxs 的两个
<Shortcut> 都带 Icon="NavopIcon",即引用 Icon 表。MSI 会把 Icon 表里的图标另存成
一份独立文件作为快捷方式的图标来源(文档还要求这类图标与目标扩展名一致,而我们
是 .ico 对 navop.exe),这份文件一旦被清理或图标缓存失效,快捷方式就只能显示通用
白纸图标;而 navop.exe 自己其实已内嵌同一份图标(main/build.rs 的 set_icon)。

因此两个快捷方式一律不再引用 Icon 表,图标改由目标 exe 的内嵌资源提供,与
file_association.rs 的 DefaultIcon = "<exe>,0" 保持同一口径。控制面板「应用和功能」
的图标仍走 Icon 表,<Icon Id="NavopIcon"> 与 ARPPRODUCTICON 原样保留。

契约收紧:test-release-packaging.mjs 新增用例断言两个 <Shortcut> 不带 Icon=、卸载项
图标仍走 Icon 表、且 build.rs 确实内嵌 resources/windows/navop.ico,保证「留空」不是
「没图标」;validate-windows-msi.ps1 新增 Assert-MsiEmptyValue 并直接校验安装产物里
Shortcut.Icon_ 为空,把这条规则钉在 Windows release 阶段。

验证:
- node --test script/test-release-packaging.mjs:31 passed / 0 failed(含新增用例)
- 变异验证:把 Icon="NavopIcon" 临时加回 Desktop 快捷方式,新增用例转红;还原后转绿
- validate-windows-msi.ps1 未在本机执行:macOS 无 pwsh、无 WindowsInstaller COM,
  只在 Windows release CI 里跑;MSI 装完快捷方式图标的真实表现需 Windows 实机验收
2026-10-07 17:35:25 +08:00

107 lines
3.8 KiB
PowerShell

param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[int]$ExpectedLanguage
)
$ErrorActionPreference = "Stop"
function Read-MsiValue {
param([object]$Database, [string]$Query)
$view = $Database.OpenView($Query)
try {
$null = $view.Execute()
$record = $view.Fetch()
if ($null -eq $record) {
throw "MSI query returned no rows: $Query"
}
$value = [string]$record.StringData(1)
return $value.Trim()
} finally {
$null = $view.Close()
}
}
function Assert-MsiValue {
param([object]$Database, [string]$Expected, [string]$Query)
$actual = Read-MsiValue -Database $Database -Query $Query
if ($actual -ne $Expected) {
throw "MSI value mismatch. Expected '$Expected', got '$actual'. Query: $Query"
}
Write-Host "Verified: $Query => $actual"
}
# 快捷方式不得引用 Icon 表:那份图标会被 MSI 另存成独立文件,丢失后快捷方式图标
# 退化成通用白纸(issue #325)。空列在自动化的 StringData 上取值为 "" 或抛异常,两者都算通过。
function Assert-MsiEmptyValue {
param([object]$Database, [string]$Query)
$view = $Database.OpenView($Query)
try {
$null = $view.Execute()
$record = $view.Fetch()
if ($null -eq $record) {
throw "MSI query returned no rows: $Query"
}
$value = ""
try {
$value = [string]$record.StringData(1)
} catch {
$value = ""
}
if (-not [string]::IsNullOrWhiteSpace($value)) {
throw "MSI value should be empty, got '$value'. Query: $Query"
}
Write-Host "Verified empty: $Query"
} finally {
$null = $view.Close()
}
}
$resolvedPath = (Resolve-Path $Path).Path
$installer = New-Object -ComObject WindowsInstaller.Installer
$database = $installer.OpenDatabase($resolvedPath, 0)
Assert-MsiValue $database "$ExpectedLanguage" `
"SELECT Value FROM Property WHERE Property = 'ProductLanguage'"
Assert-MsiValue $database "INSTALLROOT" `
"SELECT Value FROM Property WHERE Property = 'WIXUI_INSTALLDIR'"
Assert-MsiValue $database "Programs" `
"SELECT DefaultDir FROM Directory WHERE Directory = 'INSTALLROOT'"
Assert-MsiValue $database "INSTALLROOT" `
"SELECT Directory_Parent FROM Directory WHERE Directory = 'INSTALLFOLDER'"
Assert-MsiValue $database "Navop" `
"SELECT DefaultDir FROM Directory WHERE Directory = 'INSTALLFOLDER'"
Assert-MsiValue $database "DesktopFolder" `
"SELECT Directory_ FROM Shortcut WHERE Shortcut = 'DesktopShortcut'"
Assert-MsiValue $database "ApplicationProgramsFolder" `
"SELECT Directory_ FROM Shortcut WHERE Shortcut = 'StartMenuShortcut'"
Assert-MsiValue $database "DesktopShortcutComponent" `
"SELECT Component_ FROM Shortcut WHERE Shortcut = 'DesktopShortcut'"
Assert-MsiValue $database "StartMenuShortcutComponent" `
"SELECT Component_ FROM Shortcut WHERE Shortcut = 'StartMenuShortcut'"
Assert-MsiValue $database "[#NavopExecutable]" `
"SELECT Target FROM Shortcut WHERE Shortcut = 'DesktopShortcut'"
Assert-MsiValue $database "[#NavopExecutable]" `
"SELECT Target FROM Shortcut WHERE Shortcut = 'StartMenuShortcut'"
Assert-MsiEmptyValue $database `
"SELECT Icon_ FROM Shortcut WHERE Shortcut = 'DesktopShortcut'"
Assert-MsiEmptyValue $database `
"SELECT Icon_ FROM Shortcut WHERE Shortcut = 'StartMenuShortcut'"
Assert-MsiValue $database "DesktopShortcutRegistry" `
"SELECT KeyPath FROM Component WHERE Component = 'DesktopShortcutComponent'"
Assert-MsiValue $database "StartMenuShortcutRegistry" `
"SELECT KeyPath FROM Component WHERE Component = 'StartMenuShortcutComponent'"
Assert-MsiValue $database "1" `
"SELECT Root FROM Registry WHERE Registry = 'DesktopShortcutRegistry'"
Assert-MsiValue $database "1" `
"SELECT Root FROM Registry WHERE Registry = 'StartMenuShortcutRegistry'"
Assert-MsiValue $database "InstallDirDlg" `
"SELECT Dialog FROM Dialog WHERE Dialog = 'InstallDirDlg'"
Write-Host "Validated MSI: $resolvedPath"