BLOG ARTICLE nsis 궁금하니? | 15 ARTICLE FOUND

  1. 2008.03.19 GetParameters Usage in NSIS
  2. 2008.03.11 NSIS System Plug-in
  3. 2008.02.13 32비트 설치프로그램에서 64bit 레지스트리 접근 방법
  4. 2008.01.30 Quick Guide to NSIS
  5. 2008.01.29 Macro vs Function

출처: http://forums.winamp.com/showthread.php?s=8c0602674e55e76b00046b80505098ec&threadid=274505

it gets all parameters, you can then use GetOptions to check for specific parameters.

how i do it:

1) includes:

code:
!insertmacro GetParameters !insertmacro GetOptions


2) oninit:
code:
Function .onInit ; Get parameters var /GLOBAL cmdLineParams Push $R0 ${GetParameters} $cmdLineParams ; /? param (help) ClearErrors ${GetOptions} $cmdLineParams '/?' $R0 IfErrors +3 0 MessageBox MB_OK "list all command line options here!" Abort Pop $R0 ; Initialise options Var /GLOBAL option_runProgram Var /GLOBAL option_startMenu Var /GLOBAL option_startMenuAllUsers Var /GLOBAL option_shortcut Var /GLOBAL option_shortcutAllUsers StrCpy $option_runProgram 1 StrCpy $option_startMenu 1 StrCpy $option_startMenuAllUsers 0 StrCpy $option_shortcut 1 StrCpy $option_shortcutAllUsers 0 ; Parse Parameters Push $R0 Call parseParameters Pop $R0 FunctionEnd


3) parseParameters function:
code:
Function parseParameters ; /norun ${GetOptions} $cmdLineParams '/norun' $R0 IfErrors +2 0 StrCpy $option_runProgram 0 ; /nostartmenu ${GetOptions} $cmdLineParams '/nostartmenu' $R0 IfErrors +2 0 StrCpy $option_startMenu 0 ; /starmenuallusers ${GetOptions} $cmdLineParams '/startmenuallusers' $R0 IfErrors +2 0 StrCpy $option_startMenuAllUsers 1 ; /noshortcut ${GetOptions} $cmdLineParams '/noshortcut' $R0 IfErrors +2 0 StrCpy $option_shortcut 0 ; /shortcutallusers ${GetOptions} $cmdLineParams '/shortcutallusers' $R0 IfErrors +2 0 StrCpy $option_shortcutAllUsers 1 FunctionEnd
AND

출처: NSIS System Plug-in

NSIS System Plug-in

© brainsucker (Nik Medved), 2002

Table of Contents

Introduction

The System plug-in gives developers the ability to call any exported function from any DLL. For example, you can use it to call GetLogicalDriveStrings to get a list of available drives on the user's computer.

The System plug-in also allows the developer to allocate, free and copy memory; interact with COM objects and perform mathematical operations on 64-bit integers.

Programming knowledge is highly recommended for good understanding of the System plug-in.

The most useful System plug-in functions (Call, Get and Debug) are not available when compiling with GCC. To work around this, either download a MSVC-compiled version or write your own plugin that calls the functions you need.

Usage Examples From The Wiki

Available Functions

Memory Related Functions

  • Alloc SIZE

    Allocates SIZE bytes and returns a memory address on the stack.

    Usage Example

    System::Alloc 64
    Pop $0
    DetailPrint "64 bytes allocated at $0"
    System::Free $0
    
  • Copy [/SIZE] DESTINATION SOURCE

    Copies SIZE bytes from SOURCE to DESTINATION. If SIZE is not specified, SOURCE's size will queried using GlobalSize. This means that if you don't allocate SOURCE using System::Alloc, System::Call or GlobalAlloc, you must specify SIZE. If DESTINATION is zero it will be allocated and its address will be pushed on the stack.

    Usage example

    # allocate a buffer and put 'test string' and an int in it
    System::Call "*(&t1024 'test string', i 5) i .s"
    Pop $0
    # copy to an automatically created buffer
    System::Copy 0 $0
    Pop $1
    # get string and int in $1 buffer
    System::Call "*$1(&t1024 .r2, i .r3)"
    # free buffer
    System::Free $1
    # print result
    DetailPrint $2
    DetailPrint $3
    # copy to our own buffer
    System::Alloc 1028
    Pop $1
    System::Copy $1 $0
    # get string and int in $1 buffer
    System::Call "*$1(&t1024 .r2, i .r3)"
    # free
    System::Free $0
    System::Free $1
    # print result
    DetailPrint $2
    DetailPrint $3
    
  • Free ADDRESS

    Frees ADDRESS.

    Usage Example

    System::Alloc 64
    Pop $0
    DetailPrint "64 bytes allocated at $0"
    System::Free $0
    
  • Store "OPERATION [OPERATION [OPERATION ...]]"

    Performs stack operations. An operation can be pushing or popping a single register from the NSIS stack or pushing or popping all of the registers ($0-$9 and $R0-$R9) from System's private stack. Operations can be separated by any character.

    Available Operations

    • To push $#, use p#, where # is a digit from 0 to 9.
    • To pop $#, use r#, where # is a digit from 0 to 9.
    • To push $R#, use P#, where # is a digit from 0 to 9.
    • To pop $R#, use R#, where # is a digit from 0 to 9.
    • To push $0-$9 and $R0-$R9 to System's private stack, use s or S.
    • To pop $0-$9 and $R0-$R9 from System's private stack, use l or L.

    Note that the System's private stack will be lost when the System plug-in is unloaded from NSIS. If you want to use it, you must keep the System plug-in loaded into NSIS. To do that, use SetPluginUnload or the /NOUNLOAD flag in the NSIS script.

    Usage Examples

    StrCpy $0 "test"
    System::Store "p0"
    Pop $1
    DetailPrint "$0 = $1"
    
    StrCpy $2 "test"
    System::Store "p2 R2"
    DetailPrint "$2 = $R2"
    
    StrCpy $3 "test"
    System::Store /NOUNLOAD "s"
    StrCpy $3 "another test"
    System::Store "l"
    DetailPrint $3
    
    System::Store "r4" "test"
    DetailPrint $4
    

