Dirk Bertels

The greatest malfunction of spirit
is to believe things (Louis Pasteur)

Deploying .NET projects with NSIS

Last updated 08 May 2011


Index

Introduction
Which Visual Studio Version?
The NSIS Deployment package
A few basic NSIS code snippets
NSIS Code excerpts
Debugging a 'Side-by-side configuration is incorrect' error
Links and References
Comments

Introduction

The Visual Studio Express IDE does have certain limitations compared to the professional version. One of these limitations is the lack of deployment options. Deploying an application is not a menial task - several things need taking care of, such as deciding what deployment software to use and determining the additional libraries your application needs to enable it to function. To complicate matters even more, different libraries will be available on different systems.

When the Visual Studio IDE is installed on the developer's system, it automatically installs all the libraries that .NET needs. So once that developer transports his executable to a system which hasn't that particular version of Visual Studio installed, chances are that the application will not execute and throw an error. This is because a .NET application requires a few extra library packages on top of the standard Windows libraries. In effect it needs 2 library packages:

  1. A .NET Framework. The particular version will depend on the version of Framework your project targets. This can be set in Visual Studio by going Project → Properties → Common Properties → targeted Framework
  2. A Visual Studio Redistributable package. The version of this depends on the Visual Studio version your project is designed on.

The deployment software needs to know if these packages reside on the user's computer, and if not, it will need to facilitate installment of these.

Both the packages can be found in the system's Control Panel → Programs and Features. You will find entries such as Microsoft Visual C++ 2008 Redistributable... and Microsoft .NET Framework... (if they are available). Usually various versions of each package exist.

I checked these packages on many Windows 7 computers during a recent stroll around a computer store, and they all seem to have the 2008 redistributable and the Framework 3.5 installed. So it pays to aim your development project to make use of these packages. I also found that the packages are installed on many XP and Vista systems.

back to index

Which Visual Studio Version?

At this time of writing I advocate using Visual Studio 2008. First and most importantly, Visual Studio 2010 does not have intellisense working, which to me is a serious drawback. Also, the Microsoft Visual 2010 Redistributable is not available as yet on many systems, so it will take that extra step to include it within your deployment.

The Express version is free and has all the functionalities needed to develop serious software.

back to index

The NSIS Deployment package

NSIS (Nullsoft Scriptable Install System) is a professional open source system that creates Windows installers. At first, its script language seems a bit tedius, but it is very powerful and there are many examples on the net. Tedious is a bit of an understatement, I do find the NSIS scripting actually quite perplexing, especially its use of stacks and labels - normally low level mechanisms - which is odd for a scripting language. Also, creating pages is quite confusing, but the potential is certainly there, as can be learned from the many sophisticated examples on the web.

The HM NIS Edit is a free NSIS editor and includes a GUI editor to create pages. This facility is (confusingly) located in the editor's File → New Install Options File

It is easy to get lost in the countless articles and webpages on NSIS. Reading the documentation that comes with the software and running its examples provides a good first step.

A few basic NSIS code snippets

These are just a few comments on some basic NSIS scripting before delving into the more serious stuff. You will need to read the NSIS Scripting reference for a detailed analysis of the language. The semi colon (;) denotes a comment.

ini files

Ini files are generated by the HM NIS Edit IO designer. To write code to the ini file:

!insertmacro INSTALLOPTIONS_WRITE "customPage.ini" "Field 2" "State" "Blah|Value2|Foo|Bar"

ReadINIStr

user_var(output) ini_filename section_name entry_name

Reads from entry_name in [section_name] of ini_filename and stores the value into user variable $x. The error flag will be set and $x will be assigned to an empty string if the entry is not found:

ReadINIStr $0 $INSTDIR\winamp.ini winamp outname	

strcmp

str1 str2 jump_if_equal [jump_if_not_equal]

Compares (case insensitively) str1 to str2. If str1 and str2 are equal, Gotos jump_if_equal, otherwise Gotos jump_if_not_equal.

StrCmp $0 "a string" 0 +3 ;+3 means 'skip 3 steps'
DetailPrint '$$0 == "a string"'
Goto +2
DetailPrint '$$0 != "a string"'

You can use labels:

