WiX: Does setting MultiInstance attribute to yes generate unique component IDs for each instance transform, even when static guids are used? - wix

I have two msi packages that gets triggered by a bootstrapper and together install a product. I have multiple instance transforms defined for each msi, and I want to set the MultiInstance attribute to 'yes' for all the components in the harvested fragment such that a new component GUID will be generated per instance transform. (It seems that for now, there isn't a heat parameter that you can set to do this, and it has to be accomplished via an xslt transformation.)
I'd want to use -gg flag for Heat to auto-generate static GUIDs because the install directory is set during run-time as a parameter and is not necessarily a standard directory.
Basically, the output should look like:
<Fragment>
<DirectoryRef Id="TARGETDIR">
<Component Id="cmp32EAD7F5A154CBFA668F294AEEE77B45" Guid="{6529235A-EE06-47EB-A56B-1D016B2396CF}" MultiInstance="yes" >
<File Id="fil3F2F6C0F947339E1ED2CF4459569CC5A" KeyPath="yes" Source="$(var.BIN)\File1.txt" />
</Component>
</DirectoryRef>
... Etc.
I'm wondering, even if the GUID is hard-coded such that the linker does not generate it (like above, instead of Guid="*"), will the MultiInstance attribute being set to 'yes' generate unique guids for each instance transforms' components? I sot of became confused about this when I was test calling the MsiGetProductCode by passing in a component guid for a file, which was defined like below:
<Component Id="ProductComponent" Guid="{1C149757-1E1D-424D-AF77-A156CB87F0BF}" MultiInstance="yes">
<!-- TODO: Insert files, registry keys, and other resources here. -->
<File Id="Picture1" Source="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg" ProcessorArchitecture="x64" />
</Component>
* This is a test file that gets installed for all instance transforms defined.
I had two instances from the msi installed (Instance1, Instance2) and the MsiGetProductCode function ran as a part of a custom action that executes during uninstall. On the first execution of MsiGetProductCode, I got the ProductCode of Instance1. On the second execution of MsiGetProductCode (after Instance1 was removed), I got the ProductCode of Instance2. It seemed like that static component id had been used for both instance transforms.
Is the unique component ID generated by the MultiInstance attribute being set to 'yes' not supposed to replace that visible component guid? I haven't had any issues certain files or registry values not being removed due to a component still being used. Basically, I want to confirm that unique guids are being generated per instance and that it's safe to use the MultiInstance attribute to guarantee that component ID collisions will not occur, even when static guids are in use. Could someone kindly elaborate how this works in the background?
Thanks a lot in advance!

It's pretty easy to confirm WiX behavior just by logging the install. Consider the following code:
<Component Id="test" Guid="{EAF11690-2396-4EBE-A74D-37FA1751BBC3}" MultiInstance="yes">
<File Id="test" Source="C:\windows\notepad.exe" KeyPath="yes" />
</Component>
<InstanceTransforms Property="INSTANCEID">
<Instance Id="I01" ProductCode="{7474D99A-B56C-4767-B437-52F56746274A}" ProductName="ProductName2-1" UpgradeCode="{7C2BE622-7543-4F22-A0ED-A9FD28C78C8A}"/>
</InstanceTransforms>
Logging the base and secondary installation reveals that the GUID is unique / transformed.
Another thought would be to extract the instance transform from the MSI and apply it using ORCA to see the differences.
MSI (s) (E4:A4) [10:36:37:021]: Executing op:
ComponentRegister(ComponentId={EAF11690-2396-4EBE-A74D-37FA1751BBC3},KeyPath=C:\Program
Files
(x86)\MyCompany\ProductName2\notepad.exe,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
MSI (s) (E4:DC) [10:37:04:234]: Executing op:
ComponentRegister(ComponentId={BEC4E6A5-9CFB-5C77-A854-CC0179CFEDCE},KeyPath=C:\Program
Files (x86)\My
Company\ProductName2\notepad.exe,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)

Related

Set File Name to Property

I have a Merge Module which installs a file. I would like to use a property passed to the Merge Module during MSI creation. Something like:
<Configuration Name='FileNameProperty'
Format='Text'
DefaultValue='[FileNameProperty]' />
<Substitution Table='CustomAction'
Row='SetFileName'
Column='Target'
Value='[=FileNameProperty]' />
<CustomAction Id='SetFileName'
Property='MYFILENAME'
Value='[MYFILENAME]' />
<InstallExecuteSequence>
<Custom Action='SetFileName'
Before='LaunchConditions'>1</Custom>
</InstallExecuteSequence>
...
<File Name="[MYFILENAME]"
Source="my-file.exe" />
Currently I am using a Custom Action, namely Type 51, which works when substituting property values for other element attributes, such as ServiceInstall DisplayName. However, in this instance the file is deployed as [MYFILENAME].
I've looked through the other Custom Actions provided by MSDN, but can't find anything that fits this situation. Any suggestions or idea if this is even possible?
My last ditch option is to include File elements for each variation of file name and select the desired file based off condition, but I would like to avoid that.
File names aren't formatted so properties can't be used. If you use multiple files, WiX's smart cabbing ensures the copies don't take up extra space in your cabinet.

Burn: Pass RegistrySearch results value to Custom Bootstrapper

In my Bundle code, I'm trying to use the result of a registry search to set Variable to be used in my Custom Boostrapper:
<util:RegistrySearch
Id="ThirdPartyInstallDirSearch"
Variable="THIRDPARTY_INSTALL_DIR"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
Value="OceanSoftDir"
Result="exists"
/>
<Variable Name="THIRDPARTY_INSTALL_DIR" Type="string" Value="$(var.THIRDPARTY_INSTALL_DIR)"/>
But this would give an error:
Undefined preprocessor variable '$(var.THIRDPARTY_INSTALL_DIR)'
Basically, I want to pass the result of registry search to my custom bootstrapper application.
Thanks
As the error says, $(var.<NAME>) is a preprocessor define from either a wxi file or from the project file <DefineConstants>name=value</DefineConstants> or from a <?define?>. You can read more about the preprocessor here.
For your issue, the registry search itself should be defining the variable. I do something similar to what you want in a bootstrapper.
<Fragment>
<util:RegistrySearch
Id="ClientInstalledCheck"
Root="HKLM"
Key="SOFTWARE\Client"
Value="ClientPath"
Result="value"
Variable="ClientInstalled"/>
<util:DirectorySearch
Path='[ClientInstalled]'
Variable='InstallFolder'
After='ClientInstalledCheck'
Condition='ClientInstalled' />
</Fragment>
And then in the products installed by the bootstrapper I will pass in the "InstallFolder" value to these installs.
<MsiProperty Name="INSTALLDIR" Value="[InstallFolder]"/>
This way if the user has installed in a non-default install location, we pick up the custom location they chose and use that instead. If the registry key did not exist, we use the default location.
I also have the InstallFolder variable defined with a default location (since my use case is slightly different than yours) as
<Variable Name="InstallFolder" Type="string" Value="[ProgramFilesFolder]$(var.CompanyInstallDir)\" bal:Overridable="yes" Persisted="yes"/>
Where CompanyInstallDir is defined as a preprocessor variable through <DefineConstants> which is originally defined somewhere in an MSBuild properties file.
So to explain your issue, you are mixing preprocessor directives and Variables. In your registry search, you're using Result="exists" which will set the variable THIRDPARTY_INSTALL_DIR to '0' or '1'. You want to use Result="value". This will put the registry location's value in the variable you define in the Variable="" attribute.
If you do the registry search + directory search with condition, you can properly set a variable if and only if the registry exists AND the directory is actually present on the machine and properly handle cases where it isn't but the registry still exists.
Some things you may do differently since the use case is slightly different but hopefully this sets you on the right path for doing what you need to do.

PYRO0243 while building patch

I had the following "Reg2015" component in RTM in which I forgot to assign KeyPath:
<DirectoryRef Id="INSTALLLOCATION">
<Component Id="Reg2015" Guid="{xxx}" Win64="no" >
<RegistryKey Root="HKLM" Key="SOFTWARE\Mine" >
<RegistryValue Name="RefCount" Value="1" Type="integer" />
<RegistryValue Name="Name" Value="Mine" Type="string" Action="write" />
</RegistryKey>
</Component>
...
</DirectoryRef>
To prepare the patch, I changed "RefCount" to "2" and added to patch wxs.
Now PYRO.EXE complains like this:
error PYRO0243: Component 'Reg2015' has a changed keypath in the transform 'C:\Patch\Patch.Wixmst'. Patches cannot change the keypath of a component.
error PYRO0260: Product '{xxx}': Table 'CreateFolder' has a new row 'INSTALLLOCATION/Reg2015' added. This makes the patch not uninstallable.
I understand since there was no "KeyPath", its KeyPath defaulted to INSTALLLOCATION, but didn't know that component ID was considered as a directory.
(1) Can someone explain why?
(2) Is there any way to pass PYRO errors?
(3) For my next major release, if I add "KeyPath" to any of "RegistryValue" element, like
<RegistryValue Name="RefCount" Value="1" Type="integer" KeyPath="yes" />
should I be able to change "RefCount" to 2 in the future patch?
Thanks.
I think WiX selected Refcount as the keypath for the component - that's what the docs say. " If KeyPath is not set to 'yes' for the Component or for a child Registry value or File, WiX will look at the child elements under the Component in sequential order and try to automatically select one of them as a key path. Allowing WiX to automatically select a key path can be dangerous because adding or removing child elements under the Component can inadvertantly cause the key path to change, which can lead to installation problems. " You could verify that by looking in the MSI file with Orca to see what the Component table says about it.
So changing the keypath value probably resulted in that issue. It would be better to set another registry item (or create a new one) in the component to be the keypath.

File missing after upgrade

I have installation of version 1.1. I created upgrade with version 1.2.
In both products I have 2 files:
<Component Win64="yes" Id="cmpFILE1" Guid="*">
<File Id="filFILE1" KeyPath="yes" Source="$(var.BasePathCMP)\Performance.dll" />
</Component>
<Component Win64="yes" Id="cmpFILE2" Guid="*">
<File Id="filFILE2" KeyPath="yes" Source="$(var.BasePathCMP)\LockLib.dll" />
</Component>
During the upgrade LockLib.dll is removed and not being replaced.
In clean installation of 1.2 it is present.
What can cause that behavior?
One of the reasons why this happens is because the action RemoveExistingProducts is being executed after the installation package performed the upgrade. In this case, the MSI detects the file is the same as in previous version and then removes it. You could:
Change order of RemoveExistingProducts
Set the DLL to Shared
I'd recommend option one.
RemoveExistingProducts Element
I'm thinking you changed the component guid for a file that is in both MSIs, older and upgrade. When the RemoveExistingProducts is towards the end of the install the upgrade behaves like a merge, overwriting files if necessary and incrementing ref counts for shared component guids. At the end the REP is removing the old product and decrementing ref counts for the component guids. If there are no more clients for the guid it will be removed. If the guid of a "shared" file changed it will have no more clients and be removed. The thing about an REP at the end is that you must follow component sharing rules, but you don't if you sequence REP at the start of the upgrade.
I'm adding an example for future reference.
Say the first install has 3 files A, B, C, and three guids that are 1, 2 and 3. Your upgrade has the same three files but the guids are 1, 2 and 8. When REP is at the end the upgrade installs over the old product first. Guids 1 and 2 get their ref counts incremented from 1 to 2. Guid 3 stays at 1. Then the older product is uninstalled. Guids 1 and 2 get ref counted down to 1, they are still in use so the files attached to the component guids stay. Guid 3 goes down to zero, has no ref counts so the component is removed, but it's attached to file C so C gets removed even though you just installed it.

How to write a Feature state to a registry key

What is the best way to write, to a reg key, if a feature was installed during setup?
You should use Component with condition speified. Condition may reference feature states (current or requested - for more details see this link). I use '&' symbol - it gets requested (which should be achieved with installation) feature state.
<Component Id="MyComponentSpecificReg" Guid="YOURGUID">
<Condition>&MyFeatureId = 3</Condition> <!--install this component only of feature requested state is LOCAL (You may need other condition - please read the link I posted above)-->
<RegistryKey Root="YOUR REG ROOT" Key="YOUR REGISTRY PATH" Action="create">
<RegistryValue Name="YourKeyName" Type="string" Value="[YOUR_PROPERTY]"/>
</RegistryKey>
</Component>