“用户对你的第一印象是你的安装程序”
												 ——摘自NSIS网页

前言

Nullsoft Installation System是一个小巧高效的安装软件。可通过样例修改或根据自己要求编写 NSI 脚本文件来定制自己的安装系统,可实现许可协议的显示、安装类型的选择、写入注册表、写入INI文件、连接程序外壳、语句跳转、提示信息显示、创建卸载程序、定制安装和卸载程序的图标、创建快捷方式等其他安装程序应有的功能,是一款操作简单、功能强大的高效率安装软件精品。
对于非技术用户,例如儿童,家长,作家等等。如果他们无法很容易地安装某个软件,他们就会放弃这个软件!而开发者和大多数程序员,讨厌制作Windows安装程序,甚至讨厌学习如何制作安装程序。从10年前的Windows Installer Shield,Install Shield Express等等,我从来没没有耐心下来搞清楚如何制作安装程序。最近由于需要向普通用户发布一个软件,使得我不得不坐下来亲自制作一个安装程序。我没有找到好的中文资料,只得自己摸索英文材料。所以我写这样一个简易指南,希望能够有所帮助。

Why NSIS
NSIS是免费的,使用并且许多开源软件使用NSIS制作其安装程序。NSIS最早是WinAMP用于为其播放器安装皮肤的,后来成为了流行的安装程序制作工具。它使用脚本来制作安装程序,基本上比较小巧,并且易学易用。

写作方法
例子是最好的学习方法之一。本文不是一个关于NSIS的全面参考,我希望读者能够根据本文“照猫画虎”,快速制作安装程序。如果读者遇到了某些特定问题,可以通过NSIS附带的用户手册,或者通过网络搜索引擎找到答案。

下载和准备
首先需要下载NSIS,地址为:http://prdownloads.sourceforge.net/nsis/nsis-2.20-setup.exe?download
如果该链接有变动,读者可以自行到source forge下载,source forge的网址为:http://sourceforge.net/
进入后搜索NSIS即可。下载后即可安装NSIS。NSIS本身包括帮助手册、例子目录、基本编译器和一些扩展插件。基本工作原理是,开发者将自己的程序准备好,然后利用文本编辑工具写号安装脚本,最后利用NSIS编译器将脚本编译,并和程序一起打包。可以用任何文本编辑器制作自己的脚本。

编写脚本

为了不从零开始制造轮子,我们利用NSIS的例子脚本,对其加以改动。首先说明一下我的安装程序打算做什么事情。我打算制作一个 Squeak 3.9 的安装程序。它会从网络上分别下载Squeak的虚拟机和映像文件共两个zip包到用户的计算机上。然后解开这些zip包到用户的指定目录,例如C:\Program files\squeak3.9\下。最后在用户的桌面和启动菜单建立squeak执行程序的快捷方式,以及将来卸载时用的程序。
首先来看一看 NSIS 最简单的例子提供了什么,NSIS 的 Example 目录下有一个例子叫做 example1.nsi,用文本编辑器打开它

; 注释说明
;--------------------------------

; 安装程序的名字,该名字会显示在安装程序对话框的标题中
Name "Example1"

; 安装程序的最终文件名,编译后,所有文件被打包生成一个独立的安装程序,名叫example1.exe
OutFile "example1.exe"

; 缺省安装到的目录,一般为C:\Program Files\Example1
InstallDir $PROGRAMFILES\Example1

;--------------------------------
; 页,表示安装程序的对话框一共会变化几页,一般首页是版权信息,然后第二页让用户选择安装目录,
; 接下来安装文件等等。这个例子只有两个页,选择目录,和复制文件。

Page directory
Page instfiles

;--------------------------------
; 每页有若干节(section),各个节内部才真正进行各种操作
Section "" ;由于没有让用户选择需要安装的组件,所以节的名字可以忽略,脚本将从这里真正执行

  ; 安装程序解包后,将文件复制到OutPath中。本例将文件复制到用户选择的安装目录内
  SetOutPath $INSTDIR

  ; 将与脚本处于同一目录下的文件压缩打包到安装程序中,将来用户安装时,
  ; 这些文件会被解包复制到OutPath里
  File example1.nsi ; 将脚本自己打包

