NSIS使用,打包C#项目

NSIS打包WPF项目

NSIS版本

NSIS_v251_Build_20160601 - NSISFANS.zip

NSIS使用

我是使用HM VNISEdit2.0.3生成初步脚本,再编辑脚本增加其他功能。

新建脚本:向导

打开HM VNISEdit2.0.3。点击顶部菜单栏的文件,选择新建脚本:向导

第1步(1/8)

声明一些应用程序信息,没什么好说的。

第2步(2/8)

安装程序语言,用户图形界面,压缩算法我都是默认的,一般就只会改一下图标和文件名。

第3步(3/8)

从来没改动过,默认就行。

第4步(4/8)

这里如果没有授权文件,可以直接为空,不填就可以了。

第5步(5/8)

首先③是默认的2条信息,我一般直接删除。

从①处添加单独的文件,从②处添加文件夹文件。

一般先通过②添加整个项目文件。

注意红框内的选择。

如果需要创建快捷方式,再通过①添加项目中的exe文件。

最终选择如下:

第6步(6/8)

默认即可。

第7步(7/8)

默认即可

第8步(8/8)

默认即可

至此,向导结束,生成脚本。总结来说,只有第5步,选择项目文件的时候需要注意一下,其他大部分都默认即可。

通过编辑脚本,增加一些功能

安装前先卸载之前版本

Function .onInit
  !insertmacro MUI_LANGDLL_DISPLAY
  ReadRegStr $0 HKLM "${PRODUCT_UNINST_KEY}" "UninstallString"  
  
  ${If} $0 != ""  
    MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "系统需移除之前版本 ${PRODUCT_NAME} 后继续安装,确定移除之前版本?" IDNO +4
    System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1'
    WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "InstallString" "$R0"
    Exec "$0"
    Abort
  ${EndIf} 
FunctionEnd

若卸载时软件正在运行,则终止卸载并提示关闭软件

FindProcDLL::FindProc "Client.exe"
Pop $R0
IntCmp $R0 1 0 no_run
MessageBox MB_ICONSTOP "卸载程序检测到 ${PRODUCT_NAME} 正在运行,请关闭之后再卸载!"
Quit

附录:NSIS用户手册

摘录自https://www.nsisfans.com/help/index.html。其中一些我用到的和我以为能用到的知识点。

NSIS脚本

脚本格式

命令

命令行的格式为: '命令 [参数]'

File "myfile"

注释

3种注释方式。“;xxx”、“# xxx”、“/* xxx */”

; 第1种注释方式

# 第2种注释方式

/* 
第3种注释方式
*/

插件

要调用一个插件,使用 '插件::命令 [参数]'。

;调用插件nsExec的命令Exec,参数是“我的文件”。应该是这个意思
nsExec::Exec "我的文件"

数字

十进制:正常

十六进制:0x123

八进制:0678

; 注意颜色被设置为十六进制 RGB 形式,像 HTML 那样但是没有 # 开头。
SetCtlColors $HWND CCCCCC

字符串

MessageBox MB_OK "I'll be happy" ; 把一个 ' 放在字符串里
MessageBox MB_OK 'And he said to me "Hi there!"' ; 把一个 " 放在字符串里
MessageBox MB_OK `And he said to me "I'll be happy!"` ; 这里把 ' 和 " 都放到了字符串里
MessageBox MB_OK "$\"A quote from a wise man$\" said the wise man" ; 这里演示了跳过引号的解析。说实话没懂啥意思,跳过啥了?$\"代表双引号?

变量

变量以 $ 开头。用户变量应该 (不是必须) 事先被声明并且区分大小写。

Var MYVAR
StrCpy $MYVAR "变量值"

长命令

要把命令扩充为多行,需要在行尾使用反斜杠 \ ,下一行会被自动连接到上一行的尾部。

也可用于注释,容易混淆,不建议使用

例如:

CreateShortCut "$SMPROGRAMS\NSIS\ZIP2EXE project workspace.lnk" \
    "$INSTDIR\source\zip2exe\zip2exe.dsw"

MessageBox MB_YESNO|MB_ICONQUESTION \
    "是否要删除文件夹中的所有文件? \
    (如果你想保留任何你自己创建的文件 \
     请点击[否])" \
    IDNO NoRemoveLabel

# 一个注释\
    在这里仍然是注释...

配置文件

如果在 NSIS 的配置目录下存在 "nsisconf.nsh" ,那么它里面的代码默认将会被包含在任何脚本里(除非你使用了 /NOCONFIG 命令参数)。在 Window 平台下配置目录就是 makensis.exe 所在的目录,其它平台下根据安装时的设置有所不同而默认为 $PREFIX/etc/ 。你可以在运行时替换它。

没懂啥意思

变量

所有的变量都是全局的并且可以用于区段和函数。需要注意的是,在默认情况下变量被限制在 1024字节。要扩大这个限制你需要重新构建一个使用了更大 NSIS_MAX_STRLEN值的 NSIS 或使用特别版本。

这个变量是啥?和上面那个!define定义的“符号”有啥区别?

用户变量

$VARNAME

用户变量可以用Var命令来声明。你可以使用这些变量来保存值,用于字符串操作等等。

Var

语法:Var [/GLOBAL] 变量名

声明一个用户变量。变量名允许的字符: [a-z] [A-Z] [0-9] 和 '_'。 所有定义的变量都是全局的,即使在区段或函数内定义。 要使它表达更清楚一些,区段或函数内定义的变量必须使用 /GLOBAL 标记。在区段和函数之外不需要 /GLOBAL 标记。

;定义用户变量;区段外的,自然是全局
Var example

Function testVar
  ;定义用户变量;区段内的,必须使用/GLOBAL来标识它也是全局的
  Var /GLOBAL example2
  ;使用用户变量
  StrCpy $example "example value"
  StrCpy $example2 "another example value"
FunctionEnd

其他可写的变量

$0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $R0, $R1, $R2, $R3, $R4, $R5, $R6, $R7, $R8, $R9