StrCmp $0 "aValue" continue wrong
StrCmp $0 "anotherValue" continue wrong
continue:
wrong:

Using flow control

The LogicLib.nsh library enables one to use more familiar flow control. So instead of using StrCmp, you could use this:

${If} $0 == 'some value'
  MessageBox MB_OK '$$0 is some value'
${ElseIf} $0 == 'some other value'
  MessageBox MB_OK '$$0 is some other value'
${Else}
  MessageBox MB_OK '$$0 is "$0"'
${EndIf}

The Stack

The stack can be used to pass parameters to functions or plugins. It can also be used to extend the $0-$9 and $R0-$R9 values, by putting their current values on the stack, assign new values to them, doing something with it, and return the old values to the variables. Following shows what happens with an example:

Code 										Stack 
Push "Value 1"
Push "Value 2"
											Value 2
											Value 1 
Pop $0
;$0 contains: "Value 2"
											Value 1 
Push $0
Push "Value 3"
Push "Value 4"
											Value 4
											Value 3
											Value 2
											Value 1 
 

DetailPrint

Adds the string "user_message" to the details view of the installer.

DetailPrint "this message will show on the installation window" 
  

The MessageBox

Use a messagebox as per standard windows coding:

Messagebox MB_OK|MB_ICONINFORMATION \	; \ indicates new line
"SetCustom"
  

Standard pre-formatted (wizard) page

To display a page, just state it

Page xxx 	;xxx is a standard wizard page
  

Sections

Each section contains zero or more instructions. Sections are executed in order by the resulting installer. Instructions in sections are executed at runtime. Most used instructions are SetOutPath which tells installer where to extract the files to, and File which simply extracts files.

Section "xxx"  
  

Functions

Functions are like sections but are called differently. There are 2 types:

  1. user functions: called by user from sections or other functions, using Call keyword
  2. callback functions: called by installer on certain events (e.g. .onInit)

NSIS Code excerpts

The following script only shows the code necessary to do the following things:

  • Show Splash screen
  • Add a serial number registration window
  • Install proprietry drivers
  • Check and install .NET
  • Check and install the VC 2008 Redistributable
  • Check for previously installed versions

!define TEMP1 $R0           ; Temp variable to store license key
Var DotnetInstalled         ; if OK no need to install

;/////////
; INCLUDES
;/////////
!include LogicLib.nsh       ; Use more familiar flow control
!include "MUI.nsh"          ; Modern GUI
!include "x64.nsh"          ; For using {RunningX64}
!include "DotNetVer.nsh"    ; for ${HasDotNet3.5}
!include "nsDialogs.nsh"    ; To create labels on a custom page

;Request application privileges for Windows Vista
RequestExecutionLevel user

Page custom Registration "" ": Registration"
Page custom InstallDrivers "" ": Drivers"  
Page custom CheckAndInstallDotNet "" ": Dot Net Framework"
page custom CheckAndInstallVcRedist "" ": VC++ Redistributable"

Function .onInit
   InitPluginsDir
   File /oname=$PLUGINSDIR\splash.bmp "DeploymentResources\YourSplash.BMP"
   ; show x in milliseconds
   splash::show 2000 $PLUGINSDIR\splash                                   
   ; insert dialog ini file in pluginsdirectory:
   File /oname=$PLUGINSDIR\serial.ini "serial.ini"
   Pop $0
   Call RunUninstaller  ; uninstall previous versions
FunctionEnd

Function RunUnInstaller
  ; uninstall previous versions
  ReadRegStr $R0 HKLM \
  "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
  "UninstallString"
  StrCmp $R0 "" done
  MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
  "${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the \
  previous version or `Cancel` to cancel this upgrade. \
  IDOK uninst
  Abort
  ;Run the uninstaller:
uninst:
  ClearErrors
  ExecWait '$R0 _?=$INSTDIR'
  IfErrors no_remove_uninstaller done
  no_remove_uninstaller:
done:
FunctionEnd

