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

VS2022打包WinForms程序为setup.exe安装包完整实战

VS2022打包WinForms程序为setup.exe安装包完整实战 ★ FEATURED ARTICLE
最近我把手头一个用 WinForms 写的小工具做了 Visual Studio 2022 打包安装最终产出一个标准的 setup.exe 安装程序。之前我一直懒得做安装包都是编译完直接把 exe 丢给对方直到有同事反馈“打不开”“缺环境”“没有快捷方式”我才发现开发机上跑得好好的程序换到别人机器上就是另一回事。这篇文章就是把这次完整的打包经历写出来包括怎么做安装项目、怎么加依赖、怎么生成可分发安装包以及我踩过的几个比较典型的坑。如果你也是用 VS2022 开发 Windows 桌面应用想把程序交付给不太懂电脑的普通用户那这篇应该能直接帮你少走半天弯路。1. 先说清楚一个前提你到底要“打包”成哪个 exe1.1 两个“exe”经常被搞混很多刚接触打包的朋友会问我的项目不是已经能生成 exe 了吗为什么还要“打包”这里有两层含义。第一层意思是把项目编译出来的那个 exe连同它依赖的一堆 dll一起合并成一个单文件 exe这是一般说的“发布”和“合并依赖”。第二层意思是做一个安装程序用户双击它之后会把程序安装到指定目录、生成桌面快捷方式、写入开始菜单最后出现在系统的“程序和功能”里。标题里说的“打包安装成 exe 程序”通常指的是第二层也就是用 Visual Studio 2022 制作安装项目最后产出一个带引导功能的 setup.exe。我这次的目标很明确做一个标准的安装程序用户拿到手双击就能安装安装完有桌面快捷方式能正常卸载而不是只给他一个裸的 exe。1.2 为什么不能直接把项目 exe 发给别人如果你只在开发机上运行程序早就跑习惯了根本看不见别人机器上的问题。换个新环境最常见的失败原因至少有这些目标机器没装对应版本的 .NET Framework双击 exe 直接报错程序依赖的外部 dll、配置文件比如 app.config、json没有被一起带过去用户把 exe 放到桌面开始使用后续改配置、升级、卸载都无从入手部分杀毒软件对单独发布的 exe 更敏感用户自己也不知道应该装在哪个目录容易乱放后面找不到程序。一个安装包能把这些痛点都收掉Bootstrapper 引导程序先检查环境缺什么就提示或自动补msi 帮用户把文件放到规范目录开始菜单和桌面快捷方式让入口清晰卸载入口由系统统一管理。做这一个包省下的是大量远程指导用户的时间。1.3 VS2022 为什么连安装项目模板都找不到如果你当前用的 VS2022 是默认安装的“NET 桌面开发”工作负载打开新建项目对话框搜索“Setup”大概率什么都搜不到。这不是你装错了 VS而是微软从 VS2019 开始就不再默认内置安装项目模板需要单独装扩展。官方提供的扩展叫 Microsoft Visual Studio Installer Projects在 VS2022 上能直接用功能和以前 InstallShield Limited Edition 那套逻辑差不多对于中轻量级的桌面工具打包完全够用。当然你也可以用 Inno Setup、NSIS 或者 WiX这些工具也很强而且免费。对比下来我做这次项目仍然选了 VS 自带的 Installer Projects理由是不用再学一个新的脚本语法所有操作都在 VS 界面里靠勾选完成和当前项目在同一个解决方案里添加项目输出非常直观生成的是标准 msi后期自动化、静默安装、卸载都符合 Windows 平台规范。如果你的需求是几十种组件、多层 UI 的复杂安装程序再考虑换专业工具也不迟。2. 环境准备阶段装扩展、建 Setup Project、认识四个编辑器2.1 安装扩展的完整步骤这一步没有任何难度但容易在“找不到扩展”上卡住。VS2022 中文版打开顶部的“扩展”菜单选择“管理扩展”。在新窗口左侧点“联机”右上角搜索框输入“Installer Projects”注意不要输中文。搜索结果里找一个名称为 Microsoft Visual Studio Installer Projects 的扩展右侧会显示“下载”按钮。点击下载之后VS 会提示你关闭当前窗口完成扩展安装。这时候保存一下代码把 VS 完全关闭会弹出一个简易安装画面等待安装完成即可。重新打开 VS在“扩展”菜单里能看到 Installer Projects 相关条目就说明装好了。有一点我提醒下如果你公司电脑上 VS 是精简安装或者没有管理员权限扩展安装可能失败这种情况建议先在本地验证权限再执行安装。2.2 新建 Setup Project 时需要注意的选项扩展装好之后新建项目窗口里搜索“Setup”会看到 Visual Studio Installer 分类下的 Setup Project。我当时的做法是不新建独立解决方案而是右键现有解决方案选择 添加 → 新建项目直接把它加到当前方案里。这样做的好处是之后添加项目输出时可以直接在主项目列表中选到现有项目不用手动切换路径。项目命名我建议用“某产品名Setup”这类比如“某工具Setup”最后生成的 msi 文件名就会带着这个名字方便后续辨别版本。建完之后解决方案里会多出一个安装项目节点它的默认视图比较干你暂时看不到代码看到的是一堆配置编辑器。我第一次用的时候还以为项目坏了其实这是安装项目的正常形态。2.3 安装项目的“四个编辑器”分别管什么右键安装项目节点菜单里会出现“视图”子菜单里面有几个关键项文件系统、注册表、文件类型、用户界面、启动条件。文件系统编辑器是最常用的主要决定把哪些文件装到哪个目录用户界面编辑器负责安装过程中的欢迎页、选择安装路径页、确认页这些界面文案启动条件编辑器可以用来检查系统版本、检查 .NET Framework 是否满足要求注册表编辑器可以写注册表项但我个人建议能用文件解决的配置就不要碰注册表避免卸载残留。一开始不用把每个编辑器都学透先把文件系统编辑器用明白再摸用户界面编辑器调文案其余前期知道位置就行。真遇到对应需求再针对性深入。3. 核心制作环节把程序和依赖文件正确地放进去3.1 添加主项目输出而不是盲目添加 exe 文件进入文件系统编辑器之后左侧是一个树状目录默认能看到三个节点应用程序文件夹、用户的桌面、用户的“开始”菜单。应用程序文件夹就是目标机器上的安装目录一般位于 C:\Program Files\厂商名\产品名 下面。在这个文件夹上右键选择“添加” → “项目输出”弹出的对话框里选择你的主项目。项目输出类型里选“主输出”这表示把主项目的 exe 和它直接引用的托管 dll 一起打进来。很多人习惯右键 → 添加 → 文件然后手工找 bin 目录里的 exe我强烈不建议这么干因为手工添加会让 VS 失去对依赖关系的自动解析能力后面漏一个 dll 你都发现不了。添加主输出之后文件系统编辑器里会出现一个“来自某项目的主输出”条目图标上通常带一个小零件符号代表它已经挂接了项目依赖解析。此时编译安装项目时VS 会自动扫描主输出的引用程序集把需要的托管 dll 全部带进 msi 里。这一点对没有专门学安装项目的人是非常友好的。3.2 别忘了 Content Files 和手动添加的运行时文件主输出能带引用 dll但带不了配置文件。很多人程序里有一个 App.config或者运行目录下有个 config.json这些文件在编译后不会自动进入主输出的引用链。解决办法是在同一个“添加项目输出”对话框里项目输出类型选“内容文件”。内容文件对应项目里的 Content 项和部分随目录复制的内容。比如我把一个 config.json 设置成了“如果较新则复制”再勾选内容文件输出后安装包里就会自动带上。如果你的项目里有一些子目录专门放图标、模板文件只要它们被标记为 Content这一项都能统一处理。有一个坑要单独拿出来说通过反射动态加载的 dll安装项目解析不到。比如你做一个插件式工具程序启动时扫描 Plugins 文件夹下的 dll 并用 Assembly.LoadFrom 加载那这些 dll 绝不会出现在主输出里。对这种文件我建议直接在应用程序文件夹下建立一个 Plugins 子目录然后右键 → 添加 → 文件手工把整个插件 dll 加进去。安装后程序就能在 Plugins 目录里正常发现这些文件了。3.3 设置安装目录的显示名称安装包装完之后目标机器上默认是“C:\Program Files\Manufacturer\ProductName”。Manufacturer 来自项目的 Manufacturer 属性ProductName 来自项目的 ProductName 属性。如果你不修改会直接用你的项目名做目录比如“C:\Program Files\某项目X”。这不致命但很影响印象。在安装项目属性里把 Manufacturer 改成一个通用的组织名或你自己的网名ProductName 改成用户能看到的中文产品名。这样装完之后目录结构更规范卸载列表里显示的也是这个 ProductName而不是一个难懂的项目编码。3.4 实测案例一个带配置文件的小工具如何管理文件我这次做的工具属于典型的“1个exe1个config.json若干资源图”的小项目。在文件系统编辑器里我的最终配置是这样的应用程序文件夹来自主项目的主输出 来自主项目的内容文件应用程序文件夹下的资源子目录手动添加的图片素材用户的桌面主输出快捷方式用户的“开始”菜单主输出快捷方式。构建之后在干净机器上测试安装路径下能看到 exe、config.json、资源目录快捷方式也能正常打开程序。整个过程没有手动改注册表也没有写任何复杂脚本基本就是靠这几个节点组合出来的。这也能说明VS 安装项目对常见的桌面工具确实够用。4. 快捷方式、升级和卸载让安装包行为“规范”4.1 桌面快捷方式和开始菜单目录在文件系统编辑器里选中之前添加的“来自某项目的主输出”右键选择“创建快捷方式”会在当前目录下生成一个快捷方式文件。把这个快捷方式改名比如改成中文产品名然后拖到左侧的“用户的桌面”节点和“用户的‘开始’菜单”节点下。这一步做完后安装时会自动生成桌面快捷方式也会在开始菜单里出现对应入口。开始菜单里默认是直接放在根目录下如果产品比较大或者要多个入口可以在开始菜单节点下先建子目录再把快捷方式拖进去。卸载的时候这些快捷方式会跟着 msi 一起清理掉不需要额外写清理脚本。如果想改快捷方式的图标看主输出属性里的“Icon”或者快捷方式属性设置成项目里的 .ico 文件即可。我建议每个 Windows 工具都配一个正式的 .ico否则快捷方式默认是通用白图标用户很容易在桌面上找不到。4.2 版本号、ProductName 和“覆盖安装”的关系很多人第一次做安装包时会遇到一个现象装过一次之后再次安装提示“该产品的另一个版本已安装”必须先卸载才能重装。如果你还有升级迭代需求这会非常难受。这个问题的核心是安装项目的 Version 属性和 RemovePreviousVersions 属性。在安装项目属性窗口里把 RemovePreviousVersions 设为 True每次发布新版本时Version 必须比上一版递增比如从 1.0.0 改成 1.0.1。重新构建之后新安装包就会覆盖旧版本不用先卸载再装了。这里有个细节安装项目的 Version 和你代码里 AssemblyInfo 的版本号不是自动同步的。如果你用自动化构建脚本发布最好把两者都改成同一个值免得用户在“程序和功能”里看到的版本和程序“关于”里显示的版本对不上。4.3 RemovePreviousVersions 与卸载清理的注意事项把 RemovePreviousVersions 设置成 True 之后在覆盖安装时旧版本的文件会被新的覆盖但是旧版本可能遗留的桌面快捷方式、开始菜单项、注册表项按经验是能正常清理的。不过如果旧版本是通过手工发布的老安装包装的某些自定义注册表项不一定能自动删干净。所以我的建议是安装包里尽量少写注册表如果有写也一定要在“注册表编辑器”视图里明确创建的项卸载时 VS 会自动清理你自己创建的内容。如果是程序运行过程中动态写进去的东西那属于应用数据安装包管不了需要程序自己处理。4.4 用户安装 vs 所有人安装的权限问题安装项目还有一个 InstallAllUsers 属性。设为 True 时安装包会要求管理员权限安装目录在 C:\Program Files 下所有用户都能使用设为 False 时默认装在当前用户目录下不需要管理员权限。我实测后的感受是小工具类程序如果目标用户是自己或同一个单位的同事装在用户目录下其实更省事因为不需要 UAC 弹窗。但如果你是想发布到网上给不同人用或者程序需要写 Program Files 下的文件建议 InstallAllUsers 设为 True 并配合管理员权限安装。这个选择会直接影响后续程序的写文件权限设计最好一开始就定下来。5. 系统必备 Prerequisites安装包能不能真正跑起来的关键一步5.1 setup.exe 引导程序与 msi 的关系生成安装包后你会在输出目录看到两个主要文件setup.exe 和 一个 .msi 文件。很多人不清楚这两个文件为什么要同时存在简单说msi 是真正的安装内容负责把文件复制到目标机器、写注册表、创建快捷方式setup.exe 是一个 bootstrapper引导程序它会先检查目标机器是否满足“系统必备”条件比如 .NET Framework 版本、VC 运行库等如果缺失会弹窗提示或者自动安装然后调起 msi 执行正式安装。所以你直接点 msi 也能装但前置依赖不会被自动检查和处理。分发时应该默认发 setup.exe让用户双击这个文件才能获得完整的安装流程。5.2 勾选 .NET Framework 和 VC Redistributable右键安装项目选“属性”属性窗口中会有一个“Prerequisites”按钮。点进去之后可以看到一长串可选条件比如 .NET Framework 4.8、Visual C Redistributable、SQL Server Express 等。这里有一个很容易踩的坑你以为项目里引用的 NuGet 包会自动把运行库带上但实际上安装项目只会带托管 dll不会带原生运行库。比如我这次项目里引到了一个依赖原生 C 组件的库目标机器没装 VC Redistributable 就直接跑不起来而 VS 安装项目并不会自动把 VC 运行库打进 msi必须在这个“系统必备”里手动勾选。勾选的原则很简单你的主项目目标框架是哪个版本就勾哪个版本你的代码是否依赖 Visual C 运行库如果用了就勾上。勾错或者漏勾轻则安装时多弹提示重则在干净机器上直接无法启动。5.3 x64 / x86 平台和你项目平台要对应安装项目属性和主项目属性都有“目标平台”这个概念。如果你的主项目是 AnyCPU生成的程序在 64 位系统上默认以 64 位进程运行安装项目那边如果选的是“x86”文件倒是能装上但安装目录会被重定向到 Program Files (x86)有些涉及本机路径的逻辑就可能出问题。比较稳妥的做法是主项目和安装项目统一选 x64或者统一选 x86不要形成错位。特别是程序里读取了相对路径、注册表 32/64 位视角切换时错位往往会产生非常隐蔽的 bug。我这次直接把两个项目都指到 x64干净机器上安装后行为最一致。5.4 下载位置选择联网下载还是本地带上在 Prerequisites 对话框底部有一个选项是“从与我的应用程序相同的位置下载系统必备组件”也可以选择从微软官网下载。如果你选择联网下载setup.exe 体积会小很多但用户安装时如果电脑没联网或者下载源访问不稳定安装就可能失败。如果选择本地带上构建之后输出目录会多出几个前置组件的安装包整个分发目录会变大不少但用户安装几乎不会因为网络问题卡住。我这次选择的是“相同位置下载”因为在单位内网环境很多机器网络策略严格联网下载经常超时。把前置组件一起分发虽然目录里的文件多了几个但至少安装过程可控。如果你要发给外部用户也可以给一个体积较小的“在线版”再专门备注需要联网具体看你场景。6. 构建产物、干净环境验证和常见坑复盘6.1 构建后到底会出哪些文件右键安装项目选“重新生成”。构建完成后到安装项目目录下的 bin\Release或者 bin\Debug里看看通常会有这些内容文件/项目作用分发时是否必须setup.exe引导程序负责检查与安装前置依赖必发某产品名.msi实际安装包内容由 setup.exe 调用前置组件安装包 若干.NET Framework、VC 运行库等视 Prerequisites 配置而定.pdb 调试符号等调试用信息不需要分发构建配置建议用 Release同时在解决方案配置下拉框里切到 Release 再生成。Debug 版不是不能用但体积、启动速度和客户端环境的表现都不如 Release 理想。如果你发现输出目录里除了 setup.exe 和 msi 之外没有前置组件包而你又勾选了“相同位置下载”大概率是没重新生成干净先清空解决方案再重建一次。有一个历史遗留问题我会提一下某些依赖包在 Build 时不会立即复制到 msi 目录在“项目输出”上的依赖解析是懒加载的Rebuild 才能保证最新。6.2 在“干净机器”上完整走一遍验证清单打包完成不等于交付完成。我通常会在一个没有安装开发环境的虚拟机里做完整验收流程如下双击 setup.exe确认引导程序能正常启动如果目标机器缺 .NET Framework确认安装程序会提示或者自动补装进入安装向导确认产品名、版本号、默认安装目录显示正确安装完成后检查桌面快捷方式和开始菜单入口是否生成点击快捷方式启动程序确认 exe 能找到依赖 dll 和配置文件在“程序和功能”里执行卸载确认快捷方式、安装目录、开始菜单项都被清理干净再重装一次验证覆盖安装不报“另一个版本已安装”。这七个步骤走完安装包才算是真正“能交付”。我在第一次打包时直接省了第 6 步结果用户卸载后残留了一个空目录后来每次卸载都要手动删体验非常差。现在这个清单是无论如何都不会省的。6.3 打包过程中我遇到的几个具体坑和解决办法第一个坑是“无法安装因为已安装此产品的另一版本”。当时我改了代码版本号但安装项目属性里的 Version 还是 1.0.0RemovePreviousVersions 也是 False导致重装必须手动先卸旧版。解决方案就是把 RemovePreviousVersions 设为 True并且每次发布都递增安装项目 Version。第二个坑是安装后找不到 config.json。因为我在添加项目输出时只勾了“主输出”没有勾“内容文件”配置文件名都没有进 msi。后来加上内容文件输出安装目录里才出现配置文件。第三个坑是插件 dll 没生效。程序启动后报找不到某个插件模块排查半天才发现主输出不会带上反射加载的 dll必须手工在应用程序文件夹下建子目录再添加文件。这个比较隐蔽特别适合插件架构的程序。第四个坑是杀毒软件提示。新构建的 setup.exe第一次在部分机器上会触发杀毒提醒这种提醒不是病毒但影响用户体验。如果分发范围较广可以考虑给 msi 和 setup.exe 做数字签名不过在个人内部项目上我觉得没必要强求只要安装程序本身干净可以接受第一次运行的提示。6.4 一个更省心的发布习惯Build 之后整目录打包从这次之后我养成了一个习惯每次构建完把 bin\Release 下的所有内容直接打成一个 zip文件名带上版本号和构建日期再把这个 zip 发给别人。而不是单独发一个 setup.exe。原因很简单如果前置组件选择的是“相同位置下载”单独发 setup.exe 会因缺少前置组件包而失败把整目录打包则不会遗漏用户解压后运行 setup.exe 就行流程也算傻瓜式。如果你希望用户下载后少一步解压操作可以考虑用 Inno Setup 再做一层外包装但那个已经超出“VS2022 原生打包”的范畴了。目前这个方案对我来说简单、稳定、可复现特别适合小团队和频繁发版场景。我实际做下来最大的感受是Visual Studio 2022 的安装项目本身不难真正耗时的是理解“主输出”“内容文件”“系统必备”“覆盖安装”这四个关键词之间的关系。把这四个东西搞明白了后面无论加新功能还是发新版本都是五分钟内的事。下次再遇到“某个机器装不上”的问题我也基本能一眼判断是缺依赖还是缺前置组件不会再像个无头苍蝇一样让用户反复重装。
阅读完成 · 觉得有帮助?
咨询建站