SectionEnd ; 本节结束

这个例子非常简单,他把脚本自己打包进example1.exe中,然后可以发布这个example1.exe,它本身是一个安装程序。用户在自己的计算机上一旦运行此程序,就会出现一个对话框,让用户选择安装目录。用户选择好后,其将会将example1.nsi从包内解出,并复制到用户选择的安装目录。可以用这个脚本做一个测试,编译脚本生成可执行的安装程序:方法是在脚本上点击鼠标右键,选择“compile nsis script”,然后NSIS编译器就会启动,编译脚本打包文件生成example1.exe。双击这个exe就会运行此安装程序 example.nsi复制安装到指定目录。
在这个例子上的基础上,很容易写出我们自己的安装程序。
例子脚本和我们的要求有若干不同:

  1. 我们需要从网络下载zip包到用户的计算机;
  2. 我们需要亲自解开zip包到用户目录;
  3. 我们需要建立桌面和程序快捷方式;
  4. 我们需要建立卸载程序。
    首先针对第一点要求,经过查阅资料,发现NSIS有一个下载插件可以完成此功能,其典型用法是:
NSISdl::download http://www.yoursite.org/yourpack.zip "$INSTDIR\yourpack.zip"
Pop $R0 ; 获得下载结果的返回值
StrCmp $R0 success download_ok
  SetDetailsView show
  DetailPrint "download failed: $R0"
  Abort
download_ok:
  DetailPrint "download $R0 OK"

上面这段代码的意思是,首先调用NSISdl:download这个插件去网络上下载yourpack.zip文件到用户本地的安装目录下,并且保持文件名保持不变。是否下载成功的结果放在寄存器变量$R0中,如果下载成功,这个变量中的值应该是字符串success。所以StrCmp的作用就是比较返回结果是否成功,如果成功,叫跳转到download_ok标记,否则就打印失败信息并终止安装。
这段代码具有一定的代表性,实际上可以利用它从网络下载多个文件到用户计算机。唯一不同的就是下载地址和保存到本地的文件名。因此可以把它制作成一个函数,如下:

Function DownloadFile
  NSISdl::download $remote_zip_file "$INSTDIR\$local_zip_file"
    ; 略...
  download_ok:
    DetailPrint "download $R0 OK"
FunctionEnd

这个函数的参数,用两个全局变量$remote_zip_file和$local_zip_file传入。调用方法如下:

Var remote_zip_file
Var local_zip_file

StrCpy $remote_zip_file "http://ftp.squeak.org/current_stable/win/Squeak-Win32-3.7.1-VM.zip"
StrCpy $local_zip_file "Squeak-Win32-3.7.1-VM.zip"
Call DownloadFile

NSIS中的变量用Var语句声明,声明时不带有$前缀。使用时,利用$前缀进行引用。这一点和Unix的Shell一样,赋值不采用等号,而使用StrCpy语句进行字符串复制。
至此第一个需求就可以得到满足了。我们可以分别下载Squeak虚拟机和Squeak映像到用户的计算机,此时的安装脚本如下:

; 安装程序的名称
Name "Squeak 3.9 Installer"

; 安装程序的文件名
OutFile "squeak3.9_win_installer.exe"

; 缺省安装目录
InstallDir "$PROGRAMFILES\Squeak3.9"

;--------------------------------
; 安装对话框包含的页内容
; Pages

Page directory
Page instfiles

;--------------------------------
; 自定义的全局变量
Var remote_zip_file
Var local_zip_file

; 安装Section
Section ""

  ; 安装输出的目录
  SetOutPath $INSTDIR

  ; 从网络分别下载虚拟机和映像
  Call DownloadVM
  Call DownloadImage

SectionEnd ; Section结束
 

; 下载虚拟机
Function DownloadVM
  StrCpy $remote_zip_file "http://ftp.squeak.org/current_stable/win/Squeak-Win32-3.7.1-VM.zip"
  StrCpy $local_zip_file "Squeak-Win32-3.7.1-VM.zip"
  Call DownloadFile
FunctionEnd
 

; 下载映像
Function DownloadImage
  StrCpy $remote_zip_file "http://ftp.squeak.org/3.9/Squeak3.9-RC2-7064.zip"
  StrCpy $local_zip_file "Squeak3.9-RC1-7064.zip"
  Call DownloadFile
  Call ExtractFile
