# cargo-wix 打包与 WiX 常用修改指南

本文档说明本项目如何使用 `cargo-wix` 生成 Windows MSI 安装包，以及常见 WiX 配置的修改方法。当前项目的安装配置文件是 `wix/main.wxs`，Rust 包信息来自 `Cargo.toml`。

## 1. 当前项目打包方式

本项目使用：

- Rust/Cargo 构建 release 可执行文件
- `cargo-wix` 读取 `Cargo.toml` 和 `wix/main.wxs`
- WiX Toolset v3 编译并链接生成 `.msi`

当前可执行文件配置在 `Cargo.toml`：

```toml
[[bin]]
name = "ESkinPlayer"
path = "src/main.rs"
```

当前 WiX 元信息：

```toml
[package.metadata.wix]
eula = false
```

当前 MSI 默认生成到：

```text
target\wix\eskin-model-player-<version>-x86_64.msi
```

例如：

```text
target\wix\eskin-model-player-5.0.0-x86_64.msi
```

## 2. 环境准备

需要安装：

1. Rust toolchain
2. WiX Toolset v3
3. cargo-wix

检查 Rust：

```powershell
rustc --version
cargo --version
```

安装 `cargo-wix`：

```powershell
cargo install cargo-wix
```

检查 `cargo-wix`：

```powershell
cargo wix --help
```

本机常见 WiX v3 路径：

```text
C:\Program Files (x86)\WiX Toolset v3.14\bin
```

检查 WiX 工具是否存在：

```powershell
Test-Path "C:\Program Files (x86)\WiX Toolset v3.14\bin\candle.exe"
Test-Path "C:\Program Files (x86)\WiX Toolset v3.14\bin\light.exe"
```

## 3. 常用打包命令

进入项目目录：

```powershell
cd D:\eskin-player
```

清理旧的 WiX 输出：

```powershell
cargo wix clean
```

正常打包：

```powershell
cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --nocapture
```

说明：

- `--bin-path` 指定 WiX Toolset 的 `bin` 目录。
- `--nocapture` 显示 `candle.exe` 和 `light.exe` 的详细输出，排查错误时建议加上。
- 默认会先执行 `cargo build --release`。
- 默认输出到 `target\wix`。

查看生成的 MSI：

```powershell
Get-ChildItem target\wix\*.msi
```

指定输出目录：

```powershell
cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --output dist\
```

指定输出文件名：

```powershell
cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --output dist\ESkinPlayer.msi
```

只打包，不重新构建 Rust：

```powershell
cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --no-build --target-bin-dir target\release
```

打包后立即安装：

```powershell
cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --install
```

## 4. 重要文件说明

### Cargo.toml

`Cargo.toml` 决定包名、版本、描述、作者、二进制名称等。

常见字段：

```toml
[package]
name = "eskin-model-player"
version = "5.0.0"
authors = ["JOYSONQUIN"]
description = "Desktop pressure sensor visualization and playback application."
```

这些字段会影响：

- MSI 文件名
- 控制面板里的产品版本
- WiX 模板变量

修改版本号后重新打包：

```toml
version = "5.0.1"
```

Windows Installer 的升级判断依赖版本号。发布新的安装包时建议递增版本号。

### wix/main.wxs

`wix/main.wxs` 是 WiX 主配置文件。它决定：

- 安装包名称
- 安装范围
- 安装目录
- 包含哪些文件
- 是否写 PATH
- 卸载行为
- UI 样式
- 升级规则
- 图标、EULA、快捷方式等

## 5. Product 和 Package

典型结构：

```xml
<Product
    Id='*'
    Name='eskin-model-player'
    UpgradeCode='7CEF316B-BE23-4533-B4B2-87D06D2230B5'
    Manufacturer='JOYSONQUIN'
    Language='1033'
    Codepage='1252'
    Version='$(var.Version)'>

    <Package Id='*'
        Keywords='Installer'
        Description='Desktop pressure sensor visualization and playback application.'
        Manufacturer='JOYSONQUIN'
        InstallerVersion='450'
        Languages='1033'
        Compressed='yes'
        InstallScope='perUser'
        InstallPrivileges='limited'
        SummaryCodepage='1252'/>
</Product>
```

关键字段：

- `Product/@Id='*'`：每次构建生成新的 ProductCode。
- `UpgradeCode`：同一个产品线必须保持稳定，不要随意修改。
- `Version='$(var.Version)'`：使用 `Cargo.toml` 的版本。
- `InstallScope`：安装范围，常见值是 `perUser` 或 `perMachine`。
- `InstallPrivileges`：权限要求，`limited` 表示不要求管理员权限，`elevated` 表示需要提升权限。

