拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Advanced Installer软件打包实战:MSI制作、升级与CI集成

Advanced Installer软件打包实战:MSI制作、升级与CI集成

简介:Advanced Installer 20.7.1 是一款面向软件开发者、系统集成商与运维人员的 Windows 安装包制作工具,能够生成符合 MS Windows 认证要求的 MSI 安装包。其图形用户界面直观简洁,支持自定义欢迎页、安装过程页面、许可协议与对话框样式,方便用户根据产品品牌进行界面定制。该压缩包共含 2000 个文件,压缩后大小约 161.36 MB,文件构成以 png、jpg、ico 等界面图标与背景图片为主,同时包含 aip 工程文件、xsd 配置定义、rtf/xml 说明文档以及少量 msi 成品示例。这种结构大体还原了实际高级安装工程的项目布局,从视觉素材到安装规则配置均有涉及,还可看到中英文界面文件与多类辅助脚本。已有 940 人学习下载,对于希望快速上手 Advanced Installer、掌握 MSI 封装流程或搭建规范安装包模板的用户,这套资源提供了即开即用的样例与配置参考,能有效减少从零摸索的时间。

1. Advanced Installer 20.7.1软件打包工具:先搞清楚它解决什么问题

Advanced Installer 20.7.1是一款面向Windows平台的软件打包工具,最擅长的场景是“应用写完了却交不出去”——客户要一个双击能装的EXE、运维要一个能静默分发的MSI、升级时还得保住用户配置和注册表数据。它的价值是把程序文件、运行库依赖、服务、注册表、快捷方式这些散件整合成标准安装包,而不必像WiX那样对着底层表结构写XML。

适合谁?Windows桌面软件、企业内部系统、硬件配套软件的开发者和交付工程师。不适合谁?只做Linux发布、应用完全走应用商店自动更新的团队。技术团队里常把它和InstallShield、WiX放在一起比较,实际用下来它最接近“画界面就能生成专业安装包”的那一类,同时保留命令行构建,CI也能接。

2. 选型与安装:为什么是Advanced Installer而不是InstallShield或WiX

安装包工具选错,后面换一次等于把所有升级链路重做一遍,所以开头值得多花点时间。

2.1 选型对比:InstallShield、WiX、Advanced Installer差在哪

对比维度InstallShieldWiXAdvanced Installer
上手成本高,术语密集,新成员培训周期长高,需要理解MSI表结构中,向导和可视化面板为主
配置方式半界面半脚本纯XML工程文件GUI为主,底子是XML工程
MSIX/AppX支持支持但配置偏重需要额外扩展内置打包和转换向导
命令行自动化可用,但脚本繁琐天然适合编译链提供AdvancedInstaller.com
Git协作工程文件结构复杂,合并困难文本XML,适合Git.aip是XML,可合并但有冲突点
典型团队大型企业安装包团队有MSI底层功底的研发中小研发团队、独立交付

这个结论不是凭空对比,是实际项目里踩过的。InstallShield的问题是“会的人很贵,不会的人学不动”,做一个包往往卡在少数人身上。WiX的问题是编译和链接阶段太慢,改一个安装路径要在XML里翻三处,新人容易改错节点。Advanced Installer落在中间:日常配置在界面里完成,出了问题又能直接看底层MSI日志,不会被工具缚住手脚。

2.2 安装与评估模式:先确认授权再开始建项目

安装过程本身不复杂,管理员账号运行安装程序即可。有两个实际问题值得注意:杀毒软件可能拦截安装过程中注册的动态库,放行一次就好;安装完成后建议先打开一次软件,让首次运行初始化完成,再开始建项目,否则首次创建项目时会卡在初始化过程。

首次启动一般会进入评估模式。评估模式能正常建项目、构建安装包,但生成的包会带试用提示,不能用于正式交付。团队里常有“用评估版做了两周,最后要发版才发现限制”的情况,这是最容易避免的翻车点。确认手上有没有正式授权,比调任何参数都优先。

新建项目时模板选择也影响后续路线。默认向导里我会选“MSI项目”,而不是“简单项目”。MSI项目自带卸载入口、修复机制、按机器或按用户安装的完整能力;简单项目更像一个壳,适合演示,不适合做产品级交付。后续所有配置都围绕“生成标准MSI”这个目标展开。

