Intelligent Authentication (IA), the product I help develop, has for the past few versions been installed through the use of five .NET setup projects (one database and four binary installers). For anyone who has to do application deployments on a regular basis this is far from an ideal approach, even bordering on painful. From a development standpoint this is equally as painful. The .NET setup projects that ship Visual Studio.NET 2003 are a clumsy bastardization of Microsoft Installer (MSI) technology. Imagine if you will MSI, a 10,000 fingered glove allowing you to poke, prod and massage a target computer for deploying your application. Now the .NET setup projects that ship with VS.NET takes those 10,000 nimble fingers and reduce them down to five rather awkward and bulbous fingers. Causing anyone who has dealt with them to consider throwing themselves in front of a bus. But I digress.
Over the past few weeks I have condensed those five installers into two installers (one binary and one database) using Installshield 11 basic MSI projects. As part of the IA binary installation, I set some data in a config file. If IA is installed on multiple servers (e.g. a web farm) this data would need to be replicated to each server's config file, a very error prone process for us humans. To automate this task I dump the data out to an XML file for easy transport and have the installer allow the user to browse for this XML file during auxiliary installations of IA. Piece of cake, right? Not so apparently.
There doesn't appear to be any built in file browsing functionality in the Basic MSI projects. I later found out that this functionality exists in the Universal installer projects, but I'm too far along to start over. So the answer that a co-worker and I came up with was using a custom action to call .NET code.
I make extensive use of custom actions in my installers to do all sorts of things ranging from validating domain credentials to creating application pools and virtual directories. I created a file browsing dialog in C# using the OpenFileDialog class and call that code from Installscript through COM interop (it's not as painful as it sounds).
At Corillian we already had an existing class library project containing various utility classes for use with installers. I added a class to that project for holding the logic used to pop the dialog. To make this class available through COM interop, you will need to add a couple attributes to this class and include the interop name space.
public class FileTools
public string BrowseForFile(...)
Inside the class you will need to add a method that actually spawns the OpenFileDialog. To call this from InstallScript use the CreateObject function to create an instance of the new class, passing the ProgId you specified in the attribute (e.g. "InstallerUtilities.FileTools"). Once you have a handle to FileTools object, call the BrowseForFile method and viola, there is a file browsing dialog.
One undesirable catch I need to mention is that the OpenFileDialog will open behind the main installer window. To make the OpenFileDialog open in the foreground took a bit of coaxing. There is an overload to the ShowDialog method of the OpenFileDialog class which takes an IWin32Window parameter to designate that dialogs owner (the installer).
To get a handle to the installer dialog we use a function defined in user32.DLL called FindWindow, brilliant huh. To use this method we have to add an extern reference to the user32.DLL like so:
[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern IntPtr FindWindow(string ClassName, string WindowText);
You'll notice that FindWindow returns an IntPtr and we need an IWin32Window. Ryan Farley has a write up on his blog on how to convert an IntPtr into an IWin32Window by create a wrapper class that derives from IWin32Window. Call FindWindow, passing in null for the first parameter and the title to your installer dialog (usually the ProductName property + " - Installshield Wizard") for the second parameter. Wrap the returned IntPtr inside the WindowWrapper class and pass that to the ShowDialog method for the OpenFileDialog class. Your file browsing window will appear front and center for your end users.
To increase reusability I suggest having all the various properties of the OpenFileDialog class be passed in as parameters such as Filter, initial directory and Window Title (of the file browsing dialog, not the installer dialog).