NSIS安装包制作

安装包制作是软件发版的关键步骤,制作安装包的工具很多,总体分为两类:GUI工具(Advanced Installer等)、脚本工具(NSIS、Inno Setup等)。

本人在实际工作中深度使用NSIS,感受到了它的强大;同时,也喜欢Inno Setup更加简明的语法和基于Delphi的扩展开发。

实际案例:打包AutoCAD插件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# ========================= 宏定义 ========================= 
# 安装包相关信息宏定义
!define PRODUCT_NAME "XXX设计系统"
!define EXE_NAME "${PRODUCT_NAME}.exe"
!define PRODUCT_VERSION "1.0.0.0"
!define PRODUCT_PUBLISHER "XXX公司"
!define PRODUCT_LEGAL ""

# AutoCAD插件系统的主启动类库文件
!define MAINDLL "TsyShieldTunnelDesign.dll"
# 需要附带安装的安装包名称
!define TOOLKIT_INSTALLER "XXX工具箱.exe"

# 卸载注册表宏路径定义
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"

# CAD注册表路径宏定义
!define regACAD2020 "SOFTWARE\Autodesk\AutoCAD\R23.1\ACAD-3001:804"
!define regACAD2021 "SOFTWARE\Autodesk\AutoCAD\R24.0\ACAD-4101:804"


# ========================= 变量定义 =========================
var PREVIOUS


# ========================= 安装包版本信息 =========================
VIProductVersion "${PRODUCT_VERSION}"
VIAddVersionKey "ProductVersion" "${PRODUCT_VERSION}"
VIAddVersionKey "ProductName" "${PRODUCT_NAME}"
VIAddVersionKey "CompanyName" "${PRODUCT_PUBLISHER}"
VIAddVersionKey "FileVersion" "${PRODUCT_VERSION}"
VIAddVersionKey "InternalName" "${EXE_NAME}"
VIAddVersionKey "FileDescription" "${PRODUCT_NAME}"
VIAddVersionKey "LegalCopyright" "${PRODUCT_LEGAL}"


# ========================= NSIS属性 =========================
SetFont "Segoe UI" 9
#SetCompressor lzma
SetCompressor zlib
Name "${PRODUCT_NAME}V${PRODUCT_VERSION}"
OutFile "${PRODUCT_NAME}V${PRODUCT_VERSION}.exe"
InstallDir "$PROGRAMFILES64\${PRODUCT_PUBLISHER}\${PRODUCT_NAME}"
ShowInstDetails show
ShowUnInstDetails show
RequestExecutionLevel admin


# ========================= 外部插件及宏引用 =========================
!include LogicLib.nsh
!include "x64.nsh"
!include "Library.nsh"
!include "WordFunc.nsh"
!define LIBRARY_X64
!include "MUI.nsh"


# ========================= MUI设置 =========================
; MUI Settings
!define MUI_ABORTWARNING
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
;!define MUI_WELCOMEFINISHPAGE_BITMAP ".\background.bmp"
; Welcome page
!define MUI_WELCOMEPAGE_TITLE "欢迎使用《${PRODUCT_NAME}V${PRODUCT_VERSION}》安装向导"
!define MUI_WELCOMEPAGE_TEXT "此程序将引导你安装《${PRODUCT_NAME}V${PRODUCT_VERSION}》。\r\n\r\n在安装前建议关闭AutoCAD,从而确保安装程序能够更新相关系统文件,避免在安装后需要重启AutoCAD。\r\n\r\n点击[下一步(N)]继续。"
!insertmacro MUI_PAGE_WELCOME
; Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "安装程序将把《${PRODUCT_NAME}V${PRODUCT_VERSION}》安装到以下目录。要安装到另一个目录,请点击[浏览(B)...]并选择其他的文件夹。$\r$\n$\r$\n点击[安装] 开始安装。"
!insertmacro MUI_PAGE_DIRECTORY
; Instfiles page
!insertmacro MUI_PAGE_INSTFILES
; Finish page
!insertmacro MUI_PAGE_FINISH
; Uninstaller pages
!insertmacro MUI_UNPAGE_INSTFILES
; Language files
!insertmacro MUI_LANGUAGE "SimpChinese"
; Reserve files
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS


# ========================= 安装步骤 =========================
# 卸载已安装的软件
Section "" SecUninstallPrevious
${If} $PREVIOUS == ""
Goto Done
${EndIf}
;卸载已安装程序(和安装一起更安全)
RMDir /r "$INSTDIR"
RMDir /r "$SMPROGRAMS\城轨盾构法隧道设计系统"
setRegView 64
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
DeleteRegKey HKCU "${regACAD2020}\Applications\${PRODUCT_NAME}"
DeleteRegKey HKCU "${regACAD2021}\Applications\${PRODUCT_NAME}"
SetRegView Lastused
Done:
SectionEnd

# .NET Framework版本检测和安装
Section "" PRESEC
;检测.net framework版本
Call GetNetFrameworkVersion
Pop $R1
${VersionCompare} $R1 '4.8' $1
${If} $1 == "2"
;安装net framework 4.8
SetDetailsPrint textonly
SetDetailsPrint listonly
SetOutPath "$TEMP"
SetOverwrite on
File ".\ndp48-x86-x64-allos-enu.exe"
ExecWait '$TEMP\ndp48-x86-x64-allos-enu.exe /q /norestart /ChainingPackage FullX64Bootstrapper' $R1
Delete "$TEMP\ndp48-x86-x64-allos-enu.exe"
${EndIf}
SectionEnd

# 安装附带的安装包
Section "" TOOLKITINSTALLER
SetDetailsPrint textonly
SetDetailsPrint listonly
SetOverwrite on
; 将附带安装包拷贝到临时文件夹
SetOutPath "$INSTDIR\TOOLKIT"
File ".\${TOOLKIT_INSTALLER}"
; 安装
ExecWait '$INSTDIR\TOOLKIT\${TOOLKIT_INSTALLER} /S'
; 删除
Delete "$INSTDIR\TOOLKIT\${TOOLKIT_INSTALLER}"
SectionEnd

# 安装程序
Section "MainSection" SEC01
SetOutPath "$INSTDIR"
SetOverwrite ifnewer
; 复制文件Release下的所有文件,排除.pdb文件
File /r /x *.pdb "..\Release\*.*"
; 写注册表
setRegView 64
; 为CAD2020安装(先检测是否有该注册项)
ReadRegStr $1 HKLM "${regACAD2020}" "AcadLocation"
${If} $1 != ""
WriteRegStr HKCU "${regACAD2020}\Applications\${PRODUCT_NAME}" "DESCRIPTION" "${PRODUCT_NAME}"
WriteRegStr HKCU "${regACAD2020}\Applications\${PRODUCT_NAME}" "LOADER" "$INSTDIR\${MAINDLL}"
WriteRegDWORD HKCU "${regACAD2020}\Applications\${PRODUCT_NAME}" "LOADCTRLS" 0x2
WriteRegDWORD HKCU "${regACAD2020}\Applications\${PRODUCT_NAME}" "MANAGED" 0x1
${EndIf}
; 为CAD2021安装
ReadRegStr $1 HKLM "${regACAD2021}" "AcadLocation"
${If} $1 != ""
WriteRegStr HKCU "${regACAD2021}\Applications\${PRODUCT_NAME}" "DESCRIPTION" "${PRODUCT_NAME}"
WriteRegStr HKCU "${regACAD2021}\Applications\${PRODUCT_NAME}" "LOADER" "$INSTDIR\${MAINDLL}"
WriteRegDWORD HKCU "${regACAD2021}\Applications\${PRODUCT_NAME}" "LOADCTRLS" 0x2
WriteRegDWORD HKCU "${regACAD2021}\Applications\${PRODUCT_NAME}" "MANAGED" 0x1
${EndIf}
SetRegView Lastused
SectionEnd

# 创建开始菜单项
Section -AdditionalIcons
SetOutPath $INSTDIR
CreateDirectory "$SMPROGRAMS\XXX设计系统"
CreateShortCut "$SMPROGRAMS\XXX设计系统\Uninstall.lnk" "$INSTDIR\uninst.exe"
SectionEnd

# 写入卸载相关的注册表信息
Section -Post
WriteUninstaller "$INSTDIR\uninst.exe"
setRegView 64
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" ""
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
SetRegView Lastused

; 设置权限
ExecWait '"$INSTDIR\SetACL.exe" -on "$INSTDIR\Plugins" -ot file -actn ace -ace "n:everyone;p:full"'
SectionEnd

# 卸载程序
Section Uninstall
; 删除文件
RMDir /r "$INSTDIR"
; 删除开始菜单项
RMDir /r "$SMPROGRAMS\XXX设计系统"
; 删除注册表
setRegView 64
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
DeleteRegKey HKCU "${regACAD2020}\Applications\${PRODUCT_NAME}"
DeleteRegKey HKCU "${regACAD2021}\Applications\${PRODUCT_NAME}"
SetRegView Lastused
; 设置自动关闭卸载窗体
SetAutoClose true
SectionEnd