## 6. per-user 和 per-machine 的区别

### 当前用户安装：perUser

适合：

- 不希望安装时弹管理员权限
- 应用只给当前用户使用
- 安装到用户目录，例如 `%LOCALAPPDATA%`
- 写用户 PATH 或 HKCU 注册表

推荐配置：

```xml
<Package
    ...
    InstallScope='perUser'
    InstallPrivileges='limited'/>
```

目录使用：

```xml
<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='LocalAppDataFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>
```

环境变量使用用户级 PATH：

```xml
<Environment
    Id='PATH'
    Name='PATH'
    Value='[Bin]'
    Permanent='no'
    Part='last'
    Action='set'
    System='no'/>
```

注意：

- 安装到用户目录的组件需要使用 `HKCU` 注册表项作为 `KeyPath`。
- 用户目录下的安装目录建议添加 `RemoveFolder`，否则 ICE64 会报错。

### 所有用户安装：perMachine

适合：

- 应用需要给所有用户使用
- 安装到 `Program Files`
- 写系统 PATH
- 写 HKLM 注册表
- 公司 IT 管理员部署

推荐配置：

```xml
<Package
    ...
    InstallScope='perMachine'
    InstallPrivileges='elevated'/>
```

目录使用：

```xml
<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='ProgramFilesFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>
```

64 位安装包通常使用：

```xml
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
    <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
    <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
```

然后：

```xml
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
```

系统 PATH：

```xml
<Environment
    Id='PATH'
    Name='PATH'
    Value='[Bin]'
    Permanent='no'
    Part='last'
    Action='set'
    System='yes'/>
```

注意：

- `perMachine` 通常需要管理员权限。
- 如果普通用户安装，会出现权限不足提示。
- 写 `Program Files` 和系统 PATH 都需要提升权限。

## 7. 当前项目的 per-user 配置要点

当前项目已经改为当前用户安装：

```xml
InstallScope='perUser'
InstallPrivileges='limited'
```

安装目录：

```xml
<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='LocalAppDataFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            <Directory Id='Bin' Name='bin'>
                ...
            </Directory>
        </Directory>
    </Directory>
</Directory>
```

PATH 组件使用 HKCU registry key 作为 KeyPath：

```xml
<Component Id='Path' Guid='FD230ABD-75B9-47D4-870B-CF2EA72B76BE'>
    <RegistryValue
        Root='HKCU'
        Key='Software\JOYSONQUIN\eskin-model-player'
        Name='PathComponent'
        Type='integer'
        Value='1'
        KeyPath='yes'/>
    <RemoveFolder Id='RemoveBinFolder' Directory='Bin' On='uninstall'/>
    <RemoveFolder Id='RemoveApplicationFolder' Directory='APPLICATIONFOLDER' On='uninstall'/>
    <Environment
        Id='PATH'
        Name='PATH'
        Value='[Bin]'
        Permanent='no'
        Part='last'
        Action='set'
        System='no'/>
</Component>
```

程序文件组件也使用 HKCU registry key 作为 KeyPath：

```xml
<Component Id='binary0' Guid='302CAFB5-6951-426B-BC5A-988C351A2CF2'>
    <RegistryValue
        Root='HKCU'
        Key='Software\JOYSONQUIN\eskin-model-player'
        Name='BinaryComponent'
        Type='integer'
        Value='1'
        KeyPath='yes'/>
    <File
        Id='exe0'
        Name='ESkinPlayer.exe'
        DiskId='1'
        Source='$(var.CargoTargetBinDir)\ESkinPlayer.exe'/>
</Component>
```

## 8. Component、File、KeyPath 的基本规则

WiX 的核心概念：

- `Directory`：安装目录。
- `Component`：安装和卸载的最小管理单元。
- `File`：要安装的文件。
- `RegistryValue`：要写入的注册表项。
- `KeyPath`：Windows Installer 用来判断组件是否已安装的关键路径。

常见规则：

- 一个组件通常只放一个主要文件。
- 组件 GUID 一旦发布后不要随意修改。
- `perUser` 安装到用户目录时，建议用 `HKCU` registry key 作为 `KeyPath`。
- `perMachine` 安装到 Program Files 时，文件通常可以作为 `KeyPath`。
- 如果组件同时包含文件和注册表 KeyPath，WiX v3 通常不能用 `Guid='*'` 自动生成 GUID，需要写固定 GUID。

生成 GUID：