寄存器。这些变量可以像用户变量一样使用,但常用于公用函数或宏。你不需要声明这些变量,所以当你在公用代码里使用他们的时候不能有任何名字冲突。当在公用代码里使用这些变量的时候,推荐你使用堆栈保存和恢复他们原来的数据。这些变量也可以在插件里传递,因为他们可以被 DLL 插件读取和写入。

$INSTDIR

安装目录 ($INSTDIR 可以使用 StrCpy、 ReadRegStr、 ReadINIStr 等等来更改。例如在 .onInit 函数里可以用来做高级的检测安装定位)。

注意在卸载程序代码里,$INSTDIR 为卸载程序所在的目录而不是在安装程序里所指定的目录。例如, 如果你把卸载程序放在 $WINDIR 里并且用户没有移动它,那么在卸载程序里 $INSTDIR 就等于 $WINDIR。如果你要把卸载程序放到另外的位置,那么你应该先把安装程序的 $INSTDIR 值写入注册表或者其它容易保存的地方,然后在卸载程序里读取该值并赋值给卸载程序里的 $INSTDIR

$OUTDIR

当前输出目录 (通过 SetOutPath 或者通过 StrCpy、 ReadRegStr、 ReadINIStr 等等来修改)

$CMDLINE

安装程序命令行。命令行的格式可以是下列之一:

  • "完整路径\安装程序.exe" 参数1 参数2 参数3
  • 安装程序.exe 参数1 参数2 参数3
  • 对于解析“参数”部分,参阅 GetParameters。如果在命令行里指定了 /D= (用来跳过安装路径的选择),那么 /D= 后面的参数将不会被保存在 $CMDLINE (前面的可以保存)。
$LANGUAGE

当前使用的语言标识符。例如,英语是 1033。你可以在 .onInit 里更改此变量。

常量

常量通常用在 InstallDir属性里。

需要注意的是一些新的常量并不是在所有的 OS上都是正常的。例如 $CDBURN_AREA 仅在 Windows XP及以上系统中才正常。如果在 Windows 98 中使用将会得到空值。除非特别提示,否则该常量都是在所有 OS上有效的。

一些常用常量
  • $PROGRAMFILES:程序文件目录(通常为 C:\Program Files但是运行时会检测)。
  • $COMMONFILES:公用文件目录。这是应用程序共享组件的目录(通常为 C:\Program Files\Common Files但是运行时会检测)。
  • $DESKTOP:Windows 桌面目录(通常为 C:\windows\desktop但是运行时会检测)。该常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。
  • $EXEDIR:安装程序运行时的位置。(从技术上来说你可以修改改变量,但并不是一个好方法)。
  • ${NSISDIR}:包含 NSIS 安装目录的一个标记。在编译时会检测到。常用于在你想调用一个在 NSIS 目录下的资源时,例如:图标、界面……
  • $WINDIR:Windows 目录(通常为 C:\windows或 C:\winnt 但在运行时会检测)
  • $SYSDIR:Windows 系统目录(通常为 C:\windows\system或 C:\winnt\system32 但在运行时会检测)
  • $TEMP:系统临时目录(通常为 C:\windows\temp但在运行时会检测)
  • $STARTMENU:开始菜单目录(常用于添加一个开始菜单项,使用 CreateShortCut)。该常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
  • $SMPROGRAMS:开始菜单程序目录(当你想定位 $STARTMENU\程序时可以使用它)。该常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。
  • $SMSTARTUP:开始菜单程序/启动目录。该常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。
  • $QUICKLAUNCH:在 IE4 活动桌面及以上的快速启动目录。如果快速启动不可用,仅仅返回和 $TEMP一样。
  • $DOCUMENTS:文档目录。一个当前用户典型的路径形如 C:\Documents and Settings\Foo\My Documents。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext 设置。默认为当前用户。该常量在 Windows 95且 Internet Explorer 4 没有安装时无效。
  • $SENDTO:该目录包含了“发送到”菜单快捷项。
  • $RECENT:该目录包含了指向用户最近文档的快捷方式。
  • $FAVORITES:该目录包含了指向用户网络收藏夹、文档等的快捷方式。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。该常量在 Windows 95 且 Internet Explorer 4没有安装时无效。
  • $MUSIC:用户的音乐文件目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。该常量仅在 Windows XP、ME及以上才有效。
  • $PICTURES:用户的图片目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。该常量仅在 Windows 2000、XP、ME及以上才有效。
  • $VIDEOS:用户的视频文件目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。该常量仅在 Windows XP、ME及以上才有效。
  • $NETHOOD:该目录包含了可能存在于我的网络位置、网上邻居文件夹的链接对象。该常量在 Windows 95且 Internet Explorer 4 和活动桌面没有安装时无效。
  • $FONTS:系统字体目录。
  • $TEMPLATES:文档模板目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。
  • $APPDATA:应用程序数据目录。当前用户路径的检测需要 Internet Explorer 4及以上。所有用户路径的检测需要 Internet Explorer 5 及以上。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。该常量在 Windows 95 且 Internet Explorer 4和活动桌面没有安装时无效。
  • $LOCALAPPDATA:本机应用程序数据目录。该常量仅在 Windows 2000或以上系统有效。.
  • $PRINTHOOD:该目录包含了可能存在于打印机文件夹的链接对象。该常量在 Windows 95和 Windows 98 上无效。
  • $INTERNET_CACHE:Internet Explorer的临时文件目录。该常量在 Windows 95 和 Windows NT且 Internet Explorer 4 和活动桌面没有安装时无效。
  • $COOKIES:Internet Explorer的 Cookies 目录。该常量在 Windows 95和 Windows NT 且 Internet Explorer 4和活动桌面没有安装时无效。
  • $HISTORY:Internet Explorer的历史记录目录。该常量在 Windows 95 和 Windows NT且 Internet Explorer 4 和活动桌面没有安装时无效。
  • $PROFILE:用户的个人配置目录。一个典型的路径如 C:\Documents and Settings\Foo。该常量在 Windows 2000及以上有效。
  • $ADMINTOOLS:一个保存管理工具的目录。这个常量的内容(所有用户或当前用户)取决于 SetShellVarContext设置。默认为当前用户。该常量在 Windows 2000、ME及以上有效。
  • $RESOURCES:该资源目录保存了主题和其他 Windows资源(通常为 C:\Windows\Resources但在运行时会检测)。该常量在 Windows XP及以上有效。
  • $RESOURCES_LOCALIZED:该本地的资源目录保存了主题和其他 Windows资源(通常为 C:\Windows\Resources\1033但在运行时会检测)。该常量在 Windows XP及以上有效。
  • $CDBURN_AREA:一个在烧录 CD 时储存文件的目录。该常量在 Windows XP 及以上有效。
  • $HWNDPARENT:父窗口的十进制窗口句柄。
  • $PLUGINSDIR:该路径是一个临时目录,当第一次使用一个插件或一个调用 InitPluginsDir时被创建。该文件夹当安装程序退出时会被自动删除。这个文件夹的用意是用来保存给 InstallOptions使用的 INI 文件、启动画面位图或其他插件运行需要的文件。
