参考· 04

应用标识与覆盖安装

这是打包里最容易踩坑、又最不容易发现的一处:AppId 决定重新打包是覆盖升级,还是装出第二个。

适用于 v0.2.0/约 3 分钟/参考

Inno 怎么判断「升级」还是「新装」

Inno 靠 AppId 认出一个应用之前装过没有。它把记录写在 HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\<AppId>_is1:

  • AppId 相同 → 视为同一个应用的新版本:覆盖安装、更新卸载键里的版本号、 向导默认装到上次的目录(UsePreviousAppDir 默认 yes);
  • AppId 不同 → 视为另一个应用:全新安装,两个版本在「应用和功能」里并存。

所以 AppId 必须在「同一个产品的所有构建」之间保持一致。

为什么不能用工程 ID

工程 ID 是「新建工程」时随机生成的 UUID。用它当 AppId 的话:

  • 同一个工程反复打包 → AppId 不变 → 能覆盖升级;
  • 但换一个工程(哪怕选的是同一份 exe)→ AppId 变了 → 全新安装、两个版本并存。

这正是「我明明只改了几行代码重新打包,装出来却是两个软件」的原因。

现在是怎么做的

AppId 由主程序的产品身份推导:版本资源里的 ProductName + CompanyName + OriginalFilename(拿不到就退到文件名),拼接后做一次确定性哈希,摆成 UUIDv5 的形状。

  • 同一份主程序重新编译、重新打包、换台机器打包 → 同一个 AppId;
  • ProductName 或 CompanyName 改了 → AppId 跟着变 —— 那本来就该当成另一个应用;
  • 推导结果填在「应用标识」里,可以手工改(比如要强行当成另一个应用,或要接回旧的 AppId)。

推导逻辑(commands.rs::detect_app_id)有单测钉住:确定性、不同产品得到不同值、 UUID 形状合法、只改大小写不算换产品、字段之间不会串味。

两个容易忽略的前提

  1. PrivilegesRequired 不能在两次构建之间变。 它决定 Inno 把卸载键写在 HKCU 还是 HKLM; 上一次是「当前用户」、这一次改成「需要管理员」,第二次就找不到第一次的安装 → 全新安装。
  2. 绿色版(packagingMode=portable)没有升级这件事。 它写 Uninstallable=no 与 CreateUninstallRegKey=no,Inno 没有可查的记录,每次都是全新解压。这是有意的。

版本迁移的注意点

AppId 的取值方式在 v0.1.1 改过一次(v0.1.0 用的是工程 ID)。因此:

「v0.1.0 构建的安装包」与「v0.1.1 之后构建的安装包」AppId 不同,不会互相升级 —— 只会发生一次。想接回旧 AppId,在「应用标识」里手工填入旧值即可。

怎么确认自己的包是对的

  1. 打包两次(或改个版本号再打一次),分别安装;
  2. 打开「设置 → 应用 → 已安装的应用」,确认只有一条记录;
  3. 或者直接看注册表:HKCU\...\Uninstall\ 下同一个 _is1 键的 DisplayVersion 变了、键数没变。
搜索Esc

↑ ↓ 选择 · Enter 打开 · 中英文都能搜