October 26, 2006

Multiple Instance MSI's and InstallShield 12

Edit:

This article was years ahead of it's time. InstallShield 2009 will natively support this pattern as described here.

At a previous job, they wanted the ability to install multiple instances of their product but I never gave it a whole lot of attention because the application wasn't really isolated in such a way to really support the requirement in the first place. I've since moved on and the requirement has resurfaced. Fortunately this time I couldn't really see any reason why they couldn't run multiple isolated instances so I started wondering what would be involved. Fortunately Stefan Krueger was kind enough to point me to a topic in the MSI SDK that had escaped my attention. `Authoring Multiple Instances with Instance Transforms`.

The concept is you create ProductCode changing transforms and then embed them into the _Storages table of your MSI. At install time invoking will simply install your default instance. However if you add the property MSINEWINSTANCE=1 and specify your embedded transform with TRANSFORMS=:Embedded.mst then suddenly your install takes on a new identity and it can be installed side by side with your default instance. There is a bit more too it then that to make sure everything is isolated correctly so jump into the SDK and have some fun.

I had also been stuck on DevStudio 9 for awhile and low and behold InstallShield 12 has a partial implementation of this pattern. You simply add the property InstanceId and default it to 0. Then in the direct editor you populate data in the ISProductConfigurationInstance table. A simple record like:

ISProductConfiguration_1 1 ProductCode {A5F2A675-9BAB-4A36-BE7A-C4E412230404}

This tells the build process to automatically generate InstanceId1.mst when building ISProdutConfiguation_1 and to author a ProductCode property change then embed it into _Storages when complete.

Cool! But wait..... they didn't finish! You don't REALLY want a user to have to know how to invoke the instance do you? That's what those pretty little bootstrappers are for. So I started digging in and I wrote a utility that:

1) Opens the MSI in read/only mode, grabs the ProductCode out of the Property table and then iterates through the _Storages table and applies each transform to retrieve the additional ProductCode definitions.

2) Get the PackageCode of the MSI.

3) Invoke the MSI API to find out information of installed instances. Which ProductCodes are installed, what are the ProductNames and InstallLocations as well as which PackageCode installed each product.

4) If no products are installed, then proceed to installing the default instance. If products are detected then show a dialog asking the user if he'd like to install a new instance ( if any more are available ) or service an existing instance.

5) Launch the MSI with the correct options to handle a new instance, maintenance of an existing instance or upgrading of an existing install ( REINSTALL=ALL REINSTALLMODE=vomus ).

Ok were cooking with gas but wait! What about patching?

Well it seems that again InstallShield isn't quite there. The patch generation process only populates the default instances ProductCode into the patch's SummaryInformationStream Targets attribute. Tis means the patch only detects the default instance as a valid product to be upgraded. This can be fixed by again iterating through _Storages to get the ProductCodes and populating the Targets in a ; delimited manner.

Except of course now will need a better update.exe because we really don't want to have to use the /n switch to specify the ProductCode of the instance to be patched. So now we create a bootstrapper that

1) Opens the patch and grab the Targets property.
2) Call the MSI API to see what matching products are installed
3) Throw a dialog asking the user which instance he would like upgraded.
4) Call the patch.

If you do this all correctly you end up with a fairly scalable servicing mechanism for supporting multiple instance of your product. Now that's Hard Core Setup Engineering.

2 comments:

zowers said...

And such a bootstrapper is what I'm now about to implement =)
Hope I won't fall into insanity...
And on the technical part of the problem: maybe you already now any alternatives to implement it from scratch?

Anonymous said...

I am spending some time here at your blog, where's the refreshments?