字符串里的常量
  • $$:转义,用来表示 $。
  • $\r:用来表示一个回车(\r)。
  • $\n:用来表示新的一行(\n)。
  • $\t:用来表示一个 Tab(\t)。

跳转

标记

标记是 Goto 指令的目标,

或各种分支指令 (例如 IfErrors、 MessageBox、 IfFileExists, 和 StrCmp) 的目标。

标记必须存在于一个区段或函数里。标记是局限于该范围里的,这意味着这些指令只能访问和它们同处于一个区段或函数的标记。

声明标记,不能以 -, +, !, $, 或 0-9开头。 当给多个需要指定标记的指令指定了标记,不要忘了使用空字串 ("") 或 0 来表示下一个指令(表示无 Goto 跳转)。一个标记以句点 (.) 开头时表示这是一个全局的标记,你可以从任何区段或函数直接跳转到它上面(但是你不能从一个安装程序跳转到一个卸载程序的全局标记,反之亦然)。

声明标记很简单

MyLabel:

Goto MyLabel

相对跳转

和标记不一样,相对跳转就如其名一样,相对于被调用的地方进行跳转。在任何你可用到标记的地方你也都可以相对跳转。

相对跳转由数字标定。+1 跳转到下一条指令(默认的步进),+2 会跳过1条指令也并且从当前指令转到第2条指令,-2 将往后跳2条指令,+10 将会跳过 9 条指令,从当前指令跳到第10条指令。

一条指令就是在安装程序运行时可以被执行的所有命令。

MessageBox、 Goto、 GetDLLVersion、 FileRead、 SetShellVarContext 此类是指令.

AddSize、 Section、 SectionGroup、 SectionEnd、 SetOverwrite (和所有处于编译器标记)、 Name、 SetFont、 LangString 都不是指令,因为它们在编译时就被执行。

;例子1,很简单
Goto +2
MessageBox MB_OK "你将看不到这个消息框"
MessageBox MB_OK "上一条信息被略过,这条信息才会被显示"

;例子2,这里是会显示 "随后的这条信息将会被略过" 这句吗?没给答案额
;Goto +4 的结果是 Goto -3
Goto +4
MessageBox MB_OK "随后的这条信息将会被略过"
Goto +3
MessageBox MB_OK "你将不会看到这个消息框"
Goto -3
MessageBox MB_OK "完成"

需要注意的是 宏指令 并不是真正意义上的一条指令,在编译时会被展开为若干条指令,所以你不能用相对跳转来跳过一条宏指令。下面的示例演示了相对跳转并没有真正跳过插入的宏 (仅仅是跳过了宏里的第一条指令),还是会显示一个消息框。

!macro relative_jump_test
  MessageBox MB_OK "第一个宏行first macro line"
  MessageBox MB_OK "第二个宏行second macro line"
!macroend

Goto +2
!insertmacro relative_jump_test
;这里应该是显示 "第二个宏行second macro line" 这句

页面

这里用脚本向导生成的脚本,页面都是调用的宏。本身对安装,卸载也没啥要求,默认就行。暂时不需要改动。

区段

每一个 NSIS 安装程序包含一个或多个区段。所有的这些区段都使用下面的这些命令来创建、修改和结束。

  • 每一个区段包含零个或多个指令。
  • 区段是安装程序执行的实体,并且如果设置了 ComponentText ,那么用户就可以选择禁止或允许每一个可见的区段。
  • 如果一个区段名为 'Uninstall' 或以 'un.' 为前缀,那么它就是一个卸载程序区段。

区段命令

AddSize

大小(单位为:KB)

告诉安装程序当前的区段需要一个额外的 "大小" KB 磁盘空间。仅在一个区段里有效(在区段外或函数里无效)。

Section SectionEnd

语法:Section [/o] [([!]|[-])区段名] [区段索引输出] xxx SectionEnd

Section 开始并且打开一个新的区段。

如果区段名为空、遗漏或者以一个 -开头,那么它将是一个隐藏的区段,用户也不能选择禁止它。

如果一个区段名为“Uninstall”或以“un.”为前缀,那么它就是一个卸载程序区段。

如果指定了区段索引输出,该参数将被定义 (!defined)为区段索引(然后可以对它使用 SectionSetText等)。

如果区段名以一个 !开头,那么该区段的显示名称将以粗体字显示。如果指定了 /o 开关,则该区段默认为不选。

SectionEnd 关闭当前打开的区段。

Section "-隐藏区段"
SectionEnd

Section # 隐藏区段
SectionEnd

Section "!描黑区段"
SectionEnd

Section /o "可选区段"
SectionEnd

Section "MainXX" SEC_IDX
SectionEnd

若需访问区段索引,必需使用花括号括起来,且区段索引代码必需在区段之后。

