尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用 wix311-binaries.zip 构建 MSI 安装包:WiX Toolset 3.11 实战指南

用 wix311-binaries.zip 构建 MSI 安装包:WiX Toolset 3.11 实战指南 简介WiXWindows Installer XML工具集3.11版的二进制文件集合面向需要在Windows平台构建MSI安装包的开发人员与运维工程师。WiX以XML方式定义安装逻辑相比传统安装工程更灵活、可版本化管理适合中大型软件产品的安装包定制场景。压缩包约32.77MB内部主要为一组exe.config配置文件对应candle、light、heat、dark、lit等工具链组件的运行参数涵盖编译、链接、库打包、MSI逆向解析与自动化采集等环节。通过调整这些配置可控制日志级别、默认输出目录、依赖处理规则与扩展属性帮助开发者减少重复操作并按项目规范统一构建流程。资源目前已有465人学习适合正在搭建Windows安装包构建环境、或希望深入理解WiX工具链行为的开发者参考。1. 文件名里藏着的秘密wix311-binaries.zip 是什么先说结论如果你拿到一个叫wix311-binaries.zip的文件那你手里的就是 WiX Toolset 3.11 版本的命令行二进制发布包。WiX 全称 Windows Installer XML Toolset是一套用 XML 描述安装逻辑、生成 Windows 标准安装程序MSI/MSM的开源构建工具。很多做 Windows 桌面端交付的团队特别是给 .NET、C、Electron 之类的应用做安装包时都会碰到它。为什么这个 zip 值得单独聊因为 WiX 的发行方式不是像普通软件那样一个安装程序完事它分成两种形态一种是带 VS 模板和图形界面的完整安装器另一种就是这个wix311-binaries.zip它把命令行工具集全部塞进一个压缩包里解压即用不需要管理员权限也不改注册表。这种形态在 CI 服务器、内网离线环境、以及那些不想装全家桶的开发者眼里简直是刚需。我自己第一次用 WiX 是从一个老项目的构建脚本里翻到这个 zip 的。当时项目组没人说得清安装包是怎么打出来的只有一个 PowerShell 脚本在调用candle.exe和light.exe脚本旁边躺着这个 zip。后来我把里面所有 exe 挨个跑了一遍才搞清楚这套工具链的完整面貌。这篇博文就围绕这个 zip 展开从解压后的每个工具到真实打包流程再到我踩过的坑一步步写清楚。1.1 解压之后你会拿到哪些工具把这个 zip 解压到某个目录建议路径别带空格后面省心里面大概有这些核心文件文件作用我的评价candle.exe编译器把.wxs源码编译成.wixobj中间文件每次打包第一道工序light.exe链接器把.wixobj链接成.msi或.msm安装包决定安装包是否合格的关卡heat.exe文件抓取工具扫描目录自动生成.wxs处理几百个文件时救命的工具lit.exe库工具把多个.wixobj合并成.wixlib库做组件化复用才会用到torch.exe翻译/差异工具生成语言包或多语言版本做国际化才用得上melt.exe把 MSI 反编译回 WiX 源码拆别人的包、学习用WixUIExtension.dll标准安装界面扩展库经常要用后面实操会用到WixUtilExtension.dll辅助扩展提供注册表、文件、服务等增强操作老项目几乎必引每个工具各司其职但日常 80% 的场景你只需要 candycandle和 lightlight这两个。名字有点怪但记住一句就够了candle 负责把源码变成半成品light 负责把半成品变成装机包。1.2 为什么还要手动下载这个包有人会问WiX 不是有官方安装器吗直接装不就行了确实WiX Toolset 官方提供 msi 安装包装完会自动集成到 Visual Studio 里建项目就能用模板。但问题在于很多情况下你不想装或者不能装。典型场景是 CI/CD 流水线。打包机通常要求环境可重复、可脚本化装一个图形化安装器意味着每次构建环境初始化都要多一步交互操作而且安装器版本升级后可能导致构建结果漂移。用wix311-binaries.zip就清爽很多解压到固定目录把路径写进脚本构建前后环境完全可控。另一个场景是离线网络。内网服务器往往无法访问外网 NuGet 源直接下载一个 zip 丢进内部共享目录所有打包节点都能用不需要额外授权也不需要联网。GitHub 上 wix3 仓库的 Release 页面就能下载到这个 zip下载后最好先做一次哈希校验再投入使用。文件的发布版一般会附 SHA256 值这一步别偷懒尤其当你需要通过不安全的渠道分发文件时。2. 一条命令链从 .wxs 源码到 MSI 的工作流既然讲 WiX必须先把它的工作流程讲透。WiX 和传统的 InstallShield 那种“拖控件生成脚本”的工作方式完全不同它是纯文本驱动的构建系统。你写 XML编译器读 XML最终产出安装数据库。2.1 MSI 不是简单的压缩文件组件、Feature 与 GUID很多人第一次接触 MSI 时会以为它就是把文件打包一下然后给个卸载入口。实际上 Windows InstallerMSI 格式的执行引擎采用了一种更底层的“组件化数据库”模型。理解这个模型关键记三件事。第一Component组件是最小的安装粒度。每个组件可以包含几个文件、注册表项、快捷方式。Windows Installer 的修复/卸载功能是按组件统计引用计数的所以组件划分最好按功能边界来不要把两个独立功能塞进同一个组件否则卸载时只能一起删。第二Feature功能是安装时给用户看的安装粒度。比如一个软件包含主程序、帮助文档、附加工具三个 Feature用户在安装界面就能选择安装哪些功能。Feature 里面通过ComponentRef引用组件。第三GUID 是这套系统的灵魂。每个组件必须有一个唯一的 GUIDWindows 卸载时靠它识别“这个组件是否已经存在于系统里”。ProductCode 是某个版本安装包的身份证每次发新版本都必须变UpgradeCode 是产品家族的标识从第一版到最后一次升级都必须保持不变。这个关系搞反了升级就会出现“两个版本并存”或者“无法卸载”的诡异问题。理解了这个模型你再看 WiX 源码就不会晕了。它无非就是在用 XML 描述那些数据库表项。2.2 最小示例用 candle 和 light 产出第一个 MSI写一个最小的Product.wxs把单个 exe 打进安装包。?xml version1.0 encodingUTF-8? Wix xmlnshttp://schemas.microsoft.com/wix/2006/wi Product IdA1B2C3D4-0000-0000-0000-000000000001 NameDemoApp Language1033 Version1.0.0 ManufacturerMyCompany UpgradeCodeA1B2C3D4-0000-0000-0000-0000000000FF Package InstallerVersion500 Compressedyes InstallScopeperMachine DescriptionDemoApp Setup ManufacturerMyCompany / MajorUpgrade DowngradeErrorMessageA newer version of [ProductName] is already installed. / MediaTemplate EmbedCabyes / Directory IdTARGETDIR NameSourceDir Directory IdProgramFilesFolder Directory IdINSTALLFOLDER NameDemoApp Component IdMainExecutable GuidB2C3D4E5-0000-0000-0000-000000000011 File IdDemoExe Source..\src\DemoApp.exe KeyPathyes / /Component /Directory /Directory /Directory Feature IdMainFeature TitleMain Program Level1 ComponentRef IdMainExecutable / /Feature /Product /Wix把它跟DemoApp.exe放到一个干净目录下然后执行set PATHC:\wix311\bin;%PATH% candle.exe Product.wxs light.exe Product.wixobj -out DemoApp.msi第一条命令candle.exe会生成Product.wixobj。第二条light.exe链接生成DemoApp.msi。整个过程不到三秒一个能正常安装、卸载、在“程序和功能”里显示版本的安装包就出来了。这段源码有几个细节值得说明ProductCode与UpgradeCode的 GUID 必须手动生成并固定不要用在线工具生成一次用一次。MediaTemplate EmbedCabyes表示把文件数据和安装数据库一起嵌进一个 msiexec 包不需要外部 cabinet 文件。MajorUpgrade元素保证旧版本会先被移除再安装新版本这是所有生产项目里的标配。KeyPathyes告诉 Windows Installer 用这个文件作为组件的关键路径用于检测组件当前的安装状态。3. 认真做一个带界面的安装包完整实操记录光是最小示例还不够真实交付的软件通常需要自定义安装目录、开始菜单快捷方式、许可证协议、卸载入口以及注册表写入。下面我在最小示例基础上走一遍完整的实操。3.1 目录规划和 Product.wxs 编写要点开始写之前建议先规划好目录结构。我常用的布局是这样D:\work\DemoApp\ ├── setup\ │ ├── Product.wxs │ └── build.bat └── src\ ├── DemoApp.exe ├── DemoApp.exe.config └── readme.txtsetup目录放打包脚本和 WiX 源码src目录放待打包的应用文件。这样打包时把src的输出目录作为源引用setup只做构建动作两者互不污染。下面是一份更接近生产可用的Product.wxs在最小示例基础上增加安装目录由用户选择通过WixUI_InstallDir界面开始菜单快捷方式写入卸载注册表项WixUtilExtension提供安装时显示许可证可选支持中文界面?xml version1.0 encodingutf-8? Wix xmlnshttp://schemas.microsoft.com/wix/2006/wi xmlns:utilhttp://schemas.microsoft.com/wix/UtilExtension Product IdA1B2C3D4-0000-0000-0000-000000000001 NameDemoApp Language2052 Version1.0.0 ManufacturerMyCompany UpgradeCodeA1B2C3D4-0000-0000-0000-0000000000FF Package InstallerVersion500 Compressedyes InstallScopeperMachine DescriptionDemoApp Setup ManufacturerMyCompany / MajorUpgrade DowngradeErrorMessage已安装更高版本无法继续安装。 / MediaTemplate EmbedCabyes / Property IdWIXUI_INSTALLDIR ValueINSTALLFOLDER / Directory IdTARGETDIR NameSourceDir Directory IdProgramFilesFolder Directory IdINSTALLFOLDER NameDemoApp / /Directory Directory IdProgramMenuFolder Directory IdAPPLICATIONFOLDER NameDemoApp / /Directory /Directory DirectoryRef IdINSTALLFOLDER Component IdMainExecutable GuidB2C3D4E5-0000-0000-0000-000000000011 File IdDemoExe Source..\src\DemoApp.exe KeyPathyes / File IdDemoConfig Source..\src\DemoApp.exe.config / File IdReadme Source..\src\readme.txt / util:RegistryValue RootHKLM KeySoftware\MyCompany\DemoApp NameInstallPath Value[INSTALLFOLDER] Typestring KeyPathyes / /Component /DirectoryRef DirectoryRef IdAPPLICATIONFOLDER Component IdAppShortcut GuidB2C3D4E5-0000-0000-0000-000000000022 Shortcut IdStartMenuShortcut NameDemoApp DescriptionLaunch DemoApp Target[#DemoExe] / RemoveFolder IdRemoveApplicationFolder Onuninstall / RegistryValue RootHKCU KeySoftware\MyCompany\DemoApp NameShortcutInstalled Typeinteger Value1 KeyPathyes / /Component /DirectoryRef Feature IdMainFeature TitleMain Program Level1 ComponentRef IdMainExecutable / ComponentRef IdAppShortcut / /Feature UIRef IdWixUI_InstallDir / UIRef IdWixUI_ErrorProgressText / /Product /Wix几个要点逐一说明Language2052表示简体中文但界面是否显示中文取决于light.exe的-cultures参数和实际使用的 UI 扩展资源。更稳妥的做法是保持1033英文UI 资源默认英文产品名和描述用中文即可。想追求完整中文化需要额外引入语言资源这个复杂度后面再讲。WIXUI_INSTALLDIR属性的作用是告诉WixUI_InstallDir界面用户选择的安装路径要赋给哪个目录 ID。漏掉这一行界面上的路径选择框是灰色的选了也没用。开始菜单快捷方式建在ProgramMenuFolder\DemoApp目录下。注意RemoveFolder Onuninstall是为了卸载后如果目录空了能删掉避免残留空目录。注册表项固定写在HKLM\Software\MyCompany\DemoApp值InstallPath保存实际安装路径。这是很多软件的常见做法便于其他程序查找安装位置。3.2 编译、链接与安装验证执行构建命令candle.exe -utf8 Product.wxs -ext WixUtilExtension.dll -out obj\ light.exe obj\Product.wixobj -cultures:zh-CN -ext WixUIExtension.dll -ext WixUtilExtension.dll -out output\DemoApp.msi两条命令分别加了-ext参数因为源码里引用了util命名空间和 UI 对话框资源。candle阶段用的是 XML 命名空间解析light阶段需要把对应的扩展 DLL 链接进去漏掉任何一个都会报错。构建完成后在output目录得到DemoApp.msi。验证安装可以分两种方式图形界面安装直接双击 msi会看到标准的 WiX 安装向导可以选语言、选目录、点 Install。静默安装验证msiexec /i DemoApp.msi /qn /l*v install.log日志文件会详细记录每一步操作包括文件复制、注册表写入、快捷方式创建。装完后到“控制面板-程序和功能”里确认能看到DemoApp条目点“卸载”能完整移除刚才写入的内容开始菜单快捷方式也一起消失了。到这一步一个带界面、带快捷方式、带卸载的安装包才算真正落地。3.3 升级安装与卸载流程的模拟安装包最容易被忽视的是升级流程。我建议每个项目在测试时都按这个顺序过一遍安装 1.0.0 版本确认目录下有DemoApp.exe。修改Product.wxs里的Version为1.0.1重新生成一个全新ProductCodeUpgradeCode保持不变重新构建。直接安装 1.0.1 版本不手动卸载旧版。如果MajorUpgrade配置正确第二次安装会先移除旧版文件再写入新版InstallPath注册表值也会更新。如果看到“已安装更高版本”的提示说明DowngradeErrorMessage生效了说明升级策略是正常的。这里的坑在于写死了旧版本的ProductCode而不改然后直接安装“新版”Windows Installer 会因为检测到同一 ProductCode 已存在而拒绝安装。所以每次升级必须重新生成ProductCode。升级码UpgradeCode则要像“身份证号码”一样从第一版保持到最后一版这是用户卸载时能正确关联整个产品家族的关键。4. 常见问题排查与避坑我是把wix311-binaries.zip用到第三个项目后才积累起下面的经验。很多问题不实际踩一遍光看文档根本想不到。4.1 按错误码整理的速查表错误现象可能原因解决办法candle.exe无法启动提示缺少 VCRUNTIME140.dll客户机/打包机没装 VC 2015-2019 运行库安装 VC Redistributable x86 版本WiX 3.11 本身是 32 位程序light.exe报LGHT0103找不到文件.wxs里Source路径写错或工作目录不对用绝对路径或在脚本里先cd到源码目录安装时报1603权限不足或 MSI 脚本执行阶段出错用管理员 cmd 重新执行打开install.log查找失败点2755/2350错误安装包无法访问服务器从不支持的位置启动 MSI如网络共享把 MSI 复制到本地磁盘再安装升级安装时提示“已安装更高版本”新版本ProductCode没改或版本号没递增确认新版本号大于旧版本号并更新ProductCode中文乱码.wxs文件编码不是 UTF-8或者没带 BOM用 UTF-8 带 BOM 保存源码文件并在candle加-utf8参数文件正在被占用安装失败目标程序正在运行在安装前检测进程并提示退出或用Restart Manager和RemoveFile实现延迟替换卸载后配置残留注册表/文件放在组件之外写一个独立的清理组件或在卸载自定义动作中删除这里面最隐蔽的就是最后一条残留问题。很多人发现卸载安装包后注册表干干净净的因为所有写入都在Component里管理。但如果你在安装时用第三方工具或脚本往系统里写东西卸载时 WiX 完全不知道不会帮你清理。所以原则是所有要持久化的数据都必须挂到某个 Component 下让 MSI 统一管理生命周期。4.2 几个非常容易踩的细节Component GUID 写*是省事但别在正式环境用。每次编译时*会生成一个新的 GUID导致系统里旧组件的记录和新的不一致升级时可能出现“旧文件删不掉”的问题。生产环境请显式使用固定 GUID。MediaTemplatevsMedia的差异。3.11 推荐用MediaTemplate EmbedCabyes它会按压缩策略自动拆 cabinet。老项目里常见的Media Id1 Cabinetproduct.cab EmbedCabyes /也能用但前者更省心。InstallScope的选择影响很多。perMachine需要管理员权限适合机器级别安装perUser不需要提权适合当前用户安装但会被 Windows 显示在“应用和功能”中。如果你发布的是用户态工具优先考虑perUser安装体验大不同。文件关联、快捷键必须放在独立的 Component 里。不要和主 exe 放在同一个组件里否则你没法做到“升级时保留文件关联配置只替换程序文件”。语言代码不是随便填的。Language2052是给 MSI 的区域标记但 WiX 界面语言由light.exe的-cultures决定两者填错了就会出现“安装包声称中文界面却全英文”的尴尬。5. 接入自动化构建把 wix311-binaries 变成打包流水线的一部分手工敲命令打包一次两次还行但产品进入迭代期后一周出好几个安装包这时候就必须脚本化。我把wix311-binaries.zip接入自动化后的经验写在这里。5.1 PowerShell 封装脚本我常用的构建脚本长这样$ErrorActionPreference Stop $wixBin D:\tools\wix311\bin $version 1.0.1 $sourceDir ..\src $outputDir ..\output # 1. 清理旧产物 if (Test-Path $outputDir) { Remove-Item $outputDir -Recurse -Force } New-Item -ItemType Directory -Path $outputDir -Force | Out-Null # 2. 编译 .wxs $wixBin\candle.exe -utf8 -dVersion$version -dSourceDir$sourceDir -ext $wixBin\WixUtilExtension.dll Product.wxs -out $outputDir\Product.wixobj if ($LASTEXITCODE -ne 0) { throw candle failed } # 3. 链接成 MSI $wixBin\light.exe $outputDir\Product.wixobj -cultures:zh-CN -ext $wixBin\WixUIExtension.dll -ext $wixBin\WixUtilExtension.dll -out $outputDir\DemoApp-$version.msi if ($LASTEXITCODE -ne 0) { throw light failed } Write-Host Build OK: $outputDir\DemoApp-$version.msi脚本里-dVersion和-dSourceDir是 WiX 的预处理器变量需要在.wxs中引用。怎么引用在需要版本号的地方写$(var.Version)在文件路径处写$(var.SourceDir)\DemoApp.exe。这种做法让脚本和源码解耦版本号只在一个地方维护非常推荐。5.2 版本号、参数与 CI 集成建议在 CI 流水线里我建议再往前一步从 Git 标签或构建变量读取版本号而不是硬编码在脚本里。每次构建生成随机ProductCode并写回.wxs或者用预处理器变量-dProductCodeGUID传入。注意candle阶段就要确定因为ProductCode是编译期常量。把哈希校验步骤也接进流水线下载wix311-binaries.zip后执行一次Get-FileHash跟官方值比对防止供应链篡改。安装包生成后自动做msiexec /a或者/qn静默安装冒烟测试确认装机不报错再进入发布环节。做到这一步你的打包流程就和代码提交完全打通了。每次提交打标签后流水线自动拉取最新二进制更新版本号产出 MSI然后推送到内部下载站。整个过程不再依赖任何人手动操作。5.3 可选扩展方向BURN 引导程序与 heat 抓取目录如果产品依赖多个组件比如 .NET Runtime、VC 运行库单靠一个 MSI 是搞不定的因为它们各自是独立的安装包。这时候就要用 WiX 的 BURN 引导程序创建一个.wxs生成一个 exe在安装时依次下载并静默安装那些 Runtime再安装主程序 MSI。这个功能同样在wix311-binaries.zip里有对应工具和扩展需要WixBalExtension。以后有机会单独写一篇。另一个高频需求是处理大量文件。手动在.wxs里一个文件一个File写会写到怀疑人生。heat.exe可以扫描目录自动生成.wxs片段heat.exe dir ..\src -gg -scom -sreg -srd -ke -out Files.wxs生成后把Files.wxs里的Component挂进Feature即可。不过自动生成的 GUID 和路径往往需要人工微调heat适合做初稿你在此基础上修效率会高很多。关于选用 3.11 还是 4.x/5.x 的个人体会写到最后说点个人判断。WiX 目前已经出了 4.x 和 5.x但很多存量项目的构建脚本还是牢牢锁死 3.11原因主要是三条资料最多、生态最稳、老项目迁移成本高。如果你从零开始一个新项目我建议先看一下官方最新的稳定版是否满足需求再决定要不要直接用新版。但如果你在维护老项目的打包流程或者遇到了wix311-binaries.zip这个文件千万别急着扔掉换新版。3.11 的成熟度极高社区里几乎所有坑都有人踩过资料齐全遇到问题好查。我目前维护的几个生产项目仍然是 3.11 构建稳定服役两三年除了那次换机器导致 VC 运行库没装之外几乎没有出过幺蛾子。最后分享一个我坚持多年的习惯wix311-binaries.zip解压后把整个目录保存到内部代码仓库的工具目录里并在 README 里写明版本来源和哈希值。这样即使哪天网上下不到了或者仓库历史变更导致构建环境变了你依然能完整复现当年的构建。安装包构建是一个典型“长期主义”的领域短期的省事都会变成长期的事故。本文还有配套的精品资源点击获取
返回列表