improve.dk
Just another mindless drone looking for the perfect stack
posts - 227, comments - 489

Creating a .NET bootstrapped installer using NSIS

Written on June 10, 2007 by Mark S. Rasmussen in Development: .NET

If you have ever deployed .NET windows applications, you have without doubt tried the Visual Studio Install project type. The Install project will create .MSI install applications for you, they're great for basic installations, but nothing more than that. The .NET bootstrapper is quite lacking, at times it won't be able to find the framework download file as it's changed it's location, at other times it's not able to download it. And finally, if it does determine that the user needs the framework, it's shown in an ugly uncustomizable GUI.

I've looked for an alternative, I'd prefer not to pay for using some of the well established installers such as Wise, InstallShield, ActiveInstall and so forth. What I found is a remnant of WinAmp, the NSIS (Nullsoft Scriptable Install System) project.

NSIS is both free and open source and it's very much community driven. There are a plethora of plugins available for all sorts of different tasks, and of course you can write your own plugins for special needs.

I won't delve too deeply into why you should choose NSIS over any of the competitors, instead I'll show you a step by step guide of how to create an NSIS installer that bootstraps the .NET 2.0 framework as well as running custom install and uninstall actions in .NET code.

You will need two tools. The first (and actually the only required tool) is NSIS itself: Download NSIS from nsis.sourceforge.net. While you can edit the install script in any text editor, using an IDE like HM NIS Edit (HMNE) makes it a whole lot easier: Download HMNE from hmne.sourceforge.net.

Let's first create the simple application that we will be installing. It doesn't really matter what it is, in this example I've made a single Windows Forms Application:

nsis_1

Now let's start up HMNE. Click File -> New Script From Wizard. Fill in the relevant application date on the first page.

nsis_2

You may choose a custom icon for the installer, the default icon is an NSIS standard icon. You can also choose the resulting installation files name as well as it's output location. If no location is specified, it will be outputted to the location that contains our script file. A great feature of NSIS is that it's got localized versions for a lot of different languages built in, simply select the languages that the user should be able to select and the installer will automatically be localized. You can choose a couple of different GUIs, I personally prefer the Modern one. As for compression, in my tests the LZMA compression works the best, though compression time and CPU usage might be a factor for very large projects.

nsis_3

Now you specify where you want your application to be installed, notice the general use of variables like $PROGRAMFILES, $NSISDIR, $INSTDIR and so forth. You can also optionally choose a license file that the user must accept to continue the installation.

nsis_4

Now you can setup the actual files that will be installed as part of the project. You cannot select a whole project output as you can in the Visual Studio Install project, instead you must manually select the files that should be installed, usually from the Debug/Release directories of your project(s).

nsis_5

Optionally you can select which links you want to be placed in the application program group in the start menu, if such one should be created at all.

nsis_6

When the installation is done, the user can choose to view the Readme file and/or start the application - that is, if you specify an application and/or a readme file.

nsis_7

Finally we can choose to include an uninstaller, as well as specifying our custom uninstallation confirmation and report texts. If no custom text is specified, the default texts will be used.

When the wizard finishes, make sure to click "Save script" and "Convert file paths to relative paths".

Finally the install script is made and ready to run. Press Ctrl+9 to compile the script. If everything succeeds, you'll see the Setup.exe file in the same directory as the one where you saved the install script. I will not be going over the various commands and settings that are being set in the script, for that I strongly recommend the excellent built in help documents, as well as the WinAmp NSIS forums. Instead I'll focus on how to bootstrap the .NET Framework 2.0 and how to run custom install and uninstall actions.

Add these three lines to the top of your nsi file, they include some functions that we will need:

; Script generated by the HM NIS Edit Script Wizard.
!include WordFunc.nsh
!insertmacro VersionCompare
!include LogicLib.nsh

Add this line right above the .onInit function, it makes a variable (untyped) that'll contain a Yes/No value, depending on whether we need to install the .NET Framework or not.

Var InstallDotNET

Now modify the .onInit function so it matches the below, as well as adding the GetDotNETVersion function. First we ask the user what language they want to continue in (!insertmacro MUI_LANGDLL_DISPLAY). After that we initialize the InstallDotNET variable to "No". Depending on the result of the GetDotNETVersion we tell the user that we need to install the framework, either because the user does not have the framework at all, or because the version is less than 2.0. We won't actually install the framework yet, we'll just remember whether we have to or not.

