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.

9 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?

Shawny said...

Im really new to Wix. Is there any way you can post an actual example?

Kate said...

Thank you for your article. But how to create an installer with undefined quantity of installation instances?

Christopher Painter said...

To acheive an undefined quantity you have to move the transform generation process from build time to install time.

Basically you generate a setup.exe bootstrapper that dynamically generates transform and then applies it to the msi before calling.

This is the approach that InstallAware takes. It's powerful, but I really question how many people actually need it. Seriously, if you use build automation to generate say 100 transforms and stream them into the MSI, will you really have a customer that needs 101 installed instances? Same for 200, 300, 1000..... Is there really such a need?

yan said...

Well, that's definitely a rare case, but it's quite possible as for me. In this case a developer has to rebuild the solution streaming 100 more transforms in it...
And why 100? What is this number to be tied to? It seems in this case the generalized solution looks much more elegant. Then why avoiding it?

Christopher Painter said...

One reason I can think of is it would make patching difficult. When you build a patch, you have to know the ProductCode(s) that it's applicable to. If you decide the product codes at build time, you know them. If you wait until install time, you do not.

Also in general, I prefer build complexity over install complexity since failures are generally caught before you ship.

Yan Sklyarenko said...

Thanks again for your wonderful post, Christopher! It was a kind of inspiration for me while struggling with this advanced scenario. But I must admit, that patching across the instances requires some more actions than you described. I have published the whole story here, in my blog: http://ysdevlog.blogspot.com/2008/12/multiple-instance-installations-and.html

I would appreciate your comments.

Naveen Raina said...

Hi Chris,
I have another issue related to Multiple Instance MSI. I have a multi-instance Installshield setup file. I created a new installer with a different Product Version. when I try to run this setup on a machine where the earlier version is installed, it gives me an error - "A newer version of this application is already installed on this computer. If you wish to install this version, please uninstall the newer version first."
Although this a newer version I am installing, I am not sure why I get this message. I tried to change the Package Code, that didn't help. But when I changed the Upgrade Code, it fixed the problem. Did I address this issue correctly by changing the Upgrade Code?
Thanks,
Naveen