“用户对你的第一印象是你的安装程序”
——摘自NSIS网页
对于非技术用户,例如儿童,家长,作家等等。如果他们无法很容易地安装某个软件,他们就会放弃这个软件!而开发者和大多数程序员,讨厌制作Windows安装程序,甚至讨厌学习如何制作安装程序。从10年前的Windows Installer Shield, Installer 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编译器将脚本编译,并和程序一起打包。可以用任何文本编辑器制作自己的脚本。这里我推荐一个Eclipse的NSIS插件[1]。其屏幕截图如下:
该插件可以直接从Eclipse安装,需要预先安装Eclipse GEF 3.1。在Eclipse的Update Manager中加入http://eclipsensis.sf.net/update后即可安装相关插件。
[编写脚本]
为了不从0开始制造轮子,我们利用NSIS的例子脚本,对其加以改动。首先说明一下我的安装程序打算做什么事情。我打算制作一个Squeak 3.9[2]的安装程序。它会从网络上分别下载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,第二个方法是在Eclipse内选择菜单 EclipseNSIS | Compile Script,或者使用快键Alt+C,也会生成example.exe。双击这个exe就会运行此安装程序将example.nsi复制安装的指定目录。
在这个例子的基础上,很容易写出我自己的安装程序。
例子脚本和我的要求有若干不同,主要如下:
-
我需要从网络下载zip到用户计算机
-
我需要亲自解开zip包到用户目录
-
我需要建立桌面和程序快捷方式
-
我需要建立卸载程序
首先针对第1点要求,经过查阅资料,发现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[3],其可以从这里下载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
至此,第二个需求就满足了,为了方便用户使用,下面实现第3个需求——在桌面和程序菜单中建立Squeak的快捷方式,这样用户只需要双击这些快捷方式的图标,就可以运行Squeak了。squeak的运行方式是按照这样的命令行:
squeak.exe squeak_iamge_filename.image
但是Windows的目录名可能会带有空格,例如C:\ Program Files\Squeak3.9\中,Program和Files之间就存在一个空格,这样squeak.exe会把空格前的部分,C:\Program 当作映像文件名,而把后面的Files\Squea...当作参数,这就出现了歧义。为此需要把路径用""括起来。但是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提供的 UnistPage命令创建卸载程序页面,并提供一个名叫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
[继续提高]
包括国际语言支持,断点续传等。待续
[参考文献]
[1]. Eclipse NSIS plug-in: http://eclipsensis.sourceforge.net/
[2]. Squeak http://www.squeak.org
[3]. Nsisunz plug-in http://nsis.sourceforge.net/Nsisunz_plug-in
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1552043