```powershell
[guid]::NewGuid().ToString().ToUpper()
```

示例：

```xml
<Component Id='binary0' Guid='302CAFB5-6951-426B-BC5A-988C351A2CF2'>
    ...
</Component>
```

## 9. 添加文件

假设要安装一个 `README.txt` 到应用目录。

目录结构：

```text
wix\
  main.wxs
  README.txt
```

添加组件：

```xml
<Component Id='ReadmeFile' Guid='PUT-GUID-HERE'>
    <RegistryValue
        Root='HKCU'
        Key='Software\JOYSONQUIN\eskin-model-player'
        Name='ReadmeComponent'
        Type='integer'
        Value='1'
        KeyPath='yes'/>
    <File
        Id='ReadmeTxt'
        Name='README.txt'
        DiskId='1'
        Source='wix\README.txt'/>
</Component>
```

然后在 `Feature` 里引用：

```xml
<ComponentRef Id='ReadmeFile'/>
```

如果是 `perMachine` 安装，也可以简化为：

```xml
<Component Id='ReadmeFile' Guid='PUT-GUID-HERE'>
    <File
        Id='ReadmeTxt'
        Name='README.txt'
        DiskId='1'
        Source='wix\README.txt'
        KeyPath='yes'/>
</Component>
```

## 10. 添加资源目录

如果程序运行时需要模型、图片、配置文件等资源，建议明确安装到应用目录下。

示例：

```xml
<Directory Id='AssetsFolder' Name='assets'>
    <Component Id='AssetConfig' Guid='PUT-GUID-HERE'>
        <RegistryValue
            Root='HKCU'
            Key='Software\JOYSONQUIN\eskin-model-player'
            Name='AssetConfigComponent'
            Type='integer'
            Value='1'
            KeyPath='yes'/>
        <File
            Id='AssetConfigJson'
            Name='config.json'
            DiskId='1'
            Source='assets\config.json'/>
    </Component>
</Directory>
```

引用：

```xml
<ComponentRef Id='AssetConfig'/>
```

如果资源很多，不建议长期手写大量 `<File>`。可以考虑：

- 用 WiX Heat 自动收集目录
- 在构建脚本里生成 `.wxs` 片段
- 只把稳定的少量资源手写到 `main.wxs`

## 11. 修改安装目录

### 改为当前用户目录

```xml
<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='LocalAppDataFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>
```

实际路径通常类似：

```text
C:\Users\<user>\AppData\Local\eskin-model-player
```

### 改为 Program Files

```xml
<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='ProgramFilesFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>
```

64 位项目推荐：

```xml
<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='ProgramFiles64Folder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>
```

## 12. 是否添加 PATH

当前项目有一个可选 Feature：把安装目录下的 `bin` 加入 PATH。

Feature：

```xml
<Feature
    Id='Environment'
    Title='PATH Environment Variable'
    Description='Add the install location of the [ProductName] executable to the PATH system environment variable.'
    Level='1'
    Absent='allow'>
    <ComponentRef Id='Path'/>
</Feature>
```

如果不希望修改 PATH，可以删除或注释：

```xml
<ComponentRef Id='Path'/>
```

也可以把整个 `Environment` Feature 注释掉。

用户级 PATH：

```xml
System='no'
```

系统级 PATH：

```xml
System='yes'
```

注意：

- 用户级 PATH 不需要管理员权限。
- 系统级 PATH 需要管理员权限。
- 安装后新开的终端才能看到新的 PATH。
- 已打开的 PowerShell/CMD 通常不会自动刷新环境变量。

## 13. 添加开始菜单快捷方式

常见做法是在 `ProgramMenuFolder` 下创建快捷方式。

目录：

```xml
<Directory Id='ProgramMenuFolder'>
    <Directory Id='ApplicationProgramsFolder' Name='eskin-model-player'/>
</Directory>
```

在可执行文件组件里添加：

```xml
<Shortcut
    Id='ApplicationStartMenuShortcut'
    Directory='ApplicationProgramsFolder'
    Name='ESkinPlayer'
    WorkingDirectory='Bin'
    Advertise='no'
    Target='[Bin]ESkinPlayer.exe'/>

<RemoveFolder Id='RemoveApplicationProgramsFolder' Directory='ApplicationProgramsFolder' On='uninstall'/>
```

如果是 per-user 安装，快捷方式组件也应该使用 HKCU registry key 作为 `KeyPath`。

## 14. 添加桌面快捷方式

目录：

```xml
<Directory Id='DesktopFolder' Name='Desktop'/>
```

快捷方式：

