用 Inno Setup 把 .NET 程序打包成安装包:从零到发布的完整指南

📅 2026/8/9 7:35:59
用 Inno Setup 把 .NET 程序打包成安装包:从零到发布的完整指南
目录为什么选 Inno Setup准备工作第一步发布 .NET 程序第二步编写 Inno Setup 脚本定义常量[Setup] 节安装包的基本信息[Files] 节要打包的文件[Icons] 节快捷方式[Tasks] 节可选安装选项[Registry] 节注册表操作[Run] 节安装完成后执行的操作[UninstallRun] 节卸载时的清理第三步处理依赖——自动安装 .NET Runtime第四步编译安装包第五步测试安装包进阶检测 WebView2 等额外依赖常见踩坑记录完整脚本参考总结大家好我是码农刚子。作为一个 .NET 开发者你可能遇到过这样的尴尬程序写好了但要发给别人用的时候要么丢一个几百 MB 的文件夹让人家自己想办法运行要么写一份长长的部署文档教人装环境。有没有一种办法能像商业软件一样给用户一个双击就能安装、带桌面快捷方式、支持卸载的 .exe 安装包答案是 Inno Setup。这个免费的安装程序制作工具已经存在了二十多年久经考验而且对 .NET 程序的支持非常友好。今天我就用一个真实的 .NET 8 项目来手把手教你从零开始制作安装包。为什么选 Inno Setup市面上做安装包的工具不少InstallShield 动辄几千美元的授权费WiX 的学习曲线陡得让人怀疑人生NSIS 的脚本语法像汇编语言。相比之下 Inno Setup 有几个突出的优势完全免费且可商用脚本语法接近 Pascal 非常好懂内置 IDE 可以即时编译和预览生成的安装包体积小压缩率高支持 64 位、注册表操作、服务安装等高级功能。对于我们 .NET 开发者来说最重要的一点是它可以在安装时检测并自动安装 .NET Runtime 和其他依赖让用户真正做到一键安装。准备工作在开始之前你需要准备三样东西。第一安装 Inno Setup 6。去官网 https://jrsoftware.org/isdl.php 下载安装全程默认选项即可。安装完成后你会得到一个 IDEInno Setup Compiler和一个命令行编译器 ISCC.exe。第二确保你的 .NET 项目能够正常发布。我们后面会用到dotnet publish命令。第三准备好程序的图标文件.ico 格式这会让你的安装包看起来更专业。没有也没关系可以先跳过。第一步发布 .NET 程序在制作安装包之前首先需要把你的 .NET 程序发布为可独立运行的文件。打开终端进入你的项目根目录dotnet publish src/MyApp -c Release -r win-x64 --self-contained true这条命令做了三件事-c Release表示以发布模式编译优化过的代码-r win-x64指定目标平台为 64 位 Windows--self-contained true把 .NET 运行时一起打包进去这样目标机器上不需要单独安装 .NET。发布完成后产物默认在bin/Release/net8.0-windows/publish/目录下。这个目录里的所有文件就是要打包的内容。如果你想自定义输出路径可以加-o参数dotnet publish src/MyApp -c Release -r win-x64 --self-contained true -o ./dist第二步编写 Inno Setup 脚本在项目中创建一个setup目录新建一个setup.iss文件。Inno Setup 脚本由多个 Section节组成每个节用方括号标记负责安装包的一个方面。我们来逐节编写。定义常量脚本开头用#define定义一些常量方便后续维护#define MyAppName MyApp #define MyAppVersion 1.0.0 #define MyAppPublisher YourCompany #define MyAppExeName MyApp.exe这样做的好处是以后版本升级只需要改一行#define不用全文搜索替换。[Setup] 节安装包的基本信息[Setup] AppId{{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} AppName{#MyAppName} AppVersion{#MyAppVersion} AppPublisher{#MyAppPublisher} DefaultDirName{autopf}\{#MyAppName} DefaultGroupName{#MyAppName} OutputDirOutput OutputBaseFilenameMyApp_Setup_{#MyAppVersion} Compressionlzma2/ultra64 SolidCompressionyes WizardStylemodern PrivilegesRequiredadmin SetupIconFileResources\icon.ico几个关键参数解释一下。AppId是这个程序的唯一标识符用 GUID 生成器生成一个它决定了安装程序如何识别这个软件已经装过了。DefaultDirName是默认安装路径{autopf}会自动选择 Program Files 还是 Program Files (x86)。PrivilegesRequiredadmin表示需要管理员权限安装如果你的程序不需要写 Program Files 或注册表 HKLM可以改成lowest。Compressionlzma2/ultra64是最高压缩比对于包含 .NET 运行时的大文件效果很好。[Files] 节要打包的文件[Files] Source: ..\MyApp\bin\Release\net8.0-windows\publish\*; DestDir: {app}; Flags: ignoreversion recursesubdirs createallsubdirs这一行把 publish 目录下的所有文件递归复制到安装目录{app}。ignoreversion表示覆盖旧文件时不弹版本冲突提示recursesubdirs递归子目录createallsubdirs保留目录结构。[Icons] 节快捷方式[Icons] Name: {group}\{#MyAppName}; Filename: {app}\{#MyAppExeName} Name: {group}\卸载 {#MyAppName}; Filename: {uninstallexe} Name: {autodesktop}\{#MyAppName}; Filename: {app}\{#MyAppExeName}; Tasks: desktopicon{group}是开始菜单的程序组文件夹{autodesktop}是桌面。第三个图标关联了Tasks: desktopicon意味着只有用户勾选了创建桌面快捷方式选项才会创建。[Tasks] 节可选安装选项[Tasks] Name: desktopicon; Description: 创建桌面快捷方式(D); GroupDescription: 附加选项: Name: autostart; Description: 开机自动启动(A); GroupDescription: 附加选项:; Flags: checkedonce这些选项会出现在安装向导的选择附加任务页面。checkedonce表示只在首次安装时默认勾选升级安装时会记住用户上次的选择。[Registry] 节注册表操作如果需要开机自启可以写入注册表[Registry] Root: HKCU; Subkey: Software\Microsoft\Windows\CurrentVersion\Run; ValueType: string; ValueName: {#MyAppName}; ValueData: {app}\{#MyAppExeName}; Tasks: autostart; Flags: uninsdeletevalueTasks: autostart关联了前面的可选任务uninsdeletevalue确保卸载时删除这个注册表项。注意ValueData中路径两端的引号要用双引号转义两个双引号 一个字面双引号这是为了防止安装路径包含空格时出问题。[Run] 节安装完成后执行的操作[Run] Filename: {app}\{#MyAppExeName}; Description: 启动 {#MyAppName}; Flags: nowait postinstall skipifsilent这会在安装完成页面显示一个启动程序的复选框用户勾选后安装结束时会自动运行程序。nowait表示安装程序不会等它运行完才退出postinstall表示只在安装后执行skipifsilent表示静默安装时跳过。[UninstallRun] 节卸载时的清理[UninstallRun] Filename: taskkill; Parameters: /F /IM {#MyAppExeName}; Flags: runhidden; RunOnceId: KillProcess卸载前先终止正在运行的进程否则删除文件时会报文件被占用。第三步处理依赖——自动安装 .NET Runtime这是打包 .NET 程序最关键的一环。如果目标机器没有安装 .NET Desktop Runtime程序会直接报错。我们可以在安装时自动检测并安装。首先添加一个 [Code] 节来写 Pascal 脚本[Code] function IsDotNetInstalled(): Boolean; var Installed: String; begin Result : RegQueryStringValue(HKLM, SOFTWARE\dotnet\Setup\InstalledVersions\x64\Microsoft.NETCore.App, 8.0.0, Installed); end;然后在 [Run] 节最前面加上 .NET Runtime 的安装步骤[Run] Filename: https://dotnet.microsoft.com/download/dotnet/8.0; \ StatusMsg: 正在检查 .NET 运行时...; \ Check: not IsDotNetInstalled; \ Flags: shellexec runhidden Filename: {app}\{#MyAppExeName}; Description: 启动程序; \ Flags: nowait postinstall skipifsilentCheck: not IsDotNetInstalled意味着只有在检测到 .NET 没装的时候才会打开下载页面。如果你想要完全静默安装可以把 .NET Runtime 的离线安装包一起打包进去但这会显著增大安装包体积。第四步编译安装包一切就绪后编译非常简单。方式一在 IDE 中编译。双击打开 setup.iss 文件按 CtrlF9或点击菜单 Build → Compile。编译进度条跑完后安装包就生成在Output目录下了。方式二命令行编译。适合集成到 CI/CD 流程中C:\Program Files (x86)\Inno Setup 6\ISCC.exe path\to\setup.iss编译成功后你会看到类似这样的输出Successful. (4.231 s) Output: C:\...\setup\Output\MyApp_Setup_1.0.0.exe第五步测试安装包生成的安装包一定要在干净的虚拟机或沙箱环境中测试。重点检查这几项全新安装是否顺利完成、安装后程序能否正常启动、桌面快捷方式和开始菜单项是否正确、卸载是否干净文件和注册表项全部清除、升级安装先装旧版再装新版是否正常覆盖。进阶检测 WebView2 等额外依赖如果你的程序像我的项目一样依赖 WebView2 Runtime可以用同样的模式添加检测逻辑。WebView2 的安装状态可以通过注册表查询[Code] function IsWebView2Installed(): Boolean; var Installed: Cardinal; begin Result : RegQueryDWordValue(HKLM, SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}, EBWebView, Installed); end;然后在 [Run] 节添加在线安装步骤[Run] Filename: https://go.microsoft.com/fwlink/p/?LinkId2124703; \ Parameters: /silent /install; \ StatusMsg: 正在安装 WebView2 Runtime...; \ Check: not IsWebView2Installed; \ Flags: shellexec runhidden waituntilterminated常见踩坑记录路径中的反斜杠。Inno Setup 脚本中路径分隔符用反斜杠\不是正斜杠。Source 路径中..\MyApp\bin\...是相对于 .iss 文件所在目录的。中文语言包。默认的安装向导界面是英文的。要改成中文在 [Languages] 节添加[Languages] Name: chinesesimplified; MessagesFile: compiler:Languages\ChineseSimplified.islInno Setup 6 自带了简体中文语言文件无需额外下载。大文件压缩慢。包含 .NET 运行时的 publish 目录通常有 150-200MB。用lzma2/ultra64压缩后大约 60-80MB。如果嫌编译太慢可以退而求其次用lzma2/normal速度快很多但体积稍大。调试脚本。Inno Setup IDE 支持断点调试。在 [Code] 节的 Pascal 脚本中可以用MsgBox弹窗来调试变量值编译时会弹出消息框。GUID 不要复用。每个不同的程序必须用不同的 AppId GUID。如果两个程序用了同一个 GUID安装程序会认为它们是同一个软件导致升级/卸载行为异常。完整脚本参考把上面的片段组合起来这是一个可以直接使用的完整模板#define MyAppName MyApp #define MyAppVersion 1.0.0 #define MyAppPublisher YourCompany #define MyAppExeName MyApp.exe [Setup] AppId{{你的GUID} AppName{#MyAppName} AppVersion{#MyAppVersion} AppPublisher{#MyAppPublisher} DefaultDirName{autopf}\{#MyAppName} DefaultGroupName{#MyAppName} OutputDirOutput OutputBaseFilename{#MyAppName}_Setup_{#MyAppVersion} Compressionlzma2/ultra64 SolidCompressionyes WizardStylemodern PrivilegesRequiredadmin [Languages] Name: chinesesimplified; MessagesFile: compiler:Languages\ChineseSimplified.isl [Files] Source: ..\MyApp\bin\Release\net8.0-windows\publish\*; DestDir: {app}; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: {group}\{#MyAppName}; Filename: {app}\{#MyAppExeName} Name: {group}\卸载 {#MyAppName}; Filename: {uninstallexe} Name: {autodesktop}\{#MyAppName}; Filename: {app}\{#MyAppExeName}; Tasks: desktopicon [Tasks] Name: desktopicon; Description: 创建桌面快捷方式(D); GroupDescription: 附加选项: Name: autostart; Description: 开机自动启动(A); GroupDescription: 附加选项: [Registry] Root: HKCU; Subkey: Software\Microsoft\Windows\CurrentVersion\Run; ValueType: string; ValueName: {#MyAppName}; ValueData: {app}\{#MyAppExeName}; Tasks: autostart; Flags: uninsdeletevalue [Run] Filename: {app}\{#MyAppExeName}; Description: 启动 {#MyAppName}; Flags: nowait postinstall skipifsilent [UninstallRun] Filename: taskkill; Parameters: /F /IM {#MyAppExeName}; Flags: runhidden; RunOnceId: KillProcess总结Inno Setup 的学习成本很低从零到做出第一个安装包可能只需要半小时。对于 .NET 开发者来说它解决了怎么把程序优雅地交给用户这个问题一个专业的安装界面、自动处理依赖、创建快捷方式、干净的卸载体验。这些细节虽然不影响程序功能本身但会极大影响用户对你软件的第一印象。如果你正在为自己的 .NET 项目怎么分发而发愁不妨试试这个方案。从dotnet publish到 Inno Setup 编译整个流程可以很方便地集成到 CI/CD 中实现提交代码后自动生成安装包。引入地址