This blog is used as a memory dump of random thoughts and interesting facts about different things in the world of IT. If anyone finds it useful, the author will be just happy! :-)

Wednesday, September 14, 2011

Revisited: Multiple Instance installations and patches

I initially blogged about multiple instance installations couple of years ago. The way I described it worked fine for me, but the time flies and the things has changed ever since – WiX grew up to even more solid toolset, and I also gained some knowledge. So, this post is to revisit the topic and look at it through the prism of WiX 3.6.
Imagine you have an application, and you’d like to be able to install several instances of it side-by-side on a single machine. The starting point is still to author the InstanceTransforms element:
<InstanceTransforms Property="INSTANCEID">   
   <Instance Id="I01" ProductCode="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" ProductName="My Product 01"/>   
   <Instance Id="I02" ProductCode="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" ProductName="My Product 02"/>   
   <Instance Id="I03" ProductCode="{GUIDGOES-HERE-5494-843B-BC07BBC022DB}" ProductName="My Product 03"/>
    ...
</InstanceTransforms>
Obviously, the number of Instance elements is the number of instances supported by this installation program (plus the default one). In order to install the default instance, you should run the following command (assuming the generated MSI package is called MultiInstance.msi):
msiexec /i MultiInstance.msi
In order to start the installation of another instance, change the command as follows:
msiexec /i MultiInstance.msi MSINEWINSTANCE=1 TRANSFORMS=":I01"
The MSINEWINSTANCE property set to 1 instructs msiexec to start the installation of another instance instead of default one. Note that in the above example we installing the instance I01. The Instance element results into an instance transform being embedded into the MSI package, and by setting TRANSFORMS property to “:I01” we instruct msiexec to apply the embedded instance transform which corresponds to the I01 instance. The TRANSFORMS property can contain other transforms (for instance, language transforms), but that’s another topic.

Uninstalling looks quite similar, for instance, default instance uninstallation:
msiexec /x MultiInstance.msi
In order to uninstall the extra instance, you should explicitly specify its ProductCode. So, for instance I01 the uninstall command line looks like this:
msiexec /x {GUIDGOES-HERE-4731-8DAA-9E843A03D482}
So far, so good – it is quite straight-forward. Now, let’s turn to the Windows Installer documentation about multiple instances one more time. Apart from the requirement for each instance to have a unique product code and instance identifier (this is what WiX does for free with InstanceTransforms technique), it strongly recommends to keep the data isolated. For the file data, this means installing the files of each instance to a different location – the path containing instance ID as its part fits best. For the non-file data, it’s a bit more complex: the appropriate components should have different GUIDs, and again install to a different location.

In my first attempt to approach the problem, I’ve applied a workaround: generate new GUIDs for each component of new instance, embed those “component transforms” into the resulting MSI and apply along with the instance transform. Well, sounds not very efficient, but assuming a great number of components harvested automatically, this was simple enough. Fortunately, wise developers of WiX team thought this through and came up with a far more elegant solution in version 3.6.

Starting from WiX 3.6.1502.0, a Component element has an attribute MultiInstance of YesNo type. According to the WiX docs, “If this attribute is set to 'yes', a new Component/@Guid will be generated for each instance transform.” Fantastic! That’s what we need! Let’s see how it affects the multiple instance installations on a sample. Let’s say our installation program consists of the following components, and we’d like to be able to install this software at least 3 times:
<Directory Id="ProductNameFolder" Name="TestName">
   <Component Id="FileComponent" Guid="{GUIDGOES-HERE-4301-95D2-86A4C80EF5F0}">
      <File Id="dll" Source="$(var.Source)\Some.Test.dll" KeyPath="yes" />
   </Component>
   <Component Id="ConfigComponent" Guid="{GUIDGOES-HERE-4c2f-BE74-CF78D2350E48}">
      <File Id="web_config" Source="$(var.Source)\web.config" KeyPath="yes" />
   </Component>
   <Directory Id="EmptyFolderDir" Name="EmptyFolder">
      <Component Id="FolderComponent" Guid="{GUIDGOES-HERE-4543-A9F8-17491670D3A6}">
         <CreateFolder />
      </Component>
   </Directory>
   <Component Id="RegistryComponent" Guid="{GUIDGOES-HERE-45e5-ABFD-07E5CC4D7BC9}">
      <RegistryKey Id="MainRegKey" Action="createAndRemoveOnUninstall" Root="HKLM" Key="SOFTWARE\MultiInstanceTest\[ProductCode]">
         <RegistryValue Id="MainRegValue" Name="InstanceId" Value="[INSTANCEID]" Type="string" />
         <RegistryValue Id="InstallPathValue" Name="Location" Value="[ProductNameFolder]" Type="string" />
         <RegistryValue Id="ProductCodeValue" Name="ProductCode" Value="[ProductCode]" Type="string" />
         <RegistryValue Id="ProductNameValue" Name="ProductName" Value="[ProductName]" Type="string" />
         <RegistryValue Id="ProductVersionValue" Name="ProductVersion" Value="[ProductVersion]" Type="string" />
      </RegistryKey>
   </Component>