```xml
<Shortcut
    Id='ApplicationDesktopShortcut'
    Directory='DesktopFolder'
    Name='ESkinPlayer'
    WorkingDirectory='Bin'
    Advertise='no'
    Target='[Bin]ESkinPlayer.exe'/>
```

桌面快捷方式不一定适合默认创建。更推荐放到可选 Feature，或者只创建开始菜单快捷方式。

## 15. 修改控制面板图标

准备 `.ico` 文件，例如：

```text
wix\Product.ico
```

启用：

```xml
<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>
<Property Id='ARPPRODUCTICON' Value='ProductICO'/>
```

注意：

- `.ico` 最好包含多个尺寸，例如 16、32、48、256。
- 路径相对执行打包时的项目根目录。

## 16. 添加 EULA

如果需要安装时显示许可协议：

1. 准备 RTF 文件，例如 `wix\Eula.rtf`
2. 修改 `Cargo.toml`

```toml
[package.metadata.wix]
eula = true
```

3. 在 `wix/main.wxs` 中启用：

```xml
<WixVariable Id='WixUILicenseRtf' Value='wix\Eula.rtf'/>
```

如果当前使用 `WixUI_FeatureTree` 并跳过 EULA，需要检查 `<UI>` 里的 `Publish` 逻辑，避免安装流程跳转冲突。

## 17. 修改安装 UI

当前项目使用：

```xml
<UI>
    <UIRef Id='WixUI_FeatureTree'/>
    ...
</UI>
```

常见 UI：

- `WixUI_Minimal`：最简单。
- `WixUI_InstallDir`：允许选择安装目录。
- `WixUI_FeatureTree`：允许选择 Feature。
- `WixUI_Advanced`：更完整的高级安装流程。

如果要允许用户改安装路径，常用：

```xml
<UIRef Id='WixUI_InstallDir'/>
<Property Id='WIXUI_INSTALLDIR' Value='APPLICATIONFOLDER'/>
```

## 18. 升级策略

当前项目使用：

```xml
<MajorUpgrade
    Schedule='afterInstallInitialize'
    DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
```

含义：

- 安装新版本时自动卸载旧版本。
- 不允许安装更旧版本覆盖新版本。
- `UpgradeCode` 必须保持不变。
- `Version` 必须递增。

Windows Installer 版本规则：

- 通常只比较前三段版本号：`Major.Minor.Build`
- `5.0.0` 到 `5.0.1` 是升级
- 只重打同一个 `5.0.0` 可能不会触发正常升级

发布新版时建议：

```toml
version = "5.0.1"
```

## 19. 签名 MSI

未签名 MSI 在 Windows 上可能触发安全提示。正式发布建议签名。

`cargo-wix` 支持 `sign` 子命令，也可以在生成 MSI 后用 `signtool.exe`。

示例：

```powershell
signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /a target\wix\eskin-model-player-5.0.0-x86_64.msi
```

实际签名命令取决于证书来源：

- 本机证书
- PFX 文件
- 硬件 token
- 公司代码签名服务

## 20. 常见错误与处理

### 权限不足

错误示例：

```text
You do not have sufficient privileges to complete this installation for all users of the machine.
```

常见原因：

- `InstallScope='perMachine'`
- 安装到 `ProgramFilesFolder`
- 写系统 PATH：`System='yes'`
- 写 HKLM 注册表

解决：

- 以管理员身份运行 MSI
- 或改成 `perUser`
- 或安装到 `LocalAppDataFolder`
- 或把 PATH 改为 `System='no'`

### ICE38

错误示例：

```text
ICE38: Component ... installs to user profile. It must use a registry key under HKCU as its KeyPath, not a file.
```

原因：

- 组件安装到用户目录
- 但组件使用文件作为 `KeyPath`

解决：

```xml
<RegistryValue
    Root='HKCU'
    Key='Software\JOYSONQUIN\eskin-model-player'
    Name='BinaryComponent'
    Type='integer'
    Value='1'
    KeyPath='yes'/>
```

并移除 `File` 上的：

```xml
KeyPath='yes'
```

### ICE64

错误示例：

```text
ICE64: The directory Bin is in the user profile but is not listed in the RemoveFile table.
```

原因：

- 用户目录下创建了目录
- 卸载时没有明确清理

解决：

```xml
<RemoveFolder Id='RemoveBinFolder' Directory='Bin' On='uninstall'/>
<RemoveFolder Id='RemoveApplicationFolder' Directory='APPLICATIONFOLDER' On='uninstall'/>
```

### CNDL0230

错误示例：

