Integer variables at WIX - variables

I would like to install a feature according to the brand.
So in my brand.wxi I defined:
<?define brand.FeatureLevel = 1 ?>
And in my wxs I wrote:
<Feature Id="FF" Title="FF" Level="$(var.brand.FeatureLevel)">
<ComponentRef Id="..." />
<ComponentRef Id="..." />
</Feature>
This definition works fine (wheather I've placed 0 or 1 as FeatureLevel).
My only problem is a warning I get at compilation time:
The 'Level' attribute is invalid - The value '$(var.brand.FeatureLevel)' is invalid according to its datatype 'http://www.w3.org/2001/XMLSchema:integer' - The string '$(var.brand.FeatureLevel)' is not a valid Integer value.
Is there a way to fix this warning?
Can I define integer variable? I couldn't find a way...

You can safely ignore this warning. It just points your attention that this preprocessor variable must evaluate to integer. Otherwise, if you modify brand.FeatureLevel to a letter in the sample above, it will throw an error and simply won't compile.

Related

Wix Burn: Unsetting variable upon failed registry search

In my Bundle project, I'm searching registry for a version and setting a variable:
<Variable Name="Installed_Ver" bal:Overridable="yes" Type="version" Value="0.0.0.0" Persisted="yes" />
<util:RegistrySearch
Id="Self_Ver"
Root="HKLM"
Key="Software\MyCompany\MyProductName"
Value="Version"
Variable="Installed_Ver"
Format="raw"
Win64="yes"
/>
And using this in Condition as:
<util:RegistrySearchRef Id="Self_Ver" />
<bal:Condition Message="A recent or same version of this product is already installed on this machine. Please contact product support for more information." >
<![CDATA[WixBundleInstalled OR (WixBundleFileVersion > Installed_Ver)]]>
</bal:Condition>
This works fine if HKLM\Software\MyCompany\MyProductName\Version exists. But if this registry doesn't exist then registry search would fail and it is Unsetting the variable 'Installed_Ver'. This causes the condition wrongly evaluating to false.
By defining , I tried to set a default value i.e. "0.0.0.0" so it should have some value and condition would be evaluated properly i.e. to True in this case.
Here is the log, which shows that it is Unsetting the variable 'Installed_Ver'.
[5898:2AC4][2018-08-30T13:15:08]i000: Setting string variable 'WixBundleName' to value 'RegVersionCheck'
[5898:2AC4][2018-08-30T13:15:08]i000: Setting string variable 'WixBundleManufacturer' to value 'Testing'
[5898:3354][2018-08-30T13:15:08]i000: Setting numeric variable 'WixStdBALanguageId' to value 1033
[5898:3354][2018-08-30T13:15:08]i000: Setting version variable 'WixBundleFileVersion' to value '7.1.2.3'
[5898:2AC4][2018-08-30T13:15:08]i100: Detect begin, 1 packages
[5898:2AC4][2018-08-30T13:15:08]i000: Registry key not found. Key = 'Software\MyCompany\MyProductName'
[5898:2AC4][2018-08-30T13:15:08]i000: Unsetting variable 'Installed_Ver'
[5898:2AC4][2018-08-30T13:15:08]i101: Detected package: MainProduct, state: Absent, cached: None
[5898:2AC4][2018-08-30T13:15:08]i104: Detected package: MainProduct, feature: CalculatorFeature, state: Absent
[5898:2AC4][2018-08-30T13:15:08]i052: Condition 'WixBundleInstalled OR (WixBundleFileVersion > Installed_Ver)' evaluates to false.
[5898:2AC4][2018-08-30T13:15:08]e000: A recent or same version of this product is already installed on this machine. Please contact product support for more information.
[5898:2AC4][2018-08-30T13:15:08]e000: Error 0x81f40001: Bundle condition evaluated to false: WixBundleInstalled OR (WixBundleFileVersion > Installed_Ver)
[5898:2AC4][2018-08-30T13:15:08]i199: Detect complete, result: 0x0
I tried by hard-coding "0.0.0.0" in the condition in place of Installed_Ver and it works fine.
How should I get a default value for Installed_Ver if registry search fails?
Thanks
This is an open bug. You should be able to work around this by updating your condition to handle when the Variable is not defined.
WixBundleInstalled OR ((WixBundleFileVersion > Installed_Ver) AND Installed_Ver)
After some more searching, I've found a little workaround so that I can use the default value (0.0.0.0) being setup in the Variable definition. I defined another search for same registry key and getting the Boolean (exist) variable and depending upon this variable deciding to trigger the original search and get the version value. Here is my solution:
<Fragment Id="Self_Install_Check">
<?define ProdRegKey=Software\MyCompany\MyProductName?>
<Variable Name="Installed_Ver" bal:Overridable="yes" Type="version" Value="0.0.0.0" Persisted="yes" />
<util:RegistrySearch
Id="Self_Ver"
After="ProdRegExist"
Condition="ProdRegKeyExist"
Root="HKLM"
Key="$(var.ProdRegKey)"
Value="Version"
Variable="Installed_Ver"
Format="raw"
Win64="yes"
/>
<util:RegistrySearch
Id="ProdRegExist"
Root="HKLM"
Key="$(var.ProdRegKey)"
Value="Version"
Variable="ProdRegKeyExist"
Result="exists"
Format="raw"
Win64="yes"
/>
</Fragment>
So now the registry search for reading version would only be triggered if ProdRegKeyExist becomes true and Installed_Ver won't be Unset while retaining value=0.0.0.0 or else would get the Version value from registry...

Failing WiX build due to condition

My build is failing due to the following condition:
..wxs(138) : error LGHT0204 : ICE03: Bad conditional string; Table:
Condition, Column: Condition, Key(s):
<Feature .....>
<MergeRef Id="Feature" Primary="yes"/>
<Condition Level="0">((PROP1="prop1") OR (PROP2="prop2") OR (PROP3="prop3") OR (PROP4="prop4" AND OR PROP5=""))</Condition>
</Feature>
Is there any mistake in the condition?
Is there any other way to specify this condition?
I have never seen AND OR used in conditions before. Removing OR makes it compile. Hard to tell what you are trying to achieve here without more information.

Setting a value with curly brackets in XmlConfig with WIX xslt

i am trying to set a value in WIX XmlConfig which contains curly brackets
I want to set a value like this "IndexServer__%property{name}.zip"
<util:XmlFile
Id="AppConfigLocalFileDebugLogPath"
Action="setValue"
Permanent="yes"
ElementPath="//configuration/log4net/appender[\[]#name='LocalFileDebugLog'[\]]/file/#value"
Value="[LOGFILEROOTPATH]\IndexServer\Debug\IndexServer_DEBUG__%property[\{]index-name[\}].log"
File="[INSTALLFOLDER]Index.Webservice.exe.config"
SelectionLanguage="XPath"
Sequence="1"
/>
I thought it could work with [{] and [}] because [[] and []] works fine but it doesnt. I get following error from HEAT"HEAT5055: Error applying transform xxxxx to harvested Wix: unexpected token ']' in expression"
(or something like that, the original error message is in german ;-) )
If i simply try:
<util:XmlFile
Id="AppConfigLocalFileDebugLogPath"
Action="setValue"
Permanent="yes"
ElementPath="//configuration/log4net/appender[\[]#name='LocalFileDebugLog'[\]]/file/#value"
Value="[LOGFILEROOTPATH]\IndexServer\Debug\IndexServer_DEBUG__%property{index-name}.log"
File="[INSTALLFOLDER]Index.Webservice.exe.config"
SelectionLanguage="XPath"
Sequence="1"
/>
This results in a string like "E:\LogFiles\WSIndexServer\WSIndexServer__%property.log" as you can see the string "{index-name}" gets lost ;-(
Could you help me here? I need the part "{index-name}" including the curly brackets in the string.
Thanks a lot
Chris
As the question is tagged as XSLT and as you say that with Value="[LOGFILEROOTPATH]\IndexServer\Debug\IndexServer_DEBUG__%property{index-name}.log" the {index-name} is lost I suppose that value is evaluated in XSLT and you need to double the {} to avoid being treated as an attribute value template, so try Value="[LOGFILEROOTPATH]\IndexServer\Debug\IndexServer_DEBUG__%property{{index-name}}.log".

Validation of a command line parameter in WiX

I want to validate the value of a command line property in WiX given by the user during installation. If the value of the property is not one among the specific set, it should take a default value and create a registry entry.
For example, property USERLEVEL should have value 1-4. If user specifies any other value it should default to 1 and write it to the registry. The installer does not have a UI component and we want to do this using some conditional statement.
That can be done easiest with a "Launch Condition". As a child of Product element add a Condition element with a message. For example:
<Product ...>
...
<Condition Message='The USERLEVEL property has an invalid value of: [USERLEVEL]. Please ensure the value falls in the range of 1 to 4.'>
USERLEVEL>0 AND USERLEVEL<5
</Condition>
I used the > and < rather than wrapping the condition in CDATA but you can do it however you like. The end result is that you want the condition to say something like USERLEVEL > 0 and USERLEVEL < 5 (or if you prefer: USERLEVEL >=1 AND USERLEVEL <= 4).
You can use the SetProperty element to change the USERLEVEL property value. You can use the (USERLEVEL<1 OR USERLEVEL>4) Condition to check the value, but it won’t work if end user pass any non-integer value like ‘A’.
<Property Id="USERLEVEL" Secure="yes" />
<SetProperty Id="USERLEVEL" Value="1" After="AppSearch">
USERLEVEL<>1 AND USERLEVEL<>2 AND USERLEVEL<>3 AND USERLEVEL<>4
</SetProperty>
You can use the below code to write the property into registry.
<Component Id="CMP_UserLevel" Guid="{FD70BBE3-F7F1-460E-AA7C-56750F66536D}">
<RegistryKey Root="HKLM" Key="Software\Sample, Inc.\Test Installer">
<RegistryValue Name="USERLEVEL" Value="[USERLEVEL]" Type="integer" />
</RegistryKey>
</Component>

How can multiple elements be added to an XML config file with wix?

I am trying to edit an XML file with Wix. I am using the WixUtilExtension bundled with Wix 3.7. The xml file is a settings file created in Visual Studio 2010 for a C# application. In this file, I am using an element which is used to store multiple string values in an array. This is the content of the unaltered settings file:
<configuration>
<applicationSettings>
<AppName.Properties.Settings>
<setting name="StringArray" serializeAs="Xml">
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
</ArrayOfString>
</value>
</setting>
</AppName.Properties.Settings>
</applicationSettings>
</configuration>
I want to add <string> elements to the <ArrayOfString> element in this file. One way to do this is by using an <XmlConfig> element from the wix/UtilExtension namespace. I have added this element to the component which holds the config file like this:
<Component Id="ProductComponent" Guid="$(var.ConfigGuid)">
<File Source="SettingsFile.exe.config" KeyPath="yes" Id="FILE_config" />
<util:XmlConfig
Name="string"
Value="My value"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="String1"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="100"
/>
</Component>
This results in the addition of one <string> element to the <ArrayOfString> element. To add another <string> element to the settings file, another XmlConfig element has to be added to the <Component> element of the setup project with a different Id attribute and a higher value for the Sequence attribute like this:
<util:XmlConfig
Name="string"
Value="My second value"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="String2"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="101"
/>
After installation of the msi, the <ArrayOfString> element in the settings file looks like this:
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>My value</string><string>My second value</string></ArrayOfString>
I have found out that it is possible to set the Value attribute of an <XmlConfig> attribute to the value of a property like this:
<Property Id="STRING1VALUE" Value="My value" />
<util:XmlConfig Value="[STRING1VALUE]" ... />
This is good. I would like the user to be able to add multiple values in the installation process dynamically so that a variable amount of <string> elements can be added to the settings file.
My first approach was to use a <?foreach?> statement like this:
<?define values="My value;My second value"?>
<?foreach value in $(var.values)?>
<util:XmlConfig
Name="string"
Value="$(var.value)"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="String$(var.value)"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="101"
/>
<?endforeach?>
There are a few problems with this approach:
The foreach statement uses a preprocessor variable which cannot be set to the value of a property.
The value of the Sequence attribute stays the same.
I would like the user to store the values for the string elements in a Property which separates the values by semicolons and then parse them in a foreach statement like this:
<Property Id="VALUES" Value="My value;My second value" />
<?foreach value in [VALUES]?>
<util:XmlConfig
Name="string"
Value="$(var.value)"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="String$(var.value)"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="101"
/>
<?endforeach?>
This throws the following error:
The util:XmlConfig/#Id attribute's value, 'String[VALUES]', is not a legal identifier.
Identifiers may contain ASCII characters A-Z, a-z, digits, underscores (_), or periods (.).
Every identifier must begin with either a letter or an underscore.
Is there any way I can create a variable amount of elements with the XmlFile or the XmlConfig element? Is the only solution to this problem a CustomAction?
Based on Rob's answer, here is my new approach to adding multiple elements to an XML config file with Wix. I did not want to write C++ code, that is why I used DTF in my CustomAction.
I am going to describe how to turn a string containing multiple elements using a delimiter into multiple XML elements.
First there needs to be a property in the setup file containing the delimited string.
<Property Id="STRINGARRAY" Value="string1;string2;string3" />
This property could be populated by the user in a dialog, of course.
Next, a CustomAction has to be written. To make use of the DTF, a reference to the Microsoft.Deployment.WindowsInstaller.dll has to be added to the C# CustomAction project. The namespace Microsoft.Deployment.WindowsInstaller should be included with a using directive in that project. My CustomAction looks like this:
[CustomAction]
public static ActionResult Insert(Session session)
{
string strings = session["STRINGARRAY"];
string[] stringArray = strings.Split(';');
Database db = session.Database;
View view = db.OpenView("select * from `XmlConfig`");
string xpath = "/configuration/applicationSettings/AppName.Properties.Settings/setting[\\[]#name='StringArray'[\\]]/value/ArrayOfString";
for (int i = 0; i < stringArray.Length; i++)
{
string id = String.Format("String{0}", i);
int sequence = 100 + i;
string value = stringArray[i].Trim();
Record rec = new Record(
id,
"[INSTALLFOLDER]SettingsFile.exe.config",
xpath,
null,
"string",
value,
273,
"ProductComponent",
sequence);
view.InsertTemporary(rec);
}
db.Close();
return ActionResult.Success;
}
Here, at first the Property StringArray is read into a local variable which is converted to a string array. The following line establishes a connection to the current database used by the installer. A handle on the table XmlConfig is created, which is the table where the XML elements are added to. To insert the right values into that table, it is best to create an installer file which contains such a table and then take a look at that table in an editor like orca or InstEd.
In the xpath, backslashes have to be escaped by using double backslashes. The id variable holds the name of the temporary record, using a simple string and a number works flawlessly. The sequence has to be incremented for each element. I could not find any documentation on the values of the flags column, but I have found out that its value is set to 273 for elements that are created and 289 for elements that get deleted.
Once the record is filled with the correct values, it gets added to the XmlConfig table by using the InsertTemporary method of the view object. This is done for each element found in the delimited string.
A problem I have come across is that this CustomAction fails, if the XmlConfig table does not exist. To counter this problem I have added the following code to the setup file, which adds an element to the XML file and immediately deletes that element. I guess there could be a cleaner solution, but this was the easiest one for me.
<util:XmlConfig
Name="string"
Value="Dummy"
File="[INSTALLFOLDER]SettingsFile.exe.config"
Id="DummyEntry"
On="install"
Action="create"
Node="element"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="1" />
<util:XmlConfig
On="install"
Action="delete"
Id="DeleteDummyEntry"
Node="element"
File="[INSTALLFOLDER]SettingsFile.exe.config"
VerifyPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString/string"
ElementPath="/configuration/applicationSettings/AppName.Properties.Settings/setting[\[]#name='StringArray'[\]]/value/ArrayOfString"
Sequence="2" />
Finally, the CustomAction has to be added to the setup project. By adding a reference to the CustomAction project in the setup project, the location of the binary can be specified like this:
<Binary Id="XmlCustomActionDLL" SourceFile="$(var.XmlCustomAction.TargetDir)XmlCustomAction.CA.dll" />
The CustomAction has to be executed immediately, otherwise it won't be able to access the session variable:
<CustomAction Id="CA_XmlCustomAction" BinaryKey="XmlCustomActionDLL" DllEntry="Insert" Execute="immediate" Return="check" />
<InstallExecuteSequence>
<Custom Action="CA_XmlCustomAction" Before="RemoveRegistryValues" />
</InstallExecuteSequence>
To determine the right position for the CustomAction in the installation sequence, I relied on this article by Bob Arnson.
Yes, this is possible but if you want to have this determined at install time then the preprocessor is not an option. The preprocessor executes during the build process.
To get what you want, you'll need to write another custom action that takes the arbitrarily long set of user data and adds temporary rows to the XmlConfig table. The WcaAddTempRecord() function in src\ca\wcautil\wcawrap.cpp can do the work. The src\ca\wixca\dll\RemoveFoldersEx.cpp is a pretty good example of using WcaAddTempRecord() to add rows to the RemoveFile table. You'll want to do similarly.