Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating an Installer

4.77/5 (30 votes)
7 Mar 2008CPOL15 min read 1   5.2K  
Using NSIS to create a simple installer
Sample Image

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 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

Ingredients

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:

The Big Picture

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.

Compiling a Script

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."

Compiling A 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.

HM Editor

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.

Scripts in Detail

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.

Dividing A Script

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:

Dividing A Script

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

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:

AttributesWhat it does
NameSets the name of the installer
OutFileSpecifies the output file that MakeNSIS should write the installer to; name of the installer file (like MySetup.exe)
InstallDirSets the default installation directory
ShowInstDetailsSets whether or not the details of the install are shown
ShowUnInstDetailsSets whether or not the details of the uninstall are shown
SetCompressorSets the compression algorithm used to compress files/data in the installer
SetCompressorDictSizeSets the dictionary size in megabytes (MB) used by the LZMA compressor
Lab 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.

Variables

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:

ConstantWhat it means
$PROGRAMFILESUsually C:\Program Files, but may be different on different machines.
$PROGRAMFILES32, $PROGRAMFILES64On Windows X64, the first one points to C:\Program Files (x86) while the second points to C:\Program Files.
$DESKTOPThe Windows desktop directory (usually C:\Windows\Desktop, but detected at runtime).
$EXEDIRThe directory containing the installer executable.
$EXEPATHThe 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.
$WINDIRThe Windows directory.
$SYSDIRThe Windows system directory.
$TEMPThe system temporary directory.
$STARTMENUThe Start menu folder (useful in adding start menu items using CreateShortCut).
$SMSTARTUPThe 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.
$DOCUMENTSThe documents directory. The context of this constant (All Users or Current user) depends on the SetShellVarContext setting. The default is the current user.
$APPDATAThe 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_AREAA 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.

Sections

"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 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

"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 nameWhen it's called
.onInitWill be called when the installer is nearly finished initializing. If the .onInit function calls Abort, the installer will quit instantly.
.onUserAbortIs 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.
.onInstFailedIs called when the user hits the Cancel button after the install has failed.
.onMouseOverSectionIs 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.onInitWill 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 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.

Pages

"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 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."

Useful Instructions

The following table lists some of the instructions that you will probably need.

InstructionWhat it doesUsage
FileAdds file(s) to be extracted to the current output path ($OUTDIR)File "Bin\7z\*.*"
QuitCauses the installer to exit as soon as possible
ExecWaitExecutes the specified program and wait for the executed process to quitExecWait 'c:\SomeProgram.exe' $0
DetailPrintAdds the string "message" to the details view of the installerDetailPrint "message"
StrlenSets the user variable $x with the length of strStrlen $0 ${SETUP_NAME}
ReadRegStrReads from the registry into the user variableReadRegStr $0 HKLM Software\NSIS ""
CopyFilesCopies files from the source to the destination on the installing systemCopyFiles "$0\cid.dll" "$INSTDIR"
CreateDirectoryCreates (recursively if necessary) the specified directoryCreateDirectory "7z"
CreateShortCutCreates a shortcut link.lnk that links to target.file, with optional parameters parametersCreateShortCut "$SMPROGRAMS\$STMenuDirectory\Help.lnk" "$INSTDIR\Yas.chm"
IfFileExistsChecks for the existence of file(s)IfFileExists "$INSTDIR\${SQL_DATABASE_NAME}.mdf" 0 Goon_label
DeleteDeletes file (which can be a file or wildcard, but should be specified with a full path) from the target systemDelete '"$INSTDIR\${SQL_DATABASE_NAME}.mdf"'
DeleteRegKeyDeletes a registry keyDeleteRegKey[/ifempty] root_key subkey
DeleteRegValueDeletes a registry value; valid values for root_key are listed under WriteRegStrDeleteRegValue 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!

Acknowledgement and Disclaimer

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)