ISWIX, LLC View Christopher Painter's profile on LinkedIn profile for Christopher Painter at Stack Overflow, Q&A for professional and enthusiast programmers

Thursday, 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.

Monday, October 23, 2006

99 Bottles of Beer On The Wall

A few months back a former coworker of mine introduced me to a web site called 99 Bottles of Beer. The premise of the site is simple: One Program in 1023 variations. I couldn't help notice that Windows Installer was missing. I wanted to implement an MSI version but there was one catch: MSI doesn't really have any arithmetic operators and I didn't want to be cheesy and call out to a custom action since that really wouldn't be pure MSI.

Fortunatly Kalle Olavi Niemitalo pointed out to me that you could fake it using single digit string properties like this:

Property Table
Dec9=8
Dec8=7 ( and so on )

ControlEvents
[NextTemp] Dec[NextIndexOne]
[NextIndexOne] [[NextTemp]]

If you start off with NextIndexOne = "9" then NextTemp is assigned Dec9 which in turn assigns 8 to NextIndexOne. A fake decrement operator. You can do the same thing for the tens position when a rollover of the ones occurs and add some additional logic for when you get to 0.

It's kinda silly but fun and hey, Windows Installer just HAD to be represented!

Thursday, October 12, 2006

Vista Deferred CA consideration

I don't believe I've seen this mentioned elsewhere so listen up friends!

In the microsoft.public.platformsdk.msi newsgroup, Antti Nivala recently posted a thread talking about a problem running an EXE Type 3074 custom action on Vista. Antti figured out that the CA was failing because it wasn't being granted the SeBackupPrivilege and that a call to AdjustTokenPrivileges would not yield the expected power that was previously available on Windows XP.

The part that caught my eye though was Antti said this was happening even with UAC turned off. Now HOW could that be?

The answer was revealed to me in this Microsoft Document. It seems that on Vista a service can be configured to run as LocalSystem and yet also be configured to run with a reduced subset of rights. A quick peek at the registry showed that MsiServer is configured with the following rights:

SeTcbPrivilege
SeCreatePagefilePrivilege
SeLockMemoryPrivilege
SeIncreaseBasePriorityPrivilege
SeCreatePermanentPrivilege
SeAuditPrivilegeSeSecurityPrivilege
SeChangeNotifyPrivilege
SeProfileSingleProcessPrivilege
SeImpersonatePrivilege
SeCreateGlobalPrivilege
SeAssignPrimaryTokenPrivilege
SeRestorePrivilege
SeIncreaseQuotaPrivilege
SeShutdownPrivilege
SeTakeOwnershipPrivilege
SeLoadDriverPrivilege

I'm suspecting that this is a defect in the MSI 4.0 in Vista.

Friday, October 6, 2006

Vista: Winning and Losing

I've really been loving my new laptop that Cheryl gave me for my birthday. Naturally I wipped the drive them moment I got it and I decided to give Vista a try. There has been a learning curve here and there, but as we all know, struggle is critical to the path of learning.

So for the past couple of days at work I've been spending a few minutes here and there trying to get a Vista image together for testing my installations. My first road block occurred when I accidentally brought in the Feb CTP and Creative PC-DVD drive ( my Dell GX270 didn't have a DVD ) and it didn't like my RC1 product key. Then I tried to download the RC1 except the firewall and download manager wouldn't play along. Then I brought in my Vista RC1 DVD and wouldn't you know, murphy strikes again! It couldn't copy the install.wim from the disc.

I was really stuck until I figured out that I could stick the DVD in my coworkers computer and copy the sources directory to a network share and ultimatly to my second hard drive. A few minutes later I booted the Vista PE, dropped to a command prompt, switched over to the harddrive and ran setup.exe.

The windows setup is so much nicer then the old days of dropping to MSDOS and doing a winnt /b. All in all I'm VERY impressed with what I see in Vista.

I don't care what the rablle says over at SlashDot.... keep up the good work Microsoft.

Wednesday, October 4, 2006

VDPROJ replace shouldn't be Votive

While discussing the possible future replacement of Visual Studio Deployment Projects I mentioned that it would be nice if the replacement was also open source and backwards compatible with previous versions of Visual Studio and not just a feature of Orcas.

Robmen pointed out that there already was an open source add-in called Votive.

While it meets part of the definition of what I said ( an open source add-in for 2003/2005 ) it certainly does not meet the definition of a replacement for vdproj.

Personally I have no intention of authoring anything except the most simple installation fragments in direct XML regardless of now nice the text editor gets. I expect a graphical RAD tool that is designed for user work flows and data validation. Beyond that, let that tool store the data in XML behind the scenes and use WiX to build the project.

My best analogy is the way VS2005 handles Forms and the way InstallShield exposes views and the Direct Editor. I want a designer view and a code view. I want to do most of my work in the designer view knowing that it's really just generating rock solid XML for me in that I can choose to tweak in the code view.

Tuesday, October 3, 2006

Implementing Auto Updating Applications

[Edit May 2008] I no longer use MSI.Interop. That interop was full of bugs, lacked many features and was no longer actively being developed or supported. Instead I reccomend the new DTF SDK from Microsoft as released by WiX. DTF brings a new Microsoft.Deployment.WindowsInstaller library that just runs circles around MSI.Interop.

http://blog.deploymentengineering.com/2008/05/deployment-tools-foundation-dtf-custom.html

One of the nice things about Per-Machine installations is that they are always managed. So provided that you get your ducks in a row following proper design patterns ( for example: Deferred Custom actions with the msidbCustomActionTypeNoImpersonate execute option and the SecureCustomProperties property ) you can simply advertise the package with the msiexec /JM command or run it as an Administrator and future upgrades can be serviced from the same source location without Administrative privileges by simply executing the update package with the REINSTALLMODE=vomus REINSTALL=ALL properties.

But wouldn't it be nice if the user didn't have to manually run the installation to perform the upgrade? Well, they don't. :) When you install your application you can save the OriginalDatabase or the ProductCode property somewhere your application can access it. With this information available to your application at runtime you can go find the MSI database and determine if there is a newer version available.

The following example in C# uses MSI.Interop.

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Pahvant.MSI;

namespace TestApp
{
class Program
{
static void Main(string[] args)
{
string msiPath = "C:\\TestApp.msi"; // Get this as described in the blog

Pahvant.MSI.Database database = new Pahvant.MSI.Database( msiPath, Pahvant.MSI.Database.OpenMode.ReadOnly );

Pahvant.MSI.View view = new Pahvant.MSI.View( database, "select `Value` from `Property` where `Property`='ProductVersion'");

view.Execute();

Record record = view.Fetch();

string productVersion = record.GetString(1);

if( Application.ProductVersion != productVersion )
// Call Prompt For Upgrade Method

}
}
}

If your upgrade method, you'll probably want to shell out to msiexec async and terminate to avoid a locked file during Costing. Bonus points will be awarded by your user if you also pass a public property to indicate that an automatic upgrade is occuring so that your install restarts the application when it's finished upgrading.

Sunday, October 1, 2006



As mentioned on SlashDot, October is National Breast Awareness Month. In honor of my wife who is struggling with cancer we will be Going Pink For October.