跳转至

Msi installer design

1. 需求

项目需要基于 WiX Toolset 4.0.4 生成 32 位和 64 位两个 MSI 安装包,每个 MSI 内置英文和简体中文两种 UI 语言。

生成目标:

build\installer\win32\bntech_virtual_scanner_win32.msi
build\installer\win64\bntech_virtual_scanner_win64.msi

MSI 安装内容需要和 build.bat install / cmake --install 保持一致:

  • bntech_virtual_scanner.ds
  • FreeImage.dll
  • TWAIN_logo.png

安装目标目录:

32-bit: C:\Windows\twain_32\bntech\
64-bit: C:\Windows\twain_64\bntech\

MSI 支持多语言 UI:同一个 MSI 在中文 Windows 上自动显示中文界面并写入 %APPDATA%\bntech\config.ini,在英文 Windows 上显示英文界面,不写 config.ini(DS 默认为 en_US)。

2. 领域知识

2.1 TWAIN Data Source 安装位置

Windows TWAIN Data Source 是一个特殊 DLL,扩展名通常是 .ds。32 位和 64 位 TWAIN 数据源安装目录不同:

架构 安装目录
32-bit C:\Windows\twain_32\bntech\
64-bit C:\Windows\twain_64\bntech\

这两个目录都位于 Windows 系统目录下,因此安装通常需要管理员权限。

2.2 WiX 4 构建模型

WiX 4 使用 wix build 直接从 .wxs 文件生成 MSI。本项目新增:

installer\bntech_virtual_scanner.wxs

CMake 通过自定义 CMake 函数 add_multilang_msi 调用 WiX 两次(英文 + 中文),然后用 Windows SDK 工具合并为多语言 MSI。

2.3 MSI 嵌入语言转换

MSI 本身只支持单一语言 UI。多语言 MSI 是通过嵌入语言转换文件 (.mst) 实现的,Windows Installer 运行时根据系统语言自动选择对应转换。

实现使用 Windows SDK 三个工具:

工具 用途
MsiTran.exe 从两个单语言 MSI 生成 .mst 转换文件
WiSubStg.vbs .mst 嵌入到 MSI 子存储
WiLangId.vbs 更新 MSI 摘要信息,声明支持的语言列表

构建流程:

wix build ... -culture en-us → win32_en_US.msi
wix build ... -culture zh-cn → win32_zh_CN.msi
MsiTran.exe -g en.msi zh.msi zh_CN.mst
copy en.msi → final.msi
WiSubStg.vbs final.msi zh_CN.mst 2052
WiLangId.vbs final.msi Package 1033,2052
cleanup en.msi zh.msi zh_CN.mst

-culture 参数使 WiX UI 扩展自动选择对应语言的标准向导文本。

2.4 WiX UI 扩展

在当前环境中,WixToolset.UI.wixext 4.0.6 可用。MSI 基于 WixUI_InstallDir 标准向导,提供:

  • Welcome / License 页面
  • 安装目录选择
  • 安装就绪确认
  • 安装进度条
  • 完成页面

2.5 %APPDATA% 与安装上下文

MSI 中使用 AppDataFolder 表示当前安装上下文下的 AppData roaming 目录。本项目用它创建:

%APPDATA%\bntech\

并在 APP_LANGUAGE != en_US 时写入 config.ini

注意:如果以管理员身份为所有用户安装,AppDataFolder 的实际用户上下文可能与最终运行扫描应用的用户有关,后续需要进一步验证多用户场景。

3. 设计目标

  • 使用 WiX 4.0.4 生成 MSI。
  • 同时支持 32 位和 64 位安装包。
  • 单个 MSI 内置英文和简体中文 UI,安装时自动根据系统语言显示。
  • MSI 安装内容与现有 build/install 流程一致。
  • CMake 暴露 msi32msi64 target。
  • build.bat msi32 / build.bat msi64 可单独生成 MSI。
  • build.bat 不带参数时仍保留自动构建、安装到 C:\Windows 的行为,并额外生成两个 MSI。
  • MSI 支持通过 APP_LANGUAGE 写入语言配置。
  • MSI 使用 WixUI_InstallDir 标准安装向导。

非目标:

  • 当前不提供运行时语言选择页面(MSI 不支持单包运行时切换 UI 语言,语言通过嵌入转换实现)。
  • 当前不打包 VC Runtime 或 WiX bootstrapper。
  • 当前不处理所有用户的 %APPDATA% 配置同步。
  • 当前没有安装/卸载成功或失败消息弹框。

4. 总体设计

新增 WiX 源文件:

installer\bntech_virtual_scanner.wxs

CMake 新增自定义 target 和构建函数:

msi32
msi64
add_multilang_msi()

build.bat 新增命令:

build.bat msi32
build.bat msi64

不带参数时:

build.bat

执行完整流程:

build win32
build win64
install win32 to C:\Windows\twain_32\bntech
install win64 to C:\Windows\twain_64\bntech
build msi32 (en-us + zh-cn → merged)
build msi64 (en-us + zh-cn → merged)

5. 重要决策和原因

5.1 32 位和 64 位 MSI 分开生成

决策:生成两个独立 MSI,而不是一个同时包含 32 位和 64 位组件的 MSI。

原因:

  • TWAIN 32 位和 64 位目录不同。
  • WiX/MSI 对组件架构有明确区分。
  • 分开生成更简单、风险更低、易于测试。

5.2 MSI 安装文件来自 CMake build 输出目录

决策:WiX 的 SourceDir 指向:

build\win32
build\win64

原因:

  • CMake build 后这些目录已经包含 .dsFreeImage.dllTWAIN_logo.png
  • build.bat install 的文件来源一致。
  • 避免 WiX 重复理解项目源码和依赖路径。