Function .onInit
  !insertmacro MUI_LANGDLL_DISPLAY
  
  ; Check .NET version
  StrCpy $InstallDotNET "No"
  Call GetDotNETVersion
  Pop $0
  
  ${If} $0 == "not found"
        StrCpy $InstallDotNET "Yes"
  	MessageBox MB_OK|MB_ICONINFORMATION "${PRODUCT_NAME} requires that the .NET Framework 2.0 is installed. The .NET Framework will be downloaded and installed automatically during installation of ${PRODUCT_NAME}."
   	Return
  ${EndIf}

  StrCpy $0 $0 "" 1 # skip "v"

  ${VersionCompare} $0 "2.0" $1
  ${If} $1 == 2
        StrCpy $InstallDotNET "Yes"
  	MessageBox MB_OK|MB_ICONINFORMATION "${PRODUCT_NAME} requires that the .NET Framework 2.0 is installed. The .NET Framework will be downloaded and installed automatically during installation of ${PRODUCT_NAME}."
   	Return
  ${EndIf}
FunctionEnd

Function GetDotNETVersion
	Push $0
	Push $1

	System::Call "mscoree::GetCORVersion(w .r0, i ${NSIS_MAX_STRLEN}, *i) i .r1"
	StrCmp $1 "error" 0 +2
	StrCpy $0 "not found"

	Pop $1
	Exch $0
FunctionEnd

Before we continue, you'll have to install the InetC plugin.

Now find the "MainSection" section (depending on what you called it in the wizard).

Modify the section so it looks like the below. Your file names and amount may vary, the primary part of our concern is the first part. It will test whether the $InstallDotNET variable implies that we have to install the framework. If it does, it'll hide the usual GUI elements of the installer and start the download of the .NET Framework from any URL you specify, this could be the official download URL or a location you host yourself. If the user cancels the download we'll delete the half-finished file and abort. Otherwise we'll execute the dotnetfx.exe file and wait for it to complete (hence we'll now have the .NET Framework 2.0). After having installed the framework we delete the dotnetfx.exe file again. Finally we show the GUI again.

Section "MainSection" SEC01
  SetOutPath "$INSTDIR"
  SetOverwrite ifnewer

  ; Get .NET if required
  ${If} $InstallDotNET == "Yes"
     SetDetailsView hide
     inetc::get /caption "Downloading .NET Framework 2.0" /canceltext "Cancel" "http://www.url_of_the_dotnetfx.exe_file" "$INSTDIR\dotnetfx.exe" /end
     Pop $1

     ${If} $1 != "OK"
           Delete "$INSTDIR\dotnetfx.exe"
           Abort "Installation cancelled."
     ${EndIf}

     ExecWait "$INSTDIR\dotnetfx.exe"
     Delete "$INSTDIR\dotnetfx.exe"

     SetDetailsView show
  ${EndIf} 
 
  File "Install\bin\Debug\Install.exe"

  File "Install\bin\Debug\Uninstall.exe"

  File "MyApplication\bin\Debug\MyApplication.exe"
  CreateDirectory "$SMPROGRAMS\My application"
  CreateShortCut "$SMPROGRAMS\My application\My application.lnk" "$INSTDIR\MyApplication.exe"
  CreateShortCut "$DESKTOP\My application.lnk" "$INSTDIR\MyApplication.exe"
SectionEnd

Now comes the part where I'll introduce our custom .NET install and uninstall actions. Create two new Console Application projects in the solution called Uninstall and Install, like the following:

nsis_9

Add the below function to your code, it'll run the Install.exe file after the installation has successfully completed:

Function .onInstSuccess
         ExecWait "$InstDir\Install.exe"
FunctionEnd

Locate the "Section Uninstall" part and add the following line as the very first:

ExecWait "$InstDir\Uninstall.exe"

Make sure to add both Install.exe and Uninstall.exe to the list of files that will be installed, in the Main Section. It will run the Uninstall.exe application before anything else, and wait for it to finish before continuing. After it's done we'll delete all the installed files, including the Install and Uninstall.exe applications - remember to add those to file deletions manually, following the syntax of the other file deletions.

Now press Ctrl+9 to build the installer, and look at it run in all of its awesomeness:

nsis_10

nsis_11

After having downloaded the .NET Framework 2.0, it'll start the .NET installer and run it through as usual, the installation will continue as soon as the .NET installer finishes. There is currently no check for whether the user cancels the .NET installation midways or if it fails. A simple check could be made right afterwards by simple calling the GetDotNETVersion function again like we did just before, if it fails, the user hasn't installed .NET for some unknown reason and we'll have to abort.

You can see my complete install script here. Download and rename to *.nsi to compile it.

Feedback

Gravatar

tt wrote on 6/16/2007 11:38 AM

