首页 / 资讯中心 / 文章详情

Visual Studio Installer Projects:Windows桌面应用安装包制作实战

Visual Studio Installer Projects:Windows桌面应用安装包制作实战 ★ FEATURED ARTICLE
刚开始接手团队某个模拟项目X的交付时客户提了一个看起来很简单的要求给一个 Windows 桌面程序做安装包用户拿到手双击就能装卸载要干净后续还要方便升级。当时我先试了 WiX光是哪个文件进哪个目录的 XML 就写到我怀疑人生又看了几款商业安装工具功能确实强大但为了一个内部小工具去买授权感觉实在没必要。绕了一圈最后是 Visual Studio Installer Projects 帮我把问题全部解决而且全程没离开过 Visual Studio 界面。简单说Visual Studio Installer Projects 把 Windows Installer 的常见能力封装成了一套可视化模型文件、快捷方式、注册表、启动条件、先决条件都可以通过项目视图和属性窗口配置不需要写 XML也不需要学专门的脚本语法。这篇文章我按自己实际操作的流程完整走一遍扩展怎么装、Setup 项目怎么建、文件与快捷方式怎么加、属性和版本升级怎么配、最后构建和排查怎么做尽量把官方文档里含糊的地方讲清楚让你照着做也能快速产出靠谱的安装包。1. 扩展安装从 VS 2017 之后的默认缺失说起1.1 为什么 Visual Studio 不再自带安装项目如果你是从 VS 2015 时代过来的开发应该记得当时新建项目里有一个传统的安装项目Setup and Deployment可以直接生成 MSI。到 VS 2017官方把它从默认组件里移除了原因是维护成本越来越高、生态上也更希望开发团队去用 WiX 这类方案于是改成独立扩展需要自己安装。这个变化坑过不少老项目团队升级到 VS 2017 或者更高版本后突然在新建项目里找不到安装项目模板第一反应往往觉得是功能被砍了其实只是没装扩展。当前我在 VS 2022 上用的就是扩展市场里的 Visual Studio Installer Projects。装好之后它会提供四类模板Setup Project标准安装项目、Setup Wizard安装向导、Merge Module合并模块和 CAB ProjectCab 打包。日常桌面应用部署只需要关心 Setup Project 就够另外三个用得很少。1.2 分步安装过程和离线包方案安装过程不算复杂但有些环节会在不熟悉的机器上卡住。操作路径是打开 Visual Studio菜单栏选择扩展 - 管理扩展左侧切换到联机右上角搜索框输入 Installer Projects。搜索结果里通常第一个就是官方发布的 Visual Studio Installer Projects注意看清楚名称和发布者不要误装第三方同名扩展。确认之后点击下载。下载完成后 Visual Studio 一般会提示关闭窗口进行安装。这时先保存好正在编辑的代码关掉所有 VS 窗口紧接着 Visual Studio Installer 会弹出来让你确认修改。这一步千万别急着取消扩展真正写入 VS 组件就是在这个阶段完成的。整个安装过程大约一两分钟完成后再重新打开 Visual Studio模板就出现了。如果你所在的内网开发环境没法直接访问在线扩展市场也有离线方案在能联网的机器上从官方扩展市场页面把 .vsix 文件下载下来拷贝到目标机器后双击VSIX Installer 就会自动运行。批量给多台开发机安装时还可以用命令行方式例如VSIXInstaller.exe 路径\xxx.vsix /quiet注意 VSIXInstaller 的完整路径一般在 Visual Studio 安装目录的Common7\IDE下并且需要管理员权限。1.3 安装完怎么验证装完扩展后最直接的验证方式是新建项目。在 Visual Studio 的创建新项目界面搜索 Setup如果能看见安装程序项目Setup Project和安装向导Setup Wizard两个模板就说明扩展已经生效。与此同时项目模板列表里也会出现合并模块项目和CAB 项目但普通场景用不到。如果扩展 - 管理扩展 - 已安装里能看到 Visual Studio Installer Projects但新建项目时搜不到模板多半是模板缓存没刷新。把 Visual Studio 完全关闭再重开一次基本能解决还不行就去工具-获取工具和功能里对安装做一次修复通常都能救回来。2. 创建 Setup Project把安装内容可视化梳理2.1 一个完整的创建流程假设你已经有一个 WinForms 或 WPF 主项目编译后能正常运行。现在要做安装包最稳妥的操作是在当前解决方案里直接添加安装项目。右键解决方案 - 添加 - 新建项目在项目类型里找到其他项目类型下的Visual Studio Installer选择安装程序项目。给项目起名时我习惯用XxxSetup这种格式方便和主项目区分。放在同一个解决方案里有个很大好处构建时 Visual Studio 会自动处理项目依赖顺序主项目先编译安装项目接着打包。以后改完主项目代码再点一次构建安装包也跟着更新不会出现改了代码但忘了重新打包的尴尬。创建之后默认打开的是一个叫文件系统File System Editor的编辑器界面长得有点像资源管理器左侧有三个固定文件夹Application Folder、Users Desktop、Users Programs Menu。这个设计第一次用的人容易误会它显示的是安装完成之后目标计算机上的文件布局不是你当前项目里的源文件目录结构。很多人第一步就栽在这把源文件拖错了位置最后装出来的目录结构完全不是预期。2.2 三个标准文件夹各自的定位Application Folder对应安装目录默认情况下 MSI 会根据你填写的 Manufacturer 和 ProductName 生成一个类似[ProgramFilesFolder]\[制造商]\[产品名]的路径。所有需要出现在安装目录里的文件、DLL、配置文件都应该放在这里。Users Desktop桌面目录一般只放快捷方式。Users Programs Menu开始菜单程序目录同样一般只放快捷方式。这里要提醒一句这三个文件夹的具体物理位置并不是固定的。当 InstallAllUsers 属性为 true 时Application Folder 会解析到 Program Files 或 Program Files (x86)桌面和开始菜单对应的也是公共用户的目录InstallAllUsers 为 false 时则全部落在当前用户自己的 AppData 和用户专属目录。搞清楚这个因果关系你才能理解为什么同一个安装包在不同机器上装出来的路径不一样。2.3 添加主输出与附属文件的正确姿势右键 Application Folder - 添加 - 项目输出会弹出一个对话框。左边选择要被打包的项目如果你在一个解决方案里这里会列出所有可用项目。右边有一个项目输出下拉列表常见选项是主输出Primary Output项目生成的主程序集比如Xxx.exe。内容文件Content Files项目中标记为内容的文件常见的有配置 json、xml 等。文档文件Documentation FilesXML 文档注释文件。调试符号Debug SymbolsPDB 文件一般不用打包。源文件Source Files源代码发布时通常不需要。多数情况下勾选主输出就够了。Visual Studio 在构建时会对主输出做依赖检测主项目引用的程序集只要 Copy Local 为 true基本会被自动识别并加入安装包。但这个规律有几个例外运行时通过反射加载的 DLL、依赖项在运行时才从特定路径加载的 DLL、COM 组件检测机制都可能漏掉。手动补文件的方法是右键 Application Folder - 添加 - 文件找到那些不由主项目输出管理的 DLL、配置文件、帮助文档选中后它们会被复制到 Application Folder。这里特别容易犯一个重复添加的错误同一份文件既被主输出自动带进来又被手动加了一遍构建时会直接报重名冲突。遇到这种问题留一条通道就行删掉多余那条。2.4 快捷方式与桌面图标的一次搞定快捷方式这一步卡过非常多新手。正确做法是先选中 Application Folder 里的主输出文件比如Xxx.exe右键 - 创建快捷方式。此时会在当前文件夹生成一个快捷方式名字像Xxx.exe 的主输出。接下来把这个快捷方式拖到左侧的 Users Desktop 或 Users Programs Menu 中。如果桌面和开始菜单都要就复制一份过去别想着从一个节点拖到两个位置。拖过去之后名字通常很长可以直接按 F2 改也可以选中快捷方式后在属性窗口里找到 Name 属性填上用户能看懂的好名字比如数据管理客户端。快捷方式图标默认会使用目标 EXE 自身携带的图标如果你的主项目没在项目属性里设置过 Application Icon装完后快捷方式就是个通用白图标。这时候不是打包失败是图标没设置回去把主项目的图标选上再重新构建就好。我自己实际发安装包时开始菜单快捷方式一定会保留一个桌面快捷方式看需求。客户明确说装完要在桌面生成图标我才加桌面快捷键没说就尽量克制这样卸载后清理更干净桌面也不会残留一堆死链。3. 属性配置与版本升级的底层逻辑3.1 这批属性直接影响用户安装体验选中安装项目根节点后属性窗口里会列出大量配置。不是每个都要动但下面这些务必认真看属性名作用说明我的设置建议ProductName控制面板程序和功能中显示的名称也参与默认安装路径用公司简称产品名尽量用英文组合Manufacturer制造商名影响默认安装路径的子文件夹用英文或拼音规避中文乱码和路径兼容问题Version安装包版本号必须大于已安装的旧版本每次发版递增格式如 1.0.1ProductCodeMSI 的产品标识对应控制面板中的卸载条目升级时让 VS 自动生成新的UpgradeCode同系列产品的稳定标识用于识别可升级产品生成后建议不要再改RemovePreviousVersions是否在安装新版本时自动卸载旧版本一般设为 trueInstallAllUsers是否面向系统所有用户安装默认 true 就好除非你确实需要免管理员安装TargetPlatform目标位元平台x86 / x64与主项目目标平台保持一致AddRemoveProgramsIcon控制面板里显示的图标可选设置了更专业ProductName 不建议用又长又多级的中文名称。比如某企业综合管理平台标准版这种会让安装路径变得非常冗长某些 Windows 组件解析路径时可能出问题。我一般用英文缩写做安装目录名再把中文全称写到 Description 或帮助链接字段里这样既能看懂又不影响路径稳定性。3.2 版本升级UpgradeCode 定生死升级这回事本质是 Windows Installer 的一套规则。系统通过 UpgradeCode 判断两个产品是否属于同一个系列通过版本号判断新旧通过 ProductCode 区分具体某个安装实例。所以升级时只需要做三件事第一保持 UpgradeCode 不变第二把 Version 改成比已装版本更大的值第三把 RemovePreviousVersions 设为 true。构建出新的安装包后用户在旧版机器上运行新安装程序MSI 会先自动卸载旧产品再安装新产品控制面板里始终只有一个条目。反过来如果你不小心改了 UpgradeCode等于告诉系统这是一个全新产品旧版本和新版本之间就没有升级关系了轻则旧版残留重则因为文件冲突直接报错。我也见过有人图省事一直不递增版本号只在原来的 MSI 上反复重建结果安装到一半提示A later version is already installed这类现象十有八九是版本号和升级逻辑没配对。有个细节要留意当你在 Visual Studio 中修改 Version 时它通常会提示是否同时更新 ProductCode。我的经验是选是因为 MSI 认为每次发布的产品实例发生了变化需要新的 ProductCode 来做卸载注册而真正负责跨版本升级关系的 UpgradeCode 不受影响。要是手动把 ProductCode 固定不变在某些组合下控制面板的卸载条目反而不会被正确替换。3.3 x86 / x64 / AnyCPU选不对会装错目录TargetPlatform 是安装项目自身的目标平台不直接决定运行时位元但决定 MSI 在 64 位系统上访问的是哪一套系统视角。如果主项目是 x86或者主项目是 AnyCPU 但编译出来实际按 32 位执行安装项目就选 x86这样文件会装到 Program Files (x86) 而不是 Program Files。反过来主项目明确是 x64安装项目必须选 x64否则注册表视图和系统目录视图会错位程序可能因为找不到运行库崩溃。这里最容易踩的坑是 COM 组件注册。假设主程序引用了一个含 COM 组件的原生 DLL安装项目负责把 DLL 放到安装目录并写入注册表项。如果 TargetPlatform 和 DLL 的位元不匹配注册表可能写到了错误视图目标程序运行时通过 COM 找不到组件直接崩在某些调用上。所以确定主项目位元这件事一定在创建安装项目之前就想清楚别等客户机器上报故障再回头排查。3.4 安装界面与启动条件的有限定制VS Installer Projects 的安装界面自定义能力很有限能改的主要是安装欢迎页、文件夹选择页和确认页的文本。右键安装项目 - 视图 - 用户界面能看到两个安装类型Install 和 Administrative Install每种下面有一串默认对话框。选中某个对话框就可以在属性窗口里修改显示的文本比如把欢迎页标题改成欢迎使用数据管理客户端。真正实用的是启动条件编辑器入口在视图 - 启动条件。你可以在这里配置目标机器必须满足的条件比如检测是否安装指定版本的 .NET Framework不满足时就弹出自定义错误消息或跳转到下载地址。不过对普通应用来说更常用的是另一种方式右键安装项目 - 属性 - 点必备组件按钮勾上目标 .NET 版本把下载来源设置成从与我的应用程序相同的位置下载。这样构建时VS 会把 .NET Framework 安装器一起放进输出目录setup.exe 会自动先检查环境再拉起安装。需要特别提醒的是只把 MSI 发给客户不会触发先决条件检测这也是我一直建议分发时用 setup.exe 而不是单独发 MSI 的原因。4. 构建输出、静默安装与常见问题排查4.1 构建流程从主项目到安装包产物构建安装项目之前先在解决方案工具栏把配置从 Debug 切到 Release。右键安装项目选生成或者在生成菜单里选重新生成解决方案。构建过程中Visual Studio 会先编译被引用的主项目然后执行依赖检测再把文件写入 MSI 数据库。如果你在输出窗口看到一长串 Detected dependencies 列表那是它在后台做依赖收集不是报错。构建完成后在安装项目的 Debug 或 Release 文件夹里能看到几个文件setup.exe、Xxx.msi有时还有setup.ini和若干先决条件安装包。setup.exe是引导程序负责检查必备组件并拉起 MSIXxx.msi是核心安装数据库。分发时两个文件如果都用了记得放在同一目录下否则引导程序会提示找不到 MSI。如果你确定目标机器已装好 .NET Framework也可以只分发 MSI但对普通用户我还是建议发 setup.exe省掉一堆环境问题。如果要在 CI 里自动化打包也可以用 MSBuild 命令行构建包含安装项目的解决方案注意显式指定 Release 配置。到了正式发布规模我会额外用系统工具对 setup.exe 做签名避免 Windows 的未知发布者提示吓到客户。VS 安装项目本身没有内置签名步骤需要写一个构建后事件或单独流水线步骤来完成。4.2 用命令行和日志做质量验证给客户之前先自己在干净环境验证一遍安装质量。最有效的手段是命令行加日志# 安装并记录详细日志 msiexec /i setup.msi /l*v install.log # 静默安装不显示界面 msiexec /i setup.msi /qn # 卸载 msiexec /x setup.msi/l*v生成的 install.log 会记录每个属性、每个目录解析结果和每个文件操作。如果安装时路径解析出错日志里会直接给出解析后的绝对路径比到处猜快得多。静默安装的进程返回码也很关键0 表示成功1603 表示安装过程中失败3010 表示安装成功但要重启系统。写自动化脚本时不检查返回值基本等于没做验证。卸载同样可以加日志比如msiexec /x setup.msi /l*v uninstall.log。我一般把安装日志和卸载日志都留一份调试问题时能拼出完整链路。4.3 高频问题速查现象常见原因解决方向装到一半回滚日志里有 2896自定义操作执行失败多半是自定义操作指向的 EXE 返回非零不要滥用自定义操作非必要就删掉提示 The specified path does not exist快捷方式引用的目标文件不在应用文件夹中或目录名写错重建快捷方式确认目标文件已加入包安装时报文件找不到依赖检测未覆盖该 DLL手动加入文件或先把 DLL 拷到主项目输出目录控制面板里出现两个同款应用UpgradeCode 变了或上一版没有正确卸载保持 UpgradeCode 不变先手动卸载残留旧版同一版本重复安装被拒绝RemovePreviousVersions 未生效或版本号未递增递增版本号再确认 RemovePreviousVersionstrue排查时先看 install.log再决定改哪里。很多人一遇到安装失败就把整个项目删了重建其实大部分情况只是目标平台、版本号或依赖文件这三个点没对上。4.4 依赖检测的盲区怎么补救VS 安装项目的依赖检测本质上是读取编译输出目录里的文件清单。主项目引用的普通 .NET 程序集只要 Copy Local 为 true基本能被识别。但下面这些情况它看不到运行时通过反射加载的程序集。手动从外部目录拷到输出路径的非托管 DLL。App.config这类内容文件没有在主项目里标记为内容。程序运行目录下需要存在的静态资源比如数据库、图片、模板。补救思路很直接构建完成后打开主项目的bin\Release目录对照里面的文件逐一核对凡是安装包中没有的就用添加文件手动补进去。为了减少手工遗漏也可以在主项目里把需要带上却未被识别的文件属性设置为 Copy if Newer然后通过添加项目输出里的内容文件选项引入。要记住的是手动添加和主输出自动识别不要重复否则又会遇到重名冲突。5. 工具横向对比与部署选型建议5.1 VS Installer Projects 适合谁不适合谁不是所有安装需求都适合用这个扩展。适合的是传统 .NET Framework 桌面程序、轻量级内部工具、升级逻辑简单的企业应用。它覆盖了 90% 场景所需要的功能对团队协作友好一个刚接触的人花半小时就能上手。不适合的场景则包括需要定制复杂安装界面皮肤、需要在安装过程中执行数据库迁移脚本、需要给产品打补丁或做模块化安装、需要多语言界面切换等。这类需求建议直接看 WiX 或商业安装工具。WiX 是基于 XML 的编译器学习曲线陡但功能天花板高几乎能控制每一个安装行为商业工具在 GUI 和自动化上更省心但要考虑授权成本和团队培训成本。简单需求用它们确实容易变成大炮打蚊子。5.2 三条让我少踩很多坑的部署经验这几年的项目做下来有几条部署实践特别想说。第一维护一张版本记录表。每次发版记录 Version、ProductCode、UpgradeCode、发布日期、变更摘要哪怕只有三行信息都行。升级出问题时这张表能帮你迅速判断到底是版本号没递增还是 UpgradeCode 被意外改动过。很多环境冲突靠这张表就能定位不用抓头发猜。第二在干净的虚拟机或全新 Windows 环境里做一次完整的安装到卸载测试。重点看开始菜单快捷方式是否正常、桌面图标有无残留、卸载后安装目录是否清空、注册表是否还留着旧键值。有些问题在开发机上永远复现不出来因为开发机早就装过一堆依赖。第三谨慎使用自定义操作。见过有人在安装事件里挂数据库迁移脚本觉得自己很聪明结果客户机器一跑就失败。自定义操作在 MSI 权限上下文和用户会话里有各种限制调试成本远高于收益。能用默认行为完成的事情别为了看起来专业去折腾自定义操作。5.3 一个提升交付体验的构建后自动化思路如果你经常要打安装包每次手动点生成再手动拷贝文件确实很烦。可以在安装项目的属性 - 后期生成事件里写一条命令行把生成的 setup.exe 和 msi 复制到约定目录甚至顺手压缩成一个 zip。我在一个内部工具上就做了这么个小自动化每次构建完自动输出一份install-package.zip交付时只需要发这一份文件不用教别人去 Release 目录翻半天。后期生成事件里可以用if exist判断文件存在再复制避免 Debug 配置下没有产物时报错。这段逻辑不复杂但对交付体验的提升非常直观你哪怕是独立开发者也建议抽出十分钟配置一下。要说 Visual Studio Installer Projects 有多少短板我能列不少模板更新不算快对 .NET Core / .NET 5 新式项目的支持没有传统 .NET Framework 项目那么顺滑深度定制能力更是和 WiX 没法比。但如果你和我一样只是需要给团队内部工具或中小型应用做一个能安装、能卸载、能覆盖升级的安装包它确实是最快到达终点的一条路。我自己每次发新版本基本能在十分钟内完成构建加基础验证。真正上手时你只要记着三个原则版本号递增、UpgradeCode 不动、目标平台提前核对。把这几个点刻在脑子里安装包不管发多少轮都不容易翻车。
阅读完成 · 觉得有帮助?
咨询建站