Function Registration
  !insertmacro MUI_HEADER_TEXT "Registration" ""
  Push ${TEMP1}
    again: InstallOptions::dialog "$PLUGINSDIR\serial.ini"
    Pop ${TEMP1}

  StrCmp ${TEMP1} "success" 0 continue
  ; read from State in Field 1 of up.ini and store into $0
  ReadINIStr $0 "$PLUGINSDIR\serial.ini" "Field 1" "State"
  ReadINIStr $1 "$PLUGINSDIR\serial.ini" "Field 2" "State"
 ; username and password can be one of 5:
 ; user1, user1 password OR user2, user2 password OR user3, user3 password, ...
  StrCmp $0 "user1" 0 +2
    StrCmp $1 "user1 password" continue wrong
  StrCmp $0 "user2" 0 +2
    StrCmp $1 "user2 password" continue wrong
  StrCmp $0 "user3" 0 +2
    StrCmp $1 "user3 password" continue wrong
  StrCmp $0 "user4" 0 +2
    StrCmp $1 "user4 password" continue wrong
  StrCmp $0 "user5" 0 +2
    StrCmp $1 "user5 password" continue wrong
  wrong:
    IntOp $2 $2 + 1
    StrCmp $2 5 kill
    MessageBox MB_OK|MB_ICONSTOP "Wrong user name and password, try again"
    Goto again
  continue: Pop ${TEMP1}
  Return
  kill: MessageBox MB_OK|MB_ICONSTOP "5 times wrong. Please contact YourCompanyName for assistance."
  Quit
FunctionEnd

Function InstallDrivers
  !insertmacro MUI_HEADER_TEXT "FTDI Drivers" ""
  nsDialogs::Create 1018	 ; Create a Dialog Window
  ${NSD_CreateLabel} 0 10 100% 24u "Click next to install drivers ..."
  ; copy to temp location
  SetOutPath '$TEMP'
  SetOverwrite on
  File /r Drivers
   ${If} ${RunningX64}
       ExecWait '"$TEMP\Drivers\dpinst_amd64.exe" /c /PATH "$TEMP\Drivers"'
       ${NSD_CreateLabel} 0 10 100% 24u "X64 Drivers installed. Click 'Next'to continue."
       nsDialogs::Show
       RMDir /r "$TEMP\Drivers"
   ${Else}
       ExecWait '"$TEMP\Drivers\dpinst32.exe" /c /PATH "$TEMP\Drivers"'
       ${NSD_CreateLabel} 0 10 100% 24u "X32 Drivers installed. Click 'Next'to continue."
       nsDialogs::Show
       RMDir /r "$TEMP\Drivers"
   ${EndIf}
FunctionEnd

Function CheckAndInstallDotNet
  !insertmacro MUI_HEADER_TEXT "DOTNET 3.5 Framework" ""
  nsDialogs::Create 1018  ;Create a Dialog Window
  ${NSD_CreateLabel} 0 0 100% 10% "Checking for Dot NET Framework 3.5 on your system ..."
  ;Check the DotNet Version
  Call CheckDotNetFramework
  ; Install Dot net if not present
  ${If} $DotnetInstalled == ""
     ${NSD_CreateLabel} 0 40 100% 24u "Your System does not have Dot Net Framework 3.5 installed. \
     Ensure you are connected tot the Internet and click 'Next' to install the Dot Net Framework ..."
     nsDialogs::Show       ; show the dialog window
     Call InstallDotNet
  ${Else}
  ${NSD_CreateLabel} 0 40 100% 24u "Your System has the required Dot Net Framework already installed. Please click Next \
  to continue this installation."
     nsDialogs::Show
  ${EndIf}
FunctionEnd

Function CheckDotNetFramework
  ${If} ${HasDotNet3.5}
    Push "Microsoft .NET Framework 3.5 installed."
    Pop $DotnetInstalled
  ${Else}
    Push ""
    Pop $DotnetInstalled
  ${EndIf}
FunctionEnd

Function InstallDotNet
    SetOutPath '$TEMP'
    SetOverwrite on
    ;file work UNDO to save time in debugging
    File 'DeploymentResources\dotnetfx35setup.exe'
    ExecWait '"$TEMP\dotnetfx35setup" /norestart'
    Delete "$TEMP\dotnetfx35setup.exe"
FunctionEnd