Thanks for the tutorial, it worked great! Currently using it for the install of my latest project :)

Check it out - http://www.thetechtray.net/2007/06/13/clipmon-10/
Gravatar

Zophar wrote on 6/27/2007 6:34 AM

Great Tutorial ... Helped solve my .NET installation problems.
Gravatar

Webmaster wrote on 7/1/2007 6:50 AM

Thanks for a great script! A real time saver.

How could we change it to check .NET 3.0 installed, so that the user doesn't have to download .NET 2.0 if he has .NET 3.0 installed?

Thank you,

Andrei
Gravatar

Mark S. Rasmussen wrote on 7/1/2007 12:58 PM

@Andrei

Although I haven't tested it, I'd suppose you could add a third version check like this:

${VersionCompare} $0 "3.0" $1
${If} $1 == 2
StrCpy $InstallDotNET "No"
Return
${EndIf}

And then nest the other version checks inside this one in the case 3.0 isn't installed.

Let me know if you get it working, I'd like to add it to the article as well :)

Thanks,
Mark
Gravatar

Webmaster wrote on 7/2/2007 2:49 AM

Thank you, Mark.

I don't have Vista installed on my computer, therefore I can't test your code immediately.

To all: If somebody tests this 3.0 check, please, post.

Thank you,

Andrei
Gravatar

Webmaster wrote on 7/5/2007 2:37 PM

Hello Mark,

I found out a better way to check .NET 2.0 or 3.0 installation - that is to see for a certain registry value:

; Check .NET version
ReadRegDWORD $0 HKLM 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727' Install
${If} $0 == ''
StrCpy $InstallDotNET "Yes"
MessageBox MB_OK|MB_ICONINFORMATION "${PRODUCT_NAME} requires that the .NET Framework 2.0 is installed. The .NET Framework will be downloaded and installed automatically during installation of ${PRODUCT_NAME}."
Return
${EndIf}

We check for .NET 2.0 installation in registry. If .NET 3.0 is installed, the .NET 2.0 record will be there as well.

This should work better than mscoree::GetCORVersion check, because GetCORVersion returns the version number of the CLR that is running in the current process.

Credits: NSIS Quick Start article by Jared James Sullivan
(www.codeproject.com/useritems/NSIS.asp?msg=2107949)
Gravatar

zamnut6 wrote on 7/11/2007 3:33 AM

Very nice tutorial. I am a new user to NSIS and ran into a small problem. The installer needed a plugin to recognize inetc::get. I got the plugin at:

http://nsis.sourceforge.net/Inetc_plug-in

and put the inetc.dll file in:

C:\Program Files\NSIS\Plugins

I am sure that most readers had done this long ago but I put in this detail in case it might help other new users like me.
Gravatar

Indika Perera wrote on 10/25/2007 8:32 PM

Works Great!! Thanks Mark!!

zamnut6's advise too was valuable...

:-)
Cheers!
Gravatar

Ramesh Vagh wrote on 12/21/2007 1:52 PM

Hello...!

Can any one help me to call web service with parameters and from the NSIS installer and get response from the web service.

based on the response i want to continue installation and also want to check at the end of the installation over the web service.

Or

If any one can tell me to use .Net dll functions in the NSIS script.

Or

Any way to get my gole without opening any explorer, i should be able to pass parameters to the web service or .Net dll and get response from that.

if anyone can give me the solution then i'll be thankfull.

Ramesh
Gravatar

SLK wrote on 1/3/2008 4:51 AM

This solution looks great :-> But I have a few questions...

Could you explain the purpose of the console apps (install.exe and uninstall.exe)? You wrote:
"Make sure to add both Install.exe and Uninstall.exe to the list of files that will be installed, in the Main Section. It will run the Uninstall.exe application before anything else, and wait for it to finish before continuing. After it's done we'll delete all the installed files, including the Install and Uninstall.exe applications - remember to add those to file deletions manually, following the syntax of the other file deletions."

I'm confused because I don't see those lines in the Main Section of your script.

Also, the .NET Framework installer (dotnetfx.exe) seems to execute with its GUI and not silently. Is there anyway to make it silent?

And finally, since Microsoft Installer 3.0 is a prerequisite for the .NET Framework 2.0 installer, can you recommend a method for checking for and installing it? This only seems to be an issue on Windows 2000 machines.

Thanks!
Gravatar

Dhaval Bhatt wrote on 1/12/2008 11:57 AM

Ramesh
This is Not Possible from NSIS.
Althouh you can do it by using Commandline Programe
(Which you had apply in your Service already)
JA ja ja
Gravatar