Section test1 sec1_id
SectionEnd

Section test2 sec2_id
SectionEnd

Function .onInit
  SectionGetText ${sec2_id} $0
  MessageBox MB_OK "索引 ${sec2_id} 的名称:$\n$0" # 将正确显示 '索引 1 的名称: test2'
FunctionEnd
Function .onInit
  SectionGetText ${sec2_id} $0
  MessageBox MB_OK "索引 ${sec2_id} 的名称:$\n$0" # 将错误显示 '索引 ${sec2_id} 的名称: test1'
    # 加上一个警告说明:
    #   未知 变量/常量 "{sec2_id}" 查看, 忽略
FunctionEnd

;我其实是没太理解为啥后面又有一样的一段。可能是为了介绍这句:“区段索引代码必需在区段之后”。说明放在后面不对?
;Section test1 sec1_id
;SectionEnd

;Section test2 sec2_id
;SectionEnd
SectionIn
SectionGroup SectionGroupEnd

这两感觉也用不上,有个印象。要是有用再看。

卸载区段

一个特别的名为 “Uninstall” 的区段只能是被创建用于产生一个卸载程序。该区段应该用来从系统里移除由安装程序安装的所有文件、注册表健等等。下面是一个简单的卸载区段例子:

;自动生成的脚本是没有双引号的 Section Uninstall。不知道是都行还是哪个错了。有空试试
Section "Uninstall"
  Delete $INSTDIR\Uninst.exe ; 删除自我 (看下面的解释为什么可以这样)
  Delete $INSTDIR\myApp.exe
  RMDir $INSTDIR
  DeleteRegKey HKLM SOFTWARE\myApp
SectionEnd

第一个 Delete指令是可以正常执行的 (删除卸载程序本身),因为执行卸载程序的时候它会复制一个副本到系统临时目录并执行副本来完成卸载。

需要注意的是在卸载程序的代码里, $INSTDIR 包含了卸载程序所在的位置。你不需要在安装程序里给该变量重复赋值。

函数

函数类似于区段因为他们可以包含零个或多个指令。用户函数不会被安装程序直接调用,而必须在区段里使用 Call指令来调用。而当一个必然事件发生时回调函数将由安装程序调用。

函数必须在区段或其他函数之外声明。

函数命令

Function FunctionEnd

语法:Function [函数名] xxxx FunctionEnd

开始并打开一个新的函数。

一般函数名以 .开头的(例如 ".Whatever")作为回调函数保留。函数名以 un.开头的函数将会被创建在卸载程序里。因此,普通安装区段和函数不能调用卸载函数,而卸载区段和卸载函数也不能调用普通安装程序的函数。

Function myfunc
  # 一些命令
FunctionEnd

Section
  Call myfunc
SectionEnd

回调函数

安装回调
.onInit

该回调将会在当安装程序接近完成初始化时调用。如果在 '.onInit' 函数调用了 Abort,则安装程序立即退出。

 Function .onInit
   MessageBox MB_YESNO "即将安装。继续?" IDYES NoAbort
   Abort ; 使得安装程序退出。
   NoAbort:
 FunctionEnd

 Function .onInit
   ReadINIStr $INSTDIR $WINDIR\wincmd.ini Configuration InstallDir
   StrCmp $INSTDIR "" 0 NoAbort
   MessageBox MB_OK "未找到 Windows Commander 。未能取得安装路径。"
   Abort ; 使得安装程序退出。
   NoAbort:
 FunctionEnd

这写询问框是什么个语法结构。

卸载回调
un.onInit

该回调将会在当卸载程序接近完成初始化时调用。如果 'un.onInit' 函数使用了 Abort ,则卸载程序立即退出。注意如果需要时该函数可以验证和(或)修改 $INSTDIR 。

Function un.onInit
    MessageBox MB_YESNO "即将卸载。继续?" IDYES NoAbort
    Abort ;使得卸载程序退出。
    NoAbort:
FunctionEnd

Function un.onInit
    IfFileExists $INSTDIR\myfile.exe found
    Messagebox MB_OK "卸载路径不正确"
    Abort
    found:
FunctionEnd

安装程序属性

常规属性

Name

语法:Name名称 [双与名称](Name不区分大小写,Name,name都可以)

设置安装程序的名称。名称通常用来显示产品的名称比如“我的程序””。如果在名称里有一个或多个与符号(&),把第二个参数设为与第一个相同,有 &的地方使用两个 &来表示。例如你的产品名称为“foo & bar2000”,那么使用:

Name "foo & bar2000" "foo && bar2000"

如果你的安装程序名称里有 & 符号并且对名称使用了一个 LangString,那么你还需要创建另一个含有双 &字符的名称作为第二个参数。

接受变量。如果使用了变量,使用的变量必须在 .onInit被初始化。

;正常起名就1个参数就可以,但如果你的名字里有“&”,你需要2个参数,第二个参数将你名字种的“&”变为"&&"
;正常名字
Name "MyName"
;带有"&"的名字
Name "MyName&YourName" "MyName&&YourName"
;带参数的名字
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"

还有一种使用了LangString的名字。LangString什么意思?

LangString和多语言使用相关,如:LangString INFO1 ${LANG_ENGLISH} " Prompt message 1"。估计不太用得到,这里就不看了。

OutFile

语法:OutFile [路径\]安装程序.exe(也不区分大小写)

指定 MakeNSIS 要写入安装程序的输出文件。仅仅是 MakeNSIS 要写入的文件,它不会对安装程序的内容有任何影响。

就是脚本编译后生成的那个安装包的名字。

OutFile "Setup.exe"
InstallDir

设定安装包默认的安装目录。

查看常量一节得到可用于该字串的变量(尤其是 $PROGRAMFILES)。注意该字串中最后一个 \后面的部分会被保留,当用户在安装时选择“浏览”并取得新的目录字串时,该部分会自动附加在后面(要禁止该行为你需要在目录字串后面添加一个额外的 \,但是你要使用引号把目录字串括起来以避免被认为是断行处理)。如果没有起作用的话,还是使用浏览按钮吧。

