65.9K
CodeProject 正在变化。 阅读更多。
Home

创建安装程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (28投票s)

2008 年 3 月 7 日

CPOL

15分钟阅读

viewsIcon

167648

downloadIcon

5248

使用 NSIS 创建简单的安装程序

Sample Image

引言

有许多工具可以为我们的应用程序创建安装程序。我使用了其中两个。第二个是 NSIS,自从学会它之后,我就没有再去找其他的了。本文介绍了 NSIS,并展示了如何使用强大而易用的 NSIS 脚本创建简单的安装应用程序。

实验 实验 #0
实验将在本文的每个部分结束后进行。在每个实验中,我们将进一步探讨该部分讨论的内容。在最后一个实验中,我们将拥有一个完整的安装应用程序。

目录

所需材料

在开始使用 NSIS 之前,我们需要先获取其编译器。除了 NSIS 编译器,还有许多便捷的工具和插件,大部分由 NSIS 社区编写。我在这里放了一个我在这篇文章中使用的链接。

全局概览

要使用 NSIS 创建一个简单的安装程序,我们应该开始学习 NSIS 脚本语言。想法是创建一个或多个文件,其中包含脚本指令。然后,我们要求 NSIS 编译器编译该文件并创建一个安装可执行文件。

编译脚本

按照上面的链接下载并安装 NSIS 后,我们只需要创建一个名为WhatEverName.nsi 的新文本文件,输入我们的命令,右键单击该文件并选择“编译 NSIS 脚本”。

Compiling A Script

这并不算太糟,但拥有一个 IDE 会更好,不是吗?上面的第二个链接是指一个不错且小巧的 NSIS IDE,名为“HM NIS Edit”。在 NSIS 开发者中心,您可以下载其他 IDE 或集成到现有的编辑器中,如 Eclipse。这里有一个链接到 可用的文本编辑器页面。在 HM 编辑器中,我们也可以编译和运行我们的安装程序,而无需退出 IDE。我们有一个向导,一个页面设计器,并且按 F1 会启动 NSIS 脚本 CHM 帮助文件。

HM Editor

运行 IDE 后,您可以输入命令,或者使用向导创建一个简单安装程序的模板。就我个人而言,直到我开始从零开始创建自己的指令,我才学会使用 NSIS 脚本。

脚本详解

我们有了 NSIS,一个 IDE,并且知道我们需要编写一些指令来创建安装程序。但是我们可以输入什么呢?在 NSIS 中,每一行都被视为一个新的指令。指令将通过其名称来识别。如果我们查看一个示例 NSIS 脚本文件的指令,我们将看到其中一些部分:页面函数变量以及其他编译器命令属性。根据它们的名称,每个指令对编译器都有特殊的含义。下面是一个包含三种所谓指令的示例脚本。

Dividing A Script

理解页面和节非常容易,因为它们代表了最终编译的安装程序中的视觉对应部分。上面的脚本,如果编译,将产生以下安装程序

Dividing A Script

上面的示例节的创建方式是隐藏的。我们也没有任何函数。稍后,我们将更详细地研究它们。

属性

属性是定义安装程序行为、外观或定义后面指令行为的指令。例如,Name 属性定义了将在安装程序窗口标题栏中显示的安装程序的名称。

            Name "MyProduct Version 1.0"

下表列出了一些您可以在脚本中使用的有用属性。

