Google
WWW Yariv Hammer's Code Site

Thursday, January 04, 2007

Deploying Your .NET Application Using Inno Setup

Introduction
I deployed my application using Inno Setup. It is a free and very intuitive installation engine.
You can download it free here.

Basically you create a script that ends with an .iss extension using the Inno-setup editor, which is much like a Notepad, but colors the script words. The syntax of the script is very much like an ini file. There is a very good help.

In this article I will demonstrate how to create a good installation to your application.

The General Section
Use the Inno Setup help to figure out what each command do. But this is what I use:

[Setup]
AppName=My Application - Proffesional Edition
AppVerName=My Application - Proffesional Edition v0.95
OutputBaseFilename=My Application - Proffesional Edition
OutputDir=.\
AppComments=The Proffesional Edition is intended for computers in off-line environment.
AppContact=My Name and Phone
AppPublisher=My Company
AppPublisherURL=www.MyCompany.com
AppVersion=0.95
VersionInfoVersion = 1.0
DefaultDirName={pf}\My Application
DisableDirPage=yes
DefaultGroupName=My Application
AllowNoIcons=no
Compression=lzma
SolidCompression=yes
AllowUNCPath=no
UserInfoPage=no
AppCopyright=Copyright © 2007 My Company.
AlwaysRestart=yes

Of course replace your name, application, and company.
Notice the AlwaysRestart flag.
The {pf} is a short for Program Files.
It will create the installation file in the same folder as the iss file (you can change that of course).

The Tasks Section

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "DotNetFramework"; Description: ".NET Framework 1.1"; GroupDescription: "If .NET is NOT installed:";
Name: "MDAC"; Description: "Access, OleDB and Jet Providers"; GroupDescription: "Access, OleDB and Jet Providers";

During the installation the user will have a screen where he will be prompted to check various installation tasks. In this case he will be prompted to select a Desktop Icon, a Quick Launch Icon, a .NET Framework, and an MDAC.

You must install the .NET Framework on the client's computer, or he will not be able to run your .NET assembly. Here I show how to install .NET 1.1, but you might need to install .NET 2.0 as well. You also must install MDAC if you are using OleDb providers, Access databases and so on (In Windows XP you don't need to install this). You might decide to install those things without prompting the user (if they are already installed no harm can be done).

Of course you can add more tasks of your own.

The Dirs Section

[Dirs]
Name: "{app}\bin"
Name: "{app}\bin\DB"
Name: "{app}\bin\Config"
Name: "{app}\bin\Resources"
Name: "{app}\bin\DB\Xml"
Name: "{app}\DotNet"; Flags: deleteafterinstall; Tasks: DotNetFramework
Name: "{app}\MDAC"; Flags: deleteafterinstall; Tasks: MDAC
Name: "C:\Temp\"; Flags: uninsneveruninstall ;

Each directory will be created during the installation. {app} is a short for the installation folder, that is equivalent to te DefaultDirName attribute in the Setup section.

The Tasks flag is used to associate a line with a task. The task will be executed ONLY IF the task is selected by the user.
The deleteafterinstall flag makes the folder disappear after the installation. The user will not even be aware of this folder creation. I will this soon.

I also create the Temp folder just in case (some applications might depend on it).

The Files Section
This is probably the most important section. Here you list all the files you want to install.
Here are some examples:
First I install My application exes and dlls. After that I install the App.Config files. Then I install the Database and the Xml files. Then I install the resources. Last I install third-party dlls (simply copy the files).

[Files]
; MyApplication.exe
Source: "C:\Projects\My Application\bin\MyApplication.exe"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\MyApplication.tlb"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly regtypelib;Attribs: readonly;
Source: "C:\Projects\My Application\bin\MyApplication.xml"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\configuration.tbs"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;

;Basic Software
Source: "C:\Projects\My Application\bin\Server.exe"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\BusinessObjects.dll"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\CommonControls.dll"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\Forms.dll"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\BasicForms.dll"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly; Attribs: readonly;
Source: "C:\Projects\My Application\bin\Grid.dll"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly; Attribs: readonly;

;Module1
Source: "C:\Projects\My Application\bin\Module1Manager.exe"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\Module1Viewer.exe"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;