InstallDir "$PROGRAMFILES\signjing安装示例"
InstallDirRegKey

根键子键键名该属性让安装程序去检测一注册表里的一个字串,如果该字串可用那么把它用来作为安装目录。如果预置了该属性,当指定的注册表键可用时它会越过 InstallDir指定的字串,否则使用默认的 InstallDir 指定值。

查询注册表时,该命令将自动截去引号(如 "C:\program files\poop" "%1",可得到 C:\program files\poop )。

如果该字串以 .exe结尾,它还会自动移去字串里的文件名部分(如 C:\program files\poop\poop.exe,将得到 C:\program files\poop )。

对于更多高级的安装目录配置,可在 .onInit函数里设定 $INSTDIR。

[译者注:事实上它还可以自动截取如 "C:\program files\poop\poop.exe" "%1"而得到 C:\program files\poop ,这样你只要知道了某个程序关联的文件类型,就可以直接用它获得该程序的安装目录。比如 InstallDirRegKey HKCR "FlashGet.Document\shell\open\command" "" 即可获得网际快车的安装目录]

说了一大堆就没怎么看懂

这个根键子键下面还有,我也不知道是根据子键打错了,还是什么专有名词

在网上找的另一个解释,感觉更容易理解一点。好像就是通过注册表获取安装路径。

InstallDirRegKey只从注册表中读取,它从不写入。 在执行.onInit之前,NSIS会执行以下操作:

  1. 如果设置了InstallDir,则该路径将复制到$Instdir
  2. 如果设置了InstallDirRegKey并且存在注册表项,则注册表中的路径(删除文件名)将复制到$Instdir

因此,如果您希望InstallDirRegKey在下次用户运行安装程序时产生任何影响,您必须将其指向安装程序在您的某个部分中创建的密钥。它可以是卸载密钥中的UninstallString命令,也可以是HKLKM \Software\My Company\My App等特定于应用程序的密钥。 这一点的重点是,当用户重新安装或安装新版本的应用程序时,它将安装在同一文件夹中(覆盖/升级现有安装)。

;大概。没找到详细的语法结构:
InstallDirRegKey HKLM "${PRODUCT_UNINST_KEY}" "UninstallString"
ShowInstDetails

语法:ShowInstDetails hide|show|nevershow

设置是否显示安装详细信息。你可以设为 hide来隐藏详细信息但用户可以查看,show 用来默认显示详细信息,nevershow可以阻止用户查看任何信息。注意区段里可以使用 SetDetailsView 来更改它的设置。

ShowUninstDetails

语法:showUnInstDetails hide|show|nevershow

设置是否显示卸载详细信息。你可以设为 hide来隐藏详细信息但用户可以查看,show 用来默认显示详细信息,nevershow可以阻止用户查看任何信息。注意区段里可以使用 SetDetailsView 来更改它的设置。

BrandingText

语法:BrandingText Context/TRIM(LEFT|RIGHT|CENTER)

文本设置显示在安装程序窗口底部的文本(默认为“Nullsoft Install System vX.XX”)。如果设为空字串("")则使用默认值;设为" "(一个空格)则显示空白。如果你不知道哪个适合你,那就保留默认值吧,这样可以使每个人知道你使用的 NSIS版本。使用 /TRIMLEFT、/TRIMRIGHT或 /TRIMCENTER 来裁剪控制大小到字串大小。

编译器标记

SetOverwrite

语法:SetOverwrite on|off|try|ifnewer|ifdiff|lastused

该命令设置了由 File 命令使用的覆盖标记来决定目标文件已存在时是否覆盖。如果覆盖标记为 on ,则目标文件被覆盖(这个是默认值)。如果覆盖标记为 off ,则已存在的文件不会被覆盖。如果覆盖标记为 try ,文件仅当可以被覆盖时(就是说假如文件不能写入,它会自动略过而不需要用户决定)才会覆盖目标文件。如果覆盖标记为 ifnewer,则仅当已存在的文件比新文件旧时才会覆盖目标文件。如果覆盖标记为 ifdiff ,则仅当已存在的文件比新文件旧或新时才会覆盖目标文件。注意在 ifnewer 或 ifdiff模式下,目标文件的日期都会被设为新文件的日期,而不管 SetDateSave 是怎么设置的。

版本信息

指令

NSIS 用于脚本的这些指令稍微的近似于 PHP 和汇编。它们没有真正的高级语言结构,但是他们的指令(大多数情况下)却是高级的,并且你可以很容易的掌握(比如你不用担心字串的连接等等)。基本上你有 25 寄存器 (20 个常规用途, 5个特殊用途),和一个堆栈。

不知道这个“基本上你有 25 寄存器 (20 个常规用途, 5个特殊用途),和一个堆栈”这句具体啥意思。这怎么地也不止25个啊。是只有25差不过常用的?

基本指令

Delete

语法:Delete [/REBOOTOK] 文件

从目标系统删除文件(可以是文件或通配符,但必须指定一个完整的路径)。

如果指定了 /REBOOTOK并且该文件当前不可删除,则会在系统重启时删除该文件 -- 如果该文件要在系统重启时删除,你还要设置一个重启的标记。如果找到的文件不能被删除则会置一个错误标记。但该错误标记不是为尝试删除一个不存在的文件设置的。

RMDir

语法:RMDir [/r] [/REBOOTOK] 目录名

删除指定的目录(完整路径)。

没有 /r参数时只有在目录为空时才会被删除。如果指定了 /r,则目录会被递归删除,所有在指定目录下的所有文件和目录均被删除。如果指定了 /REBOOTOK,任何当前不能删除的文件或目录将会在重启后被删除——如果文件或目录需要在重启时被删除,会放置一个重启的标记。当文件或目录不能被删除时放置一个错误的标记。

