출처: 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