Sometimes, when creating an installation program, it is necessary to support multiple instance installations of the product to one particular computer. This requirement immediately brings the complexity of the installation to the advanced level. A couple of most tricky things to look after are component rules and patching.
In order to make your installation "multi-instance-aware", you should author a number of instance transforms in your source. The number of transforms is the number of instances you plan to support (except for the default one, of course). Fortunately, WiX provides very convenient way to do this:
<instancetransforms property="ANY_PROPERTY">
<instance id="InstanceId1" productcode="{42A33A91-36B0-4700-A6F5-1289D22F358C}"/>
<instance id="InstanceId2" productcode="{68C62C01-D064-4CF0-9239-F5D2FF36BD9A}"/>
...
</instancetransforms>
As always with Windows Installer, this is not the end. According to the MSI documentation about authoring multiple instances, "To keep the nonfile data of each instance isolated, the base package should collect nonfile data into sets of components for each instance". This can be done by authoring a duplicate of each component for each instance, and install conditionally. But it becomes really complex to manage when you have much more than 2 instances, let's say, 100.
I chose another way. Assuming the fact that each instance should contain the same set of components, but with different GUIDs, we can generate a number of transforms, one per each instance, which change just the GUIDs of all the components. So, the algorithm is similar to this:
- copy the MSI package
- use API to query and update the database with new GUIDs for each component
- generate a transform between the copy and original MSI
- drop the copy MSI
- repeat the steps about the number of times as many instances you plan to support
- embed all these transforms into the original MSI
Obviously, the number of such customization transforms must be equal to the number of instance transforms and the names should be convenient to use. For instance, if you did everything correctly, you should be able to run the installation of new instance as follows:
msiexec /i YourPackage.msi MSINEWINSTANCE=1 TRANSFORMS=:InstanceId1;:ComponentGUIDTransform1.mst ...
This works like a charm in conjunction with an algorithm to detect next available instance and a bootstrapper.
Now let's turn to the patching. When I browsed the internet for the info about multiple instance installs and patches, I found a great post of Christopher Painter. As he says there, one should populate the Targets property of the patch summary info stream with product codes of all the instances, otherwise the patch detects just the default instance. That's correct, but, yes, this is not the end of the story.
Let's take a look at the patch definition and its contents: "Patches contain at a minimum, two database transforms and can contain patch files that are stored in the cabinet file stream of the patch package". These two transforms contain the target product GUID and updated product GUID each. In the case of simple patching, it is just one GUID of the target product.
Hence, in order to be applied to each instance, the patch must contain a pair of transforms for each instance. Unfortunately, it is not supported by WiX torch+pyro approach and we should fall back to the powerful API:
// dump transform and change its properties
string transformFileName = GetNextValidName(transformName, nameSuffix);
patch.ExtractTransform(transformName, transformFileName);
SummaryInfo info = new SummaryInfo(transformFileName, true);
info.RevisionNumber = info.RevisionNumber.Replace(originalProductCode, productCode);
info.Persist();
info.Close();
So, as you can see, we do the following (for each instance and for each of 2 transforms in default patch):
- extract transform from the patch package
- change the original product code to this instance product code in summary info
Afterwards, we must insert these newly created transforms into the _Storages table of the patch package:
using (View insertView = patchForWrite.OpenView("INSERT INTO `_Storages` (`Name`,`Data`) VALUES ('{0}', ?)", transformFileName))
{
using (Record record = new Record(1))
{
record.SetStream(1, new FileStream(transformFileName, FileMode.Open));
insertView.Execute(record);
patchForWrite.Commit();
}
}
And finally, we should append the product GUID of each instance to the Template property of Summary info (it is shown as Targets with Orca) and the name of each transform to the LastSavedBy property of the Summary info (it is not shown with Orca). Something like this:
// update patch properties
if (!patchForWrite.SummaryInfo.Template.Contains(productCode))
{
patchForWrite.SummaryInfo.Template += ";" + productCode;
}
patchForWrite.SummaryInfo.LastSavedBy += ";:" + transformFileName;
patchForWrite.SummaryInfo.Persist();
That's it! Afterwards, the following magic line should work correctly and patch the installed instance of your application:
msiexec /p YourPatch.msp /n {YOURGUID-0002-0000-0000-624474736554} /qb
Good luck deploying! I would appreciate any comments on this.
Hi Yan - Awesome to see you blogging! Welcome!
ReplyDeleteAny chance you have some downloadable sample WiX project illustrating this please?
ReplyDeleteDavid, which point seems unclear to you in this post? I was trying to cover the key points in this approach I used, illustrating the most tricky parts with snippets.
ReplyDeleteAn full, simple example is always easier than snippests, but for me it is ok this way.
ReplyDeleteGreat post on the issue. What I am worrying is the changed component GUIDs when it comes to patching. Does it really work the way you describe?
quirrel, thanks for your comment.
ReplyDelete>> Does it really work the way you describe?
The Windows Installer documentation (http://msdn.microsoft.com/en-us/library/aa367797(VS.85).aspx) claims the following: "To keep the nonfile data of each instance isolated, the base package should collect nonfile data into sets of components for each instance. The appropriate components should then be installed based on conditional statements that depend on the instance identifier."
If a component contains a CreateFolder table entry, it is considered a nonfile data. For instance, if your WiX authoring is auto-generated using Heat, it is not very convenient to differentiate file-data components and nonfile-data components. I chose giving new GUID to every component - this is easy and GUIDs are for free.
Actually, there's an easier way to create one patch for all instances of one product. Set 'ProductId' attribute of 'Validate' element to 'no' and the patch you build will apply to every instance.
Hi Yan,
ReplyDeletethanks for the reply and the last comment on how to ease patching further.
What my thought was is that a patch also contains the component GUIDS that it is going to patch, which are different for every instance if you create instances the way you described. So how does the patch know what to patch? Anyway I think I will have to give this a try before commenting further, do you have a working that I can just use to see it works?
Hi tobias,
ReplyDelete>> So how does the patch know what to patch?
You apply the patch like this:
msiexec /i {YOUR-GUID} PATCH="path\to\patch.msp"
where {YOUR-GUID} is the ProductCode (instance) to patch.
>> I can just use to see it works?
You can try this even on a sample project referenced in a WiX manual. Just make a project multi-instance (add a couple of instance transforms), and author a patch with PatchBaseline/Validate/@ProductId = 'no'. Then install those two instances, and patch those two instances (the way shown above).
Also, take a look at this thread: "http://www.mail-archive.com/wix-users@lists.sourceforge.net/msg27915.html"
Heath helped me a lot in understanding this technique.
I don't have a standalone example right now - it is tightly incorporated into our main product build script. But you're welcome to ask further questions - I'll be glad to help you.
Hi Yan,
ReplyDeleteGreat article! Just so I am sure I understand, the algorithm you describe for managing instance transforms, the steps you describe are done manually as opposed to using the API, right?
In other words, if I wanted to define 100 instances, then using your algorithm, I am pre-defining 100 instances that are captured as 100 transform files named:
ComponentGUIDTransform1.mst
ComponentGUIDTransform2.mst
...
ComponentGUIDTransform100.mst
Would it be possible to read the MSI into memory and then use API calls to replace all component GUIDs in the tables at runtime? The new product codes could be queried from the registry later couldn't they?
Thanks,
Andy
Hi Andy,
ReplyDeleteThanks for your comment!
Well, doing this manually every time is a bad habit :) There's an algorithm to generate a deterministic sequence of GUIDs based on the initial GUID you pass in. You can be sure that if you pass the same GUID two times, the algorithm will give you identical sequences of GUIDs. You can find the sample in WiX sources, /src/wix/uuid.cs.
When I was writing this article, I used Christoper Paniter's post I reference in it as an inspiration and starting point of my experiments. Chris said that when you build patch, you should know the ProductCode it will be applied to. However, things have probably changed ever since, and Heath Stewart pointed out how to use to build a patch which can be applied to any instance (see in the comments above). With this in mind, I suppose you can now build unlimited number of instance transforms at runtime and stay calm about patching... but this should be tested - I doubt I can foresee all the "features" of this approach. And you should keep the "build-time complexity" vs. "runtime complexity" dilemma in mind.
When you play with this to prove it works fine, I would love to hear the results back from you! :)