RMDir $INSTDIR
RMDir $INSTDIR\data
RMDir /r /REBOOTOK $INSTIDR
RMDir /REBOOTOK $INSTDIR\DLLs

;需要注意的是当前的工作目录不能删除。当前的工作目录由 SetOutPath设定。例如,下面的例子里将不能删除该目录。
SetOutPath $TEMP\dir
RMDir $TEMP\dir
;而下面的例子可以删除该目录。
SetOutPath $TEMP\dir
SetOutPath $TEMP
RMDir $TEMP\dir

警告: 在卸载程序中使用 RMDir /r $INSTDIR 是不安全的。虽然用户不太可能会选择将程序安装到 Program Files 文件夹中,但假如这样的话,这个命令将会递归删除整个 Program Files 文件夹,包括其他和卸载程序没有任何关系的程序(目录)。用户还可以安装除了程序文件之外的其他文件并希望它们能被卸载程序删除。可用的解决方案可以轻松地实现只卸载由安装程序释放的唯一文件。

DeleteRegKey

语法:DeleteRegKey [/ifempty] 根键 子键

删除一个注册表键。

如果指定了 /ifempty,则该注册表键仅当它无子键时才会被删除(否则,整个注册表键将被删除)。有效的根键值在后面的 WriteRegStr列出。如果该键不能被删除(或如果它不存在)则会放置一个错误的标记。

;HKLM:根键 , 子健:"Software\My Company\My Software"
DeleteRegKey HKLM "Software\My Company\My Software"
SetOutPath

设置输出路径($OUTFIR)且当路径不存在时创建(需要时会递归创建)。必须为全路径名,通常都使用 $INSTDIR

File

语法:File [/nonfatal] [/a] ([/r] [/x文件|通配符 [...]] (文件|通配符) [...] | /oname=输出路径\文件名输入路径\文件名)

我理解就是需要进行打包的所有文件。而且这明明更像输入路径。翻译成“释放文件”好别扭。感觉提取文件到输出路径更贴切一点。

释放文件到当前输出路径($OUTDIR)。

  • 注意输出文件名是 $OUTDIR\文件名。

  • 如果使用了 /oname=X 开关,则输出文件会变为 $OUTDIR\X。当使用了 /oname=开关时只能指定一个文件,且输出的文件名可以使用变量(或完整路径如 $SYSDIR\whatever.dll)。如果输出名称包含了空格,你需要用双引号把参数括起来,包括 /oname,就像下面例子显示的那样。

  • 支持通配符。

  • 如果使用了 /r ,匹配的文件将会在子目录里被递归的搜索。如果目录名匹配则所有包含的内容都会被递归添加。目录结构也会被保持。

  • 使用 /x 可以用来来排除文件或目录。

  • 如果使用了 /a ,则被添加的文件的属性将会保持。

  • 如果覆盖模式被设定为 try 但是文件不能覆盖,那么 File 命令将会置一个错误标记,或者如果覆盖模式被设定为 on 并且文件不能覆盖并用户选择了忽略时,也会放置一个错误标记。

  • 如果使用了 /nonfatal 且当文件未找到时使用警告来代替错误。

File something.exe
File /a something.exe
File *.exe
File /r *.dat
File /r data
File /oname=$TEMP\temp.dat somefile.ext
File "/oname=$TEMP\name with spaces.dat" somefile.ext
File /nonfatal "一个可能不存在的文件"
File /r /x CVS myproject\*.*
File /r /x *.res /x *.obj /x *.pch source\*.*
Exec

语法:Exec 命令

执行一个指定的程序并且立即继续安装。注意指定的文件必须存在于目标系统而不是编译的系统。 $OUTDIR 用于指定工作路径。如果该命令不能被运行则会置一个错误标记。注意,如果该命令包含空格,你要用引号来把他们包括起来。例如: Exec '"$INSTDIR\command.exe" 参数' 。如果你不用引号括起来则在 Windows 9x下正常或丢失参数。

Exec '"$INSTDIR\someprogram.exe"'
Exec '"$INSTDIR\someprogram.exe" 某些参数'

注册表、INI文件指令

WriteINIStr

语法:WriteINIStr INI文件 区段名 项 值

把“项” =“值”写入“INI文件”的“区段名”区段。如果 INI文件不能写入则放置一个错误的标记。

WriteINIStr $TEMP\something.ini section1 something 123
WriteRegStr

语法:WriteRegStr 根键 子键 项 值

把字符串写入注册表。

WriteRegStr HKLM "Software\My Company\My Software" "String Value" "dead beef"

WriteRegStr和WriteRegExpandStr不知道是啥个关系

拓展WriteRegExpandStr

语法也相同:WriteRegExpandStr 根键 子键 项 值

把字符串写入注册表。 root_key 必须为下面列表之一:

  • HKCRHKEY_CLASSES_ROOT
  • HKLMHKEY_LOCAL_MACHINE
  • HKCUHKEY_CURRENT_USER
  • HKUHKEY_USERS
  • HKCCHKEY_CURRENT_CONFIG
  • HKDDHKEY_DYN_DATA
  • HKPDHKEY_PERFORMANCE_DATA
  • SHCTXSHELL_CONTEXT

如果 根键SHCTXSHELL_CONTEXT, 当 SetShellVarContext 设置为 all 时,它将被替换成HKLM ;当 SetShellVarContext设置为 current 时,它将被替换成 HKCU

如果字符串不能写入注册表,则放置一个错误的标记。字符串的类型为 REG_SZ 对应 WriteRegStr ,或 REG_EXPAND_STR 对应 WriteRegExpandStr 。如果注册表键不存在,则会自动创建。

WriteRegExpandStr HKLM "Software\My Company\My Software" "Expand String Value" "%WINDIR%\notepad.exe"
ReadRegStr

语法:ReadRegStr 用户变量(输出) 根键 子键 项

从注册表读取一个字符串值并输出到用户变量 $x 。有效的根键值在后面的 WriteRegStr 列出。