zee wrote on 2/18/2008 5:02 PM

Thanks for the great tutorial it was a great help for me :)
i have a question.
is there a way to check the setup folder for dotnetfx.exe first and if it does not exist then jump to the download process??

thank you very much
Gravatar

Anand wrote on 3/26/2008 1:39 PM

Hi,

How to create a Application Icon...I have created the setup with Icon...after the application installed ...my appication having Windows default icon for shorcuts....
pls help
Gravatar

Karl wrote on 4/18/2008 12:59 PM

Great article! I love NSIS but when I switched to .Net programming I didn't find any easy ways to check if the framework was installed, now I can finally toss out the Setup Project crap from Microsoft!
Gravatar

Geetha wrote on 7/28/2008 6:58 AM

Hi, plz let me knw sooooon how to create virtual directory usng NSIS in .Net Applications
Gravatar

shraddha wrote on 7/31/2008 11:21 AM

Hi...thanks for these posts...it helped me alot as a beginner in NSIS. I am not able to solve a small problem. my .nsi script calls a powershell script. i want that my nsi script should call this script from the same folder where the installer exe is placed. i dont want to hard code the path for my ps1 script. please can anyone give mea command which can fetch my ps1 file from the same path where installer is placed.
thanks in advance :)
Gravatar

Andrei wrote on 6/19/2009 11:53 AM

Hi,
Nice tutorial!
I need something more than this. I want to include .net fr in my project, not to download it on intallation. My setup will be x MB + 25 MB (.net fr).
How to do that ?

Andrew
Gravatar

Alexey wrote on 7/23/2009 1:03 PM

Hi! Thanks for the great explanations, but I have a little problem - GUI with downloading status is not appeared though I don't have "silent" mode... What can I do in order to see this GUI?
Gravatar

Diego Sanchez wrote on 9/21/2009 4:15 PM

I've read your tutorial but when I execute the next instruction- I didn't get any result.

The instruction is 'ExecWait "$INSTDIR\dotnetfx.exe"'.

After call this function We should view .NET install page. But I didn't see that.

Why didn't ExecWait show nothing?

Thanks
Good gay ahead!
Gravatar

Jamie wrote on 3/9/2010 7:22 PM

How could I modify this for .NET 4.0 RC

I have tried changing
${VersionCompare} $0 "4" $1
${VersionCompare} $0 "4.0" $1

etc but still asks me on my development machine to install .net 4 (which is installed)

Thanks
Gravatar

Batcha wrote on 3/28/2010 7:29 AM

Oh what a nice tutorial! But still i could not use it for my app.
My app.pre requirements are Windows installer 3.1,.NetFramework 3.5 and cards.dll. And i dont want users download them from net instead i want to include them in my setup how do you do that?
thanks in advance :)
Gravatar

Aaron wrote on 3/29/2010 6:22 AM

Thanks so much for this script its helped heaps!
Gravatar

Skynet wrote on 9/28/2010 9:35 AM

Hello.
At the begining I must admit - this is great an article, but I have one question. What exactly should contain Install.exe and Uninstall.exe Console Application? This is one problem which I have with this topic. I will be very gratefull if someone could explain this for me.
Cheers.
Gravatar

matthew wrote on 5/24/2011 7:11 AM

nice thanks very much
Gravatar

Squiffy wrote on 12/2/2011 9:22 AM

For .NET 4, the following seems to work. I've modified earlier contributions, using the registry key specified by Microsoft for .NET 4.

Regarding including prerequisites like this in your compiler, I'm not sure but I think the Microsoft licence prevents you from doing so. So you need to upload them to your website somewhere where your installer can download them.

Function .onInit

; Check .NET version
ReadRegDWORD $0 HKLM 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client' Install
${If} $0 == ''
StrCpy $InstallDotNET "Yes"
MessageBox MB_OK|MB_ICONINFORMATION "${PRODUCT_NAME} requires that the .NET Framework 4.0 is installed. The .NET Framework will be downloaded and installed automatically during installation of ${PRODUCT_NAME}."
Return
${EndIf}

FunctionEnd
Gravatar

Rajeev Lochan wrote on 3/12/2012 3:44 AM

Hi,
Thanks a lot for the explanation and code. I was looking for the solution :)
Gravatar

gybseoservice wrote on 4/17/2012 9:43 PM

i like this post i think this is very informative for all. Here you get free ladyvalura, geldherrin etc.
geldherrin | geldsklave | gelddomina

Post Comment

Name  
Email
Url
Comment
Please add 3 and 6 and type the answer here: