NSIS 3.0.4.1安装包制作实战:从解压到静默安装与避坑
简介NSIS 3.0.4.1 是免费开源的 Windows 安装制作工具官方版本面向需要为软件生成专业安装包的开发者、系统管理员及个人项目维护者。压缩包约 1.29MB体积小巧、便于快速下载部署内含搭建完整 NSIS 环境所必需的核心组件与资源。已有 415 人学习下载适合零基础入门到进阶定制的不同层级。借助这份资源可以系统学习 NSIS 基于 ANSI C 风格的脚本语言掌握条件分支、循环、函数与变量等编程元素能理解从文件复制、注册表操作到环境变量设置、服务启动的完整安装逻辑通过编辑 .nsi 脚本并调用 makensis.exe 编译即可生成自定义界面与交互逻辑的可执行安装程序。同时NSIS 支持多语言、数字签名以及 Windows XP 至 Windows 11 的广泛兼容能帮助开发者交付安全、高效且用户体验良好的安装体验是开源软件、商业软件及个人项目构建安装包的实用选择。1. nsis-3.0.4.1.7z 是什么先看清这个 7z 文件里装的到底是谁nsis-3.0.4.1.7z 这个文件名对没碰过 Windows 安装包制作的人就是一个普通压缩包对需要每天在构建机上出安装包的工程师来说它是 NSIS 3.x 这条产品线里一个相当稳定的编译器版本解压开就能把脚本编译成 setup.exe。NSIS 全称 Nullsoft Scriptable Install System用一段 nsi 脚本描述「把哪些文件放到哪个目录、写哪些注册表、要不要创建快捷方式」最终产出独立的安装程序。它不需要目标机器预装任何运行时特别适合把安装包构建接进 CI、用文本维护多版本安装逻辑还要同时出 x86 和 x64 两种包的交付场景。在 InstallShield 和纯自研安装器之间它是很务实的一条路。2. 从解压到跑通把 nsis-3.0.4.1.7z 变成第一个可执行安装包2.1 先认清目录结构别急着找安装脚本拿到 nsis-3.0.4.1.7z 之后第一步不是去翻目录找某个 exe 双击而是确认这个包是哪一类分发形式。NSIS 官方发布时一般给 zip 和 tar.bz2像标题里这种 7z 后缀通常是内部服务器或网盘重新压过的版本压缩算法不同但解压后内容一致推荐用 7-Zip 或者新版 WinRAR 解压。解压出来你会看到一个标准的 NSIS 发行目录Bin下是编译器本体makensis.exeInclude放各种.nsh头文件Plugins放插件 DLLStubs是安装程序的外壳Contrib是官方示例和扩展工具。后三个目录在你做插件调用和自定义页面时会频繁用到但日常写脚本只需要记得三个入口脚本文件、makensis.exe、Include路径。3.0.4.1 属于 NSIS 3.x 中期维护版本从这一版开始 Unicode 构建是默认能力中文路径和中文界面不再像 2.x 时代那样处处是坎这也是很多项目至今仍停留在 3.x 主线的原因。2.2 用 makensis /VERSION 确认编译器版本解压完成后先把Bin目录加到 PATH或者直接用绝对路径调用。验证一下你手上的编译器确实能跑C:\nsis-3.0.4.1\Bin\makensis.exe /VERSION正常会输出版本号比如3.0.4.1。这一步看起来多余但在多人协作的交付环境里非常值得做我之前遇到过构建机上有多个 NSIS 版本混装结果 CI 里实际调用的还是 2.x 老版本编译出的安装包在 Win10 上表现完全不一样。用/VERSION在脚本开头打一条日志能省掉很多后续排错的时间。2.3 写一个最小 nsi 脚本把它编成 setup.exe新建一个demo.nsi内容如下; 最小可编译的 NSIS 脚本生成 demo-setup.exe Name Demo OutFile demo-setup.exe InstallDir $PROGRAMFILES\Demo !include MUI2.nsh ; 页面宏决定安装界面的流程 !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH ; 卸载页面 !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES ; 界面语言简体中文 !insertmacro MUI_LANGUAGE SimpChinese Section 主程序 SetOutPath $INSTDIR File myapp.exe WriteUninstaller uninstall.exe SectionEnd Section 卸载程序 RMDir /r $INSTDIR SectionEnd这段脚本的逻辑很直接Name和OutFile决定安装包显示名和输出文件名InstallDir给出默认安装路径MUI2 头文件提供了一套现代安装向导界面。Section是安装动作的最小单元第一个 Section 负责把myapp.exe释放到$INSTDIR并生成卸载器第二个 Section 是卸载时执行的清理动作。File指令会把你当前目录下的文件打进安装包注意它不会递归子目录需要带子目录时用File /r。在同一个目录下放一个真实的myapp.exe然后跑编译makensis.exe /V2 demo.nsi/V2是标准日志级别输出关键信息不刷屏如果编译失败想看细节改成/V4。编译成功后同目录下会多出一个demo-setup.exe这个文件就是可以分发给用户的安装包。2.4 把安装包真正跑一遍装、卸、看日志编译通过只是开始我建议任何脚本改动后都走一遍完整流程双击demo-setup.exe按默认路径装完确认C:\Program Files\Demo下文件齐全、开始菜单快捷方式正常再运行uninstall.exe回来看目录是否清干净。这套动作看起来基础但能挡掉大量发布后才发现的问题。需要排查时用静默模式加日志最省事。NSIS 支持用LogSet on让安装过程在当前目录写日志也可以在编译时加/V4让编译日志包含每一条指令的执行结果。如果你用命令行直接验证安装demo-setup.exe /S /DC:\Demo/S表示静默安装不弹界面/D...直接指定安装目录。这里有个关键约束/D必须放在整条命令的最后一位而且路径不能加引号这是 NSIS 解析命令行参数的老规矩违反它经常导致目录名被截断这是我踩过最多的一类坑后面会专门展开。3. 写一个能交给验收的 nsi 脚本文件分发、注册表卸载项与静默安装3.1 Section 与页面宏先把脚本骨架搭出层次NSIS 脚本的学习曲线不陡但容易写成一坨顺序执行的代码。我一般会先搭骨架Section是安装逻辑的原子单位一个 Section 对应安装器上可勾选的一个部件。想给用户多选组件就用多个 Section 加SectionGroup分组不想暴露界面选项就用一个 Section 把主程序装完。页面宏则由 MUI2 提供MUI_PAGE_WELCOME、MUI_PAGE_DIRECTORY、MUI_PAGE_INSTFILES依次定义了安装向导的欢迎、选目录、执行安装这三步比自己做跨页跳转稳定得多。页面宏的顺序就是安装界面出现的顺序想增加一个「安装前阅读协议」的页面就在中间插入MUI_PAGE_LICENSE并指向一个license.txt。卸载流程同样由页面宏控制MUI_UNPAGE_CONFIRM是卸载前确认MUI_UNPAGE_INSTFILES是执行卸载。这套骨架定下来之后业务需求只是往 Section 里填指令的问题。3.2 一份「能进控制面板卸载」的完整安装脚本真实交付的安装包比 demo 多三件事注册表卸载信息、开始菜单快捷方式、卸载时清干净注册项。下面这段脚本是更接近生产的形态!include MUI2.nsh !include x64.nsh !include LogicLib.nsh Name DemoClient OutFile DemoClient-setup.exe InstallDir $PROGRAMFILES\DemoClient InstallDirRegKey HKLM Software\DemoClient InstallPath !define MUI_ABORTWARNING !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_LANGUAGE SimpChinese Section 主程序 SetOutPath $INSTDIR File bin\DemoClient.exe File bin\config.ini CreateDirectory $SMPROGRAMS\DemoClient CreateShortCut $SMPROGRAMS\DemoClient\DemoClient.lnk $INSTDIR\DemoClient.exe CreateShortCut $DESKTOP\DemoClient.lnk $INSTDIR\DemoClient.exe SetRegView 64 WriteRegStr HKLM Software\DemoClient InstallPath $INSTDIR WriteRegStr HKLM Software\Microsoft\Windows\CurrentVersion\Uninstall\DemoClient DisplayName DemoClient WriteRegStr HKLM Software\Microsoft\Windows\CurrentVersion\Uninstall\DemoClient DisplayVersion 1.0.0 WriteRegStr HKLM Software\Microsoft\Windows\CurrentVersion\Uninstall\DemoClient Publisher MyCompany WriteRegStr HKLM Software\Microsoft\Windows\CurrentVersion\Uninstall\DemoClient UninstallString $INSTDIR\uninstall.exe SetRegView 32 WriteUninstaller uninstall.exe SectionEnd Section Uninstall Delete $INSTDIR\DemoClient.exe Delete $INSTDIR\config.ini RMDir $INSTDIR Delete $SMPROGRAMS\DemoClient\DemoClient.lnk RMDir $SMPROGRAMS\DemoClient Delete $DESKTOP\DemoClient.lnk SetRegView 64 DeleteRegKey HKLM Software\DemoClient DeleteRegKey HKLM Software\Microsoft\Windows\CurrentVersion\Uninstall\DemoClient SetRegView 32 SectionEnd这里最值得关注的是注册表卸载信息。HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\DemoClient这组键是 Windows 控制面板和设置里「应用和功能」列表的数据来源缺少它们用户只能手动删目录体验立刻折半。UninstallString必须指向$INSTDIR\uninstall.exe否则点卸载没反应。SetRegView 64和SetRegView 32之间的内容在 64 位系统上写入 64 位注册表视图避免被重定向到WOW6432Node下面这是 NSIS 3.0 之后才有的干净做法2.x 时代想写 64 位注册表得靠插件。3.3 区分 x86 与 x64编译期决定而不是运行时碰运气如果你的程序同时出 32 位和 64 位版本安装脚本最容易翻车的地方就是路径和注册表视图。我习惯在脚本开头用编译期宏分叉!include x64.nsh !ifdef X64 !define APP_DIR $PROGRAMFILES64\DemoClient !define APP_NAME DemoClient x64 !else !define APP_DIR $PROGRAMFILES\DemoClient !define APP_NAME DemoClient !endif InstallDir ${APP_DIR} Name ${APP_NAME}编译时用两条命令分别产物makensis /DX64 demo.nsi makensis demo.nsi${RunningX64}是运行时判断当前系统是不是 64 位而!ifdef X64是编译期决定。两套逻辑有本质区别编译期的宏直接产出两个独立安装包各自知道自己该装到哪里不会出现在 64 位系统上犹豫「我到底该往哪个 Program Files 走」的情况。运行时判断适合单包兼容双架构的场景但会把脚本里所有路径都写成分支维护成本高不少。我一般优先推荐编译期出双包。3.4 静默安装与卸载给 CI 留一条干净的通路安装包要接进自动化测试和交付流水线静默参数是必须的。安装侧用/S和/D前面已经说过/D必须最后且不带引号。卸载侧对应的写法是uninstall.exe /S _?$INSTDIR_?这个参数告诉卸载器直接在原始安装目录运行而不是先把自己复制到临时目录。它能配合静默模式在无人值守时完成卸载同时保留$INSTDIR下的用户数据文件。需要注意用了_?之后卸载器不会删除自身那一份可执行文件所以脚本里要自己Delete $INSTDIR\uninstall.exe。想要拿到安装结果的退出码用ExecWait等待进程结束并检查$R0ExecWait $INSTDIR\DemoClient.exe /register $R0 DetailPrint 注册退出码$R0如果你在 PowerShell 里串 CI 步骤可以这么做$makensis C:\nsis-3.0.4.1\Bin\makensis.exe $makensis /V2 /DMyVersion1.0.0 demo.nsi if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Start-Process -Wait .\DemoClient-setup.exe -ArgumentList /S$LASTEXITCODE就是安装包返回给进程的退出码NSIS 默认成功返回 0失败会返回非 0。把这套东西固化到脚本里安装包就可以进入每天夜间构建的环节。4. 避坑实战NSIS 3.0.4.1 安装包最容易翻车的 5 个点4.1 中文界面变乱码问题出在脚本文件的编码现象安装界面上的中文全是问号或者方块安装目录名里的中文变成乱码卸载时界面也花。原因NSIS 3.x 编译器读取.nsi脚本文件时有 UTF-8 BOM 就按 UTF-8 解析没有 BOM 就按系统当前 ANSI 代码页解析。在简体中文系统上老编辑器默认把文件存成 GBK看起来正常一旦换到英文系统或者构建机代码页变了中文字面量全废。解决把 nsi 文件统一另存为 UTF-8 with BOM。VS Code 和 Notepad 都能在「编码」菜单里显式切换。这里有个细节脚本文件本身必须带 BOM否则 NSIS 不认 UTF-8不带 BOM 的 UTF-8 会被按 ANSI 读照样乱。我现在的习惯是项目里放一个.editorconfig强制.nsi文件用utf-8-bom杜绝这个玄学坑。4.2 编译好的安装包被杀毒软件报毒现象刚编译出来的 setup.exe 被 Windows Defender 或第三方杀软拦截报 HackTool、Downloader 之类有些安装包装上之后释放出来的主程序也被隔离。原因NSIS 安装包本体是自解压壳无数字签名且很多程序会写启动项、释放文件到 Temp、执行外部命令这些行为特征与恶意软件高度重叠。杀软拿不准时宁可利用「未签名」和「行为类似」判毒。解决最有效的手段是代码签名证书有条件优先上 EV 签名普通 OV 签名能消除绝大多数误报。脚本层尽量少做「从 Temp 释放再运行」「安装后立刻写 HKCU Run 启动项」这类动作主程序直接从$INSTDIR运行不二次释放。遇到误报就通过杀软厂商的申诉入口提交流程正规但没有必要跟杀软对抗式调脚本那是没有尽头的。4.3 静默安装到错误目录/D的引号和位置现象执行setup.exe /S /DC:\Program Files\Demo之后程序装到了C:\Program或安装直接失败。原因NSIS 的命令行解析器把/D之后的内容当作完整路径但如果路径带引号引号也被当成路径的一部分如果/D后面还跟着其他参数解析就乱了。文档里写得明白/D必须最后一个出现且路径不能加引号。解决路径含空格时不加引号/DC:\Program Files\Demo是合法写法NSIS 会从/D之后一直取到命令行结尾。如果脚本里使用InstallDir且用户在安装界面改过目录静默安装时/D会覆盖它但安装界面路径不受影响。最稳妥的防御是脚本里在.onInit中检查$INSTDIR合法性路径不对直接Abort并给日志。4.4 卸载之后目录清不干净残留文件被用户吐槽现象用户卸载后Program Files下目录还在或者%APPDATA%里残留一堆日志和配置用户质疑安装器不干净。原因卸载 Section 里只删了$INSTDIR下的主程序文件没有处理用户在程序运行期间生成的用户数据或者因为文件被占用Delete静默失败后没有重试机制。解决把「程序文件」和「用户数据」分区对待。程序文件在卸载时直接删遇到占用可以用/REBOOTOK标记到重启后再删用户数据默认保留在卸载界面用MessageBox问一下是否连配置一起删MessageBox MB_YESNO|MB_ICONQUESTION 是否同时删除配置文件 IDNO skip_data RMDir /r $APPDATA\DemoClient skip_data:这种保留式清理既避免误删用户数据又给用户一个干净卸载的选择。最后要检查逻辑调用uninstall.exe /S _?$INSTDIR时卸载器不会删除自己那一份脚本里需要补一个Delete $INSTDIR\uninstall.exe。4.5 32 位安装包写 64 位注册表写偏到 WOW6432Node现象64 位系统上安装完成后HKLM\Software\DemoClient在 64 位注册表里找不到反而出现在HKLM\Software\WOW6432Node\DemoClient下面程序读取配置时报错。原因NSIS 默认生成的安装程序是 32 位进程注册表操作默认走 32 位视图定向附加\WOW6432Node。程序读配置时如果是 64 位进程读的是 64 位视图两边对不上。解决写关键注册项前用SetRegView 64切换视图写完切回 32 位。注意如果安装包本身被编译成 64 位 stub默认视图又会反过来。这个参数不是一劳永逸的开关而是每次写注册表前都要显式声明。更简单的心法是脚本开头定义两个辅助宏一个负责开 64、一个负责切回 32所有注册表操作都包进去不让裸奔的WriteRegStr出现。5. 进阶技巧一套脚本靠 /D 宏同时产出 x86 与 x64 安装包前面在第 3 章提过编译期宏分叉这里把完整方案补上方便你直接抄进工程。做法是在脚本顶部集中定义产品名、版本、目录和文件名用!ifdef区分两种架构然后给安装包写注册表和卸载信息时也用这些宏!ifdef X64 !define VERBOSE_NAME DemoClient x64 !define REG_KEY Software\DemoClient-x64 !define UNINST_KEY Software\Microsoft\Windows\CurrentVersion\Uninstall\DemoClient-x64 !else !define VERBOSE_NAME DemoClient !define REG_KEY Software\DemoClient !define UNINST_KEY Software\Microsoft\Windows\CurrentVersion\Uninstall\DemoClient !endif这样一份 nsi 脚本编译时只差一个/D参数。构建脚本里直接生成两个产物并给它们起不同的文件名makensis.exe /V2 /DX64 demo.nsi move DemoClient-setup.exe DemoClient-x64-setup.exe makensis.exe /V2 demo.nsi move DemoClient-setup.exe DemoClient-x86-setup.exe两个安装包互不干扰各自的注册表键也分开升级时只更新对应架构的包不会出现 32 位包覆盖 64 位程序目录的情况。发布前我一般还会跑一次命令行验证用/S静默安装两个包确认装完的目录对得上再手动执行一次卸载把注册表残留查一遍。这套做法看起来朴素但能在 Beta 阶段就挡掉大部分架构混乱问题。我现在的习惯是每个 nsi 都留一个build-all.bat把所有makensis命令、产物重命名、发布前自检脚本全放进去交付时不依赖任何开发机希望帮到你。本文还有配套的精品资源点击获取