Calling Functions

  • Call PROC [( PARAMS ) [RETURN [? OPTIONS]]]
  • Get PROC [( PARAMS ) [RETURN [? OPTIONS]]]

    Call and get both share a common syntax. As the names suggest, Call calls and Get gets. What does it call or get? It depends on PROC's value.

    PARAMS is a list of parameters and what do to with them. You can pass data in the parameters and you can also get data from them. The parameters list is separated by commas. Each parameter is combined of three values: type, source and destination. Type can be an integer, a string, etc. Source, which is the source of the parameter value, can be a NSIS register ($0, $1, $INSTDIR), the NSIS stack, a concrete value (5, "test", etc.) or nothing (null). Destination, which is the destination of the parameter value after the call returns, can be a NSIS register, the NSIS stack or nothing which means no output is required. Either one of source or destination can also be a dot (`.') if it is not needed.

    RETURN is like a single parameter definition, but source is only used when creating callback functions. Normally source is a dot.

    OPTIONS is a list of options which control the way System plug-in behaves. Each option can be turned off by prefixing with an exclamation mark. For example: ?!e.

    PARAMS, RETURN and OPTIONS can be repeated many times in one Get/Call line. When repeating, a lot can be omitted, and only what you wish to change can be used. Type, source and/or destination can be omitted for each parameter, even the return value. Options can be added or removed. This allows you to define function prototypes and save on some typing. The last two examples show this.

    PROC can also be repeated but must be prefixed with a hash sign (`#').

    Possible PROC Values and Meanings

    Value Meaning Example
    DLL::FUNC FUNC exported from DLL user32::MessageBox
    ::ADDR Function located at ADDR see below
    *ADDR Structure located at ADDR see below
    * New structure see below
    IPTR->IDX Member indexed IDX from
    interface pointed by IPTR
    see below
    <nothing> New callback function see below
    PROC PROC returned by Get see below

    Available Parameter Types

    Type Meaning
    v void (generally for return)
    i int (includes char, byte, short, handles, pointers and so on)
    l large integer, int64
    t text, string (pointer to first character)
    w WCHAR text, Unicode string
    g GUID
    k callback
    &vN N bytes padding (structures only)
    &iN integer of N bytes (structures only)
    &l structure size (structures only)
    &tN N bytes of text (structures only)
    &wN N bytes of Unicode text (structures only)
    &gN N bytes of GUID (structures only)

    Additionally, each type can be prefixed with an asterisk to denote a pointer. When using an asterisk, the System plug-in still expects the value of the parameter, rather than the pointer's address. To pass a direct address, use `i' with no asterisk. A usage example is available. Alloc returns addresses and its return value should therefore be used with `i', without an asterisk.

    Available Sources and Destinations

    Type Meaning
    . ignored
    number concrete hex, decimal or octal integer value. several integers can be or'ed using the pipe symbol (`|')
    'string'
    "string"
    `string`
    concrete string value
    r0 through r9 $0 through $9 respectively
    r10 through r19
    R0 through R9
    $R0 through $R9 respectively
    c $CMDLINE
    d $INSTDIR
    o $OUTDIR
    e $EXEDIR
    a $LANGUAGE
    s NSIS stack
    n null for source, no output required for destination

    Callbacks

    Callback functions are simply functions which are passed to a function and called back by it. They are frequently used to pass a possibly large set of data item by item. For example, EnumChildWindows uses a callback function. As NSIS functions are not quite regular functions, the System plug-in provides its own mechanism to support callback functions. It allows you to create callback functions and notifies you each time a callback function was called.

    Creation of callback functions is done using Get and the callback creation syntax. As you will not call the callbacks yourself, the source of the parameters should be omitted using a dot. When the callback is called, the destination of the parameters will be filled with the values passed on to the callback. The value the callback will return is set by the source of the return "parameter". The destination of the return "parameter" should always be set as that's where System will notify you the callback was called.

    System::Get "(i .r0, i .r1) iss"

    To pass a callback to a function, use the k type.

    System::Get "(i .r0, i .r1) isR0"
    Pop $0
    System::Call "dll::UseCallback(k r0)"

    Each time the callback is called, the string callback#, where # is the number of the callback, will be placed in the destination of the return "parameter". The number of the first callback created is 1, the second's is 2, the third's is 3 and so on. As System is single threaded, a callback can only be called while calling another function. For example, EnumChildWindows's callback can only be called when EnumChildWindows is being called. You should therefore check for callback# after each function call that might call your callback.

    System::Get "(i .r0, i .r1) isR0"
    Pop $0
    System::Call "dll::UseCallback(k r0)"
    StrCmp $R0 "callback1" 0 +2
    DetailPrint "UseCallback passed ($0, $1) to the callback"
    

    After you've processed the callback call, you should use Call, passing it the value returned by Get - the callback. This tells System to return from the callback. Destination of the return "parameter" must be cleared prior to calling a function, to avoid false detection of a callback call. If you've specified a source for the return "parameter" when the callback was created, you should fill that source with the appropriate return value. Callbacks are not automatically freed, don't forget to free it after you've finished using it.

    SetPluginUnload alwaysoff
    System::Get "(i .r0, i .r1) isR0"
    Pop $0
    System::Call "dll::UseCallback(k r0)"
    loop:
    	StrCmp $R0 "callback1" 0 done
    	DetailPrint "UseCallback passed ($0, $1) to the callback"
    	Push 1 # return value of the callback
    	StrCpy $R0 "" # clear $R0 in case there are no more callback calls
    	System::Call $0 # tell system to return from the callback
    	Goto loop
    done:
    SetPluginUnload manual
    System::Free $0
    

    A complete working example is available in the usage examples section.

    Notes

    • To find out the index of a member in a COM interface, you need to search for the definition of this COM interface in the header files that come with Visual C/C++ or the Platform SDK. Remember the index is zero based.
    • Always remember to use NSIS's /NOUNLOAD switch or SetPluginUnload when working with callbacks. The System plug-in will not be able to process the callback calls right if it's unloaded.
    • If a function can't be found, an `A' will be appended to its name and it will be looked up again. This is done because a lot of Windows API functions have two versions, one for ANSI strings and one for Unicode strings. The ANSI version of the function is marked with `A' and the Unicode version is marked with `W'. For example: lstrcpyA and lstrcpyW.

    Available Options

    Option Meaning
    c cdecl calling convention (the stack restored by caller). By default stdcall calling convention is used (the stack restored by callee).
    r Always return (for GET means you should pop result and proc, for CALL means you should pop result (at least)). By default result is returned for errors only (for GET you will pop either error result or right proc, and for CALL you will get either your return or result at defined return place).
    n No redefine. Whenever this proc will be used it will never be redefined either by GET or CALL. This options is never inherited to children.
    s Use general Stack. Whenever the first callback defined the system starts using the temporary stacks for function calls.
    e Call GetLastError() after procedure end and push result on stack.
    u Unload DLL after call (using FreeLibrary, so you'll be able to delete it for example).

    Usage Examples

    System::Call "user32::MessageBox(i $HWNDPARENT, t 'NSIS System Plug-in', t 'Test', i 0)"
    
    System::Call "kernel32::GetModuleHandle(t 'user32.dll') i .s"
    System::Call "kernel32::GetProcAddress(i s, t 'MessageBoxA') i .r0"
    System::Call "::$0(i $HWNDPARENT, t 'GetProcAddress test', t 'NSIS System Plug-in', i 0)"
    
    System::Get "user32::MessageBox(i $HWNDPARENT, t 'This is a default text', t 'Default', i 0)"
    Pop $0
    System::Call "$0"
    
    System::Get "user32::MessageBox(i $HWNDPARENT, t 'This is a default text', \
    	t 'Default', i 0x1|0x10)"
    Pop $0
    System::Call "$0(, 'This is a System::Get test', 'NSIS System Plug-in',)"
    
    System::Call "advapi32::GetUserName(t .r0, *i ${NSIS_MAX_STRLEN} r1) i.r2"
    DetailPrint "User name - $0"
    DetailPrint "String length - $1"
    DetailPrint "Return value - $2"
    
    System::Alloc 4
    Pop $0
    System::Call "*$0(i 5)"
    System::Call "*$0(i .r1)"
    DetailPrint $1
    
    System::Call "*(i 5) i .r0"
    System::Call "*$0(i .r1)"
    DetailPrint $1
    
    # defines
    !define CLSCTX_INPROC_SERVER 1
    !define CLSID_ActiveDesktop {75048700-EF1F-11D0-9888-006097DEACF9}
    !define IID_IActiveDesktop {F490EB00-1240-11D1-9888-006097DEACF9}
    # create IActiveDesktop interface
    System::Call "ole32::CoCreateInstance( \
    	g '${CLSID_ActiveDesktop}', i 0, \
    	i ${CLSCTX_INPROC_SERVER}, \
    	g '${IID_IActiveDesktop}', *i .r0) i.r1"
    StrCmp $1 0 0 end
    # call IActiveDesktop->GetWallpaper
    System::Call "$0->4(w .r2, i ${NSIS_MAX_STRLEN}, i 0)"
    # call IActiveDesktop->Release
    System::Call "$0->2()"
    # print result
    DetailPrint $2
    end:
    
    InitPluginsDir
    SetOutPath $PLUGINSDIR
    File MyDLL.dll
    System::Call "MyDLL::MyFunc(i 5) ? u"
    Delete $PLUGINSDIR\MyDLL.dll
    
    SetPluginUnload alwaysoff
    System::Get "(i.r1, i) iss"
    Pop $R0
    System::Call "user32::EnumChildWindows(i $HWNDPARENT, k R0, i) i.s"
    loop:
    	Pop $0
    	StrCmp $0 "callback1" 0 done
    	System::Call "user32::GetWindowText(ir1,t.r2,i${NSIS_MAX_STRLEN})"
    	System::Call "user32::GetClassName(ir1,t.r3,i${NSIS_MAX_STRLEN})"
    	IntFmt $1 "0x%X" $1
    	DetailPrint "$1 - [$3] $2"
    	Push 1 # callback's return value
    	System::Call "$R0"
    	Goto loop
    done:
    SetPluginUnload manual
    System::Free $R0
    
    !define MB "user32::MessageBox(i$HWNDPARENT,t,t'NSIS System Plug-in',i0)"
    System::Call "${MB}(,'my message',,)"
    System::Call "${MB}(,'another message',,) i.r0"
    MessageBox MB_OK "last call returned $0"
    
    System::Call "user32::SendMessage(i $HWNDPARENT, t 'test', t 'test', i 0) i.s ? \
    	e (,t'test replacement',,) i.r0 ? !e #user32::MessageBox"
    DetailPrint $0
    ClearErrors
    Pop $0
    IfErrors good
    MessageBox MB_OK "this message box will never be reached"
    good:
    

64-bit Functions

  • Int64Op ARG1 OP [ARG2]

    Performs OP on ARG1 and optionally ARG2 and returns the result on the stack. Both ARG1 and ARG2 are 64-bit integers. This means they can range between -2^63 and 2^63 - 1.

    Available Operations

    • Addition -- +
    • Subtraction -- -
    • Multiplication -- *
    • Division -- /
    • Modulo -- %
    • Shift right -- >>
    • Shift left -- <<
    • Bitwise or -- |
    • Bitwise and -- &
    • Bitwise xor -- ^
    • Logical or -- ||
    • Logical and -- &&
    • Less than -- <
    • Equals -- =
    • Greater than -- >
    • Bitwise not (one argument) -- ~
    • Logical not (one argument) -- !

    Usage Examples

    System::Int64Op 5 + 5
    Pop $0
    DetailPrint "5 + 5 = $0" # 10
    
    System::Int64Op 64 - 25
    Pop $0
    DetailPrint "64 - 25 = $0" # 39
    
    System::Int64Op 526355 * 1565487
    Pop $0
    DetailPrint "526355 * 1565487 = $0" # 824001909885
    
    System::Int64Op 5498449498849818 / 3
    Pop $0
    DetailPrint "5498449498849818 / 3 = $0" # 1832816499616606
    
    System::Int64Op 0x89498A198E4566C % 157
    Pop $0
    DetailPrint "0x89498A198E4566C % 157 = $0" # 118
    
    System::Int64Op 1 << 62
    Pop $0
    DetailPrint "1 << 62 = $0" # 4611686018427387904
    
    System::Int64Op 0x4000000000000000 >> 62
    Pop $0
    DetailPrint "0x4000000000000000 >> 62 = $0" # 1
    
    System::Int64Op 0xF0F0F0F | 0xF0F0FFF
    Pop $0
    # IntFmt is 32-bit, this is just for the example
    IntFmt $0 "0x%X" $0
    DetailPrint "0xF0F0F0F | 0xF0F0FFF = $0" # 0xF0F0FFF
    
    System::Int64Op 0x12345678 & 0xF0F0F0F0
    Pop $0
    # IntFmt is 32-bit, this is just for the example
    IntFmt $0 "0x%X" $0
    DetailPrint "0x12345678 & 0xF0F0F0F0 = $0" # 0x10305070
    
    System::Int64Op 1 ^ 0
    Pop $0
    DetailPrint "1 ^ 0 = $0" # 1
    
    System::Int64Op 1 || 0
    Pop $0
    DetailPrint "1 || 0 = $0" # 1
    
    System::Int64Op 1 && 0
    Pop $0
    DetailPrint "1 && 0 = $0" # 0
    
    System::Int64Op 9302157012375 < 570197509190760
    Pop $0
    DetailPrint "9302157012375 < 570197509190760 = $0" # 1
    
    System::Int64Op 5168 > 89873
    Pop $0
    DetailPrint "5168 > 89873 = $0" # 0
    
    System::Int64Op 189189 = 189189
    Pop $0
    DetailPrint "189189 = 189189 = $0" # 1
    
    System::Int64Op 156545668489 ~
    Pop $0
    DetailPrint "1 ~ = $0" # -156545668490
    
    System::Int64Op 1 !
    Pop $0
    DetailPrint "1 ! = $0" # 0
    

FAQ

  • Q: How can I pass structs to functions?

    A: First of all, you must allocate the struct. This can be done in two ways. You can either use Alloc or Call with the special struct allocation syntax. Next, if you need to pass data in the struct, you must fill it with data. Then you call the function with a pointer to the struct. Finally, if you want to read data from the struct which might have been written by the called function, you must use Call with the struct handling syntax. After all is done, it's important to remember to free the struct.

    Allocation

    To allocate the struct using Alloc, you must know the size of the struct in bytes. Therefore, it would normally be easier to use Call. In this case it's easy to see the required size is 16 bytes, but other cases might not be that trivial. In both cases, the struct address will be located on the top of the stack and should be retrieved using Pop.

    System::Alloc 16
    
    System::Call "*(i, i, i, t)i.s"
    

    Setting Data

    Setting data can be done using Call. It can be done in the allocation stage, or in another stage using the struct handling syntax.

    System::Call "*(i 5, i 2, i 513, t 'test')i.s"
    
    # assuming the struct's memory address is kept in $0
    System::Call "*$0(i 5, i 2, i 513, t 'test')"
    

    Passing to the Function

    As all allocation methods return an address, the type of the passed data should be an integer, an address in memory.

    # assuming the struct's memory address is kept in $0
    System::Call "dll::func(i r0)"
    

    Reading Data

    Reading data from the struct can be done using the same syntax as setting it. The only difference is that the destination part of the parameter will be set and the source part will be omitted using a dot.

    # assuming the struct's memory address is kept in $0
    System::Call "*$0(i .r0, i .r1, i .r2, t .r3)"
    DetailPrint "first int = $0"
    DetailPrint "second int = $1"
    DetailPrint "third int = $2"
    DetailPrint "string = $3"
    

    Freeing Memory

    Memory is freed using Free.

    # assuming the struct's memory address is kept in $0
    System::Free $0
    

    A Complete Example

    # allocate
    System::Alloc 32
    Pop $1
    # call
    System::Call "Kernel32::GlobalMemoryStatus(i r1)"
    # get
    System::Call "*$1(i.r2, i.r3, i.r4, i.r5, i.r6, i.r7, i.r8, i.r9)"
    # free
    System::Free $1
    # print
    DetailPrint "Structure size: $2 bytes"
    DetailPrint "Memory load: $3%"
    DetailPrint "Total physical memory: $4 bytes"
    DetailPrint "Free physical memory: $5 bytes"
    DetailPrint "Total page file: $6 bytes"
    DetailPrint "Free page file: $7 bytes"
    DetailPrint "Total virtual: $8 bytes"
    DetailPrint "Free virtual: $9 bytes"
    
AND

출처: http://forums.winamp.com/showthread.php?s=ff25614477fdc4857d4d96af41a1fd08&threadid=261166


P.S. Something that most users might not know is that if you pass in KEY_WOW64_64KEY when running 32-bit on 32-bit, the bit is ignored so the same Read/WriteReg call in your NSIS script will work "correctly" on 32-bit and 64-bit host platforms. For example, the following line will always read from the native registry:

ReadRegStr $2 HKLM "SOFTWARE\Crystal Decisions\10.2\Crystal Reports" "CommonFiles" ${KEY_WOW64_64KEY}
AND

출처: http://fredshack.com/docs/nsis.html

Quick Guide to NSIS

Introduction

Nullsoft Scriptable Install System (NSIS) is an alternative, equally open-source, to InnoSetup to build packages (I haven't checked Inno's Pascal scripting language, so can't tell how it fares against NSIS.) Unlike VB's Packaging and Deployment Wizard (PDW), NSIS generates a single EXE.

Writing Scripts

The NSIS install program added items in the context menu that pops up when you right-click in Windows Exploder. Create an NSIS script, right-click on it, and choose "Compile NSIS Script". This will launch the MAKENSISW.EXE front-end, display compiling messages, and whether it succeeded or failed. Once the compiling is done, you'll have an EXE installer in the output directory you specified in the NSI script.

The default extension for a script file is .nsi. Header files have the .nsh extension. To include a header file in your script use !include. Header files that reside in the Include directory under your NSIS directory can be included just by their name.

A NSIS script can contain Installer Attributes and Sections/Functions. You can also use Compiler Commands for compile-time operations. Required is the OutFile instruction, which tells NSIS where to write the installer, and one section. A non-silent installer has a set of wizard pages to let the user configure the installer. You can set which pages to display using the Page command (or PageEx for more advanced settings).

In a common installer there are several things the user can install. For example in the NSIS distribution installer you can choose to install the source code, additional plug-ins, examples and more. Each of these components has its own piece of code. If the user selects to install this component, then the installer will execute that code. In the script, that code is in sections. Use of multiple Sections only make sense if you also include Pages since Sections show up as options that the user can choose to install.

Functions can contain script code, just like sections. The difference between sections and functions is the way they are called. There are two types of functions, user functions and callback functions.  User functions will not execute unless you call them. Callback functions are called by the installer upon certain defined events such as when the installer starts.

You can declare your own variables ($VARNAME) with the Var command. Variables are global, case-sensitive, and can be used in any Section or Function. Note that constants, ie. data declared using a !define line are accessed through ${CONST} instead of $CONST, and constants are not case-sensitive. An alternative to using your own variables is to use global variables declared by NSIS, namely $0 through $9, and $R0 through $R9.

Compiler commands will be executed on compile time on your computer. They can be used for conditional compilation, to include header files, to execute applications, to change the working directory and more. The most common usage is defines. Defines are compile time constants. You can define your product's version number and use it in your script.

For example:

!define VERSION "1.0.3"
Name "My Program ${VERSION}"

Another common use is macros. Macros are used to insert code on compile time, depending on defines and using the values of the defines.

The second thing you need to do in order to create your installer after you have created your script is to compile your script. MakeNSIS.exe is the NSIS compiler. It reads your script, parses it and creates an installer for you. To compile you have to right-click your .nsi file and select Compile NSI or Compile NSIS (with bz2)

NSIS support plug-ins that can be called from the script. Plug-ins are DLL files written in C, C++, Delphi or another programming language and therefore provide a more powerful code base to NSIS.

A plug-in call looks like this:

DLLName::FunctionName "parameter number 1" "parameter number 2" "parameter number 3"

The plug-ins that NSIS knows of are listed at the top of the output of the compiler. NSIS searches for plug-ins in the Plugins folder under your NSIS directory and lists all of their available functions. You can use !addPluginDir to tell NSIS to search in other directories too.

To add comments, you can use the prefix ; or #, or C's /*... */ .

Using wizards

There are several tools that generate a working NSIS script using wizards and templates. A great way to get started, and allow users who don't know NSIS to build basic installers without learning much about NSIS.

HM NSIS

  • http://hmne.sourceforge.net/
  • Great editor that lets you generate NSIS scripts through a wizard, build a UI to be used with the InstallOptions plug-in, and acts as an IDE to write and compile NSIS scripts manually
  • Doesn't offer to register OCX's

Venis

GUI NSIS

Atomic GUI

PimBot

Calling a DLL

http://nsis.sourceforge.net/System.html

Usage

The System plug-in lets you call functions in DLL's, whether this DLL is a regular C file, or a COM DLL. The System first looks for the DLL you're calling in the current directory. There seems to be two syntaxes available. Here's the old syntax:

System::Call 'mydll.dll::myfunc(param1_type, param2_type) return_type (.r0, r1) .r2'

... and here's the new syntax:

System::Call 'mydll.dll::myfunc(param1_type .r0 , param2_type r1) return_type .r2'

In other words, the new syntax combines declaration and definition, while the old syntax keeps the two separate.

Here's an example:

System::Call 'kernel32::GetSystemDirectoryA(t .r0, *i r1r1) i .r2'

This function takes two parameters, a string and a pointer to an integer which will return the number of characters written into the string, and an integer as its return type. Since the string is only used for writing, you must prepend its name with a dot (.r0); Since the second parameter is used for both input and output, you must give it twice (r1r1); Finally, since, by definition, return variables are only used as output, remember to always prepend a dot (.r2).

Another one, using the void notation:

System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (${SHCNE_ASSOCCHANGED}, ${SHCNF_IDLIST}, 0, 0)'

Here's how it works:

  • If you have the skills, you might want to write plug-in DLL's such as those located in NSIS\Plugins\. The System plug-in is meant to call non-NSIS DLL's
  • Like in C, each parameter must have its type prepented, eg. (i r1) means that this function takes an integer
  • if a variable is used for both reading and writing, ie. input and output, simply use its name, eg. r1 (shouldn't be r1r1?); If a variable is read-only, ie. only used by the function as input, use the variable as-is, eg. r1; If a variable is write-only, ie. only used as output, prepend a dot, eg. .r1. Since a return value is always used as output, it's always used in the form of ".r1"
  • NSIS $R0..$R9 maps to System.dll R0...R9 (or r10..r19)
  • NSIS $0-$9 maps to System.dll r0-r9
  • Instead of the stack variables (rx, Rx), you can also use your own variables, eg. "... i ${MYVAR})"
  • If you just want to input a numeric variable, you can give its value as is, instead of using a variable: System::Call 'mydll::myfunc(i -16) i .r1'
  • If a function returns nothing, use the "v" (void) notation, eg. "(i, i, i, i) v"
  • If you need to allocate a certain number of bytes to a string, use "StrCpy $1 1024", but you should really use ${NSIS_MAX_STRLEN} instead of 1024, so you can be sure it's the right number
  • If you make repeated calls to the same DLL, you might want to put this in a define, eg.

    !define sysGetDiskFreeSpaceEx 'kernel32::GetDiskFreeSpaceExA(t, *l, *l, *l) i'
    [...]
    System::Call '${sysGetDiskFreeSpaceEx}(r1,.r2,,.)' 
  • To call a COM DLL, use the -> notation, eg. IPtr->MemberIdx to call member with member index from interface given by interface pointer IPtr (IPtr will be passed to proc automatically, use like C++ call) 

Retrieving the full path to SYSDIR

Here's how to use the Win32 API GetSystemDirectory() to retrieve the full path to Windows' system directory:

outfile "test.exe"
SetPluginUnload  alwaysoff
Section "Calling GetSystemDirectory"
        ;Declare Function GetSystemDirectory Lib "kernel32" Alias "GetSystemDirectoryA"
        ;(ByVal lpBuffer As String, ByVal nSize As Long) As Long
        StrCpy $1 ${NSIS_MAX_STRLEN}          ; assign memory to $0
        ;Old syntax System::Call 'kernel32::GetSystemDirectoryA(t, *i) i (.r0, r1r1).r2'
        System::Call 'kernel32::GetSystemDirectoryA(t .r0, *i r1r1) i .r2'
        DetailPrint 'Path: "$0"'
        DetailPrint "Path length: $1"
        DetailPrint "Return value: $2"
        ; last plugin call must not have /NOUNLOAD so NSIS will be able to delete
        ; the temporary DLL
        SetPluginUnload manual
        ; do nothing (but let the installer unload the System dll)
        System::Free 0
SectionEnd

I'm surprised I don't have to first make sure that $0 is set to ${NSIS_MAX_STRLEN} to make sure there's enough room to fill this variable, but it works.

Note: First, it is recommended to turn 'PluginUnload' off before making multiple calls to System.dll. According to Brainsucker (and others), this will speed up execution of the installer package. However, if you call the System plug-in just once, you can remove SetPluginUnload alwaysoff, SetPluginUnload manual and System::Free 0.

System::Free doesn't free resources used by the System plug-in. It frees resources allocated using the System plug-in using System::Alloc or the special System::Call syntax. If you pass $0 to it, it will try to free the memory pointed to by $0 which is a bad idea unless you've saved an address of some allocated memory in it.

If you use System::Free 0 it will simply do nothing. Why this is needed will be clear once you read about SetPluginsUnload.

     ; last plugin call must not have /NOUNLOAD so NSIS will be able to delete the temporary DLL
     SetPluginUnload manual
     ; do nothing
     System::Free 0

/NOUNLOAD on System::... plugin calls or SetPluginUnload alwaysoff

Second, you have to change the output directory to that where the DLL you want to use is. It may also work if the DLL is on the system path, but this hasn't been tested.

SetPluginUnload  alwaysoff
[...]
SetPluginUnload manual
; do nothing (but let the installer unload the System dll)
System::Free 0

Playing with structures

Some functions make use of structures. I seem to understand that you must append & when you need to use a specific size, eg. &i2 when you want to use a two-byte integer in case the OS uses four-byte integers. Here's how to use them:

GetFileTime "$INSTDIR\file.txt" $1 $0
System::Int64Op $1 * 0x100000000
Pop $1
System::Int64Op $1 + $0
Pop $0
;Let's allocate space for a structure with eight two-byte integers, and pass its address to r1
System::Call "*(&i2, &i2, &i2, &i2, &i2, &i2, &i2, &i2) i .r1"
System::Call "Kernel32::FileTimeToSystemTime(*l r0, i r1)"
System::Call "Kernel32::GetDateFormatA(i 0, i 0, i r1, t 'dd/MM/yy', t .r0, i ${NSIS_MAX_STRLEN})"

Here, we create and fill a structure on the stack, extract its address into $1, read each of its members into variables, and display their contents. Note that the size of the structure is appended to the structure (read into r6, here):

; Create & Fill structure, pop address into $1
System::Call "*(i 123123123, &t10 'Hello', &i1 0x123dd, &i2 0xffeeddccaa) i .s"
Pop $1
; Read data from structure   
System::Call "*$1(i .r2, &t10 .r3, &i1 .r4, &i2 .r5, &l0 .r6)"
; Show data and delete structure
MessageBox MB_OK "Structure example: $\nint == $2 $\nstring == $3 $\nbyte == $4 $\nshort == $5 $\nsize == $6"
System::Free $1
-------------
System::Call '*(&t1024 "Just a clipboard demo!") i .r0'
System::Call 'user32::SetClipboardData(i 1, i r0)'

Checking if connected through dial-up or DSL/cable

!define INTERNET_CONNECTION_MODEM "1"
!define INTERNET_CONNECTION_LAN "2"
!define INTERNET_CONNECTION_PROXY "4"
!define INTERNET_CONNECTION_MODEM_BUSY "8"
System::Call 'wininet.dll::InternetGetConnectedState(*i .r11, i 0) .r10'
returns connection flags in $R1

To read

  • Contrib\System\System.txt
  • Contrib\System\System.nsi ("; This is just an example of System Plugin")
  • Contrib\System\System.nsh : Some often-used Windows functions
  • Include\WinMessages.nsh
  • System Plugin tutorial (CHM by Lobo Lunar)
  • System plugin examples, including Calling an external DLL using the System.dll plugin a.k.a. Appendix C (

    missing the g datatype for GUID, and the "w" for WCHAR text;

    Space missing "YourDllFunction(i, *i, t) i(.r0, r1, .r2)" : With no space following the "i" between the two parts, it's not obvious that this bit indicates that it's the type of the return parameter. It should be written this way instead:

    System::Call 'YourDllName::YourDllFunction(i, *i, t) i (r0, .r1, r2) .r3'

    In plain English, this functions takes 3 parameters (one integer as input, one pointer to an integer as output, one C-type string as input), and returns an integer. The actual variables that are used to call this function are r0, r1, r2, and r3, respectively.

    The presence of a dot (".") before a variable indicates that this variable is only used as output, ie. it will be filled by the called function. The absence of a dot means that this variable in only used as input, ie. its content is read by the called function, but its content will not be changed, written to.

    Note that in case you need a parameter to be used as input and output, you'll have to give its name twice, eg. r1r1.

    In other words, before calling a function, you must know precisely how parameters work, ie. whether each parameter is read, written, or read/written by the called function.  

    Also, it should be said that NSIS $R0..$R9 maps to either System.dll r10..r19 _or_ R0...R9.

    Also, it looks like the syntax evolved, and we are no longer required to start with declaring the type and number of variables to expect. The appendix says "Using System.dll::Call To call a function in a third party DLL, the Call function is used like this: System::Call 'YourDllName::YourDllFunction(i, *i, t) i(r0, .r1, r2) .r3' The '(r0, .r1, r2) .r3' section at the end are the parameters that are passed between your DLL and your NSIS script. As can be seen in this parameters list type and input/output can be seperated. Each block of "(parms list) return value" overrides and/or adds to the last one. In this case, the first block specifies the types and the second specifies input and output) 
  • (Forum) Problem calling DLL function using System plugin
  • (Forum) Creating/launching RAS connection?
  • (Forum) Using external DLL's

Calling an ActiveX control

You actually need 16 bytes to be allocated for GUID (128 bit) (I meaning your system::alloc). The alternative way completely thru ole and kernel functions (string guid at $3):

System::Alloc 16

Pop $1

System::Alloc 80

Pop $2

System::Call 'ole32::CoCreateGuid(i r1) i'

System::Call 'ole32::StringFromGUID2(i r1, i r2, i 80) i'

System::Call 'kernel32::WideCharToMultiByte(i 0, i 0, i r2, i 80, t .r3, i ${NSIS_MAX_STRLEN}, i 0, i 0) i'

System::Free $1

System::Free $2

Create a GUID, a Globally Unique Identifier

Create a GUID, a Globally Unique Identifier

ExeHead & Ole request

IPtr->MemberIdx -> Call member with member index from interface given by interface pointer IPtr (IPtr will be passed to proc automatically, use like C++ call).

Questions

1. No need to specify datatype?

System::Call '${sysGetDiskFreeSpaceEx}(r1,.r2,,.)'

2. The documentation doesn't say that we can use $0 in System:

  System::Call 'user32::PostMessageA(i,i,i,i) i ($0,${WM_CLOSE},0,0)'

5. What is the "n" datatype?

:OpenSCManagerA(n, n, i

System::Call 'advapi32::CloseServiceHandle(i r5) n'

=> Options section in system.txt? "None -> n (0 (null) for input / specifies no output is required)"

"n - no redefine. Whenever this proc will be used it will never be redefined either by GET or CALL. This options is never inherited to childs."

7. No need to specify return variable?

System::Call 'advapi32::ControlService(i r5, i ${SERVICE_CONTROL_PAUSE},i $R1) i'

9. Confused with variable mappings:

  • NSIS $R0..$R9 maps to System.dll R0...R9 (or r10..r19)
  • NSIS $0..$9 map to System r0...r9?

10. In Lobo's CHM tutorial, why the trailing R1? Void = any type?

system::Call "kernel32::GetCurrentDirectory(i 255, t .r0) v r1"

11. What's the "?e" for?

System::Call 'kernel32::CreateMutexA(i 0, i 0, t "myMutex") i .r1 ?e'

"Additional regs -> c(Cmdline) d(instDir) o(Outdir) e(Exedir) a(lAng)"

"e - call GetLastError() after procedure end and push result on stack,"

Example from the latest System plug-in doco

To find out the index of a member in a COM interface, you need to search for the definition of this COM interface in the header files that come with Visual C/C++ or the Platform SDK. Remember the index is zero based.

# defines
!define CLSCTX_INPROC_SERVER 1
!define CLSID_ActiveDesktop {75048700-EF1F-11D0-9888-006097DEACF9}
!define IID_IActiveDesktop {F490EB00-1240-11D1-9888-006097DEACF9}
# create IActiveDesktop interface
System::Call "ole32::CoCreateInstance(g '${CLSID_ActiveDesktop}', i 0, i ${CLSCTX_INPROC_SERVER}, \
g '${IID_IActiveDesktop}', *i .r0) i.r1"
StrCmp $1 0 0 end
# call IActiveDesktop->GetWallpaper
System::Call "$0->4(w .r2, i ${NSIS_MAX_STRLEN}, i 0)"
# call IActiveDesktop->Release
System::Call "$0->2()"
# print result
DetailPrint $2
end:

Examples

The most basic script

Here, we'll just display a message box. Just copy/paste this code into dummy.nsi, right-click on this file, and select "Compile NSIS Script":

OutFile "dummy.exe"
Section
    MessageBox MB_OK "Hello, world!"
SectionEnd

A section is a bit like a routine, ie. a way to pack instructions. If you give it a name (the section above doesn't have any, so is silently ran), and add a "Page components" + "Page instfiles" instructions, the user is presented with a list of options, and invited to click on Install to actually run each section that was selected in the first page:

OutFile "dummy.exe"
Page components
Page instfiles
  
Section "Config.sys"
        ;Where to, Sir?
        SetOutPath $DESKTOP
        ;Let's add c:\config.sys into the installer, and have it extracted on the desktop
        File "c:\config.sys"
SectionEnd

Building a custom page

io2.chm

Connecting to a BBS and downloading files with Zmodem

Connecting to a RAS server, and downloading files

Here, we either find a way to check that the user has TCP/IP installed, how to dial out in PPP, and tx/rx files with eg. ftp or wget, OR we find a small and reliable DOS TCP/IP + PPP package, and use this instead.

Downloading and decompressing a zipped file

In the Archive section of the site, two plug-ins are available that can decompress a zipped file:

  • ExtractDLL, which uses its own compression format (hence, you must use its compressfile.exe compression utility)
  • ZipDLL, which handles the standard ZIP format

To use ZipDLL...

  1. copy ZipDLL.dll into NSIS' Plugins/ , and ZipDLL.nsh in Include/
  2. (Not needed with latest and greatest?) if not using the ModernUI interface, comment out the following part:
    ;!ifndef MUI_MACROS_USED
    ;  !error "Please include modern UI first!!"
    ;!endif
  3. ZipDLL::extractall "c:\test.zip" "c:\output"

Alernatively, if you prefer to use the MUI, it appears that you can use this instead:

!include "MUI.nsh"
!include "ZipDLL.nsh"
[...]
!insertmacro ZIPDLL_EXTRACT "C:unzippedZipDLL\test.zip" "c:output\" "<ALL>"

"On Success, the string "success" is on top of the stack, else an  error message." ?

Extracting a file from the installer

This minimal script includes a file inside the installer, and extracts it on the host. For this to work, you must have a dummy "myfile.exe" in the directory where this NSIS script lives. Just copy/paste this code in a text file named eg. myinst.nsi, right-click on this file, and choose "Compile NSIS Script":

OutFile "myinst.exe"
BrandingText "Acme Inc."
Caption "Generated on ${__DATE__}"
Section
     SetOutPath $DESKTOP
     File "myfile.exe"
SectionEnd

Downloading a file from the web

Here, we check if a file exists on the host; If not, we download it from the web, and run it silently:

Section "VB5 Runtime"
        ;Quick and dirty way to check if the runtime is installed
        IfFileExists "$SYSDIR\msvbvm50.dll" end
        StrCpy $1 "msvbvm50.exe"
        StrCpy $2 "$SYSDIR\$1"
        NSISdl::download "http://www.acme.com/downloads/$1" $2
        Pop $R0
        StrCmp $R0 "success" install
        MessageBox MB_OK|MB_ICONSTOP "Error while downloading $1."
        Quit
install:
        ExecWait "$2 /q"
end:
        DetailPrint "Runtime VB5 OK."
SectionEnd
Function .onInit
        Call ConnectInternet
FunctionEnd
Function ConnectInternet
  Push $R0
     
  ClearErrors
  Dialer::AttemptConnect
  IfErrors noie3
  Pop $R0
  StrCmp $R0 "online" connected
    MessageBox MB_OK|MB_ICONSTOP "Check Internet connection."
    Quit
   
  noie3:
        ; IE3 not installed
        MessageBox MB_OK|MB_ICONSTOP "Internet Explorer not installed."
        Quit
        
  connected:
          Pop $R0
FunctionEnd

Adding pages

There are two basic commands regarding pages, Page and UninstPage. The first adds a page to the installer, the second adds a page to the uninstaller. On top of those two there is the PageEx command which allows you to add a page to either one and with greater amount of options. PageEx allows you to set options to the specific page you are adding instead of using the default that's set outside of PageEx. The page order is set simply by the order Page, UninstPage and PageEx appear in the script.

Each (non-silent) NSIS installer has a set of pages. Each page can be a NSIS built-in page or a custom page created by a user's function (with InstallOptions for example).

Here's how to display a license page, followed by an inputbox to let the user choose a destination directory, followed by the list of sections available, before actually installing stuff:

Caption "My Caption"
OutFile "page.exe"
LicenseText "A test text, make sure it's all there"
LicenseData "license.txt"
InstallDir "C:\TEMP\MyNSISDir"
Page license
Page directory
Page components
Page instfiles
Section "Some component"
  DetailPrint "Section X done."
SectionEnd

Here's how to display a custom page:

Page custom customPage "" ": custom page"
Function customPage
   GetTempFileName $R0
   File /oname=$R0 customPage.ini
   InstallOptions::dialog $R0
   Pop $R1
   StrCmp $R1 "cancel" done
   StrCmp $R1 "back" done
   StrCmp $R1 "success" done
   error: MessageBox MB_OK|MB_ICONSTOP "InstallOptions error:$\r$\n$R1"
   done:
FunctionEnd

Different installation sets

Here's how to let the user select the familiar "Basic, Full, Custom":

InstType "Basic"
InstType "Full"
;To limit the user to the available list of options above...
;InstType /NOCUSTOM
Section "Some section"
    ;This section only available in Full
    SectionIn 2
SectionEnd
Section "Some other section"
    ;This section available in both Basic and Full
    SectionIn 1 2
SectionEnd

Sub-sections

While the Modern UI module available in NSIS 2 offers enhanched support for tree-like lists, you can build this kind of interface with NSIS 1. This example displays a one-level section "MS FlexGrid", followed by a two-level section "VideoSoft" that contains two items "VS Print" and "VS FlexGrid".

SubSection "Components"
        Section "MS FlexGrid"   
        SectionEnd
        SubSection "VideoSoft"
                Section "VS Print"
                SectionEnd
                Section "VS FlexGrid"
                SectionEnd
        SubSectionEnd
SectionEnd

Making two sections dependent

It may happen that you want a section to be automatically checked if the user checks another section, such as when selecting an EXE that requires an OCX to work. Note that I didn't handle the case where the user unselects the Application section which should also uncheck the OCX section as well, and also remember to add !include "Sections.nsh" at the top of your script.

Here's a first way to do this :

Function .onSelChange
    ;If the Application section is checked, check the OCX section as well
    !insertmacro SectionFlagIsSet ${app} ${SF_SELECTED} "" end
        !insertmacro SelectSection ${ocx}
end:
FunctionEnd

Here's a second way:

Function .onSelChange
    SectionGetFlags ${app} $0
    IntOp $0 $0 & ${SF_SELECTED}
    ;Is Application selected? If yes, select OCX section
    StrCmp $0 ${SF_SELECTED} 0 end
        SectionGetFlags ${ocx} $1
        IntOp $1 $1 | ${SF_SELECTED}
        SectionSetFlags ${ocx} $1
end:
FunctionEnd

(Sections) To try

Use GetWindowsVersion in .onInit and accodring to the results disable or enable sections using SectionSetFlags or hide it using SectionSetText.

Comparing the version numbers of two binaries

Here, we'll extract the version number from an OCX to get a string in the form "1.2.3.4", download an INI file from a web server that will give tell us the version number of the file over there, compare the two token by token, each time deciding whether to go on to the next test, exit, or download the version on the web server if it is newer. Since I'm still a NSIS newbie, there's most likely a cleaner way to do this, but here goes, using BigMac2003, StrTok():

Section "Comparing versions"
        StrCpy $2 "$SYSDIR\mscomctl.ocx"
        GetDllVersion "$2" $R0 $R1
        IntOp $R2 $R0 / 0x00010000
        IntOp $R3 $R0 & 0x0000FFFF
        IntOp $R4 $R1 / 0x00010000
        IntOp $R5 $R1 & 0x0000FFFF
        StrCpy $5 "$R2.$R3.$R4.$R5"
        ReadINIStr $9 ".\VERSIONS.INI" "mscomctl" "mscomctl.ocx"
        Push "$9"
        Push "."
        Call StrTok
        Pop $R6
        Push "."
        Call StrTok
        Pop $R7
        Push "."
        Call StrTok
        Pop $R8
        Pop $R9
                ;Comparing token by token
                ;Reminder: $2 = local file = $R2.$R3.$R4.$R5 and $9 = file on www = $R6.$R7.$R8.$R9
                IntCmpU $R2 $R6 0 download end
                IntCmpU $R3 $R7 0 download end
                IntCmpU $R4 $R8 0 download end
                IntCmpU $R5 $R9 end download end
                download:
                        DetailPrint "File $2 installed but in older version. Downloading..."
                        Call Download
                        Goto register
register:
        RegDLL "$2"
                                        
end:
        DetailPrint "File $1 OK"
        
SectionEnd

Another way to achieve this, using Sunjammer's VersionCheck():

GetDllVersion "$2" $R0 $R1
IntOp $R2 $R0 / 0x00010000
IntOp $R3 $R0 & 0x0000FFFF
IntOp $R4 $R1 / 0x00010000
IntOp $R5 $R1 & 0x0000FFFF
StrCpy $R8 "$R2.$R3.$R4.$R5"
Push $R8
;$9 contains a version number in the form "1.2.3.4" pulled from eg. an INI file
Push $9
Call VersionCheck
Pop $0
;$0 = 1 if first number higher,= 2 if second number higher, = 0 if equal
IntCmp $0 1 1 end
download:
    Call Download
end:

Yet another way, using Deguix's StrTok macro in Include\StrFunc.nsh:

!include "StrFunc.nsh"
outfile "test.exe"
${StrTok}
Section
    ;If looping, don't use $R1, because seems to be used internally by this macro
    ${StrTok} $R2 "1.2.3.4" "." "1" "1"
    ${StrTok} $R3 "1.2.3.4" "." "2" "1"
    ${StrTok} $R4 "1.2.3.4" "." "3" "1"
    ${StrTok} $R5 "1.2.3.4" "." "4" "1"
    MessageBox MB_OK "$R2.$R3.$R4.$R5"
SectionEnd

To read

If you are referring to GetDllVersion in my sysinfo DLL it will work on a path because it calls LoadLibrary on the DLL. *However* not all DLLs support the GetDllVersion mechanism. The DLL has to export a method called DllGetVersion - if it doesn't then this method will not return any version information. In that case you have to fall back to one of the other means of querying the DLL for a version number, i.e. GetFileVersion.

I can't be sure if GetFileVersion will search the default paths or not, it's a bit hard to tell from the Win32 API docs. Basically they say that GetModuleHandle which it calls does not need a path, it doesn't say what happens if it doesn't have one.

I *believe* it will search the default path since that whole chunk of the Win32 API is connected with loading and using DLLs which involves obeying path information.

Options in sections

  • To make a section compulsory, add SectionIn RO ("read only"?) in a section
  • To make it unselected by default, add the /o switch, eg. Section /o "Some section"
  • To have it displayed in bold, add a ! right before the name of the section, eg. Section "!My section"
  • To make a section compulsory but invisible, either give it no name, or add an hyphen or an exclamation mark before its name, eg. Section, Section - "This not displayed", Section ! "This not displayed"

Localizing installers

NSIS 1

InstType /CUSTOMSTRING=Personnalisée

NSIS 2

It appears that the Modern UI options provides a much better solution to build multi-language installers.

Modern Interface

A popular user interface for NSIS is the Modern User Interface, it has an interface like the wizards of recent Windows versions. The Modern UI is not only a customized resource file, it has a lots of new interface elements. It features a white header to describe the current step, a description area on the component page, a welcome page, a finish page that allows the user to run the application or reboot the system and more.

Looping

Here's how to loop 5 times:

StrCpy $1 "x"
LoopPoop:
    StrCpy $1 "x$1"
    StrCmp $1 "xxxxxx" 0 LoopPoop
MessageBox MB_OK "Done!"

Another way:

StrCpy $R1 1
loop:
    MessageBox MB_OK "R=$R1"
    IntOp $R1 $R1 + 1
    IntCmp $R1 5 done
    Goto loop1
done:

Yet another way:

!define LoopCount 100
Push $R0
StrCpy $R0 0
Loop:
    # do stuff here
    IntOp $R0 $R0 + 1
StrCmp $R0 ${LoopCount} 0 Loop
Pop $R0
!undef LoopCount

Locating and running a file

SearchPath $1 notepad.exe
MessageBox MB_OK "notepad.exe=$1"
Exec '"$1"'

Opening a directory

ExecShell "open" '"$INSTDIR"'
BringToFront

Registering/unregistering personal ActiveX files

In case you don't need to check for version numbers...

UnRegDLL "$SYSDIR\spin32.ocx"
RegDLL "$SYSDIR\spin32.ocx"

Checking if a process is running

This uses the FindProcDLL::FindProc plug-in (here also):

StrCpy $1 "mybin.exe"
FindProcDLL::FindProc "$1"
;0   = Process was not found
;1   = Process was found
;605 = Unable to search for process
;606 = Unable to identify system type
;607 = Unsupported OS
;632 = Process name is invalid
StrCmp $R0 0 0 error
File "mybin.exe" ; Can't use a variable
    Goto end
error:
        MessageBox MB_OK|MB_ICONSTOP "The application $1 is currently running. Press CTRL-ALT-DEL to display the list of running processes."
        Quit
end:

Other solutions in Uninstall: How to make sure the app isn't running?

Q&A

Can I call a DLL without using the System plug-in?

Joost Verburg >>You can call any DLL using the System DLL. That quote refers to a direct call without the System plug-in.

Does this refer to NSIS plug-in's written as DLL's and located in NSIS\Plugins\ ?

What is the difference between $VAR and ${VAR}?

$VAR is a variable reference, ${VAR} is a define (preprocessor constant) reference.

Is there a tool to generate NSIS scripts?

Justin wrote a PHP script to generate a basic script.

What is the difference between GetDLLVersion() and GetDLLVersionLocal()?

The Local one is for the compiler system, it is actually being converted to two StrCpy commands that set two variables. This way, the EXE that you generate holds a copy of the version number of the DLL. With the value of the Local command (which is the version on your system), you can compare the version on the user's system using the other command. Thanks to GetDLLVersionLocal(), you don't have to save the version number in a file that you will insert in the EXE using the File command.

How to write a function to extract a bunch of files?

ie. how to run File with files that have a different extension (so *.x won't work)? Can't do. File only works with constants, not variables. The following doesn't work:

Function Check
    SetOutPath $SYSDIR
    ;Doesn't work
    File "d:\temp\$R0"
    ;Doesn't work any better
    File /oname="$R0" "d:\temp\$R0"
    ;Works
    File "d:\temp\myfile.dll"
    ClearErrors
    RegDLL $SYSDIR\$R0
    IfErrors 0 end
        MessageBox MB_OK "Erreur enregistrement $R0"
end:
FunctionEnd

System DLL not upgraded?

Note that DLLs & OCXs sometimes fail to register automatically at install time because they depend on a newer version of some runtime library (msvcrt.dll and msvcp60.dll being common culprits). These can't yet be upgraded because it's being used by the installer itself, so registration will fail and if it's not storing the result to DllRegisterServer (or just shelling to regsvr32 /s) you won't notice until you launch the app.

Macro vs. Function?

Abort or Quit?

How is File() used?

What does this do?

SetOutPath $1
File "C:\program files\winamp\plugins\vis_nsfs.dll"

How to add a " in a quoted string?

Escape it with $\"

Tutorial For Developers New To Nsis

By Neal Olander

What is NSIS?

NSIS stands for Nullsoft Scriptable Install System.  It is a free software package that you, as a developer, can use to package your software application for delivery to users.   Almost every software package is installed using an installer like NSIS or  InstallShield or Wise.  Most installers are commercial products that you have to pay for, but NSIS is free.

Where can I get information about NSIS?

There are three main sources of information about NSIS:  (1) the NSIS web site at nsis.sourceforge.net;  (2) the NSIS discussion forum at forums.winamp.com/forumdisplay.php?s=&forumid=65; and (3) the on-line help within the NSIS product, which will be on your computer after you download NSIS from the NSIS web site.

How do I download NSIS?

Go to the NSIS web site at nsis.sourceforge.net and follow the instructions.

What do I use NSIS for?

NSIS allows you (a developer) to package files for delivery to users.  The files are packaged into a single file, called an "installer" file.  The installer file is an executable, normally called setup.exe. You can deliver this installer file either on a CD or users can download the installer over the web.  The files may include software applications, DLLs, help files, data files, or any kind of file you want.  NSIS helps the user put the files onto their computer, and makes sure the files get put into the right places.  It also can setup registry entries, desktop shortcuts, and Start menu buttons.  NSIS will pop-up windows for the user to read and follow.  You do not have to deal with the hassle of creating the windows: NSIS does all the hard work for you.  Your biggest job is identifying the files that are to be delivered.

What Steps to I follow to create an NSIS installation for my application?

The steps are

  1. Decide what files you want to install on the user's computer.
  2. Decide what other things you want set-up on the user's computer (Start menu, etc).
  3. Think about the experience you want your users to have, that is, what sequence of steps they should go through while installing your application.
  4. Write an NSIS script.
  5. Run the NSIS compiler (this will build and NSIS installer executable)
  6. Burn the NSIS installer executable onto a CD (or DVD)
  7. Deliver the CD to your user
  8. The user puts the CD (or DVD) into their computer and runs the NSIS installer executable
  9. The NSIS installer executable will pop-up some windows and prompt the user for input
  10. The NSIS installer executable will install the files on the users computer and perform other setup tasks that you defined.

How does NSIS know what to install?

NSIS follows the instructions that you put into your script file.  NSIS will do a lot automatically (such as pop-up windows, check for disk space, create necessary folders) but you have to tell it what files to install, and where to install them.

What is the NSIS script file?

The NSIS script file is a text file that you create in a text editor.  It contains instructions about what files to install.  You can create the script file in several ways: (1) you can type it by hand in any plain-text editor;  (2) You can modify a sample NSIS script file from the NSIS web site or the NSIS dowload files; or (3) You can download and run the HM NIS Edit program which includes a wizard to create scripts.

How does the script file differ from the NSIS installer executable?

The script file is a text file that you write.  After you write it, you run it through the NSIS compiler and the compiler will tell you if there are any errors.  If there are no errors, the NSIS compiler will output the installer executable, normally named setup.exe.   The installer file contains all the installation instructions, plus the files you are delivering.  The installer file is binary and you cannot read it in a text editor.

Where are the files I'm delivering to my user?

The files you are delivering are embedded inside the installer executable.  The compiler, when compiling, reads the files from your computer's disks and compresses the files and puts them inside the installer exectuable.  That way, the user only has to fetch a single file, the installer executable, and run it.  

What happens on the users computer?

After the user gets the installer executable from you (either from a CD, or downloaded from the web) the user just runs the installer (usually called setup.exe).  The installer will pop-up some windows asking the user to read a license agreement, and to select a folder for the files to be put into.  The installer will then extract your files from within its own file, decompress them, and put them into folders on the users computer.  

Which folders do the delivered files go into on the users computer?

By default, the files go into a folder that the user selects in the pop-up windows.  But you can force files to go into any folder you want.  If the folders do not exist, you can create them in the script.

How to I learn what commands to put inside my NSIS script file?

The syntax of the NSIS script files is simple but a little bit primitive.  For details, visit the on-line help in the NSIS web site.  The key thing to remember is that everything you need to do in your installation has been done before, so there is _some_ way to get it done.  Ask questions on the NSIS forum and you will get detailed instructions on how to achieve your goals.  NSIS is pretty powerful, and will do a lot of things automatically, so the script files tend to be rather small.

What does a typical NSIS script look like?

You don't need to memorize all the overhead stuff from a NSIS file ... most developers just copy existing scripts and modify the "middle" where the list of files is contained.    Go to the online documenation and start reading sample scripts.

What are the most important commands in the script?

The most important command in the script is the File command.  This command looks like:

File  "C:\Program Files\My Application\whizbang.exe"

The file command tells the NSIS compiler to fetch the named file (whizbang.exe) from your disk (at compile time) and put the file into the compiler's output installer file.  The folder here is the folder on your (the developer's) computer, not the users computer.  The destination folder on the users computer will be chosen by the user during the installation process, from the GUI, so you do not have to worry about that in your script.

What if I want to create a folder tree on the users computer?

When you are delivering lots of files, NSIS will let you create a folder tree.  It is best if you let the user (in the GUI) identify where the tree should start, say C:\Program Files\Any Place.  That folder will be identified in the script as $INSTDIR.  You can put files in subfolders under $INSTDIR by using the SetOutPath command immediately before the File command, as in:  

   SetOutPath "$INSTDIR"
   File  "C:\Program Files\My Application\whizbang.exe"
   SetOutPath "$INSTDIR\Help"
   File  "C:\Program Files\My Application\whizbang_help.doc"
   SetOutPath "$INSTDIR\Examples"
   File  "C:\Program Files\My Application\whizbang\examples\*"

You can also use the CreateDirectory command to explicitly create specfic folders on the users computer. Generally, you do not want to use hard-coded paths for the destination folders, because it is dangerous to make assumptions about the users drive ID letter (C: or D:, etc) and also the user should control where files go.  That is why you should always use $INSTDIR as the starting point for the destination folder.

What if I want to let the operator select which parts of my application to install?

If your application has lots of pieces, and some pieces are optional, then you use the Section command in your script.  The Section command breaks your files into groups, and the NSIS intall GUI will (automatically!) build a list of these pieces and present them to the operator. After the operator selects which pieces he or she wants installed, the NSIS installer will install only those pieces selected. Use of Sections is optional.  Your script may use sections like this:

Section "Application"
    File  "C:\Program Files\My Application\whizbang.exe"
SectionEnd
Section "Plug-ins"
    SetOutPath "$INSTDIR\DLLS"
    File  "C:\Program Files\My Application\*.dll"
SectionEnd
Section "Documentation"
    SetOutPath "$INSTDIR\Docs"
    File  "C:\Program Files\My Application\whizbang\documents\*"
SectionEnd

What if I want the operator to select the pieces hierarchically?

You can create tree-like file groupings using the SectionGroup command, as in:

Section "Application"
    File  "C:\Program Files\My Application\whizbang.exe"
SectionEnd
SectionGroup "Optional Stuff"
    Section "Plug-ins"
        SetOutPath "$INSTDIR\DLLS"
        File  "C:\Program Files\My Application\*.dll"
    SectionEnd
    Section "Documentation"
        SetOutPath "$INSTDIR\Docs"
        File  "C:\Program Files\My Application\whizbang\documents\*"
    SectionEnd
SectionGroupEnd

What is the MUI?

The MUI is an optional user-interface that can be used with NSIS.  It presents the user with "modern" looking windows.  You can get sample NSIS scripts that use MUI from the NSIS web site, or you can use the NIS EDit program wizard to generate a script containing MUI constructs.  The on-line NSIS documentation includes MUI instructions.

What does MUI code look like?  

The important MUI commands each pop-up a single window for the user to read or act on.  A typical sequence of MUI command (each one pops up a single window) is:

!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "C:\Program Files\Spectrasonics\RMX_License_Agreement.txt"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_STARTMENU
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH

How many files can NSIS install?

There is no limit to the number of files.  But the NSIS installer file is limited to 2 GB.  If you need to install more than that, you can either create two separate installer files, or deliver some files "outside" the installer executable.

What if I want to deliver files outside the installer file?

If you want to deliver files to your users outside the installer file, and you are delivering on a CD or DVD, then you manually copy the "outside" files to the CD, and use the CopyFiles command in your script.  This command will copy files from the CD (or DVD) to the users drive.  The CopyFiles command looks like:

CreateDirectory  "$INSTDIR\data_subfolder"    ; make sure the destination folder exists
CopyFiles  "$EXEDIR\..\data\*"  "$INSTDIR\data_subfolder"  ; source -> destination

What is the $EXEDIR?

The $EXEDIR is often used in the CopyFiles command.  $EXEDIR is the path that the user is running the installer executable from.  When the user is installing from a CD, $EXEDIR might be "E:" or "G:\top_folder" When installing from a CD (or DVD) you can use $EXEDIR as a starting point and then use relative paths from it to identify the location of the "outside" files on the CD.

What if I want the user to specify two or three destination folders?

By default, a normal NSIS script will prompt the user to browse and identify a single destination folder, which is refered to as $INSTDIR in your script.  But if you need to prompt the operator to identify multiple destinations then you use the following MUI logic.  Put this logic _after_ the first MUI_PAGE_DIRECTORY line (which is the first GUI prompt for a folder):

Var ALTERNATIVE_GUI_TITLE
Var ALTERNATIVE_TEXT
Var ALTERNATIVE_FOLDER
!define MUI_DIRECTORYPAGE_VARIABLE          $ALTERNATIVE_FOLDER   ;selected by user
!define MUI_DIRECTORYPAGE_TEXT_DESTINATION  $ALTERNATIVE_TEXT     ;descriptive text
!define MUI_DIRECTORYPAGE_TEXT_TOP          $ALTERNATIVE_GUI_TITLE  ; GUI page title
!insertmacro MUI_PAGE_DIRECTORY  ; this pops-up the GUI page
Function .onInit
    StrCpy $ALTERNATIVE_GUI_TITLE "Select a folder for ALTERNATIVE"
    StrCpy $ALTERNATIVE_TEXT "ALTERNATIVE Folder"  
    StrCpy $ALTERNATIVE_FOLDER "$INSTDIR\ALTERNATIVE"    ;default path
FunctionEnd

In the example above, this will pop-up a second window asking the user to browse and identify a folder.  The folder selected by the user will be stored in $ALTERNATIVE_FOLDER (in the example above) and your script can then ouput files there such as:

SetOutPath   "$ALTERNTIVE_FOLDER"
File  "C:\Program Files\Alternative Application\*"

Resources

You'll find information on how to build NSIS scripts at the following places:

Good stuff:

AND

출처: http://nsis.sourceforge.net/Macro_vs_Function

Macros and Functions in NSIS are powerful tools to help make coding your installer easier and more flexible. They can also be very powerful tools, allowing you to extend the NSIS scripting language to an extent - LogicLib is a great example of this.

Contents

[hide]

Definitions

Macros

From the NSIS help file: "Macros are used to insert code at compile time, depending on defines and using the values of the defines. The macro's commands are inserted at compile time. This allows you to write a general code only once and use it a lot of times but with a few changes."

If you are new to macros in any scripting language, then this may sound a bit confusing. Let's go through a few examples to show what the above means.

"Macros are used to insert code at compile time"

What this means is that the code you define in a macro will simply be inserted at the location of your !insertmacro, as if copy/pasted, when you compile your installer script.

Example 1.1.1

!macro Hello
  DetailPrint "Hello world"
!macroend
 
Section Test
  !insertmacro Hello
SectionEnd

Could be seen as just:

Example 1.1.2

Section Test
  DetailPrint "Hello world"
SectionEnd

The above is obviously just a simple example with a single line and really wouldn't be a good use of macros. But if you have multiple lines of code that you may have to use over and over again, it may be a good idea to start using a macro for it; it allows you to just make the any changes once in the macro, and it will automatically be changed anywhere you insert it. It also just makes your code look a lot cleaner (a single !insertmacro line vs perhaps a dozen lines) which makes it a lot easier to follow and edit.

"depending on defines and using the values of the defines"

That bit is most likely to sound confusing, but gets a little more clear once you read the definition of macros in another section of the NSIS help:

"macro definitions can have one or more parameters defined. The parameters may be accessed the same way a !define would (e.g. ${PARMNAME}) from inside the macro."

If you are familiar with scripting in other languages, this may sound familiar to you, except in the context of functions - but please do not mix this up with Functions as they exist in the NSIS language.. we'll get to those later.

What the above means is that you can have a macro take parameters, or arguments, and use those within the macro:

Example 1.1.3

!macro Hello What
  DetailPrint "Hello ${What}"
!macroend
 
Section Test
  !insertmacro Hello "World"
  !insertmacro Hello "Tree"
  !insertmacro Hello "Flower"
SectionEnd

Could be seen as just:

Example 1.1.4

Section Test
  DetailPrint "Hello World"
  DetailPrint "Hello Tree"
  DetailPrint "Hello Flower"
SectionEnd

However, you only needed a single macro definition to get these three different results. Let's take a more complex example, straight from the NSIS Help. In NSIS, a Function can only be specified as being for the Installer, or the Uninstaller (prefixed by "un."). The reason for this is that it allows the compiler to make a smaller Uninstaller if it does not need the Installer's functions, and vice-versa. However, sometimes you may have a function that both the Installer and the Uninstaller require. You could then code the Function twice:

Example 1.1.5

Function SomeFunc
  ; Lots of code here
FunctionEnd
 
Function un.SomeFunc
  ; Lots of code here
FunctionEnd

However, as it should be apparent, if there's lots of code involved you have two separate areas where you have to make code changes, your code looks less clean, etc.

With the use of macros, this can easily be resolved by writing a macro around the function that simply adds the "un." prefix when requested:

Example 1.1.6

!macro SomeFunc un
  Function ${un}SomeFunc
    ; lots of code here
  FunctionEnd
!macroend
!insertmacro SomeFunc ""
!insertmacro SomeFunc "un."

The above will then be written out as example 1.1.5, but now you only have a single portion of code to maintain.

For more information on this specific topic, see: Sharing functions between Installer and Uninstaller

Functions

Let's start again with what the NSIS Help file says on them:

"Functions are similar to Sections in that they contain zero or more instructions."

Let's try a different definition. Functions are like macros, except that the code does -not- actually get inserted or copy/pasted, if you will, when compiling. The compiled code only exists just once in your final installer.

A Function use equivalent to the Macro example 1.1.1 would be:

Example 1.2.1

Function Hello
  DetailPrint "Hello world"
FunctionEnd
 
Section Test
  Call Hello
SectionEnd

You also cannot pass parameters/arguments to Functions as easily as you can with Macros. I.e. the following is NOT a valid code equivalent of Example 1.1.3:

Example: 1.2.2

Function Hello What
  DetailPrint "Hello ${What}"
FunctionEnd
 
Section Test
  Call Hello "World"
  Call Hello "Tree"
  Call Hello "Flower"
SectionEnd

You could, of course, try to take a page out of the earlier installer/uninstaller book and wrap the function in a macro:

Example 1.2.3

!macro Hello What
  Function Hello
    DetailPrint "Hello ${What}"
  FunctionEnd
!macroend
 
Section Test
  !insertmacro Hello "World"
SectionEnd

But this is -also- invalid code, as the code would end up having a Function definition within a section, which is not allowed:

Example 1.2.4

Section Test
  Function Hello
    DetailPrint "Hello ${What}"
  FunctionEnd
SectionEnd

( even if Function definitions were allowed within Sections, keep in mind that you would have more than a single Function named "Hello" - which is also not allowed )

Instead, passing parameters/arguments to Functions is typically done through variables...

Example 1.2.5

var myVar
 
Function Hello
  DetailPrint "Hello $myVar"
FunctionEnd
 
Section Test
  StrCpy $myVar "World"
  Call Hello
SectionEnd

...or, more commonly, the stack (to save memory used by variables)

Example 1.2.6

Function Hello
  Exch $0
  DetailPrint "Hello $0"
  Pop $0
FunctionEnd
 
Section Test
  Push "World"
  Call Hello
SectionEnd

Which ends up acting as (but please note that the code is not actually inserted as such!):

Example 1.2.7

Section Test
  Push "World"
  ; At this point, the stack looks like the following (top is left, bottom is right)
  ; :: "World" -rest of the stack, if anything-
  Exch $0
  ; At this point the old value of $0 is now at the top of the stack, while "World" is copied into variable $0
  ; :: old$0 -rest of the stack, if anything-
  DetailPrint "Hello World"
  Pop $0
  ; At this point the old value of $0 is restored, and the stack just looks like the following:
  ; :: -rest of the stack, if anything-
SectionEnd

Functions can either clear the stack relevant to the function, as in the above, or they can leave an item on the stack for use after the function.

Example 1.2.8

Function isDir
  Exch $0
  IfFileExists "$0\*.*" _dir _notdir
  _dir:
    StrCpy $0 "true"
    return
  _notdir:
    StrCpy $0 "false"
FunctionEnd
 
Section Test
  Push "some path on your drive"
  Call isDir
  Pop $0
SectionEnd

Where in the above code, after the call to "isDir", $0 contains "true" if the path was a directory, and "false" if not.

Hybrid

Mainly because of the fact that you can't easily pass parameters to Functions, many users adopt a hybrid approach employing both macros -and- functions, and a little bit of a define. (a define just allows you to say that e.g. "NSIS" means "Nullsoft Scriptable Install System" without having to write out as much).

Example 2.1

!define writeFile "!insertmacro writeFile"
 
!macro writeFile File String
  Push ${File}
  Push ${String}
  Call writeFile
!macroend
 
Function writeFile
                              ; Stack: <file> <string>
  ClearErrors
  Exch $0                     ; Stack: $0 <string>
  Exch                        ; Stack: <string> $0
  Exch $1                     ; Stack: $1 $0
  Push $2                     ; Stack: $2 $1 $0
  ; $0 = file
  ; $1 = string
  FileOpen $2 "$0" "a"
  FileSeek $2 0 END
  FileWrite $2 $1
  FileClose $2
  Pop $2                      ; Stack: $1 $0
  Pop $1                      ; Stack: $0
  Pop $0                      ; Stack: -empty-
FunctionEnd
 
Section Test
  ${writeFile} "$TEMP\a_log_file.txt" "A log entry"
SectionEnd

As you can see, this allows you to combine the best of both worlds by reducing code redundancy both in your installation script -and- in the compiled installer and allowing you to 'pass' parameters/arguments to a function by using a macro as an intermediate. And in the end, your code looks even cleaner.

Caveats - or Pros/Cons

Now you might think that macros and functions rather look the same - when should you use which? That all depends on your needs / desires for the most part. However, there are certainly pros/cons to both.

As pointed in the definitions already, there are some immediately obvious advantages over each other:

Macros

  • + easier follow (the code simply gets copy/pasted)
  • + can easily pass parameters / arguments
  • - code gets duplicated in the compiled result

Note that the code duplication is marginal (the files you are installing are typically much larger than the base installer code), and with compression it becomes even less of an issue.

Functions

  • + code does not get duplicated in the compiled result
  • - cannot easily pass parameters / arguments
  • - a bit less easy to follow

( The Hybrid approach pretty much eliminates the parameter / argument passing con of Functions )

However, there are also some less obvious Pros/Cons. For example, Macros are faster to execute than Functions, as there are no opcodes for calling / returning required in the installer. However, this is also marginal and even on older hardware negligable.

More intricate Pros/Cons are described in the next sections.

Macros

labels

As a Macro literally gets inserted, or copy/pasted, into your code when compiling you may find yourself running into an issue of duplicating labels;

Example 3.1.1

!macro LoopThreeTimes
  StrCpy $0 0
  loop:
    IntOp $0 $0 + 1
    IntCmp $0 3 end
    goto loop
  end:
!macroend
 
Section Test
  IfFileExists "$TEMP\install.log" end
  !insertmacro LoopThreeTimes
  end:
SectionEnd

The above could be read as:

Example 3.1.2

Section Test
  IfFileExists "$TEMP\install.log" end
  StrCpy $0 0
  loop:
    IntOp $0 $0 + 1
    IntCmp $0 3 end
    goto loop
  end:
  end:
SectionEnd

And the problem should be clear - there are now two labels named "end", which is not allowed. You could rename your labels in your macros to be supposedly unique - in example 3.1.1 you could call them "LoopThreeTimes_loop" and "LoopThreeTimes_end". But what if you use the macro more than once in the same section?

Example 3.1.3

Section Test
  IfFileExists "$TEMP\install.log" end
  StrCpy $0 0
  loop:
    IntOp $0 $0 + 1
    IntCmp $0 3 LoopThreeTimes_end
    goto loop
  LoopThreeTimes_end:
  StrCpy $0 0
  loop:
    IntOp $0 $0 + 1
    IntCmp $0 3 LoopThreeTimes_end
    goto loop
  LoopThreeTimes_end:
  end:
SectionEnd

You then again get a duplicate label name. A common way to prevent this is to actually make the label names unique for each time the macro is inserted. You can do this by adding a line number that is first defined to the label;

Example 3.1.3

!macro LoopThreeTimes
  !define ID ${__LINE__}
  StrCpy $0 0
  loop_${ID}:
    IntOp $0 $0 + 1
    IntCmp $0 3 end_${ID}
    goto loop_${ID}
  end_${ID}:
  !undef ID
!macroend
 
Section Test
  IfFileExists "$TEMP\install.log" end
  !insertmacro LoopThreeTimes
  end:
SectionEnd

Which would then end up as (presuming the line number the !define was on was 55): Example 3.1.4

Section Test
  IfFileExists "$TEMP\install.log" end
  StrCpy $0 0
  loop_55:
    IntOp $0 $0 + 1
    IntCmp $0 3 end_55
    goto loop_55
  end_55:
  end:
SectionEnd

You can read more about this in: Tutorial: Using labels in macro's

Although this is a reasonably minor Con, given the easy workaround, it should be clear that Functions do not suffer from any issues with labels at all.

Functions

File command

When you use a Macro, the code gets inserted or copy/pasted if you will into the area of use. This becomes especially important when you use the File command. Consider the following two examples:

Example 3.2.1 - Macro

!macro InstallFilesTo Path Filespec
  SetOutPath "${Path}"
  File "${Filespec}
!macroend
 
Section Test
  !insertmacro InstallFilesTo "$INSTDIR" "data\*.*"
SectionEnd

Example 3.2.2 - Function

Function InstallFilesTo
  Exch $0
  Exch
  Exch $1
  SetOutPath "$1"
  File "$0"
  Pop $1
  Pop $0
FunctionEnd
 
Section Test
  Push "data\*.*"
  Push "$INSTDIR"
  Call InstallFilesTo
SectionEnd

The two perform exactly the same job, namely installing all files in the installer source's "data" sub-folder to the installation folder.

The big difference is that with the Macro variant, the size of the files can directly be added to the Section. Which means that your installer will correct report the amount of free space required on the drive, and the "SectionGetSize" command will work correctly.

With the Function variant, the compiler doesn't know to figure this out, and the required space is not calculated correctly, nor does "SectionGetSize" work correctly.

A workaround for this would be to use "SectionSetSize" where appropriate, but it does mean that you would need to know the size of the files -before- you use "SectionSetSize", which would have to be handled by a separate NSIS script, or some cheating by extracting the file before actually installing, getting the file size, and then moving it or deleting it when installing/not installing. In other words, there is no easy / proper workaround for it.

Conclusion

Although Macros and Functions are very similar beasts, each has its strengths and weaknesses, and certainly things to look out for. In general, however, you can follow these guidelines:

  • if you're not using the code more than once and it's just a single line or a few lines, don't bother using either a Macro or a Function.
  • if you're not using the code more than once, but it's a good number of lines and is really a procedure that you want to easily be able to maintain or is otherwise just clogging up the view of the surrounding code, consider putting it into a Macro.
  • if you're using non-trivial code more than once, always consider putting it into a Macro or Function
  • if it's a lot of code (many, many lines), considering using a Function rather than a Macro
  • if it's code that gets used only once in the installer -and- only once in the uninstaller, consider using a macro rather than a function, as this saves un.Function hassles
  • if you need to use the File command, consider using a Macro rather than a Function - or, if possible, re-think the process to keep the File command outside of the Function.
  • if you don't want to fuss around with labels, consider using a Function rather than a Macro - or consider using the work-around which is really quite painless.
  • consider setting up a !define for your Macros to make the code cleaner
  • consider using the Hybrid approach whenever you decided to go with a Function but need to pass parameters/arguments
AND