</Directory>
<InstanceTransforms Property="INSTANCEID">
   <Instance Id="I01" ProductCode="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" ProductName="My Product 01"/>
   <Instance Id="I02" ProductCode="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" ProductName="My Product 02"/>
</InstanceTransforms>
The MSDN recommendations about multiple instances are followed, except for “keeping non-file data isolated”. Let’s see how it affects the install/uninstall. Run the installation of the default and I01 instance as described above. Both instances are installed to the different locations correctly:
Instance00installedInstance00RegInstalled

Instance01installedInstance01RegInstalled

Now uninstall the default instance – you’ll see that non-file data was not removed properly:

Instance00brokenInstance00RegBroken

This is happening because the components which hold this data are considered shared by the Windows Installer, and during uninstallation of one instance it detects that there’s another one pointing to the same components and leaves those untouched. Now if you uninstall the other instance, it successfully removes both EmptyFolder and registry key, but as a result we’ll still have orphaned resources of the first instance.

That’s the initial problem, and let’s see how elegant new WiX feature deals with it. You should only add the MultiInstance=’yes’ attribute to the components holding non-file data, and forget about the problem of orphaned resources forever. Like this:
<Directory Id="ProductNameFolder" Name="TestName">
   <Component Id="FileComponent" Guid="{GUIDGOES-HERE-4301-95D2-86A4C80EF5F0}">
      <File Id="dll" Source="$(var.Source)\Some.Test.dll" KeyPath="yes" />
   </Component>
   <Component Id="ConfigComponent" Guid="{GUIDGOES-HERE-4c2f-BE74-CF78D2350E48}">
      <File Id="web_config" Source="$(var.Source)\web.config" KeyPath="yes" />
   </Component>
   <Directory Id="EmptyFolderDir" Name="EmptyFolder">
      <Component Id="FolderComponent" Guid="{GUIDGOES-HERE-4543-A9F8-17491670D3A6}" MultiInstance="yes">
         <CreateFolder />
      </Component>
   </Directory>
   <Component Id="RegistryComponent" Guid="{GUIDGOES-HERE-45e5-ABFD-07E5CC4D7BC9}" MultiInstance="yes">
      <RegistryKey Id="MainRegKey" Action="createAndRemoveOnUninstall" Root="HKLM" Key="SOFTWARE\MultiInstanceTest\[ProductCode]">
         <RegistryValue Id="MainRegValue" Name="InstanceId" Value="[INSTANCEID]" Type="string" />
         <RegistryValue Id="InstallPathValue" Name="Location" Value="[ProductNameFolder]" Type="string" />
         <RegistryValue Id="ProductCodeValue" Name="ProductCode" Value="[ProductCode]" Type="string" />
         <RegistryValue Id="ProductNameValue" Name="ProductName" Value="[ProductName]" Type="string" />
         <RegistryValue Id="ProductVersionValue" Name="ProductVersion" Value="[ProductVersion]" Type="string" />
      </RegistryKey>
   </Component>
</Directory>
Now check the above scenario once again: install 2 instances and uninstall them. You’ll see that both install correctly and uninstall clearly. Isn’t it GREAT?! Smile

Now, let’s turn to patching. Again, if we look back to my initial post on this topic, I was using an ugly method to make the patch applicable for all instances of the installed product. That method assumed opening the binary patch for read/write and rude injection into its structure. Though it worked, there’s much more elegant way of doing this. I’d like to thank Heath Stewart for the hint – here’s the full thread on wix-users mailing list.

So, the default behavior is the following: if you author the PatchBaseline element with its default validation settings, the patch will be applicable to the default instance only. That’s because it tracks the ProductCode is the product baseline it was built against, and checks it during install time. The trick is to add a Validate child to the PatchBaseline, and instruct it not to check the ProductCode:
<Media Id="5000" Cabinet="RTM.cab">
   <PatchBaseline Id="RTM">
      <Validate ProductId="no" />
   </PatchBaseline>
</Media>
So, after you build this patch, you’ll be able to apply it to a particular instance:
msiexec /i {GUIDGOES-HERE-4412-9BC2-17DAFFB00D20} PATCH=patch.msp /l*v patch.log
Or to all the installed instances at once (so-called “double-click scenario”):
msiexec.exe /p patch.msp /l*vx patch.log
There’s still one more obvious inconvenience in the patch authoring, as for me. You have to specify the ProductCode entries twice: in the main installation sources (InstanceTransform/@ProductCode) and in the patch sources (TargetProductCode/@Id). It would be just fantastic if during patch building the WiX tools could look into the instance transforms collection of the baseline package and take the list of product codes out of there. That would omit the necessity to always specify the following section in the patch:
<TargetProductCodes Replace="no">
    <TargetProductCode Id="{GUIDGOES-HERE-4412-9BC2-17DAFFB00D20}" />
    <TargetProductCode Id="{GUIDGOES-HERE-4731-8DAA-9E843A03D482}" />
    <TargetProductCode Id="{GUIDGOES-HERE-4f1a-9E88-874745E9224C}" />