2.3 项目属性先锁三样:Product Name、Version、Upgrade Code

打开项目属性(Project Properties)面板,第一步不是拖文件,是先把三个字段定下来。

Product Name是显示在Windows“程序和功能”里的名字,客户和IT管理员看到的就是它。不要把内部代号填进去,我之前就把一个项目代号发出去了,客户那边的资产清单对不上,来来回回查了一下午。Version是版本号,这里有个容易错的地方:MSI版本只识别前三位,填1.0.0.1时,系统里显示的是1.0.0,第四位被忽略。版本比较、升级判断都只以前三位为准。

Upgrade Code是升级链路的“身份证”,在整个产品生命周期里都不要改。项目创建时工具会自动生成一个GUID,我习惯把这个GUID复制到Git仓库里的一个固定文件中,比如docs/product-identity.md,每次改版本都对照一下。如果有人不小心点了“重新生成GUID”,所有老客户都会变成新装,升级链路彻底断掉,这是比改错版本号更隐蔽的坑。

还要顺手定一下Install Scope:企业分发选per machine,配合管理员权限安装;个人小工具可以选per user。这个选项影响注册表写入位置、服务运行身份和卸载权限,后期再改会牵连一串路径,所以开头就要定死。

2.4 先构建一次空项目,把环境问题排除在配置问题之前

正式填充文件之前,我通常先构建一次空项目:只配置Product Name和Version,不加任何文件。目的是验证授权状态、构建工具链、输出目录三项是否正常。

构建按钮在Builds面板,输出目录设置成一个稳定路径,例如D:\build\out。第一次构建看到生成的app.msi和app.exe,就说明环境没问题。之后再出问题都是配置层面的,排查范围小很多。

这里能看到一个常见细节:同一配置可以同时产出MSI和EXE。EXE本质是一个引导程序,把MSI包在壳里,用户双击EXE时引导器自动触发MSI安装流程。企业场景里,大家更愿意给客户EXE,但内部批量部署时用MSI加静默参数,所以两种产物一起产出最省事。

3. 把应用装进安装包:文件、注册表、服务与快捷方式的落地配置

这一章是安装包的核心内容,95%的交付问题都出在这里的配置细节上。

3.1 Files and Folders:同步源目录,别手动拖文件

见过有同事把几十个文件一个个拖进安装目录树,下个版本加了一个DLL,漏拖,上线当晚客户报错。后来换成“同步文件夹”的方式,再也没有出现过“漏文件”这种低级问题。

在Files and Folders面板里,把整个发布目录映射为安装目录下的一个文件夹。比如应用程序发布在bin\Release下,就在安装树里建一个AppFolder,把bin\Release整个映射过去。之后每次构建自动同步源目录,新加的文件自动进包,删掉的文件自动从包中去除。维护的是源目录本身,不是安装内容的清单,人工遗漏的概率大幅下降。

目标文件夹字段要用默认的AppFolder属性,不要写死C:\Program Files\MyApp。用户安装时可能会选其他路径,写死路径会让“选择安装目录”这一步失效,在64位系统上还可能出现路径重定向,文件去了意想不到的位置。文件安装这部分的原则很简单:用属性引用目录,别用绝对路径。

3.2 注册表写入:时机、位数和Wow6432Node的坑

注册表在Registry面板配置。常见写入位置有两个:HKCU\Software\产品名,存放用户级配置;HKLM\Software\产品名,存放机器级配置。两者要对应Install Scope的选择,per user安装写HKCU,per machine安装写HKLM。

最隐蔽的问题是32位和64位视图。64位系统上,32位程序写HKLM\Software会被系统重定向到HKLM\Software\Wow6432Node。如果安装包同时带32位和64位组件,注册表值要按真实位数分开配置,否则程序运行起来读不到自己的配置。判断方法很简单:在目标机器上用regedit查一下实际写入位置,再对照程序读取的逻辑。这一条属于典型的“装完不报错,但程序行为不对劲”的疑难问题。

写入时机也要注意。有一类注册表项必须在卸载时移除,比如文件关联;另一类应该保留,比如用户偏好设置。MSI的注册表配置默认跟着组件管理走,安装写入、卸载删除。想让某个键值跨版本保留,不能写在注册表面板里,要在应用首次启动时自己创建,否则升级卸载旧版时就被清掉了。