Function CheckAndInstallVcRedist
!insertmacro MUI_HEADER_TEXT "VC++ 2008 Redistributable" ""
  nsDialogs::Create 1018   ;Create a Dialog Window
  ${NSD_CreateLabel} 0 0 100% 10% "Checking for VC 2008 C++ Redistributable ..."
  Call CheckVCRedist
  StrCmp $R0 "-1" 0 +4
    ${NSD_CreateLabel} 0 40 100% 10% "Installing VC 2008 C++ Redistributable..."
    Call InstallVcRedist
    Goto +2
    ${NSD_CreateLabel} 0 40 100% 10% "VC 2008 C++ Redistributable already installed. \
    Please click next to continue installation"
    nsDialogs::Show       ;show the dialog window
FunctionEnd

Function CheckVCRedist
   ; check for 2 possible keys in registry, if any one is good, 
   ; the distributable has already been installed:
   ReadRegDword $R0 HKLM "SOFTWARE\Wow6432Node\Microsoft\DevDiv\VC\Servicing\9.0\RED\1033" "SPName"
   ${If} $R0 == "SP1"
        return
   ${Else}
        ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\DevDiv\VC\Servicing\9.0\RED\1033" "SPName"
        ${If} $R0 == "SP1"
           return
        ${Else}
           StrCpy $R0 "-1"
        ${EndIf}
   ${EndIf}
FunctionEnd


; install Visual Studio 2008 C++ redistributable
Function InstallVcRedist
         SetOutPath '$TEMP'
         SetOverwrite on
         ;file work
         File 'DeploymentResources\vcredist_x86.exe'
     ExecWait '"$TEMP\vcredist_x86.exe" /norestart'
    Delete "$TEMP\vcredist_x86.exe"
FunctionEnd

Debugging a 'Side-by-side configuration is incorrect' error

I encountered a Side-by-side Error when trying to execute one of my applications on a different system.

Here's a clear explanation of Side-bySide from Wikipedia:

A common issue in previous versions of Windows was that users frequently suffered from DLL hell, where more than one version of the same dynamically linked library (DLL) was installed on the computer. As software relies on DLLs, using the wrong version could result in non-functional applications, or worse. Windows XP solved this problem for native code by introducing side-by-side assemblies. The technology keeps multiple versions of a DLL in the WinSxS folder and runs them on demand to the appropriate application keeping applications isolated from each other and not using common dependencies.

Quoted from Wikipedia: Features new to Windows XP


From the Microsoft MSDN website:

A side-by-side assembly contains a collection of resources - a group of DLLs, Windows classes, COM servers, type libraries, or interfaces - that are always provided to applications together. These are described in the assembly manifest.

Quoted from MSDN: Side-By-Side Assemblies


From XP systems onwards, the shared components of side-by-side applications are located in a huge (5 GB +) folder, named WinSxS (SxS being an acronym for Side-by-Side). It is the biggest systems folder on your computer.

When the system throws an error, a log file is automatically created. This file can be read by going to Control Panel → Administrative Tools → Event viewer → Windows Logs → Applications. Just clear the panel and start the executable that throws this error. Reopen the System Viewer and one or more errors should appear. Double click on an error message to view the details.

But you can do a more detailed analysis using a system tool called sxstrace:

  • run in the command line: sxstrace trace -logfile:sxstrace.etl
  • run your app and click OK in the Error Message
  • stop tracing by pressing Return in the command line
  • convert the binary error file to txt: sxstrace parse -logfile:sxstrace.etl -outfile:sxstrace.txt

Another tool that proved most useful of all is the Dependency Walker which can be freely downloaded from the dependency walker website. The application is called depends.exe. Use it as follows:

  • start depends.exe
  • drag your application into it
  • profile
  • save as dwl file and read
  • right click the module and select full paths

See also Determining Which DLLs to Redistribute

As it turned out in my case, depends.exe allerted me to the fact it couldn't find msvcrtd.lib. I solved this by adding the following lines in Visual Studio's project command line properties (project → Properties → command line):

/NODEFAULTLIB:libcmt.lib 
/NODEFAULTLIB:libct.lib 
/NODEFAULTLIB:libcd.lib 
/NODEFAULTLIB:libcmtd.lib 
/NODEFAULTLIB:msvcrtd.lib

back to index

Links and references

back to index


Comments