属性 它的作用
名称 设置安装程序的名称
OutFile 指定 MakeNSIS 应将安装程序写入的输出文件;安装程序文件名(如MySetup.exe
InstallDir 设置默认安装目录
ShowInstDetails 设置是否显示安装详情
ShowUnInstDetails 设置是否显示卸载详情
SetCompressor 设置用于压缩安装程序中的文件/数据所使用的压缩算法
SetCompressorDictSize 设置 LZMA 压缩器使用的字典大小(以兆字节 MB 为单位)
实验 实验 #1

使用 HM NIS Edit 创建一个新的 NSI 脚本文件,并在其中键入以下行:

!include "LogicLib.nsh"
!include "MUI2.nsh" 

!define PRODUCT_NAME "CP Lab"
!define SETUP_NAME "CPLabSetup.exe"
!define PRODUCT_VERSION "1.0"


OutFile ${SETUP_NAME}
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"


;Default installation folder
InstallDir "$PROGRAMFILES\CPLab"

;Get installation folder from registry if available InstallDirRegKey HKLM 
;"Software\CP Lab" ""

ShowInstDetails show
ShowUnInstDetails show 

SetCompressor /SOLID lzma
SetCompressorDictSize 12

;Request application privileges for Windows Vista RequestExecutionLevel user 
;Could be 'admin' 

在 NSIS 脚本中,我们可以添加宏(以!define开头的行),并且我们还可以使用其他文件中的脚本,使用!include编译时命令。

*.nsh 文件包含普通的 NSIS 脚本,就像我们的脚本一样。唯一的区别是,我们使用*.nsi 来编译我们的脚本,但要包含另一个脚本,我们必须给该文件一个*.nsh 扩展名。

请注意,在使用宏时,我们需要同时使用$符号和{},而在使用变量时,只需要$符号。

变量

它们为我们保存值,就像我们在编程语言中所做的那样。要定义一个变量,我们需要使用var命令。要使用它,我们必须在变量名前面加上$

        var varName
    
    ...
    
    
    StrCpy $varName "example value"

使用变量时的注意事项

  • 所有变量都是全局的,因此在函数内部定义时,我们必须显式使用/GLOBAL开关:Var /GLOBAL varName
  • 变量名允许的字符是:[a-z]、[A-Z]、[0-9] 和_
  • 默认情况下,变量限制为 1024 个字符。

预定义变量

  • NSIS 中已经定义了一些变量,我们可以直接使用它们而无需声明。此外,我们必须小心并避免我们的名称与这些名称发生冲突:$0$1$2$3$4$5$6$7$8$9$R0$R1$R2$R3$R4$R5$R6$R7$R8$R9。NSIS 有时将这些变量称为寄存器。
  • $INSTDIR:安装目录。可以在脚本的任何地方使用,并在运行时替换为安装目录路径。当您使用InstallDir属性时,此变量已由您设置,但在运行时用户可以更改它。您也可以使用StrCpy命令修改此变量。
  • $OUTDIR:当前输出目录。可以使用SetOutPathStrCpy来设置此变量。

有关变量的更多信息,请参阅 NSIS 帮助文件中的变量部分。

常量

这里是命名一些 NSIS 为我们定义的有用常量的好地方。它们也已添加到帮助文件的变量部分。还记得InstallDir属性吗?如果您不记得了,它是设置安装目录的属性。除非您确定希望将安装程序文件准确地复制到哪里(例如C:\MyFolder),否则您需要知道一些路径,例如Windows文件夹路径或Program Files文件夹路径。常量在这里很有帮助。在运行时,它们将被替换为最终用户计算机的路径。请看下面的例子。

        InstallDir "$PROGRAMFILES\MyApp"

在这里,我们在InstallDir属性前面使用了$PROGRAMFILES常量,该属性期望一个字符串。$PROGRAMFILES将在用户计算机上被替换为确切的路径字符串。下表显示了一些有用的常量列表。

恒定 含义
$PROGRAMFILES 通常是C:\Program Files,但在不同机器上可能不同。
$PROGRAMFILES32$PROGRAMFILES64 在 Windows X64 上,前者指向C:\Program Files (x86),后者指向C:\Program Files
$DESKTOP Windows 桌面目录(通常是C:\Windows\Desktop,但会在运行时检测)。
$EXEDIR 包含安装程序可执行文件的目录。
$EXEPATH 安装程序可执行文件的完整路径。
${NSISDIR} 一个包含 NSIS 安装路径的符号。如果您想调用 NSIS 目录中的资源,例如图标、UI,则很有用。
$WINDIR Windows 目录。
$SYSDIR Windows 系统目录。
$TEMP 系统临时目录。
$STARTMENU 开始菜单文件夹(使用CreateShortCut添加开始菜单项时很有用)。
$SMSTARTUP 开始菜单程序/启动文件夹。此常量的上下文(所有用户或当前用户)取决于SetShellVarContext设置。默认是当前用户。
$DOCUMENTS 文档目录。此常量的上下文(所有用户或当前用户)取决于SetShellVarContext设置。默认是当前用户。
$APPDATA 应用程序数据目录。此常量的上下文(所有用户或当前用户)取决于SetShellVarContext设置。默认是当前用户。
$CDBURN_AREA 存储待刻录到 CD 的文件的目录;此常量在 Windows XP 及以上版本可用。

有关常量的完整列表或其要求,请参阅 NSIS 帮助的变量部分。

“每个 NSIS 安装程序包含一个或多个节。”这意味着我们的脚本中至少需要有一个节。但是节有什么用呢?您肯定见过许多安装程序允许您选择要安装的内容,例如非常著名的 Microsoft Office,它允许您选择安装 Powerpoint 或不安装。

在 NSIS 中,可以通过节将这些选项提供给用户。在我们的示例中,Microsoft Powerpoint 应该有一个特定的节,而该节实际上为每个您选择安装的功能都有其他节。至少有一个节是合乎逻辑的,因为我们至少有一个要安装的功能。不是吗?节可以隐藏,这样用户就看不到任何供其选择的功能,这很可能用于只有一个节的安装程序。

        Section "Installer"

        ; Instructions go here

    SectionEnd

在上面的示例中,我创建了一个名为“Installer”的新节。不要被名称误导;它只是一个名称,您可以将其命名为 MySec。我们可以在节块内自由放置任意数量的指令。我在这里不详细描述节;这会使本文过长。但是,您可以找到许多关于节的信息,例如如何禁用或重新启用一个节,如何创建节组,如何使节名称显示为粗体,使其成为可选或强制的等等。

实验 实验 #2

将上一个文件重新加载到您的编辑器中,然后键入以下内容:

Section "Dummy Section" SecDummy

SetOutPath "$INSTDIR"

;Store installation folder
WriteRegStr HKCU "Software\CP Lab" "" $INSTDIR

;Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"

SectionEnd 



Section "Uninstall"

Delete "$INSTDIR\Uninstall.exe"

RMDir "$INSTDIR"

DeleteRegKey /ifempty HKCU "Software\CP Lab"

SectionEnd 

上面的代码创建了两个节。我们希望第一个可见。上面的第二个节将在卸载时调用。NSIS 编译器通过名称识别这一点。如果节的名称是 UnInstall 或以 un 开头,它将在卸载时调用。

警告:请注意,调用RMDir删除我们的应用程序文件夹非常危险。原因很简单。试想一下,一个初学者用户将默认文件夹路径更改为C:\Program Files。您在安装时保存了路径,然后调用RMDir时,安装程序会尝试删除整个Program Files文件夹。

函数

“函数类似于节,因为它们包含零个或多个指令。”函数有两种类型:用户函数和回调函数。用户函数将通过使用Call指令手动调用。“回调函数将在某个事件发生时由安装程序调用。”

“函数必须在节或其他函数之外声明。”

        Function func
      ; some commands
    FunctionEnd

    Section
      Call func
    SectionEnd

上面的代码展示了如何创建一个用户函数。还有另一种类型的函数称为“回调”。回调函数通过它们的名称来识别。这些名称是唯一的,NSIS 编译器可以识别它们。这些函数将在特殊事件发生时被调用,您可以自由地在其中放置您喜欢的指令,以便在事件发生时执行它们。

         Function .onInit
       MessageBox MB_YESNO "This will install. Continue?" IDYES NoAbort
         Abort ; causes installer to quit.
       NoAbort:
     FunctionEnd

上面的代码展示了如何编写.onInit,以便一旦安装程序启动,一个消息框就会询问用户是否要继续。下表列出了一些常见的回调函数。

回调函数名 调用时间
.onInit 将在安装程序几乎完成初始化时调用。如果.onInit函数调用Abort,安装程序将立即退出。
.onUserAbort 当用户按下“取消”按钮且安装尚未失败时调用。如果此函数调用Abort,则安装不会被中止。
.onInstFailed 在安装失败后用户按下“取消”按钮时调用。
.onMouseOverSection 每当鼠标在节树上的位置发生变化时调用。这允许您为每个节设置描述,例如。当前鼠标悬停的节 ID 暂时存储在$0中。
un.onInit 将在卸载程序几乎完成初始化时调用。如果un.onInit函数调用Abort,卸载程序将立即退出。

有关函数的更多信息,请参阅 NSIS 帮助中的函数部分。

实验 实验 #3

让我们为我们的安装程序添加两个函数:一个回调函数和一个用户函数。

Function .OnInit

StrCpy $0 "Welcome to my first setup wizard"

push $0

Call ShowWelcome

FunctionEnd 



Function ShowWelcome

pop $R0

${If} $R0 == ''
StrCpy $R0 "Message from function"
${EndIf}

MessageBox MB_OK $R0
FunctionEnd 

这里我们创建了一个回调函数.onInit,它将在安装程序启动时、页面显示之前调用。然后,我们将一条消息复制到由编译器预定义的$0变量中。最后,我们将其压栈,以便稍后在函数中使用,然后调用一个函数。

用户函数尝试从堆栈中弹出一个字符串到$R0,该变量也由编译器预定义。我们检查它是否具有值。如果没有,我们将另一个值放入$R0,然后显示一个消息框。

这展示了我们如何创建函数、传递参数(使用内置堆栈)、使用变量以及使用我们已经包含的logiclib有多么容易。如果我们不使用logiclib,我们就必须编写类似 IBM x86 汇编的代码,而不是$IF

页数

“每个(非静默)NSIS 安装程序都有一组页面。每个页面可以是 NSIS 内置页面,也可以是自定义页面。”在我写这篇文章的时候,NSIS 有了一个新版本,它提供了更好的自定义页面支持,但我懒得去读。我将这项任务留给热情的读者。Page 指令很简单。

        Page license

上面的代码显示了一个简单的空许可证页面。要强制显示您的许可证文件,您应该使用页面选项。事实上,不仅是许可证页面,所有其他默认页面也都有可以通过属性提供的选项。另一种编码 UI 的方法是使用现代 UI。它不仅更简单,而且更熟悉、更美观。Modern UI 在 NSIS 2.0 版本之后包含。UI 的文档可在此处找到。在我撰写本文时,Modern UI 的第二版已经可用,并具有更强大的功能。

        !insertmacro MUI_PAGE_LICENSE "License.rtf"

上面的代码添加了一个许可证页面,并加载指定的文件供用户签名。页面中最后一个重要的东西是它们都有事件(还记得回调吗?)。有了这些回调处理程序,您可以轻松修改它们的行为方式,或决定在页面显示之前或之后做什么。例如,当我们要忽略下一页或根据前几页启用/禁用某些选项时,这很有用。

        !define MUI_PAGE_CUSTOMFUNCTION_PRE PAGE_LICENSE
    !insertmacro MUI_PAGE_LICENSE "License.rtf" "" PreLicense
    
    ...
    
    Function PreLicense

        ; If app installed already, license signed, ignore it
        ${If} $bAppExists == '1'
        Abort
        ;${Else}
         ${EndIf}

    FunctionEnd

上面的代码在显示许可证页面之前调用一个函数。该函数检查一个变量以查看应用程序是否已存在。如果存在,将忽略许可证页面,并显示下一页。

实验 实验 #4

如果我们现在运行我们的安装程序,它将起作用!它使用节、我们的属性以及一些默认行为来执行所需的操作。问题是,用户没有任何自定义选项。让我们通过现代 UI 的页面给用户一些选择。

!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "License.txt"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH

!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH

!insertmacro MUI_LANGUAGE"English"

;--------------------------------
;Descriptions

;Language strings
LangString DESC_SecDummy ${LANG_ENGLISH} "A section"

;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy)
!insertmacro MUI_FUNCTION_DESCRIPTION_END