3.3 服务、快捷方式与启动条件:三个容易忽略的字段

服务配置在Services面板。添加服务后需要填写服务名、可执行文件路径、启动类型。服务路径必须用[AppFolder]这样的属性引用,不要手打C:\Program Files\MyApp\MyApp.exe,原因和文件目录一样:用户改了安装路径,服务就指向了一个不存在的位置。

服务设为自动启动时,MSI会在安装结束时尝试启动它。如果启动失败,安装不会自动回滚,但会留下一个禁用状态的服务,用户看到安装成功,服务却是死的。验证方法:安装完成后马上执行net start 服务名,确保返回正在启动。

快捷方式面板里最容易漏的是“工作目录”字段。不填的话,用户双击快捷方式时工作目录是system32,程序里所有相对路径都会读错文件。至少要填[AppFolder],这样双击时工作目录和程序目录一致。

启动条件也是必配项,最常见的是检查.NET运行时版本。这里的判断逻辑容易变成玄学:检测到版本号满足条件,程序启动还是报缺运行库。根源往往是检测的是“运行库安装值”,而不是实际生效的运行时版本。我的建议是条件写保守一点,检测主版本,不要锁次版本,避免一个补丁版本差异就挡住整批客户。

3.4 运行库依赖:Prerequisites比往目录塞DLL更可靠

.NET和VC++运行库是Windows桌面应用最常见的依赖。Advanced Installer提供Prerequisites机制,把对应Redistributable加进安装包,安装时先装依赖,再装主程序。

不要用“文件复制”的方式把整个.NET运行时目录塞进安装目录。这样做表面能跑,但系统更新和Windows Update不会主动修复这些目录里的运行时文件,版本会一直卡在打包那一刻,安全补丁也打不进去。正确做法是让Redistributable进入系统常规的安装位置,由系统自身管理版本。

处理运行库依赖有个判断标准:在干净的虚拟机里只装系统补丁,不装开发环境,然后安装你的包。能跑,说明依赖齐全;不能跑,说明缺依赖或版本策略太激进。很多“我机器上正常,客户机器上报错”的问题,都是在这个环节漏了。

4. 升级与补丁方案:从1.0到2.0不丢数据也不丢配置

安装包的第一版往往容易,升级链路才是真正拉开差距的地方。

4.1 Upgrade Code和ProductCode:升级的“身份证”到底指谁

MSI的升级判断基于三样东西:Upgrade Code、版本号、语言。Upgrade Code标识产品家族,ProductCode标识具体版本。同一个产品的不同版本,Upgrade Code必须一致,ProductCode必须不同。

升级时要避免两类错:一是Upgrade Code变了,新包被当作独立产品,客户机器上出现两个同名应用;二是同一版本的包ProductCode不一致,导致批量部署时旧的“程序和功能”条目没被正确替换。正确做法是同一个版本的MSI固定用同一个ProductCode,版本递增时才生成新的。

在实际项目里,我见过最头疼的情况是“升级后用户配置全部丢失”。查下来往往是升级策略配置成了“先卸载旧版再装新版”,卸载时把MSI组件跟踪的数据文件删掉了。要区分:用户数据如果是应用运行时创建的,不受MSI卸载影响;如果一开始就放在Files and Folders里,就会被卸载一起清掉。所以用户数据目录不要通过安装包分发,应用首次启动时自动生成,是最稳的。

4.2 三种构建产物:MSI、EXE、MST各在什么场景用

一个安装配置可以产出多种格式。

MSI是标准安装包,支持msiexec静默参数、组策略分发、批量部署工具,是企业环境的根基。EXE是引导包,适合给不懂技术的最终用户双击安装。MST是转换文件,配合msiexec /t在分发时覆盖属性,同一份MSI可以适配不同部门的不同安装路径或配置。

日常交付产线通常同时出MSI和EXE:给客户的下载链接放EXE,给IT管理员的目录放MSI。MST只有在“一个安装包要按部门差异化部署”时才用得上,普通项目可以先不碰。