FunctionEnd
 

; 下载文件的通用函数
Function DownloadFile
  NSISdl::download $remote_zip_file "$INSTDIR\$local_zip_file"
  Pop $R0 ; Get the return value
  StrCmp $R0 success download_ok
    SetDetailsView show
    DetailPrint "download failed: $R0"
    Abort
  download_ok:
    DetailPrint "download $R0 OK"
FunctionEnd

读者可以使用NSIS编译这个脚本并运行安装程序。它会将虚拟机和映像的zip文件下载到指定目录下(如C:\Program Files\Squeak3.9)并结束。
下面着手实现第二个需求:将zip包解开。查阅NSIS手册,没有发现类似功能的函数或者插件。其原因是,NSIS在打包时,会自动将普通文件按照zip标准的压缩算法进行打包;而执行exe时会自动解包。所以NSIS没有考虑将zip再次打包,以及显示解压缩zip文件的功能。经过在网络上使用搜索引擎查找,发现了一个第三方插件NsisUnz。可以从这里下载
http://home.no.net/nxs/nsis/nsisunz.7z ,该文件是7zip格式,需要下载相应的解压缩工具:http://www.7-zip.org/ 。解开软件包后,将其中的nsisunz.dll复制到NSIS目录下的Plugins子目录下即可(如C:\Program Files\NSIS\Plugins),该Plugin的使用方法为:

nsisunz::UnzipToLog "$INSTDIR\myfile.zip" "$INSTDIR"

若解压缩成功,该命令返回字符串success,否则标识解压缩失败。因此可以仿照上面的下载函数,写一个通用的解压缩函数。它接受一个zip文件的文件名字符串,然后对其解压缩:

Function ExtractFile
  nsisunz::UnzipToLog "$INSTDIR\$local_zip_file" "$INSTDIR"
  Pop $R0
  StrCmp $R0 "success" unzip_ok
    DetailPrint "$R0"
    Abort
  unzip_ok:
    DetailPrint "extract $R0 OK"
    Delete "$INSTDIR\local_zip_file"
FunctionEnd

这个解压缩函数一旦成功,就会删除原来下载的zip文件,以节约用户控件。采用这个通用的解压缩函数,可以修改上面的虚拟机和映像下载函数为:

Function DownloadVM
  StrCpy $remote_zip_file "http://ftp.squeak.org/current_stable/win/Squeak-Win32-3.7.1-VM.zip"
  StrCpy $local_zip_file "Squeak-Win32-3.7.1-VM.zip"
  Call DownloadFile
  Call ExtractFile
FunctionEnd

Function DownloadImage
  StrCpy $remote_zip_file "http://ftp.squeak.org/3.9/Squeak3.9-RC2-7064.zip"
  StrCpy $local_zip_file "Squeak3.9-RC1-7064.zip"
  Call DownloadFile
  Call ExtractFile
FunctionEnd

编译这个改进的脚本并运行,可以发现zip被下载后解开成各自的文件了。这里有一些小问题,有些zip包里,就包含一些文件;但是有些zip包里,却包含目录。例如上面两个zip包解开后,在$INSTDIR下就会出现这样的文件结构:

+Squeak.exe
+SqueakFFIPrims.dll
+Squeak3.9-RC2-7064\
  +Squeak3.9-RC2-7064.image
  +Squeak3.9-RC2-7064.changes
  +SqueakV39.source
  +WelcomeSqueak39

而我需要所有的这6个文件都在同一目录下。因此需要使用NSIS的Rename函数移动这些文件。为此修改DownloadImage函数如下:

Function DownloadImage
  ;略...
  Call DownloadFile
  Call ExtractFile

  ; 将文件移动到$INSTDIR下,并删除空掉的子目录
  Rename "$INSTDIR\Squeak3.9-RC2-7064\Squeak3.9-RC2-7064.changes" "$INSTDIR\Squeak3.9-RC2-7064.changes"
  Rename "$INSTDIR\Squeak3.9-RC2-7064\Squeak3.9-RC2-7064.image" "$INSTDIR\Squeak3.9-RC2-7064.image"
  Rename "$INSTDIR\Squeak3.9-RC2-7064\SqueakV39.sources" "$INSTDIR\SqueakV39.sources"
  Rename "$INSTDIR\Squeak3.9-RC2-7064\WelcomeSqueak39" "$INSTDIR\WelcomeSqueak39"
  RMDir "$INSTDIR\Squeak3.9-RC2-7064"