只需添加一些页面,我们就完成了。在完成本次实验之前,请注意我们如何使用SecDummy为“选择功能”页面添加描述。如果您回过头来看节,您会注意到,除了名称之外,行末还有一个SecDummy。实际上,这被称为节索引,用于访问节。

最后 8 行只是为了在鼠标悬停在“Dummy Section”上时显示“A Section”文本。

实用说明

下表列出了一些您可能会需要的指令。

指令 它的作用 用法
文件 将文件添加到当前输出路径($OUTDIR)进行解压 File "Bin\7z\*.*"
退出 导致安装程序尽快退出
ExecWait 执行指定的程序并等待已执行进程退出 ExecWait 'c:\SomeProgram.exe' $0
DetailPrint 将字符串“message”添加到安装程序的详细信息视图中 DetailPrint "message"
Strlen str 的长度设置用户变量 $x Strlen $0 ${SETUP_NAME}
ReadRegStr 从注册表中读取到用户变量 ReadRegStr $0 HKLM Software\NSIS ""
CopyFiles 将文件从源复制到目标安装系统 CopyFiles "$0\cid.dll" "$INSTDIR"
CreateDirectory 创建(必要时递归创建)指定目录 CreateDirectory "7z"
CreateShortCut 创建一个快捷方式 link.lnk,链接到 target.file,并带有可选参数 parameters CreateShortCut "$SMPROGRAMS\$STMenuDirectory\Help.lnk" "$INSTDIR\Yas.chm"
IfFileExists 检查文件是否存在 IfFileExists "$INSTDIR\${SQL_DATABASE_NAME}.mdf" 0 Goon_label
删除 从目标系统删除文件(可以是文件或通配符,但应指定完整路径) Delete '"$INSTDIR\${SQL_DATABASE_NAME}.mdf"'
DeleteRegKey 删除注册表项 DeleteRegKey[/ifempty] root_key subkey
DeleteRegValue 删除注册表值;root_key 的有效值列在 WriteRegStr DeleteRegValue root_key subkey key_name

最后说明

请注意,本文包含的安装程序将在您的Program Files目录下创建一个名为CPLab的文件夹。文件夹内有一个Uninstall.exe,它将为您清理所有内容。这里还有很多特性或需要提示的内容需要提及,但如果我那样做,我就会把 NSIS 帮助重新排序。我认为这足以开始创建您的安装程序,并且正如我创建自己的安装程序时所做的那样,您一定会很快找到所需的内容。祝您好运!

致谢与免责声明

这里的大部分文本和代码都摘自 NSIS 帮助和示例。我试图在文本周围加上引号,但对于代码,这反而会显得杂乱。感谢 NSIS 社区,这里有大量可供下载的代码 这里

历史

2008/03/04:教程创建

© . All rights reserved.