</TargetProductCodes>
As usual, WiX Toolset developers have done and keep doing fantastic job making our lives as setup developers easier!

Feel free to leave a comment in case you have a note or a question. Feedback is welcome, as usual!

11 comments:

  1. Wondering if you can help me out with my question posted here: http://stackoverflow.com/questions/9471790/patching-multiple-instance-installs-with-either-installshield-or-wix

    I can't get wix to put the files in the cab needed for the patch.

    ReplyDelete
  2. I have read this post. collection of post is a nice one ..that am doing website designing company chennai india and website development company chennai india. That I will inform about your post to my friends and all the best for your future posts..

    ReplyDelete
  3. Hi Yan do you have any thoughts on this? I'm trying to add the Patchbaseline element to my Patch XML. Can you show my an example patch XML for WIX 3.6

    http://stackoverflow.com/questions/9982843/wix-3-6-patch-file-xml

    ReplyDelete
  4. Published an answer on SO - let's continue the dialog there.

    ReplyDelete
  5. Thanks for the article! I just don't understand how can you kept your files separated for each instance. How do you have organized your directory inside wix? I have tried using
    <Directory Id="InstanceDirectory" Name="[INSTANCEID]">
    but doesn't work.

    thanks
    davide

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Davide, sorry for the indecently long delay with this reply. For some reason, I've just mentioned this post has got more comments.

      In order to achieve the unique paths for each instance, I did the following:

      1) The directory structure is like this:

      <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
      <Directory Id="INSTALLLOCATION" Name="MultiInstance36">
      <Directory Id="ProductNameFolder" Name="TestName">
      ....
      </Directory>
      </Directory>
      </Directory>
      </Directory>

      2) Then there's a CA, which sets the install location to the proper value:

      <SetDirectory Id="ProductNameFolder" Sequence="execute" Value="[INSTALLLOCATION][ProductName]" />

      3) And finally, each transform sets the ProductName to the desired unique value:

      <Instance Id="I01" ProductCode="{YOURGIUD-GOES-HERE-8DAA-9E843A03D482}" ProductName="My Product 01"/>
      <Instance Id="I02" ProductCode="{YOURGUID-GOES-HERE-9E88-874745E9224C}" ProductName="My Product 02"/>
      ...

      Hope it answers your question.

      Delete
  6. Thank you, your article is very clean and helpful.

    I am using this method to allow multiple instances for my product. I was wondering, how would you approach management of additional resources which wix/installer do not support, like Certificates. I my case every instance is has a self-signed certificate which I create on install and remove on uninstall (this is not a problem because their names are different).
    But in addition to that, I have another self-signed certificate which is in shared use for all of them (in this case their common DataLayer component).

    I have no problem creating it and skipping creation if it exists, but how would you suggest I should build the condition under which to delete it? How do I know that the current Uninstall session is the last?
    (Checking the registry doesn't do it, because then you'd need a deferred action to return some result value for the condition...)

    Thanks in advance for any thoughts,
    Roman.

    ReplyDelete
    Replies
    1. Roman, sorry for the indecently long delay with this reply. For some reason, I've just mentioned this post has got more comments.

      I would suggest you looking into the shared components direction. Here's a good explanation about how it works from Heath Stewart: http://blogs.msdn.com/b/heaths/archive/2009/12/21/about-shared-components.aspx. You should try to leverage the standard facilities of Windows Installer as much as possible.

      If this is not what you're looking for and you seek for a different type of behavior, I would suggest you to store some flag in registry, and check it each time you uninstall. For instance, you can have a registry key as a shared MSI component, and tie your custom functionality to the existence of that key.

      Delete
  7. Hi Yan,

    Thank you for this excellent article!!
    Can you share your views on major upgrade when we do the multiple instance installations. What if I have to only upgrade only one version of the product installed?

    --Pavan

    ReplyDelete
  8. I get this when I am trying to build my WiX project

    Error 2 Unresolved reference to symbol 'Property:INSTANCEID' in section 'Product:{38EEE9BE-86BF-49FB-813B-953DD945575E}'. DSetup\Product.wxs 15 1 McPrintingSetup

    Where {38EEE9BE-86BF-49FB-813B-953DD945575E} is the GUID on my product tag. Do you have any idea why this could be?

    ReplyDelete