I'm a novice WiX user, converting over several existing installations from a no longer maintained (.MSI-producing) commercial product.
As part of that I'm restructuring the original feature/component arrangements to take advantage of some of the new options WiX has opened up. One of these is adding Conditions at Feature rather than Component level, and as part of that I've tried specifying a conditional Feature like this (demo/test items throughout):
<Feature Id="Feature1" Title="Test Only" Level="0">
<Component Id="C1" Guid="*" Directory="SomeDir">
<File Id="File1" KeyPath="yes" Checksum="yes" Name="Test file.txt" Source="D:\xxx.dat" />
</Component>
<Condition Level="1"><![CDATA[INSTALLTYPE <> 1]]></Condition>
</Feature>
Where INSTALLTYPE is a Property defined at <Product> level and subsequently set to 0, 1 or 2 by the user via a RadioButtonGroup in the UI sequence.
Summarising what I've found after a day or so of trying different options and head-scratching:
With an initial Level=0, set to 1 by the condition (as in the sample above), the Feature is never installed.
With an initial Level=1, set to 0 by the condition, the Feature is installed when the condition is satisfied as long as an = test is used in the condition. If a INSTALLTYPE <> 1 is used, or Not (INSTALLTYPE = 1), the feature is never installed.
If a pre-defined Property (such as INSTALLLEVEL) is used in the test rather than my INSTALLTYPE, everything works correctly/as expected.
If equivalent tests are applied at Component, rather than Feature, level, everything works as expected for all Propertys and test operators.
Something seems to be going wrong with INSTALLTYPE but I can't for the life of me work out what. I've tried firing a VBScript custom action (MsgBox("INSTALLTYPE=" & Session.Property("INSTALLTYPE"))) in the Execute sequence to check the value of the Property, and as far as I can see this does display the value I'd expect it to have from the UI setting.
In principle I could get Features installing the way I want using the subset of settings I've found to work (e.g. INSTALLTYPE = 0 OR INSTALLTYPE = 2 rather than INSTALLTYPE <> 1), but my bafflement over apparently simple things not behaving as expected suggests I'm missing something fundamental...so I'm wary of just shrugging and applying workarounds.
If anyone has any thoughts on what I might be doing wrong with the items above, I'd be very happy to hear them!
Related
I am facing issue for RegistrySearch for multistring, where as string search working fine.
Checked in installation logs
Action start 13:40:07: AppSearch. MSI (s) (40:E0) [13:40:07:381]:
PROPERTY CHANGE: Adding MYKEY property. Its value is ''. MSI (s)
(40:E0) [13:40:07:381]: PROPERTY CHANGE: Adding MYSERVICE property.
Its value is 'myvalue2'.
I have trimmed some logs here
Action ended 13:40:51:
ScheduleReboot. Return value 1. Action ended 13:40:51: INSTALL. Return
value 1. You must restart your system for the configuration changes
made to XXXXX to take effect. Click Yes to restart now or No if you
plan to manually restart later. Property(S): UpgradeCode =
{XXXXXX-XXXX-XXX-XXXX-XXXXXXXX}
Property(S): MYKEY = [~]myvalue1[~] Property(S): MYSERVICE = myvalue2
At end of installation it seems it has correctly evaluated the MYKEY but not during AppSearch, resulting in failing my Condition evaluation
<Feature Id="MyFeature" Level="" Display="" Title="" Description="" AllowAdvertise="no" ConfigurableDirectory="INSTALLDIR">
<MergeRef Id="MyFeature" Primary="yes"/>
<Condition Level="0">((MsiNTProductType=1) OR
(MYKEY="[~]MyValue[~]") OR
(MYSERVICE="MyService" AND MYKEY=""))</Condition>
</Condition>
</Feature>
<Property Id="MYKEY" Secure="yes">
<RegistrySearch Id="MyKey"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Services\MyService"
Name="mykey"
Type="raw" />
</Property>
<Property Id="MYSERVICE" Secure="yes">
<RegistrySearch Id="MYSERVICE"
Root="HKLM"
Key="SYSTEM\CurrentControlSet\Services\MyService"
Name="DisplayName"
Type="raw" />
</Property>
UPDATE: I might have missed you stating it already, but when checking whether the property set by the AppSearch search has any assigned value at all using simply PROPERTYNAME as condition, the condition shows up as true - meaning that "something" exists in the property in question, the text is just not displayed.
Is it sufficient to test just for the presence of a value, or do you need to check the specific value of MYKEY? If the mere presence of a value is enough, then you may be able to use this condition:
((MsiNTProductType=1) OR (MYKEY) OR (MYSERVICE="MyService" AND MYKEY=""))
I guess this answer from Rob Mensching from the WiX-users mailing list answers the question with certainty. Multi-string is simply not supported for AppSearch.
There is no need to doubt the accuracy of this since Rob was on the original MSI team. You need to abandon this approach. Sorry to say. Unless the above workaround that I just added could work (check not the value, but if there is a value at all being retrieved from the registry).
A couple of other, potential workarounds:
You could read the multi-string from a custom action. I just verified that it works with a test VBScript - the forbidden MSI tool :-).
Could you search for a file or directory on disk that would signify the same thing you retrieve from the registry with this multi string?
As my motto goes every now and then: let's obsess over this (as opposed to: "careful, we don't want to learn from this" - which is another motto of mine - which tends to be the better option).
It is truly odd, that I can replicate what you state about your log file. I see a CommandLine entry which shows the multi-sting correctly, albeit with several extra null characters (slightly shortened log entry):
CommandLine: NORMALSTRING="sample regular string" MULTISTRING="[~~~]String 1[~~~]String 2[~~~]String 3[~~~]" INSTALLFOLDER="C:\Program Files (x86)\WiX3_GenericTestProject\" TARGETDIR="C:\" ACTION="INSTALL" EXECUTEACTION="INSTALL" ROOTDRIVE="C:\" INSTALLLEVEL="1" SECONDSEQUENCE="1" ADDLOCAL=Empty,Modules,ProductFeature
and also, later in the log file, after InstallFinalize:
Property(S): MULTISTRING = [~]String 1[~]String 2[~]String 3[~]
I really don't understand how that comes about. Somehow the AppSearch must have really set the property in question even if it didn't look like it did - the property just can't be retrieved correctly (or formatted correctly), and hence doesn't work in (feature) conditions either?
Maybe the underlying data model in Windows Installer has stored the retrieved registry multi string value as a BSTR (the abomination of a COM string format which allows embedded nulls and can be compiled and linked without being properly allocated / constructed via SysAllocateString - "burnt child, smells burnt - and all that...").
Anyway, I suppose AppSearch expectes a regular, null-terminated string buffer and interprets the BSTR as such? Hence stumbling on the first null value which is the first character of the data string section of the BSTR (not the length prefix section - the BSTR pointer points 4 bytes into the allocated BSTR memory) and reports an empty string overall? The property values that show up in the log file must have been read directly from the underlying data model by other means? I would assume the MSI Win32 C++ functions? But wouldn't that also be the case for AppSearch? Something is wrong with how this property string - with embedded nulls - is being displayed and used in conditions.
So in summary: maybe the retrieval of the multi string actually works, but the exposed value via Session.Property("PROP") erroneously reads the potential, native BSTR as a null-terminated string buffer and interprets the leading null as the end of the string buffer? Sort of doesn't make sense considering Session.Property is a COM call and should definitely understand a BSTR? Theories like these are never correct, but maybe they can help create some new ideas at least. What seemed like a missing Windows Installer feature, sort of smells like a bug I think. Or as it is in the real world: a technical problem, not easily fixed and hence seen and accepted as a missing feature.
Let me link together your questions on this issue for reference (and a couple of other answers):
RegistryValue Element of type multiString.
Failing condition wix.
Passing multiString values to installer through command-line.
When I add a Condition within a Feature to assure that IIS is installed, it is working as expected (it searches installed IIS, when feature is selected. No check, when feature is not selected):
<Feature Id="feat.WebApplication"
Title="Web Application"
Level="1"
ConfigurableDirectory="DIR.WEBAPP">
<Condition Level="0">
<![CDATA[Installed OR (IISMAJORVERSION AND IISMAJORVERSION >= "#7")]]>
</Condition>
<!-- ... -->
</Feature>
But - as documented in Condition Element - it is not allowed to have a message within the condition. So the installation silently fails, whereas it shows a message when the condition is placed directly in the Product node.
How can I show a message also for conditions within features?
As I understand the Condition node, after reading about the Level attribute of the Feature Element,
Sets the install level of this feature. A value of 0 will disable the feature. Processing the Condition Table can modify the level value (this is set via the Condition child element). The default value is "1".
by using Condition within feature you get the ability to disable or enable features depending on its own value for Level, because you can override it if the condition matches with the value of the Level attribute of the condition.
So the best would be to add some text about the precondition to the feature Description.
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)
I am trying to install the features based on the condition. Initially i set the feature level to 1 and place a condition inside the feature to modify the feature level.
I am unable to modify the feature level and it is always set to 1 only irrespective of condition.
<Feature
Id = "AddinsFeature"
Title = "InstallAddin"
Level = "1"
Absent="allow">
<ComponentRef Id = "AddInComp"/>
<Condition Level="0">
<![CDATA[FALSE]]>
</Condition>
</Feature>
How to use WiX feature conditions is essentially explained here:
https://www.firegiant.com/wix/tutorial/getting-started/conditional-installation/
For a feature to be set to the level specified by your condition, the condition has to evaluate to true. You can force it to be true by setting it to 1:
<Feature Id="AddinsFeature" Title="InstallAddin" Level="1" Absent="allow">
<!-- Force condition to be true, which sets the feature to the Level attribute value -->
<Condition Level="0">1</Condition>
<ComponentRef Id = "AddInComp"/>
</Feature>
Above we force the feature's install level to 0 because its condition of 1 is true (the number 1 is true in MSI logic - by definition - as in boolean). In the real world the condition would be something much more complicated - of course.
Every setup has an overall INSTALLLEVEL - and it acts as a high water mark as explained here by Chris Painter. Every feature which evaluates to a feature level below or at the INSTALLLEVEL gets installed by default.
Note: When you set the Feature level to 0 in your WiX source, the feature is not show in the setup GUI and it is not
going to be installed by default either (more details in link below).
Feature manipulation can be very involved. A few links:
How to install feature based on the property set in custom action?
Unselected Feature Being Installed (over the top detailed)
Failing condition wix (Level=0 hides feature from GUI)
http://www.joyofsetup.com/2008/04/09/feature-states-in-component-conditions/
https://www.joyofsetup.com/2008/05/16/make-sure-features-are-always-enabled-so-they-can-be-removed/
You can ship the components present inside the feature by setting the condition 'True' like below. Whenever the property 'SAMPLEFEATURE_UNLOCKED' sets to true, the feature is unlocked.
I've got different installation modes. Depending on the parameters I do things like:
<Property Id="PROP1" Value="Value1" />
<SetProperty Id="PROP1" Before="CostFinalize" Sequence="execute" Value="Value2"></SetProperty>
The problem is that I've got more than 2 options, when I'm adding them I'm getting.
Duplicate symbol 'CustomAction:SetPROP1' found.
Is there a way to have some sort of switch statement or how do you handle multiple conditionals?
Another trouble is, I've got multiple variables set depending on the value (about 10 right now) and it's very cumbersome to list them all with absolutely the same code
<SetProperty Id="PROP2" Before="CostFinalize" Sequence="execute" Value="Value2"></SetProperty>
<SetProperty Id="PROP3" Before="CostFinalize" Sequence="execute" Value="Value3"></SetProperty>
etc
Is there any way to make it like:
<Condition val="...">
<setProperty.../>
<setProperty.../>
<setProperty.../>
</Condition>
Thanks!
First of all, try to re-think the architecture of your installation program. Is it really necessary to set all those properties based on the same condition? Or maybe it's better to "condition"-ize the appropriate features and components in a more plain way?
Let's get back to the technical side of your question. The SetProperty element is just a shortcut which is "all-in-one" solution for defining a custom action which sets a property and scheduling it appropriately. It is true that you can't use it to set the same property to different value, because there's no ID attribute of the SetProperty element itself.
Instead, use the good old style of defining a custom action and scheduling it manually:
<CustomAction Id="SetProp11" Property="PROP1" Value="Value1" />
<CustomAction Id="SetProp12" Property="PROP1" Value="Value2" />
...
<InstallExecuteSequence>
<Custom Action="SetProp11" After="...">your condition here</Custom>
<Custom Action="SetProp12" After="...">your condition here</Custom>
...
</InstallExecuteSequence>
This definitely adds extra typing work and makes your code less readable, but this way you can work your problem around. However, let me emphasize once again - the fact that you have to invent workarounds means that the code starts to smell and it might make sense to re-think it through.
And I'm not aware about the way to set a number of properties in a bunch, like in switch statement. Alternatively, technically you can create a e.g. C# custom action and let it do the job for all the properties at once.
Hope this helps.