;Applications Configuration Files
Source: "C:\Projects\My Application\bin\MyApplication.exe.config"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\Module1Manager.exe.config"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\Module1Viewer.exe.config"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\Server.exe.config"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;

;Additional files
Source: "C:\Projects\My Application\bin\DB\Database.mdb"; DestDir: "{app}\bin\DB"; Flags: ignoreversion overwritereadonly uninsremovereadonly
Source: "C:\Projects\My Application\bin\Config\ConfigurationFile.xml"; DestDir: "{app}\bin\Config"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\My Application\bin\Resources\*.bmp"; DestDir: "{app}\bin\Resources"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;

;Additional Dlls
Source: "C:\Projects\IOS\STU\bin\AxInterop.SHDocVw.dll"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\IOS\STU\bin\Interop.SHDocVw.dll"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;
Source: "C:\Projects\IOS\STU\bin\Nini.dll"; DestDir: "{app}\bin"; Flags: ignoreversion overwritereadonly uninsremovereadonly;Attribs: readonly;

In addition I need to Install several MFC dlls, because one of my apps is from MFC in VC6. In that case I add the following section:

;System Files
Source: "C:\WINNT\system32\MFC80d.dll"; DestDir: "{sys}"; Flags: allowunsafefiles
Source: "C:\WINNT\system32\msvcr80d.dll"; DestDir: "{sys}"; Flags: allowunsafefiles
Source: "C:\WINNT\system32\MFC42d.dll"; DestDir: "{sys}"; Flags: allowunsafefiles
Source: "C:\WINNT\system32\MSVCRTD.DLL"; DestDir: "{sys}"; Flags: allowunsafefiles
Source: "C:\WINNT\system32\MFCO42D.DLL"; DestDir: "{sys}"; Flags: allowunsafefiles
Source: "C:\WINNT\system32\MFCN42D.DLL"; DestDir: "{sys}"; Flags: allowunsafefiles

The {sys} folder is the System32 folder on the client's machine. It will not replace the files if newer versions exist.

Next, we need to prepare the .NET Framework and MDAC installations. We will put the files in the folder of the iss file, pack the files to the installation file. When the client will run the setup, we will unpack the installation files into the folder we marked earlier to remove after installtion. This is simply to copy the installation file. It will NOT run the setup files:

;.NET Framework
Source: "C:\Share\installs\Developer\ Installation\dotnetfx.exe"; DestDir: {app}\DotNet; Tasks: DotNetFramework; Flags: deleteafterinstall;

;MDAC
Source: "C:\Projects\IOS\STU\ Installation\MDAC_TYP2.8.EXE"; DestDir: "{app}\MDAC"; Tasks: MDAC; Flags: deleteafterinstall;

Notice the Tasks attribute - we only unpack the files if the user asked us to. The installation files will be deleted after the installation as well. You can download those executables free from the internet.

Last, note that if you need to register a COM dll, or an OCX, you should use the regserver flag.

Icons Section
This will create the relevant shortcuts and icons on the client's computer.

[Icons]
Name: "{group}\My Application - Proffesional Edition"; Filename: "{app}\bin\MyApplication .exe"; WorkingDir: "{app}\bin"
Name: "{group}\{cm:UninstallProgram,My Application - Proffesional Edition}"; Filename: "{uninstallexe}"
Name: "{userdesktop}\My Application - Proffesional Edition"; Filename: "{app}\bin\MyApplication .exe"; Tasks: desktopicon ; WorkingDir: "{app}\bin"
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\My Application - Proffesional Edition"; Filename: "{app}\bin\MyApplication .exe"; Tasks: quicklaunchicon ; WorkingDir: "{app}\bin"

Registry and Environment Variables
Here is an example of how to install an environment variable on the client's machine. The same way applies to all registry keys.

[Registry]
Root: HKLM ; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: string; ValueName: "VARNAME"; ValueData: "VALUE"; Flags: uninsdeletevalue

Note that the running application will not be notified of the new environment variables, and will need to restart (there is a way to solve this issue).