构建面板里还能产出补丁包,它记录两个版本之间的增量差异,体积小,走网络分发快。但补丁包只能从固定基线版本上升级,如果客户机器上有多个版本散落,补丁链会变得非常难维护。面向内部系统时,我倾向于用完整MSI做版本升级,除非网络带宽真的限制到KB级,否则不建议依赖补丁来做主力升级通道。

4.3 多版本管理:尽量用完整MSI升级,补丁包只留少数基线

升级方案在设计阶段就要想清楚“从哪些版本能升上来”。常见策略是允许从上一个发布版本升级,客户跨度太大时,强制先装最新基线再升级,避免一条升级链覆盖太多版本组合。

版本号策略我建议用三段式:主版本.次版本.修订号。主版本和次版本决定功能边界,修订号用于修复。MSI对第四段忽略,所以不用费心维护四位版本号。每次发版时记录Version、ProductCode、Upgrade Code三个值到发布清单,下次构建前对照,确保没有意外变化。

4.4 MSIX边界:新分发可以迁,传统服务型软件别硬搬

20.7.1这一版对MSIX的支持已经比较成熟。MSIX适合新软件开发,例如.NET Core/WinUI类应用,沙箱运行、自动更新、应用商店分发都很顺手。

但MSIX不适合以下场景:程序需要安装Windows服务常驻后台;需要写系统级驱动;需要和外部老DLL深度耦合。这些能力在MSIX容器里会被限制,硬迁移会产生大量运行时不兼容问题。传统WinForms加服务的软件,留在MSI体系里更可靠,至少升级、卸载、服务管理都是成熟路径。

选型上有个简单的判断标准:应用是否能接受“以用户身份运行在容器内”?能接受,MSIX值得投入;必须用系统服务或驱动,继续用MSI,别折腾。

5. 构建、验证与避坑记录:五条踩坑现象和它们的真实原因

安装包做得成不成功,不是看构建是否通过,而是看安装、卸载、升级三个动作在干净机器上是否都正确。

5.1 verbose日志:msiexec /l*v 打开后看哪几行

遇到安装失败,第一步不是猜,是拿详细日志:

msiexec /i "D:\build\out\MyApp-1.0.0.msi" /qb /l*v "C:\logs\myapp_install.log"

参数含义:/i指定安装包,/qb显示基础进度界面,/l*v输出详细日志。日志生成后,搜Return value 3或Installation failed,找第一个出现错误码的位置,那通常就是失败根因。不要拉到最后看总结,MSI日志的失败原因一定藏在路径操作或属性赋值失败的上下文里。

下面几个MSI返回值值得记在脑子里:

返回值含义常见原因
0安装成功无
1603致命错误访问权限、系统策略拦截
1619安装包无法打开路径无效或文件损坏
3010成功但需重启文件被占用或系统组件更新

3010是唯一一种“成功但需要重启”的情况,批量部署时要特殊处理,否则后续步骤会踩在未重启的系统上。

5.2 静默安装与卸载:企业分发前必须跑通的命令

企业环境里,安装包必须支持静默安装:

msiexec /i "D:\build\out\MyApp-1.0.0.msi" /qn /l*v "C:\logs\silent_install.log" INSTALLDIR="C:\Program Files\MyApp"

/nq表示不显示用户界面,全程静默。INSTALLDIR可以覆盖安装路径,但要看项目属性里该属性是否允许公共修改,有些项目把它设成私有,命令行传了也不生效。

静默安装最怕UAC弹窗。如果命令从一个普通用户Shell发起,UAC弹窗会挂在那边没人点,安装进程一直等待。验证方式是:从非管理员Shell执行一遍这条命令,确认它不会停在某个隐藏窗口上。批量分发工具一般以系统身份运行,问题不大,但手动测试时要注意这一点。

卸载测试同样要跑:

msiexec /x "{PRODUCT-GUID}" /qn /l*v "C:\logs\uninstall.log"

PRODUCT-GUID可以在项目构建信息里查到。卸载完成后要检查三处:安装目录是否清空、“程序和功能”是否移除条目、服务是否停止。这三处有一处残留,都说明卸载逻辑有问题,不能发版。

5.3 五条踩坑记录:现象、原因、解决

第一条:64位机器上安装后,程序跑到SysWOW64目录找文件。现象是程序能启动,但实际读取的目录不是预期路径。原因是MSI目标平台没选x64,构建时还停留在x86。解决方法是到构建面板把目标平台设为x64或x86分别对应,不要用Neutral,然后重新构建并在64位机器上复测。

第二条:升级后“程序和功能”里出现两个同名应用。现象是最新版本装完,旧版本还列在列表里。原因是新包的Upgrade Code与旧包不一致,MSI判定为两个独立产品。解决方法是把旧版本的Upgrade Code找出来,填进新项目的对应字段,升级测试要在真机完整跑一遍“1.0升到2.0”。

第三条:卸载后用户配置被清空。现象是卸载干净了,但用户下次装回来时配置全没了。原因是用户数据目录被误认为MSI组件内容,安装时由包创建,卸载时被包回收。解决方法是安装包只分发程序文件和默认模板,用户数据由应用首次启动时生成到AppData或ProgramData,不纳入MSI跟踪范围。

第四条:杀毒软件把安装包隔离。现象是构建出来的EXE在部分机器上被拦截。原因是引导型EXE的行为特征容易和捆绑器混淆,尤其在没有数字签名时。解决方法是正式发布使用有代码签名的MSI和EXE,发版前用杀毒引擎扫一遍,必要时提交厂商做白名单。

第五条:批量静默安装返回码0,但程序实际没有装上。现象是部署平台显示成功,目标机器上没有可执行文件。原因是检查脚本只看退出码,而MSI在某种情况下退出码为0但安装被系统策略跳过。解决方法是安装后检查关键文件或注册表值,不要只信退出码;同时在日志里搜Could not access或ShellExecuteEx,定位被跳过的那一步。

6. 自动化打包与冒烟验证:把Advanced Installer接进CI的最后一公里

6.1 命令行构建:AdvancedInstaller.com接入CI

Advanced Installer安装目录下自带AdvancedInstaller.com,这是命令行构建入口。CI里我习惯这样调用:

$advinst = Get-ChildItem "C:\Program Files*\Caphyon\Advanced Installer\bin\x64\AdvancedInstaller.com" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $advinst) { throw "AdvancedInstaller.com not found" } & $advinst.FullName /build ".\installer\MyApp.aip" -builds "MSI" -outdir ".\output\release" if ($LASTEXITCODE -ne 0) { throw "build failed with code $LASTEXITCODE" }

这段脚本用Get-ChildItem自动定位不同版本号路径下的命令行工具,避免把安装路径写死。单个CI节点上不会装多个版本,所以取第一个找到的就够。日志文件路径加入CI系统的归档目录,构建失败时能直接拉出MSI日志定位。

版本号不建议在CI里用脚本改.aip的XML节点,这个XML结构会随版本微调,正则替换早晚踩坑。更稳的做法是在GUI里为CI单独建一个构建配置,版本号固定在该配置中,发版时手动改配置属性,CI只负责按配置名构建。

6.2 三项冒烟:装得上、升得动、卸得净

构建产物出来,最后一道关是冒烟测试。我在干净虚拟机或隔离环境跑下面这个脚本:

$version = git describe --tags --abbrev=0 $version = $version.TrimStart('v') $msi = ".\output\release\MyApp-$version.msi" Start-Process msiexec -ArgumentList "/i `"$msi`" /qn /l*v `".\output\smoke.log`"" -Wait if (-not (Test-Path "C:\Program Files\MyApp\MyApp.exe")) { throw "install smoke failed" } Start-Process msiexec -ArgumentList "/x `"$msi`" /qn /l*v `".\output\uninstall.log`"" -Wait if (Test-Path "C:\Program Files\MyApp\MyApp.exe") { throw "uninstall smoke failed" } Write-Host "SMOKE PASSED $version"

用Start-Process -Wait保证msiexec完全结束后再检查文件系统,不要立刻轮询。卸载验证不要用Win32_Product查询,它会触发系统一致性检查,拖慢执行。文件存在性检查比写一堆状态判断可靠得多。

我现在的固定习惯是:每次Release合并前,拉一台干净Windows虚拟机跑一遍这个冒烟脚本。版本对不上或卸载残留会当场红掉,这十几分钟能拦住大多数“到客户那边才发现装不上”的尴尬。希望帮到你。

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

返回列表