☰
用 wix311-binaries.zip 构建 MSI 安装包:WiX Toolset 3.11 实战指南
2026/10/1 10:19:29 网站建设 项目流程

简介:WiX(Windows 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% 的场景你只需要 candy(candle)和 light(light)这两个。名字有点怪,但记住一句就够了: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 Installer(MSI 格式的执行引擎)采用了一种更底层的“组件化数据库”模型。

理解这个模型,关键记三件事。

第一,Component(组件)是最小的安装粒度。每个组件可以包含几个文件、注册表项、快捷方式。Windows Installer 的修复/卸载功能是按组件统计引用计数的,所以组件划分最好按功能边界来,不要把两个独立功能塞进同一个组件,否则卸载时只能一起删。

第二,Feature(功能)是安装时给用户看的安装粒度。比如一个软件包含主程序、帮助文档、附加工具三个 Feature,用户在安装界面就能选择安装哪些功能。Feature 里面通过<ComponentRef>引用组件。

第三,GUID 是这套系统的灵魂。每个组件必须有一个唯一的 GUID,Windows 卸载时靠它识别“这个组件是否已经存在于系统里”。ProductCode 是某个版本安装包的身份证,每次发新版本都必须变;UpgradeCode 是产品家族的标识,从第一版到最后一次升级都必须保持不变。这个关系搞反了,升级就会出现“两个版本并存”或者“无法卸载”的诡异问题。

理解了这个模型,你再看 WiX 源码就不会晕了。它无非就是在用 XML 描述那些数据库表项。

2.2 最小示例:用 candle 和 light 产出第一个 MSI

写一个最小的Product.wxs,把单个 exe 打进安装包。

<?xml version="1.0" encoding="UTF-8"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> <Product Id="A1B2C3D4-0000-0000-0000-000000000001" Name="DemoApp" Language="1033" Version="1.0.0" Manufacturer="MyCompany" UpgradeCode="A1B2C3D4-0000-0000-0000-0000000000FF"> <Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" Description="DemoApp Setup" Manufacturer="MyCompany" /> <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." /> <MediaTemplate EmbedCab="yes" /> <Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="ProgramFilesFolder"> <Directory Id="INSTALLFOLDER" Name="DemoApp"> <Component Id="MainExecutable" Guid="B2C3D4E5-0000-0000-0000-000000000011"> <File Id="DemoExe" Source="..\src\DemoApp.exe" KeyPath="yes" /> </Component> </Directory> </Directory> </Directory> <Feature Id="MainFeature" Title="Main Program" Level="1"> <ComponentRef Id="MainExecutable" /> </Feature> </Product> </Wix>

把它跟DemoApp.exe放到一个干净目录下,然后执行:

set PATH=C:\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 EmbedCab="yes"表示把文件数据和安装数据库一起嵌进一个 msiexec 包,不需要外部 cabinet 文件。
  • MajorUpgrade元素保证旧版本会先被移除再安装新版本,这是所有生产项目里的标配。
  • KeyPath="yes"告诉 Windows Installer 用这个文件作为组件的关键路径,用于检测组件当前的安装状态。

3. 认真做一个带界面的安装包:完整实操记录

光是最小示例还不够,真实交付的软件通常需要:自定义安装目录、开始菜单快捷方式、许可证协议、卸载入口,以及注册表写入。下面我在最小示例基础上,走一遍完整的实操。

3.1 目录规划和 Product.wxs 编写要点

开始写之前,建议先规划好目录结构。我常用的布局是这样:

D:\work\DemoApp\ ├── setup\ │ ├── Product.wxs │ └── build.bat └── src\ ├── DemoApp.exe ├── DemoApp.exe.config └── readme.txt

setup目录放打包脚本和 WiX 源码,src目录放待打包的应用文件。这样打包时把src的输出目录作为源引用,setup只做构建动作,两者互不污染。

下面是一份更接近生产可用的Product.wxs,在最小示例基础上增加:

  • 安装目录由用户选择(通过WixUI_InstallDir界面)
  • 开始菜单快捷方式
  • 写入卸载注册表项(WixUtilExtension提供)
  • 安装时显示许可证(可选)
  • 支持中文界面
<?xml version="1.0" encoding="utf-8"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"> <Product Id="A1B2C3D4-0000-0000-0000-000000000001" Name="DemoApp" Language="2052" Version="1.0.0" Manufacturer="MyCompany" UpgradeCode="A1B2C3D4-0000-0000-0000-0000000000FF"> <Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" Description="DemoApp Setup" Manufacturer="MyCompany" /> <MajorUpgrade DowngradeErrorMessage="已安装更高版本,无法继续安装。" /> <MediaTemplate EmbedCab="yes" /> <Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" /> <Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="ProgramFilesFolder"> <Directory Id="INSTALLFOLDER" Name="DemoApp" /> </Directory> <Directory Id="ProgramMenuFolder"> <Directory Id="APPLICATIONFOLDER" Name="DemoApp" /> </Directory> </Directory> <DirectoryRef Id="INSTALLFOLDER"> <Component Id="MainExecutable" Guid="B2C3D4E5-0000-0000-0000-000000000011"> <File Id="DemoExe" Source="..\src\DemoApp.exe" KeyPath="yes" /> <File Id="DemoConfig" Source="..\src\DemoApp.exe.config" /> <File Id="Readme" Source="..\src\readme.txt" /> <util:RegistryValue Root="HKLM" Key="Software\MyCompany\DemoApp" Name="InstallPath" Value="[INSTALLFOLDER]" Type="string" KeyPath="yes" /> </Component> </DirectoryRef> <DirectoryRef Id="APPLICATIONFOLDER"> <Component Id="AppShortcut" Guid="B2C3D4E5-0000-0000-0000-000000000022"> <Shortcut Id="StartMenuShortcut" Name="DemoApp" Description="Launch DemoApp" Target="[#DemoExe]" /> <RemoveFolder Id="RemoveApplicationFolder" On="uninstall" /> <RegistryValue Root="HKCU" Key="Software\MyCompany\DemoApp" Name="ShortcutInstalled" Type="integer" Value="1" KeyPath="yes" /> </Component> </DirectoryRef> <Feature Id="MainFeature" Title="Main Program" Level="1"> <ComponentRef Id="MainExecutable" /> <ComponentRef Id="AppShortcut" /> </Feature> <UIRef Id="WixUI_InstallDir" /> <UIRef Id="WixUI_ErrorProgressText" /> </Product> </Wix>

几个要点逐一说明:

  • Language="2052"表示简体中文,但界面是否显示中文取决于light.exe的-cultures参数和实际使用的 UI 扩展资源。更稳妥的做法是保持1033(英文),UI 资源默认英文,产品名和描述用中文即可。想追求完整中文化,需要额外引入语言资源,这个复杂度后面再讲。
  • WIXUI_INSTALLDIR属性的作用是告诉WixUI_InstallDir界面,用户选择的安装路径要赋给哪个目录 ID。漏掉这一行,界面上的路径选择框是灰色的,选了也没用。
  • 开始菜单快捷方式建在ProgramMenuFolder\DemoApp目录下。注意RemoveFolder On="uninstall"是为了卸载后如果目录空了能删掉,避免残留空目录。
  • 注册表项固定写在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. 安装 1.0.0 版本,确认目录下有DemoApp.exe。
  2. 修改Product.wxs里的Version为1.0.1,重新生成一个全新ProductCode(UpgradeCode保持不变),重新构建。
  3. 直接安装 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 EmbedCab="yes",它会按压缩策略自动拆 cabinet。老项目里常见的<Media Id="1" Cabinet="product.cab" EmbedCab="yes" />也能用,但前者更省心。
  • InstallScope的选择影响很多。perMachine需要管理员权限,适合机器级别安装;perUser不需要提权,适合当前用户安装但会被 Windows 显示在“应用和功能”中。如果你发布的是用户态工具,优先考虑perUser,安装体验大不同。
  • 文件关联、快捷键必须放在独立的 Component 里。不要和主 exe 放在同一个组件里,否则你没法做到“升级时保留文件关联配置,只替换程序文件”。
  • 语言代码不是随便填的。Language="2052"是给 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,或者用预处理器变量-dProductCode=<GUID>传入。注意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 里写明版本来源和哈希值。这样即使哪天网上下不到了,或者仓库历史变更导致构建环境变了,你依然能完整复现当年的构建。安装包构建是一个典型“长期主义”的领域,短期的省事都会变成长期的事故。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询