如果字符串不存在,会放置一个错误标记并把 $x 设为空字符串 ("") 。如果该值存在且为 REG_DWORD ,则会转换为字符串类型并放置一个错误标记。

ReadRegStr $0 HKLM Software\NSIS ""
DetailPrint "NSIS is installed at: $0"

常规用途指令

CreateDirectory

语法:CreateDirectory 要创建的路径

创建 (递归创建)指定的目录。当目录不能创建时会放置一个错误标记。

你也可以指定一个绝对路径。

CreateDirectory $INSTDIR\some\directory
CreateShortCut

语法:CreateShortCut 快捷文件.lnk 目标文件 [参数 [图标文件 [图标索引号 [启动选项 [键盘快捷键 [描述]]]]]]

创建一个指向 '目标文件' 的快捷方式 '快捷文件.lnk' ,可以带 '参数' 参数。

用于快捷方式的图标为 "图标文件,图标索引号" ;要使用默认图标的话把 "图标文件" 和 "图标索引号" 设为空字符串。

"启动选项" 可以是它们之一: SW_SHOWNORMAL, SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED, 或一个空字符串。

"键盘快捷键" 应该为 'flag|c' 格式且 flag 可以联合使用 (使用 |) : ALT, CONTROL, EXT, 或 SHIFT 。c 为要使用的字符 (a-z, A-Z, 0-9, F1-F24, 等等)。注意在这些字串里不能含有空格。一个典型的例子为 "ALT|CONTROL|F8" 。

$OUTDIR 被用来作为工作目录。你可以在创建快捷方式之前使用 SetOutPath 来指定或更改。

"描述" 为快捷方式的描述,或在 XP 下作为注释调用。 当快捷方式不能创建的时会放置一个错误标记(例如: 路径 (链接路径或目标路径) 不存在或一些其它错误)。

CreateShortCut "$SMPROGRAMS\My Company\My Program.lnk" "$INSTDIR\My Program.exe" \
	"some command line parameters" "$INSTDIR\My Program.exe" 2 SW_SHOWNORMAL \
	ALT|CONTROL|SHIFT|F5 "a description"
WriteRegStr

语法:WriteRegStr 根键 子键 项 值

把字符串写入注册表。详细信息请查看 WriteRegExpandStr。

WriteRegStr HKLM "Software\My Company\My Software" "String Value" "dead beef"

流程控制指令

Abort

语法:Abort [用户信息]

取消安装,停止执行脚本,并且在状态显示里显示[用户信息]。

注意:你可以用于回调函数来实现一些特殊功能。页面回调也可以用 Abort 来实现特殊目的。

MessageBox

语法:MessageBox 消息框选项列表 消息框文本 [/SD返回] [返回值 对应的跳转标记] [返回值2 对应的跳转标记2]

显示一个包含“消息框文本”的消息框。“消息框选项列表”必须为下面的一个或多个,多个使用 | 来隔开(例如 MB_YESNO|MB_ICONSTOP)。

MB_OK - 显示 OK 按钮
MB_OKCANCEL - 显示 OK 和取消按钮
MB_ABORTRETRYIGNORE -显示退出、重试、忽略按钮
MB_RETRYCANCEL -显示重试和取消按钮
MB_YESNO - 显示是和否按钮
MB_YESNOCANCEL -显示是、否、取消按钮
MB_ICONEXCLAMATION -显示惊叹号图标
MB_ICONINFORMATION -显示信息图标
MB_ICONQUESTION -显示问号图标
MB_ICONSTOP - 显示终止图标
MB_TOPMOST - 使消息框在最前端显示
MB_SETFOREGROUND -设置前景
MB_RIGHT - 右对齐文本
MB_RTLREADING - RTL阅读次序
MB_DEFBUTTON1 -默认为按钮 1
MB_DEFBUTTON2 -默认为按钮 2
MB_DEFBUTTON3 -默认为按钮 3
MB_DEFBUTTON4 -默认为按钮 4

“返回值”可以为 0(或空,或保留关闭),或下列之一:
IDABORT - 退出按钮
IDCANCEL - 取消按钮
IDIGNORE - 忽略按钮
IDNO - 否按钮
IDOK - OK 按钮
IDRETRY - 重试按钮
IDYES - 是按钮

如果消息框的返回值为对应的“返回值”,则安装程序执行跳转。
用 /SD 来指定一个上面列出的返回值当在安装程序静默安装时作为返回值
;终于明白了。
;NSIS可以设置静默安装、卸载。也就是安装、卸载过程完全不需要用户参与,也没有显示页面。此因此用户无法对这些询问框做出应答。此时使用“/SD”给询问框一个默认进行的选项。

;MessageBox也是纯顺序执行。若设置了[返回值 相应跳转标记]就跳向标记。否则继续向下执行
;如下:例1
;首先MessageBox设置了了[返回值 相应跳转标记],即[返回IDYES,就跳转true标记][返回IDNO,就跳转false标记]
;所以返回IDYES时,跳转true输出“是真的!”然后跳转next继续执行。
;返回IDNO时,跳转false输出"是假的"。
;然后会继续执行后续指令next
;再次设计一个MessageBox,[返回IDNO 就跳转false2标记] [当静默安装,默认返回IDYES],但是没有设置IDYES的对应跳转
;我理解这种情况,返回IDYES时,就应该默认继续执行下一条指令,
;即输出“是真的 (或静默)!”,然后跳转next2
;当返回IDNO,跳转false2,输出“是假的”,然后默认执行下一条指令,即next2:
MessageBox MB_OK "简单消息框"
MessageBox MB_YESNO "真的吗?" IDYES true IDNO false
true:
  DetailPrint "是真的!"
  Goto next
false:
  DetailPrint "是假的"
next:
    MessageBox MB_YESNO "真的吗?(静默安装时默认为是)" /SD IDYES IDNO false2
      DetailPrint "是真的 (或静默)!"
      Goto next2
    false2:
      DetailPrint "是假的"
    next2:

