安装包制作是软件发版的关键步骤,制作安装包的工具很多,总体分为两类: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插件。