```text
The Component/@Guid attribute's value '*' is not valid for this component because it does not meet the criteria for having an automatically generated guid.
```

原因：

- 组件里有文件
- 但 KeyPath 是 registry value
- WiX v3 不能自动生成这种组件 GUID

解决：

生成固定 GUID：

```powershell
[guid]::NewGuid().ToString().ToUpper()
```

然后写入：

```xml
<Component Id='binary0' Guid='302CAFB5-6951-426B-BC5A-988C351A2CF2'>
```

### ICE91

警告示例：

```text
ICE91: The file 'exe0' will be installed to the per user directory ...
```

原因：

- 文件安装到用户目录
- ICE 提醒如果该包被当作 per-machine 安装，文件不会复制到每个用户的 profile

处理：

- 如果安装包明确是 `perUser`，通常可以忽略。
- 如果目标是 `perMachine`，应改用 `ProgramFilesFolder`，不要安装到用户 profile。

### 新 MSI 没有覆盖旧 MSI

常见原因：

- `Cargo.toml` 版本号没变
- `UpgradeCode` 改了
- 旧版本是 per-machine，新版本是 per-user

解决：

- 发布时递增版本号
- 保持 `UpgradeCode` 不变
- 如果旧版本是 per-machine，先用管理员权限卸载旧版本

## 21. 调试 MSI 安装

生成详细安装日志：

```powershell
msiexec /i target\wix\eskin-model-player-5.0.0-x86_64.msi /l*v install.log
```

静默安装：

```powershell
msiexec /i target\wix\eskin-model-player-5.0.0-x86_64.msi /qn /l*v install.log
```

卸载：

```powershell
msiexec /x target\wix\eskin-model-player-5.0.0-x86_64.msi /l*v uninstall.log
```

如果安装失败，优先查看日志里：

```text
Return value 3
```

它附近通常是真正失败原因。

## 22. 推荐发布流程

1. 更新 `Cargo.toml` 版本号。
2. 确认 `wix/main.wxs` 的安装范围符合发布目标。
3. 执行 release 构建和 MSI 打包。
4. 在干净机器或干净用户环境中测试安装。
5. 测试启动程序。
6. 测试卸载后文件、快捷方式、PATH 是否清理干净。
7. 正式发布前对 MSI 签名。

命令：

```powershell
cd D:\eskin-player
cargo wix clean
cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --nocapture
```

验证：

```powershell
Get-ChildItem target\wix\*.msi
msiexec /i target\wix\eskin-model-player-5.0.0-x86_64.msi /l*v install.log
```

## 23. 本项目常用修改清单

### 改包名

修改 `Product/@Name`：

```xml
Name='eskin-model-player'
```

以及 `Cargo.toml`：

```toml
name = "eskin-model-player"
```

### 改显示厂商

修改：

```xml
Manufacturer='JOYSONQUIN'
```

以及：

```toml
authors = ["JOYSONQUIN"]
```

### 改安装目录名

修改：

```xml
<Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
```

### 改可执行文件名

修改 `Cargo.toml`：

```toml
[[bin]]
name = "ESkinPlayer"
```

同步修改 WiX：

```xml
<File
    Id='exe0'
    Name='ESkinPlayer.exe'
    Source='$(var.CargoTargetBinDir)\ESkinPlayer.exe'/>
```

### 关闭 PATH 写入

删除或注释：

```xml
<ComponentRef Id='Path'/>
```

也可以删除整个 `Environment` Feature。

### 从 per-user 改回 per-machine

修改：

```xml
InstallScope='perMachine'
InstallPrivileges='elevated'
```

安装目录改为：

```xml
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
```

PATH 改为：

```xml
System='yes'
```

注意：

- 这会重新要求管理员权限。
- 如果已经发布过 per-user 版本，切换安装范围前需要测试升级和卸载路径。

## 24. 维护注意事项

- `UpgradeCode` 是产品线标识，正常发布不要改。
- 发布新版本时递增 `Cargo.toml` 的 `version`。
- 已发布组件的 `Guid` 不要随意变更。
- 用户目录安装使用 `HKCU` registry key 作为 `KeyPath`。
- 系统目录安装通常需要管理员权限。
- 修改 PATH 后要测试安装和卸载。
- 加文件后必须在 `Feature` 中添加 `ComponentRef`，否则不会被安装。
- WiX 报错时优先看 `candle` 还是 `light` 阶段：
  - `candle`：XML 结构、语法、GUID、路径等编译错误。
  - `light`：链接、ICE 校验、MSI 表规则问题。

