Introduction
There are many tools to create an Installer for our applications. I used two of them. The second one was NSIS and since I learned it, I did not go further. This article introduces NSIS and shows how to create a simple setup application using powerful, yet easy, NSIS scripts.
Lab #0 |
Labs come at the end of each part of this article. In each lab, we will examine a bit more than what we talked about in that part. In the final lab, we will have a complete setup application. |
Table of Contents
Before starting NSIS, we first need to get its compiler. Beside the NSIS compiler comes a lot of handy tools and plug-ins, mostly written by the NSIS community. I put a link to those I used in this article here:
To create a simple installer with NSIS, we should start learning the NSIS scripting language. The idea is that we create a single (or multiple) file(s) that contain(s) the script instructions. Then we ask the NSIS compiler to compile the file and create a setup executable file.
After downloading and installing NSIS using the link provided above, all we need to do is create a new text file called WhatEverName.nsi, type down our commands, right click on the file and select "Compile NSIS Script."
It's not quite bad, but it's much better to have an IDE, isn't it? The second link above refers to a good yet small IDE for NSIS called "HM NIS Edit." In the NSIS Developer Center, you can download other IDEs or integrations to current Editors like Eclipse. Here's a link to the available text editors page. In the HM editor, we can also compile and run our installers without the need to exit the IDE. We have a wizard, a page designer and also, F1 launches the NSIS Script CHM help file.
After running the IDE, you can either type in commands or use the wizard to create a template for a simple installer. Personally, I didn't learn to use NSIS scripts until I started creating my own instructions from scratch.
We got NSIS, an IDE and we know we shall write some instructions to create an installer. But what can we type in? In NSIS, each line is considered a new instruction. The instruction will be known by its name. If we look at the instructions of a sample NSIS script file, we will see some of these parts: Pages, Sections, Functions, variables and other compiler commands or attributes. Depending to their names, each of these instructions has special meanings to the compiler. Below is a sample script that has three of the so-called instructions.
Understanding pages and sections is quite easy, since they represent the visual equivalent in the final compiled installer. The above script, if compiled, will produce the following installer:
The above example section was created in a way that is hidden. We also did not have any functions. Later on, we will examine them in more detail.
Attributes are instructions that define the behavior of the installer, its look and feel or define the behavior of the commands that will come after them. For example, the Name
attribute defines the name of the installer that will be shown at the title bar of the installer window.
Name "MyProduct Version 1.0"
The following table lists some of the useful attributes that you can use in your script:
Attributes | What it does |
Name | Sets the name of the installer |
OutFile | Specifies the output file that MakeNSIS should write the installer to; name of the installer file (like MySetup.exe) |
InstallDir | Sets the default installation directory |
ShowInstDetails | Sets whether or not the details of the install are shown |
ShowUnInstDetails | Sets whether or not the details of the uninstall are shown |
SetCompressor | Sets the compression algorithm used to compress files/data in the installer |
SetCompressorDictSize | Sets the dictionary size in megabytes (MB) used by the LZMA compressor |
Lab #1 |
Using HM NIS Edit to create a new NSI script file, type the following lines into it:
!include "LogicLib.nsh"
!include "MUI2.nsh"
!define PRODUCT_NAME "CP Lab"
!define SETUP_NAME "CPLabSetup.exe"
!define PRODUCT_VERSION "1.0"
OutFile ${SETUP_NAME}
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
;Default installation folder
InstallDir "$PROGRAMFILES\CPLab"
;Get installation folder from registry if available InstallDirRegKey HKLM
;"Software\CP Lab" ""
ShowInstDetails show
ShowUnInstDetails show
SetCompressor /SOLID lzma
SetCompressorDictSize 12
;Request application privileges for Windows Vista RequestExecutionLevel user
;Could be 'admin'
In NSIS script, we can add macros (lines started with !define ) and also we can use scripts in other files, using the !include compile-time command.
*.nsh files contain normal NSIS Scripts, just like our script. The only difference is that we put *.nsi to compile our script, but to include another one we have to give that file a *.nsh extension.
Please note that when using macros, we need to use both the $ sign and {} while in variables it is just the $ sign.
|
They hold values for us, just like what we have in our programming languages. To define a variable, we need to use the var
command. To use it, we must use a $
before the variable name:
var varName
...
StrCpy $varName "example value"
Notes when using variables:
- All variables are global, so when defining inside functions, we must explicitly use the
/GLOBAL
switch: Var /GLOBAL varName
- Allowed characters for variable names are: [
a
-z
], [A
-Z
], [0
-9
] and _
- By default, variables are limited to 1024 characters
Predefined variables:
- There are some variables that are already defined in NSIS and we can use them without declaration. Also, we must be careful and prevent possible conflicts in our names with these:
$0
, $1
, $2
, $3
, $4
, $5
, $6
, $7
, $8
, $9
, $R0
, $R1
, $R2
, $R3
, $R4
, $R5
, $R6
, $R7
, $R8
, $R9
. NSIS sometimes refers to these variables as registers. $INSTDIR
: the installation directory. Can be used anywhere in your script and will be replaced by the installation directory path at runtime. This variable is already set by you when using the InstallDir
attribute, but at runtime the user is able to change it. You can also modify this variable using the StrCpy
command. $OUTDIR
: the current output directory. SetOutPath
or StrCpy
could be used to set this variable.
For more information about variables, refer to the variables section of the NSIS help file.
Constants
Here's a good place to name some of the useful constants that NSIS defines for us. They also added these to their variable part of the help. Remember the InstallDir
attribute? If you don't, it is the attribute that sets the installation directory for us. Unless you are sure about where exactly you want your installer files to be copied (like C:\MyFolder), you'll need to know some paths like the Windows folder path or the Program Files folder path. Constants help you here. At runtime, they will be replaced with end-user computer paths. Please look at the following example:
InstallDir "$PROGRAMFILES\MyApp"
Here we used the $PROGRAMFILES
constant in front of the InstallDir
attribute, which expected a string. $PROGRAMFILES
will be replaced by the exact path string on the user's computer. The following table shows a list of useful constants:
Constant | What it means |
$PROGRAMFILES | Usually C:\Program Files, but may be different on different machines. |
$PROGRAMFILES32 , $PROGRAMFILES64 | On Windows X64, the first one points to C:\Program Files (x86) while the second points to C:\Program Files. |
$DESKTOP | The Windows desktop directory (usually C:\Windows\Desktop, but detected at runtime). |
$EXEDIR | The directory containing the installer executable. |
$EXEPATH | The full path of the installer executable. |
${NSISDIR} | A symbol that contains the path where NSIS is installed. Useful if you want to call resources that are in NSIS directory, e.g. Icons, UIs. |
$WINDIR | The Windows directory. |
$SYSDIR | The Windows system directory. |
$TEMP | The system temporary directory. |
$STARTMENU | The Start menu folder (useful in adding start menu items using CreateShortCut ). |
$SMSTARTUP | The start menu programs / startup folder. The context of this constant (All Users or Current user) depends on the SetShellVarContext setting. The default is the current user. |
$DOCUMENTS | The documents directory. The context of this constant (All Users or Current user) depends on the SetShellVarContext setting. The default is the current user. |
$APPDATA | The application data directory. The context of this constant (All Users or Current user) depends on the SetShellVarContext setting. The default is the current user. |
$CDBURN_AREA | A directory where files awaiting to be burned to CD are stored; this constant is available on Windows XP and above. |
For a complete list of constants or requirements for them, please refer to the variables section of the NSIS help.
"Each NSIS installer contains one or more sections." That means that we need to have at least one section in our script. But what is the use of a section? You have certainly seen dozens of installers that let you choose what to install, like the very well-known Microsoft Office that lets you chose to install, say, Powerpoint or not.
In NSIS, these options can be given to a user with the help of sections. In our example, Microsoft Powerpoint should have a particular section and that section indeed has other sections for each feature you select to be installed. It's logical to have at least one section, since we have at least one feature to install. Don't we? Sections can be hidden, so the user will not see any feature to select one of them, which most probably will be used in installers with only one section.
Section "Installer"
; Instructions go here
SectionEnd
In the above example, I created a new section and named that "Installer." Don't let the name fool you; it's just a name and you can put it MySec if you want. We are free to put any amount of instruction we like inside a section block. I'm not going to describe sections in detail here; it makes this article too lengthy. However, there's a lot that you can find on sections, like how to disable a section, or re-enable it, how to create section groups, how to show section names in bold, make it optional or forced, etc.
Lab #2 |
Reload previous file into your editor and add the following:
Section "Dummy Section" SecDummy
SetOutPath "$INSTDIR"
;Store installation folder
WriteRegStr HKCU "Software\CP Lab" "" $INSTDIR
;Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
SectionEnd
Section "Uninstall"
Delete "$INSTDIR\Uninstall.exe"
RMDir "$INSTDIR"
DeleteRegKey /ifempty HKCU "Software\CP Lab"
SectionEnd
The above code creates two sections. We want the first one to be visible. The second section above will be called when uninstalling. The NSIS compiler realizes that because of the name. If a section name is UnInstall or it starts with un, it will be called when uninstalling.
WARNING: please note that calling RMDir to remove our application folder is really DANGEROUS. The reason is quite simple. Think about a beginner user that changes the default folder path to C:\Program Files. You save the path when installing and, upon calling RMDir , the installer tries removing the whole Program Files folder.
|
"Functions are similar to Sections in that they contain zero or more instructions." There are two types of functions: user functions and callback functions. User functions will be called manually by using the Call
instruction. "Callback functions will be called by the installer when a certain event occurs."
"Functions must be declared outside of Sections or other Functions."
Function func
; some commands
FunctionEnd
Section
Call func
SectionEnd
The above code shows how to create a user function. There is also another type of function called "callback." Callback functions are known by their names. The names are unique and the NSIS compiler can recognize them. These functions will be called upon special events and you're free to put your favorite instructions in them, so that upon the event, they'll run.
Function .onInit
MessageBox MB_YESNO "This will install. Continue?" IDYES NoAbort
Abort ; causes installer to quit.
NoAbort:
FunctionEnd
The above code shows how to write .onInit
so that, as soon as the installer starts, a message box asks the user if he/she would like to continue. The following table lists some of the common callback functions.
Callback function name | When it's called |
.onInit | Will be called when the installer is nearly finished initializing. If the .onInit function calls Abort , the installer will quit instantly. |
.onUserAbort | Is called when the user hits the Cancel button, and the install hasn't already failed. If this function calls Abort , the install will not be aborted. |
.onInstFailed | Is called when the user hits the Cancel button after the install has failed. |
.onMouseOverSection | Is called whenever the mouse position over the sections tree has changed. This allows you to set a description for each section, for example. The section ID on which the mouse is currently over is stored temporarily in $0 . |
un.onInit | Will be called when the uninstaller is nearly finished initializing. If the un.onInit function calls Abort , the uninstaller will quit instantly. |
For more information on functions, refer to the function section in NSIS help.
Lab #3 |
Let's add two functions to our installer: a callback one and a user one.
Function .OnInit
StrCpy $0 "Welcome to my first setup wizard"
push $0
Call ShowWelcome
FunctionEnd
Function ShowWelcome
pop $R0
${If} $R0 == ''
StrCpy $R0 "Message from function"
${EndIf}
MessageBox MB_OK $R0
FunctionEnd
Here we create a callback function .onInit that will be called whenever the installer starts up before the pages being shown. Then we copy a message into the $0 variable that has already been defined for us by the compiler. Finally, we push it so that we can use it in a function later on and we call a function.
The user function tries to pop a string off the stack and into $R0 that again has already been defined by the compiler. We check to make sure it has a value. If it does not, we put another value into $R0 and we finally show a message box.
This shows how we create functions, pass arguments (using built-in stacks), use variables and how easy it is to use logiclib , which we already included. If we were not to use logiclib , we would have to write a code similar to IBM x86 assembly instead of that of $IF .
|
"Each (non-silent) NSIS installer has a set of pages. Each page can be an NSIS built-in page or a custom page." At the time I wrote this article, there was a new version of NSIS available that gave better support for custom pages, but I was too lazy to read them. I leave that to the enthusiastic reader. The page instruction is simple:
Page license
The above code shows a simple empty license page. To force it to show your license file, you should use the page options. In fact, not only the license page, but also all other default pages have options that can be provided by the help of attributes. Another way of coding the UI is to use a modern UI. It's not only simpler, but also much more familiar and nicer. Modern UI is included in NSIS after version 2.0. Documentation to the UI is available here. As I'm writing this article, the second version of the modern UI is available with even better features.
!insertmacro MUI_PAGE_LICENSE "License.rtf"
The above code adds a license page and loads the specified file to be signed by the user. The last important thing in the pages is that they all have events (remember callbacks?). With these callback handlers, you can simply modify the way they behave or decide what to do after or before a page is showed. It is, for example, useful when we want to ignore the next page or enable/disable some options according to previous pages.
!define MUI_PAGE_CUSTOMFUNCTION_PRE PAGE_LICENSE
!insertmacro MUI_PAGE_LICENSE "License.rtf" "" PreLicense
...
Function PreLicense
; If app installed already, license signed, ignore it
${If} $bAppExists == '1'
Abort
;${Else}
${EndIf}
FunctionEnd
The above code calls a function before displaying a license page. The function checks a variable to see if the application already exists. If it exists, the license page will be ignored and the next page will be shown.
Lab #4 |
If we run our installer right now, it will work! It uses sections, our attributes and also some default behavior to do required operations. The problem is that there is not any customization available to the user. Let's get user some choices using pages of the modern UI.
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "License.txt"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
!insertmacro MUI_LANGUAGE"English"
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecDummy ${LANG_ENGLISH} "A section"
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
Just add some pages and we're done. Before we finish this lab, please note how we used SecDummy to add a description to the "select feature" page. If you return back and take a look at sections, you'll notice that, other than the name, there's a SecDummy at the end of the line. In fact, this is called the section index and it is used to access a section.
The last 8 lines are there just to show "A Section" text when the mouse is over the "Dummy Section." |
The following table lists some of the instructions that you will probably need.
Instruction | What it does | Usage |
File | Adds file(s) to be extracted to the current output path ($OUTDIR ) | File "Bin\7z\*.*" |
Quit | Causes the installer to exit as soon as possible | |
ExecWait | Executes the specified program and wait for the executed process to quit | ExecWait 'c:\SomeProgram.exe' $0 |
DetailPrint | Adds the string "message" to the details view of the installer | DetailPrint "message" |
Strlen | Sets the user variable $x with the length of str | Strlen $0 ${SETUP_NAME} |
ReadRegStr | Reads from the registry into the user variable | ReadRegStr $0 HKLM Software\NSIS "" |
CopyFiles | Copies files from the source to the destination on the installing system | CopyFiles "$0\cid.dll" "$INSTDIR" |
CreateDirectory | Creates (recursively if necessary) the specified directory | CreateDirectory "7z" |
CreateShortCut | Creates a shortcut link.lnk that links to target.file , with optional parameters parameters | CreateShortCut "$SMPROGRAMS\$STMenuDirectory\Help.lnk" "$INSTDIR\Yas.chm" |
IfFileExists | Checks for the existence of file(s) | IfFileExists "$INSTDIR\${SQL_DATABASE_NAME}.mdf" 0 Goon_label |
Delete | Deletes file (which can be a file or wildcard, but should be specified with a full path) from the target system | Delete '"$INSTDIR\${SQL_DATABASE_NAME}.mdf"' |
DeleteRegKey | Deletes a registry key | DeleteRegKey[/ifempty] root_key subkey |
DeleteRegValue | Deletes a registry value; valid values for root_key are listed under WriteRegStr | DeleteRegValue root_key subkey key_name |
Final Note
Please note that the installer included in this article will create a folder in your Program Files named CPLab. Inside the folder, you have an Uninstall.exe that will clean up everything for you. There are still plenty features or needed tips to mention here, but if I do that, then I'm putting the NSIS help in another order here. I think this is enough to start creating your installers and, as I did when creating my own, you'll be sure find what you need quickly. Good luck!
Most of the text and codes here have been extracted from NSIS help and samples. I tried to put quotes around texts, but for codes, it would rather make it dirty. Thanks to the NSIS community, there are plenty of codes available to download here.
History
2008/03/04 : Tutorial created