Browsing for a file in Installshield#

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.

using System.Runtime.InteropServices;

namespace InstallerUtilities
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("InstallerUtilities.FileTools")]
[Guid("0ec929cd-5135-441a-98df-10f227f3afc8")]
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).

Saturday, June 23, 2007 6:23:57 AM (Pacific Daylight Time, UTC-07:00) #     | 

 

Controlling Assembly Signing in Visual Studio 2003#

At my company I've become the go to guy for installers and the like. Naturally one of my jobs is doing builds in visual studio for including in those installers. As we write security software we always sign our assemblies to prevent tampering.

For anyone that has had this job you know that signing assemblies is a fairly trivial task. You change the AssemblyInfo file to point to your private key pair, set delay sign to false and off you go. In a matter of minutes you have a set of fully signed assemblies to deploy to your hearts content.

The problem lies in when your solutions start to get large, say greater than 8 projects. That 8 AssemblyInfo files to update. Since you all work in perfect worlds where builds go out on schedule and everything works magically I'm sure this has never happened to you. Imagine you receive word from a customer your software, which is in production, has a defect. *GASP* sound the alarm, drop everything and jump! Your team mobilizes into seek and destroy mode to find the defect and fix it. But you need to get the fix to the customer in a easy to deploy fashion. So you whip up a patch installer throw in the requisite assemblies and off it goes.

As your customer is happily installing your fix you start to wonder, did I update all the assembly info files in my solution?? Maybe I missed one. Just as you go check your code your customer calls back stating the installation failed due to a signed assembly problem. AHHHHH! If only there was a way to update one AssemblyInfo file and have it affect all projects. Well there is!

To begin solving this problem we turn to some built in functionality in Visual Studio. In Visual Studio there is a concept of linking files. The idea is you have one common file, an AssemblyInfo file in this case, and you link all your projects to that one file. So you change the file in one place and it is affected in all projects. Slick eh? The one exception to this is web projects. I'm guessing because of how web projects are built and ran (from temporary ASP.NET) you can't link files to them. To access the menu in the Add Existing Item dialog, click on the small arrow to the right of the Open button.

Now having a common file is really handy, but what if you type the path to the private key pair wrong or forget to turn off delay sign. Well you're in luck, by using #IF/#ENDIF statements you can control assembly signing simply by changing the configuration.

To make Visual Studio take advantage of this you will need to edit the project settings and define the symbols to switch on. The DEBUG symbol is already defined by default, so all you will need to do is define the RELEASE symbol. This is accomplished by opening the project properties, selecting Configuration Properties, selecting Build, and adding the word RELEASE to the list of semi-colon separated list for Conditional Compilation Constants.

When you put this all together you can control whether you delay sign or fully sign you assembly by changing one option in your development environment. By selecting Release from the configuration drop down the code in the AssemblyInfo file that controls signing is switched to use fully sign the assembly.

 | 
Thursday, November 09, 2006 1:02:13 AM (Pacific Standard Time, UTC-08:00) #     | 

 

All content © 2012, Matthew Lapworth