# ========================= 回调函数 =========================
# 安装初始化事件处理
Function .onInit
; 添加互斥体,防止同时创建多个安装进程
System::Call 'kernel32::CreateMutex(p 0, i 0, t "myMutex") p .r1 ?e'
Pop $R0
StrCmp $R0 0 +3
MessageBox MB_OK|MB_ICONEXCLAMATION "安装程序已运行。"
Abort
; 检测是否已安装
setRegView 64
ReadRegStr $PREVIOUS ${PRODUCT_UNINST_ROOT_KEY} ${PRODUCT_UNINST_KEY} "UninstallString"
SetRegView Lastused
${If} $PREVIOUS == ""
Goto Done
${EndIf}
; 已安装,让用户选择是否覆盖安装,否则终止安装
MessageBox MB_YESNO "检测已安装《${PRODUCT_NAME}》,是否覆盖安装?" IDYES true IDNO false
true:
Goto Done
false:
Abort
Done:
FunctionEnd

# 卸载初始化事件处理
Function un.onInit
MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "是否确定彻底卸载 $(^Name)?" IDYES +2
Abort
FunctionEnd

# 卸载完成事件处理
Function un.onUninstSuccess
HideWindow
MessageBox MB_ICONINFORMATION|MB_OK "$(^Name)已从您的电脑上彻底卸载。"
FunctionEnd


# ========================= 用户函数 =========================
# .NET Framework版本检测
Function GetNetFrameworkVersion
Push $1
Push $0
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" "Install"
ReadRegDWORD $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" "Version"
StrCmp $0 1 KnowNetFrameworkVersion +1
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5" "Install"
ReadRegDWORD $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5" "Version"
StrCmp $0 1 KnowNetFrameworkVersion +1
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.0\Setup" "InstallSuccess"
ReadRegDWORD $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.0\Setup" "Version"
StrCmp $0 1 KnowNetFrameworkVersion +1
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" "Install"
ReadRegDWORD $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" "Version"
StrCmp $1 "" +1 +2
StrCpy $1 "2.0.50727.832"
StrCmp $0 1 KnowNetFrameworkVersion +1
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322" "Install"
ReadRegDWORD $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322" "Version"
StrCmp $1 "" +1 +2
StrCpy $1 "1.1.4322.573"
StrCmp $0 1 KnowNetFrameworkVersion +1
ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\.NETFramework\policy\v1.0" "Install"
ReadRegDWORD $1 HKLM "SOFTWARE\Microsoft\.NETFramework\policy\v1.0" "Version"
StrCmp $1 "" +1 +2
StrCpy $1 "1.0.3705.0"
StrCmp $0 1 KnowNetFrameworkVersion +1
StrCpy $1 "not .NetFramework"
KnowNetFrameworkVersion:
Pop $0
Exch $1
FunctionEnd

关键点总结

  • 压缩方式:平衡速度和安装包体积
  • 界面语言和字体设置
  • 开始菜单和桌面快捷方式设置
  • 检测是否已安装软件:通过读相关的注册表信息
  • 注册表操作(读、写、删)
  • .NET Framework版本检测
  • 禁止启动多个安装进程:使用Windows API创建内核对象,实现进程之间的互斥
  • 使用SetACL.exe设置目录的用户权限

补充

读写xml配置文件

在进行Revit插件安装时,需要配置addin配置文件,在其中写入真正的插件dll路径。addin文件本质上是一种xml文件,采用以下方式可以动态设置。

注意:需要事先部署好nsisXML插件。

1
2
3
4
5
6
7
8
9
10
; 复制xml配置文件
SetOutPath "${ADDINPATH}"
File "..\ShieldTunnelRevit\TsyShieldTunnelRevitDesign.addin"
; 修改xml配置文件
nsisXML::create
nsisXML::load ${ADDINPATH}\TsyShieldTunnelRevitDesign.addin
nsisXML::select '/RevitAddIns/AddIn/Assembly'
nsisXML::setText "$INSTDIR\TsyShieldTunnelRevitDesign.dll"
nsisXML::save "${ADDINPATH}\TsyShieldTunnelRevitDesign.addin"
nsisXML::release $0

权限修改

除了上文提到的SetACL.exe程序外,也可以使用AccessControl插件。

评论