Installing the .NET Framework, MDAC, and other installations
The Run section allows you to run additional installations. We need to run the installation of the .NET Framework, and MDAC. We use certain flags to make this a Silent Installation.
[Run]
;.NET Framework 1.1
Filename: "{app}\DotNet\dotnetfx.exe"; Parameters:"/q:a /c:""install /q""" ;StatusMsg: "Installing Microsoft .NET Framework (this may take a few minutes)"; Flags: skipifdoesntexist; Tasks: DotNetFramework; WorkingDir: {app}\DotNet; Description: Installs the DotNET Framework

;MDAC
Filename: "{app}\MDAC\MDAC_TYP2.8.exe"; Parameters:"/Q:A /C:""dasetup /Q:D /N"""; WorkingDir:"{app}\MDAC"; Flags: skipifdoesntexist; Tasks: MDAC; StatusMsg: "Installing Access, OleDb and JET Providers (MDAC) (This may take a few minutes)"; Description: Installs MDAC

Testing your install file
After the script is done, we can compile it using Inno Setup. A setup executable will be generated for us, which we should ship to the client and he should be able to install and run your application.

Great, huh? Well, imagine a client installing your application only to find out that it doesn't work because you forgot a dll? What would happen if you by mistake ruined the Client's registry, or deleted his Environment Variable? What about when uninstalling you by mistake deleted the Windows folder?

Don't send it anywhere until you test it. The best way to test it is by using a Virtual PC. Create yourself one Virtual PC of Windows 2000, and one for Windows XP. Create a backup before you play with it. Then run the installation on the Virtual PC. Don't forget to uninstall and see the results. You get several benefits by applying this method:
1. You can be 100% sure that you cannot do any damage, and that everything work.
2. If something went wrong you can fix it, delete the VPC, and try again (don't forget to backup the original VPC).

Labels: , , ,

A Deployment Issue: "the application has generated an exception that could not be handled."

Introduction
I built an application on my computer using .NET Framework 1.1, and it worked. This was a complicated application with a bunch of exe and dll files, app.config files, xml files. I had a server, clients, an MS-Access mdb file. I had GUI libraries, and Data access libraries.

I supplied an installation file, that installed .NET Framework 1.1, MDAC 2.8, my application, and several MFC files. It worked on other machines as well.

Then I had an update. I created a new installation file and shiped it to another computer.
One of the .NET applications crashed, and gave me the very intimidating MessageBox: "the application has generated an exception that could not be handled. Process id=0xFFFF Thread id=0xFFFF. Press OK to terminate the application, and CANCEL to debug the application.".

Of course I had no debugger on that machine, and basically I was stuck!

First Tries
At first I thought it was a .NET Framework installation issue. So I uninstalled .NET Framework on the other computer, and re-installed it. It did not solve the issue. I checked the versions of the .NET Framework (using Administrative Tools), and it was the same as in my computer.

Second, I checked that the App.Config file existed oin the folder of installation. It was there.

Third, I installed Dependency Walker on the other computer, open my exe with it (no red signs appeared), pressed F7, but could not make sense of the output. It did not smell like a problem with dependencies or other dlls.

The Solution
I added to the application the following code (I made it start from the Main method):
-------------------------------------------------
<STAThread()> _
Public Shared Sub Main()
Dim currentDomain As AppDomain = AppDomain.CurrentDomain
AddHandler currentDomain.UnhandledException, AddressOf UnhandledExceptionOccured
Application.Run(New Form1)
End Sub

Public Shared Sub UnhandledExceptionOccured(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
Dim ex As Exception = CType(e.ExceptionObject, Exception)
MessageBox.Show(ex.Message & vbCrLf & ex.StackTrace)
End Sub
-------------------------------------------------


This catches any Unhandled Exceptions, and shows a message with the stack trace.

I compiled and installed this exe in the client's computer.

I was amazed: There was a bug involving DBNull in one of my dlls, a bug that I have just added to my application. I could have never guessed that the problem was in that place!

My Lesson
From now on I put this code in all my applications! When there is such a bug I must at least know what happened!

I advice you to do the same.

Labels: , , ,

Feel free to use everything here. Add links to my site if you wish.

Do not copy anything to other sites without adding link to here.

All the contents of the site belong to Yariv Hammer.