Josh Rowe
2008-05-08 22:39:26 UTC
Okay, I've figured out a lot about multiple instance transforms and the sorts of contortions you have to go through to make them work. I even figured out how to make a single MSI drive multiple instance transforms without the need for a bootstrapping program, relying on the fact that the InstallUISequence executes indepently from the InstallExecuteSequence. But one thing remains in WiX to make this a lot easier: automatic GUID-changing of components. In this message, I discuss how to create a multiple instance transform, how to make a single MSI drive multiple instance transforms, a common pitfall, and a feature request for WiX.
I want a user, when they double-click an MSI file, to by default install a new instance. First, obviously, we will need to define some instance transforms:
<Property Id="MYINSTANCE"
Value="DontUseThis"
Secure="yes"
/>
<InstanceTransforms Property="MYINSTANCE">
<Instance ProductCode="444e8aa6-c034-4294-b460-07c2d3262f72"
ProductName="Instance 1"
Id="Instance1"/>
<Instance ProductCode="ff0a0580-bc5d-4562-83ec-2edb3511254b"
ProductName="Instance 2"
Id="Instance2"/>
</InstanceTransforms>
So far, so good. What if the new instance ProductName should be driven by user selection items? No problem:
<CustomAction Id="SetProductName"
Property="ProductName"
Value="[[ProductNamePropertyPrefix][MYINSTANCE]]"
/>
<Property Id="ProductNamePropertyPrefix"
Value="ProductName_"
/>
<Property Id="ProductName_DefaultInstance"
Value="Multiple Instance Transforms - Default instance"
/>
<Property Id="ProductName_Instance1"
Value="Multiple Instance Transforms - Instance #1"
/>
<Property Id="ProductName_Instance2"
Value="Multiple Instance Transforms - Instance #2"
<InstallExecuteSequence>
<Custom Action="SetProductName"
Before="ValidateProductID"/>
</InstallExecuteSequence>
Obviously, use whatever algorithm you want in the SetProductName custom action. You will probably want a different install location, too:
<Directory Id="INSTALLLOCATION" Name="TestMultipleInstanceTransformUi">
<Directory Id="InstanceDirectory">
</Directory>
</Directory>
<CustomAction Id="SetInstanceDirectory"
Property="InstanceDirectory"
Value="[INSTALLLOCATION][MYINSTANCE]\"/>
<InstallExecuteSequence>
<Custom Action="SetInstanceDirectory"
Before="CostFinalize"><![CDATA[InstanceDirectory = ""]]></Custom>
</InstallExecuteSequence>
Okay, looking good. Now, the user can specify MSINEWINSTANCE=1 TRANSFORMS=Instance1 on the msiexec command line to install a new instance, right? But that seems a little in-depth for users to have to worry about. For example, how do they know which instance names are available? How can they reliably choose the next available instance id? We can obviously do better. It turns out that the MSI system works in two phases when a UI is presented. First, the InstallUISequence is executed in the user's space. Second, the InstallExecuteSequence is executed in a system process. The two sequences act completely independently; the InstallUISequence's ExecuteAction just passes a set of property names to the system msiexec service. So, we can make our parent process pass in the right transform as follows:
<CustomAction Id="SetTransforms"
Property="TRANSFORMS"
Value="{:[MYINSTANCE];}[TRANSFORMS]"
/>
<CustomAction Id="SetMsiNewInstance"
Property="MSINEWINSTANCE"
Value="1"/>
<InstallUISequence>
<Custom Action="SetTransforms"
Before="ExecuteAction"><![CDATA[ACTION = "INSTALL"]]></Custom>
<Custom Action="SetMsiNewInstance"
Before="ExecuteAction"><![CDATA[ACTION = "INSTALL"]]></Custom>
</InstallUISequence>
Now we need a reliable way of setting MYINSTANCE. That's pretty simple, too. We make our install register each instance id during the install, and look for the first not-yet-registered instance:
<Property Id="INSTANCE1INSTALLEDPRODUCTCODE">
<RegistrySearch Id="LookForInstance1InstalledProductCode"
Key="[InstancesKey]\Instance1"
Name="ProductCode"
Root="HKLM"
Type="raw"/>
</Property>
<Property Id="INSTANCE2INSTALLEDPRODUCTCODE">
<RegistrySearch Id="LookForInstance2InstalledProductCode"
Key="[InstancesKey]\Instance2"
Name="ProductCode"
Root="HKLM"
Type="raw"/>
</Property>
<Property Id="InstancesKey"
Value="Software\Manufacturer\TestMultipleInstance"
/>
Now we have information about each registered instance. (More on actually getting this registration information into the registry later.) A little more tricky custom action work sets MYINSTANCE to the first unused instance id:
<CustomAction Id="SetMyInstance_Instance1"
Property="MYINSTANCE"
Value="Instance1"
/>
<CustomAction Id="SetMyInstance_Instance2"
Property="MYINSTANCE"
Value="Instance2"
/>
<InstallUISequence>
<Custom Action="SetMyInstance_Instance1"
Before="SetTransforms"><![CDATA[ACTION = "INSTALL" AND MYINSTANCE = "DontUseThis" AND INSTANCE1INSTALLEDPRODUCTCODE = ""]]></Custom>
<Custom Action="SetMyInstance_Instance2"
After="SetMyInstance_Instance1"><![CDATA[ACTION = "INSTALL" AND MYINSTANCE = "DontUseThis" AND INSTANCE2INSTALLEDPRODUCTCODE = ""]]></Custom>
</InstallUISequence>
Now, when the ACTION = "INSTALL", just before the SetTransforms action is called that sets the TRANSFORMS property, the MYINSTANCE property will be set to the value of the first unused instance id. Assuming, that is, that we actually got the instance information into the registry. This is where it gets tricky.
Multiple Instance Transforms don't really work out-of-the-box the way most people would want to use them. MSI does not uninstall non-file-data for components that are used by multiple product codes until the final client product is uninstalled; MSI assumes that non-file data is shared. This is, of course, bunk, but that's the way it works. If your component GUID is shared by multiple product installs, then all but the last uninstall will get an Action: FileAbsent for the shared components, thus not uninstalling things other than files. So, if you perform multiple installs of your wonderful multiple-instance transform that includes the instance id in registry key names and then uninstall them, the registry keys from all but the last package to be uninstalled will continue to exist. Ditto for ServiceInstall elements, etc. So, each of your instances needs a new GUID for each component that contains non-file-data that must be uninstalled. Further, these components need to be installed only with that particular instance. One naive way of doing this is to declare multiple components with conditions on them:
<Component Id="Registry_Instance1"
Guid="54412340-1f29-44f5-a733-157efb25c8a6">
<Condition><![CDATA[MYINSTANCE = "Instance1"]]></Condition>
<RegistryKey Root="HKLM"
Key="[InstancesKey]\[MYINSTANCE]"
<RegistryValue Id="Presence_Instance1"
Action="write"
Name="ProductCode"
Value="[ProductCode]"
Type="string"
KeyPath="yes"
/>
</RegistryKey>
</Component>
<Component Id="Registry_Instance2"
Guid="ffe23417-09ba-485f-912b-c063bebbac2a">
<Condition><![CDATA[MYINSTANCE = "Instance2"]]></Condition>
<RegistryKey Root="HKLM"
Key="[InstancesKey]\[MYINSTANCE]"
<RegistryValue Id="Presence_Instance2"
Action="write"
Name="ProductCode"
Value="[ProductCode]"
Type="string"
KeyPath="yes"
/>
</RegistryKey>
</Component>
That's just for registration data. Also add any ServiceInstall, ServiceControl, other stuff, etc., and you'll see that this gets out of hand very rapidly.
Here is we get to the feature request for WiX. Wouldn't it be nice if the InstanceTransforms element interacted with the Component element to produce transformed rows for each component. For example, imagine if you could specify:
<Component Id="MyDisparateComponent"
Guid="fa6d4980-8780-4cff-b52c-5c3f57e05f48"
GenerateGuidForInstanceTransform="yes">
<RegistryKey></RegistryKey>
</Component>
Since component GUIDs are only referenced in the Component table and nowhere else (except possibly ComponentSearch, but you have that problem anyway), there isn't any reason InstanceTransforms shouldn't output rows to change the GUIDs for those components that contain non-file-data that the user would like to have uninstalled. This dramatically reduces the amount of code necessary to go into a multiple instance installer file.
Feel free to change the mechanism. You want explicit guids? How about:
<Component Id="MyDisparateComponent"
Guid="fa6d4980-8780-4cff-b52c-5c3f57e05f48">
<InstanceTransformGuid TransformId="Instance1" Guid="dd2e906b-0192-43ce-86c2-9e9cec8b1049"/>
<InstanceTransformGuid TransformId="Instance2" Guid="8a493697-8fdb-455c-a542-5bd4667e8473"/>
</Component>
I thought a nice deterministic algorithm for computing the next GUID requested by the GenerateGuidForInstanceTransform attribute would be nice so that you wouldn't need to maintain this list on each component if you didn't want to.
Thoughts?
jmr
I want a user, when they double-click an MSI file, to by default install a new instance. First, obviously, we will need to define some instance transforms:
<Property Id="MYINSTANCE"
Value="DontUseThis"
Secure="yes"
/>
<InstanceTransforms Property="MYINSTANCE">
<Instance ProductCode="444e8aa6-c034-4294-b460-07c2d3262f72"
ProductName="Instance 1"
Id="Instance1"/>
<Instance ProductCode="ff0a0580-bc5d-4562-83ec-2edb3511254b"
ProductName="Instance 2"
Id="Instance2"/>
</InstanceTransforms>
So far, so good. What if the new instance ProductName should be driven by user selection items? No problem:
<CustomAction Id="SetProductName"
Property="ProductName"
Value="[[ProductNamePropertyPrefix][MYINSTANCE]]"
/>
<Property Id="ProductNamePropertyPrefix"
Value="ProductName_"
/>
<Property Id="ProductName_DefaultInstance"
Value="Multiple Instance Transforms - Default instance"
/>
<Property Id="ProductName_Instance1"
Value="Multiple Instance Transforms - Instance #1"
/>
<Property Id="ProductName_Instance2"
Value="Multiple Instance Transforms - Instance #2"
<InstallExecuteSequence>
<Custom Action="SetProductName"
Before="ValidateProductID"/>
</InstallExecuteSequence>
Obviously, use whatever algorithm you want in the SetProductName custom action. You will probably want a different install location, too:
<Directory Id="INSTALLLOCATION" Name="TestMultipleInstanceTransformUi">
<Directory Id="InstanceDirectory">
</Directory>
</Directory>
<CustomAction Id="SetInstanceDirectory"
Property="InstanceDirectory"
Value="[INSTALLLOCATION][MYINSTANCE]\"/>
<InstallExecuteSequence>
<Custom Action="SetInstanceDirectory"
Before="CostFinalize"><![CDATA[InstanceDirectory = ""]]></Custom>
</InstallExecuteSequence>
Okay, looking good. Now, the user can specify MSINEWINSTANCE=1 TRANSFORMS=Instance1 on the msiexec command line to install a new instance, right? But that seems a little in-depth for users to have to worry about. For example, how do they know which instance names are available? How can they reliably choose the next available instance id? We can obviously do better. It turns out that the MSI system works in two phases when a UI is presented. First, the InstallUISequence is executed in the user's space. Second, the InstallExecuteSequence is executed in a system process. The two sequences act completely independently; the InstallUISequence's ExecuteAction just passes a set of property names to the system msiexec service. So, we can make our parent process pass in the right transform as follows:
<CustomAction Id="SetTransforms"
Property="TRANSFORMS"
Value="{:[MYINSTANCE];}[TRANSFORMS]"
/>
<CustomAction Id="SetMsiNewInstance"
Property="MSINEWINSTANCE"
Value="1"/>
<InstallUISequence>
<Custom Action="SetTransforms"
Before="ExecuteAction"><![CDATA[ACTION = "INSTALL"]]></Custom>
<Custom Action="SetMsiNewInstance"
Before="ExecuteAction"><![CDATA[ACTION = "INSTALL"]]></Custom>
</InstallUISequence>
Now we need a reliable way of setting MYINSTANCE. That's pretty simple, too. We make our install register each instance id during the install, and look for the first not-yet-registered instance:
<Property Id="INSTANCE1INSTALLEDPRODUCTCODE">
<RegistrySearch Id="LookForInstance1InstalledProductCode"
Key="[InstancesKey]\Instance1"
Name="ProductCode"
Root="HKLM"
Type="raw"/>
</Property>
<Property Id="INSTANCE2INSTALLEDPRODUCTCODE">
<RegistrySearch Id="LookForInstance2InstalledProductCode"
Key="[InstancesKey]\Instance2"
Name="ProductCode"
Root="HKLM"
Type="raw"/>
</Property>
<Property Id="InstancesKey"
Value="Software\Manufacturer\TestMultipleInstance"
/>
Now we have information about each registered instance. (More on actually getting this registration information into the registry later.) A little more tricky custom action work sets MYINSTANCE to the first unused instance id:
<CustomAction Id="SetMyInstance_Instance1"
Property="MYINSTANCE"
Value="Instance1"
/>
<CustomAction Id="SetMyInstance_Instance2"
Property="MYINSTANCE"
Value="Instance2"
/>
<InstallUISequence>
<Custom Action="SetMyInstance_Instance1"
Before="SetTransforms"><![CDATA[ACTION = "INSTALL" AND MYINSTANCE = "DontUseThis" AND INSTANCE1INSTALLEDPRODUCTCODE = ""]]></Custom>
<Custom Action="SetMyInstance_Instance2"
After="SetMyInstance_Instance1"><![CDATA[ACTION = "INSTALL" AND MYINSTANCE = "DontUseThis" AND INSTANCE2INSTALLEDPRODUCTCODE = ""]]></Custom>
</InstallUISequence>
Now, when the ACTION = "INSTALL", just before the SetTransforms action is called that sets the TRANSFORMS property, the MYINSTANCE property will be set to the value of the first unused instance id. Assuming, that is, that we actually got the instance information into the registry. This is where it gets tricky.
Multiple Instance Transforms don't really work out-of-the-box the way most people would want to use them. MSI does not uninstall non-file-data for components that are used by multiple product codes until the final client product is uninstalled; MSI assumes that non-file data is shared. This is, of course, bunk, but that's the way it works. If your component GUID is shared by multiple product installs, then all but the last uninstall will get an Action: FileAbsent for the shared components, thus not uninstalling things other than files. So, if you perform multiple installs of your wonderful multiple-instance transform that includes the instance id in registry key names and then uninstall them, the registry keys from all but the last package to be uninstalled will continue to exist. Ditto for ServiceInstall elements, etc. So, each of your instances needs a new GUID for each component that contains non-file-data that must be uninstalled. Further, these components need to be installed only with that particular instance. One naive way of doing this is to declare multiple components with conditions on them:
<Component Id="Registry_Instance1"
Guid="54412340-1f29-44f5-a733-157efb25c8a6">
<Condition><![CDATA[MYINSTANCE = "Instance1"]]></Condition>
<RegistryKey Root="HKLM"
Key="[InstancesKey]\[MYINSTANCE]"
<RegistryValue Id="Presence_Instance1"
Action="write"
Name="ProductCode"
Value="[ProductCode]"
Type="string"
KeyPath="yes"
/>
</RegistryKey>
</Component>
<Component Id="Registry_Instance2"
Guid="ffe23417-09ba-485f-912b-c063bebbac2a">
<Condition><![CDATA[MYINSTANCE = "Instance2"]]></Condition>
<RegistryKey Root="HKLM"
Key="[InstancesKey]\[MYINSTANCE]"
<RegistryValue Id="Presence_Instance2"
Action="write"
Name="ProductCode"
Value="[ProductCode]"
Type="string"
KeyPath="yes"
/>
</RegistryKey>
</Component>
That's just for registration data. Also add any ServiceInstall, ServiceControl, other stuff, etc., and you'll see that this gets out of hand very rapidly.
Here is we get to the feature request for WiX. Wouldn't it be nice if the InstanceTransforms element interacted with the Component element to produce transformed rows for each component. For example, imagine if you could specify:
<Component Id="MyDisparateComponent"
Guid="fa6d4980-8780-4cff-b52c-5c3f57e05f48"
GenerateGuidForInstanceTransform="yes">
<RegistryKey></RegistryKey>
</Component>
Since component GUIDs are only referenced in the Component table and nowhere else (except possibly ComponentSearch, but you have that problem anyway), there isn't any reason InstanceTransforms shouldn't output rows to change the GUIDs for those components that contain non-file-data that the user would like to have uninstalled. This dramatically reduces the amount of code necessary to go into a multiple instance installer file.
Feel free to change the mechanism. You want explicit guids? How about:
<Component Id="MyDisparateComponent"
Guid="fa6d4980-8780-4cff-b52c-5c3f57e05f48">
<InstanceTransformGuid TransformId="Instance1" Guid="dd2e906b-0192-43ce-86c2-9e9cec8b1049"/>
<InstanceTransformGuid TransformId="Instance2" Guid="8a493697-8fdb-455c-a542-5bd4667e8473"/>
</Component>
I thought a nice deterministic algorithm for computing the next GUID requested by the GenerateGuidForInstanceTransform attribute would be nice so that you wouldn't need to maintain this list on each component if you didn't want to.
Thoughts?
jmr