一开始纠结了很久这个标记的范围。后来感觉好像没范围,就是顺序执行。暂时就都这么理解。

文件指令

卸载程序指令

WriteUninstaller

语法:WriteUninstaller [路径\]可执行文件名.exe

由指定的文件名(路径为可选项)写入卸载程序。仅在一个安装区段或函数里有效,并且你的脚本里必须有一个卸载区段。也可以参考卸载配置。你可以调用一次或多次来写入一个或多个卸载程序(副本)。

我理解就是生成执行卸载的那个执行文件。

混合指令

字符串操作指令

堆栈指令

Exch

语法:Exch [用户变量 | 堆栈索引]

当不指定参数时,交换堆栈顶部的两个单元。

当指定了一个参数并且是一个用户变量时,交换堆栈顶部的单元和该变量的值。

当指定了一个参数并且是正整数时,Exch 将会交换堆栈顶部单元和根据参数从堆栈顶部偏移到指定单元的值。

如果堆栈里没有足够的单元来完成交换时,会产生一个致命的错误(来帮助你调试你的代码)。

Push 1
Push 2
Exch
Pop $0 # 输出 1

Push 1
Push 2
Push 3
Push 4
Push 5
Exch 2
Pop $0 # 输出 3

StrCpy $0 1
Push 2
Exch $0 # = 2
Pop $1 # = 1
Pop

语法:Pop 用户变量(输出)

Push

整数支持

重新启动指令

安装记录指令

区段管理

用户界面指令

HideWindow

隐藏安装程序。

Function un.onUninstSuccess
  HideWindow
FunctionEnd
SetAutoClose

语法:SetAutoClose true|false

取代默认的窗口自动关闭标记(由 AutoCloseWindow指定,且对于写在程序为 false)。指定 true将使得安装程序在安装完成时立即关闭窗口,或者 false 来使它需要手动关闭。

多语言指令

编译命令

基础编译命令

!include

语法:!include [/NONFATAL] 文件

这个命令可以将一个文件包含到脚本中,就像是脚本的一部分一样。请注意,如果一个文件包含在了另一个目录中,那么当前目录仍是编译脚本的地方(不是要包含的文件所在的目录)。如果编译器无法找到文件,那么它将会在每一个包含目录中查找。

这怎么看都像机翻。一个文件包含在另一个目录啥意思?没有路径!include默认去哪里找这个文件额,搞不明白。

如果使用了 /nonfatal 的话,当要包含的文件未找到的时候用警告来代替错误。

; MUI 1.67 compatible ------ 导入现代界面的相关文件
!include "MUI.nsh"

预定义

读环境变量

条件编译

编译器有一个定义符号列表,定义符号可以通过 !define 定义或使用 /D 命令行切换。这些定义符号可以用于条件编译 (通过 !ifdef 定义) 或用于符号替换(一种格式简单的宏)。若要用它的值替换一个符号,请使用 ${符号} (如果 "符号" 没有定义,那么不会产生转换)。这个转换为先到先得(first-come-first-served),这意味着如果你做了:

!define 符号1 ${符号2}

如果定义了 "${符号2}" ,那么当出现那一行时,它将会被替换。否则,当 ${符号1} 被引用时,任何替换都会发生。

没懂

与定义、条件编译相关的命令:

!define

语法:!define ([/date|/utcdate] 符号 [值]) | (/math 符号 值1 运算符 值2) | (/file 符号 filename.txt)

这个命令将会向全局定义列表中添加“符号”。这个效果与编译器使用 /D 命令行切换效果相似(只有在 !define 命令之后,定义才有效)。

如果使用了 /date/utcdate,则定义的值会被格式化为 strtime格式。strtime 会把代表当前的时间日期转换为实际的值。例如 %H会转换为当前时间的 24 小时格式。

如果使用了 /math ,把(值1 运算符 值2)的运算结果作为 符号 的定义值。“运算符” 在这里可以是 +,-,*,&,|,^,/ 或 % 。请注意,值1 和 值2 都必须为整型值!

如果使用了 /file ,把指定的整个文本文件(包括空白和新行)读取并填充到 符号 中。

!define USE_SOMETHING
!define VERSION 1.2
!define /date NOW "%H:%M:%S %d %b, %Y"
!define /math RESULT 3 + 10
!define /math REST 15 % ${RESULT}
!define /file BUNCHASTUFF somesourcefile.cpp

这和定义变量有啥区别?编译过程中可用?

!macro !insertmacro

语法: !macro 宏名称 [参数] [……] !insertmacro 宏名称 [参数] [……]

插入一个由 !macro 创建的宏的内容。如果创建的宏带有参数,那么你必须按宏的需求向它传送足够的参数。

调用函数的感觉。

; !macro 定义一个宏
!macro MyPrint text
	DetailPrint "${text}"
!macroend

; !insertmacro 调用这个宏
!insertmacro Print "show these contents"
!if !else !endif

语法:!if [!] 值 [运算符 值2] xxx !endif

语法:!else [if|ifdef|ifndef|ifmacrodef|ifmacrondef [...]]

当这个命令与 !endif 命令组成一对时,将会告诉编译器是否编译含在其两者之间的代码。

如果 为非零 或者 值2 的逻辑运算结果为真,那么所含的代码将会被编译。否则,那些代码将会被跳过。

运算符 可以是 == or != (字符串比较), <=, < > 或 >= (实数比较), && or || (布尔运算)。 如果设置了 [!] ,则逻辑运算结果取非。

当有不同的定义或设置了不同的宏时,!else命令允许轻松插入不同的代码。你可以创建类似 !ifdef/!else/!endif, !ifdef/!else if/!else/!endif 等代码块。

!if 1 < 2
  !echo "1 is smaller than 2!!"
!else if ! 3.1 > 1.99
  !error "this line should never appear"
!else
  !error "neither should this"
!endif
posted @ 2022-02-23 15:38  几个酒菜成这样  阅读(1833)  评论(0编辑  收藏  举报