FunctionEnd

至此,第二个需求满足了。为了方便用户使用,下面实现第三个需求:在桌面和程序菜单中建立Squeak的快捷方式。这样用户只需要双击这些快捷方式,就可以运行Squeak了。Squeak的运行方式是按照这样的命令行:

squeak.exe squeak_iamge_filename.image

但是windows的目录名可能会带有空格(如C:\Program Files\Squeak3.9\),这样squeak.exe就会把空格前面的部分(C:\Program)当做映像文件名,而把后面的部分(Files\Squeak3.9\)当做参数,这就出现了歧义。因此需要把路径用""括起来。但是NSIS中,会把""忽略掉。解决办法是在""外再加一层''(或者使用$"来转义")。此后就可以使用NSIS的CreateShortCut函数创建快捷方式了。当必要的文件下载并解压缩成功后就进行这一步的内容,为此修改Section如下:

Section ""

  SetOutPath $INSTDIR

  Call DownloadVM
  Call DownloadImage

  ; 创建快捷方式
  CreateDirectory "$SMPROGRAMS\Squeak\3.9\"
  CreateShortcut "$SMPROGRAMS\Squeak\3.9\squeak.lnk" $INSTDIR\Squeak.exe
     '"$INSTDIR\Squeak3.9-RC2-7064.image"'
  CreateShortcut "$DESKTOP\squeak3.9.lnk" $INSTDIR\Squeak.exe
     '"$INSTDIR\Squeak3.9-RC2-7064.image"'

  Exec '$INSTDIR\Squeak.exe "$INSTDIR\Squeak3.9-RC2-7064.image"' ; 安装成功后自动运行Squeak
  SetAutoClose true ; 安装结束后自动关闭安装程序
SectionEnd

为了进一步增强用户友好型,还在安装成功后,自动运行Squeak并关闭安装程序。
最后,良好的安装程序还应该提供写在功能,并干净地清除用户计算机上的内容。为此可以利用NSIS提供的UinstPage命令创建卸载程序页面,并提供一个名叫Uninstall的Section实际进行卸载工作。对于Squeak这点卸载工作的内容包括:删除程序文件、删除快捷方式等。其实现如下:

; 用于安装的页

Page directory
Page instfiles
 
; 用于卸载的页
UninstPage uninstConfirm
UninstPage instfiles
;--------------------------------
; 略...

Section "Uninstall"
  ; 删除文件
  Delete "$INSTDIR\*.image"
  Delete "$INSTDIR\*.changes"
  Delete "$INSTDIR\*.dll"
  Delete "$INSTDIR\*.sources"
  Delete "$INSTDIR\*.exe"
  Delete "$INSTDIR\WelcomeSqueak39"
  RMDir "$INSTDIR"

  ; 删除快捷方式
  Delete "$SMPROGRAMS\Squeak\3.9\squeak.lnk"
  Delete "$DESKTOP\squeak3.9.lnk"
  RMDir "$SMPROGRAMS\Squeak\3.9"

  SetAutoClose true
SectionEnd

提供Uninstall功能的脚本,需要在安准的最后一步生成uninstall.exe供用户使用,为此在安装Section最后增加这样两行:

CreateShortcut "$SMPROGRAMS\Squeak\3.9\uninstall.lnk" "$INSTDIR\uninstall.exe"
WriteUninstaller "$INSTDIR\uninstall.exe"

并且在Uninstall的Section中增加对卸载快捷方式的删除动作:

Delete "$SMPROGRAMS\Squeak\3.9\uninstall.lnk"

至此一个完整的安装程序就制作完成了。该安装脚本的完整代码可以在这里下载。
squeak3.9_win_install.nsi

引用

http://xltx.blog.hexun.com/32224064_d.html
https://www.cnblogs.com/chulia20002001/archive/2011/03/01/1968029.html