Using NSIS to make a setup
引言
NSIS是一款制作Windows应用安装包的软件。由于其插件繁多以及不错的可扩展性,所以到今天依旧是一个不错的选择。
准备
文中使用的NSIS是3.08版本。下载地址: https://nsis.sourceforge.io/Download
在VS Code中调试NSIS脚本十分便利,相当推荐。
安装VS Code插件,NSIS
安装后,需要指定下makensis目录。
Build
当然可以通过makensis命令来build。但为了调试方便,我直接用插件提供的build功能。
脚本编写
Debug
NSIS的调试主要是靠两个手段: !echo
和 DetailPrint
以 !
开头表示的是编译时的命令,也就是说 !echo
是在编译时被执行的,在最终安装时不会被执行。相对地,DetailPrint
就是安装时执行的指令。
需要说明的是,!echo
会把跟在后面的内容原模原样的输出到命令行终端。
比如:
; 注释以分号开头
Section FirstStep
Var /GLOBAL greeting ; 声明一个变量,变量都看成字符串类型
StrCpy $greeting "hello world" ; 给变量赋值
!echo $greeting
DetailPrint $greeting
SectionEnd
编译脚本得到
可以看出!echo
不会打印出任何变量中的内容。所以基本上没什么用。
DetailPrint
可以打印变量内容,但是由于它是安装时的指令,所以它打印的内容是在安装时打印的。我们修改上面的脚本,加入 DetailPrint
。
Section FirstStep
Var /GLOBAL greeting ; 声明一个变量,变量都看成字符串类型
StrCpy $greeting "hello world" ; 给变量赋值
!echo $greeting
DetailPrint $greeting
SectionEnd
编译然后build。因为我们这个脚本文件名叫example.nsi,所以同目录下产生了同名安装文件example.exe。
双击运行example.exe。因为我们并没有任何实际上的安装,所以安装立即就完成了。
点击 “Show details”, 我们就看到了DetailPrint
打印的内容。
脚本示例
这个脚本是打包.NET6应用的基本安装流程,包括
- Windows系统版本检查
- 多国语言安装界面
- 注册表读写
- .NET 6 runtime检查以及安装
- 卸载
Unicode True
!define PRODUCT_NAME "YourApplication"
!define PRODUCT_VERSION "0.0.1"
!define PRODUCT_PUBLISHER "You"
!define PRODUCT_CODE "YourApplication"
!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\${PRODUCT_NAME}.exe"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
!define SETUP_FILENAME "YourApplication.Install.exe"
!define WIN_RUNTIME "windowsdesktop-runtime-6.0.11-win-x64"
!include "MUI2.nsh"
!include "x64.nsh"
!include "nsProcess.nsh"
!include "LogicLib.nsh"
!include "WinVer.nsh"
!include "DotNetChecker.nsh"
; MUI Settings
!define MUI_ABORTWARNING
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\orange-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall.ico"
!define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\orange.bmp"
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp"
; Welcome page
!insertmacro MUI_PAGE_WELCOME
;Directory page
!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 "English"
!insertmacro MUI_LANGUAGE "SimpChinese"
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "${SETUP_FILENAME}"
InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}"
InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
ShowInstDetails show
ShowUnInstDetails show
RequestExecutionLevel admin
BrandingText " ${PRODUCT_PUBLISHER} . "
!define BINARY_SOURCE_DIR "path\to\your\build\output\directory"
Section "Bin" SEC01
SetOutPath "$INSTDIR\Bin"
File "..\runtimes\${WIN_RUNTIME}.exe"
Call DotNetRuntimeCheck
File "${BINARY_SOURCE_DIR}\*.dll"
File "${BINARY_SOURCE_DIR}\${PRODUCT_NAME}.exe"
File "${BINARY_SOURCE_DIR}\${PRODUCT_NAME}.pdb"
File "${BINARY_SOURCE_DIR}\${PRODUCT_NAME}.runtimeconfig.json"
;start menu shortcut
SetOutPath "$INSTDIR\Bin"
SetShellVarContext current
CreateDirectory "$SMPROGRAMS\${PRODUCT_CODE} ${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_CODE} ${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\Bin\${PRODUCT_NAME}.exe"
CreateShortCut "$SMPROGRAMS\${PRODUCT_CODE} ${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninst.exe"
SetShellVarContext all
CreateDirectory "$SMPROGRAMS\${PRODUCT_CODE} ${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_CODE} ${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\Bin\${PRODUCT_NAME}.exe"
CreateShortCut "$SMPROGRAMS\${PRODUCT_CODE} ${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninst.exe"
;desktop shortcut
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\Bin\${PRODUCT_NAME}.exe"
SectionEnd
Section -Post
WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\Bin\${PRODUCT_NAME}.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\Bin\${PRODUCT_NAME}.exe"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
WriteRegStr HKLM "Software\${PRODUCT_CODE}\${PRODUCT_NAME}" "AppPath" "$INSTDIR\Bin"
WriteRegStr HKLM "Software\${PRODUCT_CODE}\${PRODUCT_NAME}" "InstallPath" $INSTDIR
SectionEnd
Function .onInit
;Language selection dialog
!insertmacro MUI_LANGDLL_DISPLAY
${IF} ${AtLeastWin10}
${Else}
MessageBox MB_OK|MB_ICONEXCLAMATION "This application can only be run on Windows 10"
Abort
${EndIf}
${nsProcess::FindProcess} "${PRODUCT_NAME}.exe" $R0
${If} $R0 == 0
MessageBox MB_OK|MB_ICONEXCLAMATION "${PRODUCT_NAME} is running. Please close it first !" IDOK
Abort
${EndIf}
ReadRegStr $R0 HKLM "Software\${PRODUCT_CODE}\${PRODUCT_NAME}" "InstallPath"
StrCmp $R0 "" done
StrCpy $INSTDIR $R0
done:
FunctionEnd
Function un.onInit
${nsProcess::FindProcess} "${PRODUCT_NAME}.exe" $R0
${If} $R0 == 0
MessageBox MB_OK|MB_ICONEXCLAMATION "${PRODUCT_NAME} is running. Please close it first !" IDOK
Abort
${Else}
MessageBox MB_ICONQUESTION|MB_YESNO "Are you sure you want to completely remove $(^Name) and all of its components ?" /SD IDYES IDNO 0 IDYES +2
Abort
${EndIf}
FunctionEnd
Function un.onUninstSuccess
HideWindow
MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) has been removed from your computer successfully !" IDOK
FunctionEnd
Function DotNetRuntimeCheck
SetRegView 64
ReadRegStr $R0 HKLM "SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.WindowsDesktop.App" "6.0.11"
${If} $R0 == ""
DetailPrint "${WIN_RUNTIME} hasn't been installed"
ExecWait "$INSTDIR\Bin\${WIN_RUNTIME}.exe"
Delete "$INSTDIR\Bin\${WIN_RUNTIME}.exe"
${Else}
DetailPrint "${WIN_RUNTIME} has been installed"
${EndIf}
FunctionEnd
Section Uninstall
SetOutPath "$TEMP"
Delete "$INSTDIR\${PRODUCT_CODE}.url"
Delete "$INSTDIR\uninst.exe"
SetShellVarContext current
RMDir /r "$SMPROGRAMS\${PRODUCT_CODE} ${PRODUCT_NAME}"
SetShellVarContext all
RMDir /r "$SMPROGRAMS\${PRODUCT_CODE} ${PRODUCT_NAME}"
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
RMDir /r "$INSTDIR"
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
DeleteRegKey HKLM "Software\${PRODUCT_CODE}\${PRODUCT_NAME}\InstallPath"
DeleteRegKey HKLM "Software\${PRODUCT_CODE}\${PRODUCT_NAME}\AppPath"
DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
DeleteRegKey HKLM "Software\${PRODUCT_CODE}\${PRODUCT_NAME}"
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Run"
SetAutoClose true
SectionEnd
NSIS脚本编写涉及到非常多的内容,详细可以参考 官网文档: https://nsis.sourceforge.io/Docs/ 以及 https://documentation.help/NSIS/ ,后者排版相对好一些 😄。
🔚