5.3 使用嵌入语言转换实现多语言

决策:通过 Windows SDK 工具(MsiTran / WiSubStg / WiLangId)将两个单语言 MSI 合并为一个多语言 MSI。

原因:

  • MSI 标准不支撑运行时 UI 语言切换。
  • 嵌入转换是 Windows Installer 原生支持的多语言机制。
  • 用户安装时无需额外操作,系统语言自动匹配。
  • 仍可强制指定语言:msiexec /i xxx.msi TRANSFORMS=:2052

5.4 使用 WixUI_InstallDir 标准向导

决策:引入 WixToolset.UI.wixext,使用 WixUI_InstallDir

原因:

  • 在当前环境中扩展 4.0.6 可用,不再有找不到扩展的问题。
  • 提供完整的安装目录选择、进度条和完成页面。
  • 配合 -culture 参数自动提供中文或英文标准向导文本。

5.5 不弹安装/卸载结果消息框

决策:移除全部 VBScript CustomAction 消息弹框。

原因:

  • WixUI_InstallDir 已有进度条和完成页面,用户可见安装结果。
  • 免除了 VBScript 在企业环境和静默安装模式下的兼容性问题。
  • 简化了构建流程和文件维护。

5.6 使用编译期 AppLanguage 预处理变量

决策:APP_LANGUAGE 在 WiX 编译期通过 $(var.AppLanguage) 预处理变量写死。

原因:

  • 与嵌入转换方案配合,每种语言 MSI 自带对应的 APP_LANGUAGE 值。
  • 用户无需通过命令行传入语言参数。
  • APP_LANGUAGE 控制 config.ini 写入逻辑:en_US 不写,zh_CN 写入。

6. 架构各层改动

6.1 installer 层

新增:

installer\bntech_virtual_scanner.wxs

bntech_virtual_scanner.wxs 负责:

  • 根据 $(var.Platform) 定义 32/64 位产品名、TWAIN 目录和 UpgradeCode。
  • 根据 $(var.AppLanguage) 定义产品显示名和内置 APP_LANGUAGE 属性。
  • 定义安装文件组件:.ds, FreeImage.dll, TWAIN_logo.png
  • 定义 %APPDATA%\bntech 目录。
  • APP_LANGUAGE != en_US 时写 config.ini
  • 使用 WixUI_InstallDir 标准向导。

6.2 CMake 层

文件:

CMakeLists.txt

新增 CMake 函数和变量:

  • WIX_EXECUTABLE / WIX_UI_EXTENSION / WIX_SOURCE_FILE
  • WINSDK_BIN / MSITRAN / WISUBSTG / WILANGID
  • add_multilang_msi(msi64 x64 x64 ...)
  • add_multilang_msi(msi32 x86 win32 ...)

每个 add_multilang_msi 调用执行:

  1. wix build 英文 MSI(-culture en-us
  2. wix build 中文 MSI(-culture zh-cn
  3. MsiTran.exe -g 生成语言转换
  4. 复制英文 MSI 为最终文件名
  5. WiSubStg.vbs 嵌入转换(LCID 2052)
  6. WiLangId.vbs 声明多语言(1033,2052)
  7. 清理中间文件

6.3 build.bat 层

文件:

build.bat

新增:

  • 参数 msi32
  • 参数 msi64
  • :msi 子过程

不带参数行为:

build both → install both → package both MSI

6.4 i18n 配置层

MSI 不直接修改 DS 代码,而是写入 DS 已支持的配置文件:

%APPDATA%\bntech\config.ini

这使 installer 与运行时 i18n 逻辑保持松耦合。

7. 安装和打包流程

7.1 不带参数完整流程

build.bat
  → 请求管理员权限
  → 构建 win32
  → 构建 win64
  → cmake --install build/win32 --prefix C:/Windows
  → cmake --install build/win64 --prefix C:/Windows
  → cmake --build build/win32 --target msi32
  → cmake --build build/win64 --target msi64

7.2 单独生成 MSI

build.bat msi32
  → 配置 build/win32
  → cmake --build build/win32 --target msi32
build.bat msi64
  → 配置 build/win64
  → cmake --build build/win64 --target msi64

7.3 安装及语言

自动匹配系统语言:

msiexec /i bntech_virtual_scanner_win64.msi

强制中文:

msiexec /i bntech_virtual_scanner_win64.msi TRANSFORMS=:2052

8. 局限性

  • MSI 不可运行时切换 UI 语言(单 MSI 包的限制)。语言通过一篇嵌入转换覆盖,安装时按系统语言自动选择。
  • AppDataFolder 在管理员安装和多用户环境中的实际落点需要进一步测试。
  • 当前没有安装/卸载成功或失败消息弹框。用户通过 WixUI 进度条和完成页面了解安装状态。
  • 当前没有自动检查 WiX 版本。
  • 当前没有把 MSI 构建纳入 CI。
  • 删除 .vbs 后,静默安装模式完全无声(无弹框),这在某些场景下可能是优点。

9. 下一步工作

  • 增加 WiX 版本和 Windows SDK 工具版本检测。
  • 增加安装前检查:目标 .ds 是否被扫描应用占用。
  • 增加安装日志输出说明,例如提示用户使用 /l*v install.log
  • 测试管理员安装、多用户登录和不同 %APPDATA% 上下文。
  • 支持卸载时清理或保留 config.ini 的明确策略。
  • 在 CI 中生成并归档 win32/win64 MSI。
  • 评估是否需要更新 MSI 内部的 APP_LANGUAGE 注释(当前仍写 "baked-in")。