Often you will need to add different pieces of your setup during build time depending on many factors such as the SKU being built. This is done by using conditional statements that will filter the xml before it is sent to the WiX compiler (candle). If the statement evaluates to true, the block of xml will be sent to candle. If the statement evaluates to false, candle will never see that section of xml.
The conditional statements are Boolean expressions based on environment variables, variables defined in the xml, literal values, and more.
Let’s start with an example. Say you want to include a file if you’re building the “Enterprise SKU.” Your build uses an environment variable
%MySku%=Enterprise to specify this sku.
When you build the enterprise sku, this file will be included in the xml passed on to candle. When you build a different sku, the xml from EnterpriseFeature.wxi will be ignored.
<?if $(env.MySku) = Enterprise ?> <?include EnterpriseFeature.wxi ?> <?endif ?>
Include Files <?include?>
As shown in the example above, files can be included by using the include tag. The filename referenced in the tag will be processed as if it were part of this file.
The root element of the include file must be <Include>. There are no other requirements beyond the expected wix schema. For example,
<Include> <Feature Id='MyFeature' Title='My 1st Feature' Level='1'> <ComponentRef Id='MyComponent' /> </Feature> </Include>
Any variable can be tested for its value or simply its existence. Custom variables can also be defined in your xml.
Three types of variables are supported:
- Gets the environment variable %_NtPostBld%
- Gets the system variable for the current directory
- Gets the variable A that was defined in this xml
The preprocessor evaluates variables throughout the entire document, including in <?if?> expressions and attribute values.
Any environment variable can be referenced with the syntax $(env.VarName). For example, if you want to retrieve the environment variable %_BuildArch%, you would use $(env._BuildArch). Environment variable names are case-insensitive.
WiX has some built-in variables. They are referenced with the syntax $(sys.VARNAME) and are always in upper case.
CURRENTDIR - the current directory where the build process is running
SOURCEFILEPATH – the full path to the file being processed
SOURCEFILEDIR – the directory containing the file being processed
PLATFORM – the platform (Intel, x64, Intel64) this package is compiled for (set by the Package element's Platform attribute)
NOTE: All built-in directory variables are “\” terminated.
Custom variables <? define ?>
If you want to define custom variables, you can use the <?define?> statement. You can also define variables on the command line using candle.exe using the -d switch. Later, the variables are referred to in the <?if?> statements with the syntax $(var.VarName). Variable names are case-sensitive.
How to define the existence of a variable:
<?define MyVariable ?>
How to define the value of a variable (note: quotes are required if the value or the expansion of other variables in the value contain spaces):
<?define MyVariable = “Hello World” ?>
<?define MyVariable = “$(var.otherVariableContainingSpaces)” ?>
The right side of the definition can also refer to another variable:
<?define MyVariable = $(var.BuildPath)\x86\bin\ ?>
How to undefine a variable:
<?undef MyVariable ?>
To define variables on the command line, you can type a command similar to the following:
candle.exe -dMyVariable="Hello World" ...
You can refer to variables in your source that are defined only on the command line, but candle.exe will err when preprocessing your source code if you do not define those variables on the command line.
There are several conditional statements, they include:
- <?if ?>
- <?ifdef ?>
- <?ifndef ?>
- <?elseif ?>
The purpose of the conditional statement is to allow you to include or exclude a segment of xml at build time. If the expression evaluates to true, it will be included. If it evaluates to false, it will be ignored.
The conditional statements always begin with either the <?if ?>, <?ifdef ?>, or <?ifndef ?> tags. They are followed by an xml block, an optional <?else?> or <?elseif ?> tag, and must end with an <?endif?> tag.
Expressions (used in <?if ?> and <?elseif ?>)
For example: <?if [expression]?>
The expression found inside the <?if ?> and <?elseif ?> tags is a Boolean expression. It adheres to a simple grammar that follows these rules:
- The expression is evaluated left to right
- Expressions are case-sensitive with the following exceptions:
- Environmental variable names
- These keywords: and, or, not
- The ~= operator is case-insensitive.
- All variables must use the $() syntax or else they will be considered a literal value.
- If you want to use a literal $(, escape the dollar sign with a second one. For example, $$(
- Variables can be compared to a literal or another variable
- Comparisons with =, !=, and ~= are string comparisons.
- Comparisons with inequality operators (<, <=, >, >=) must be done on integers.
- If the variable doesn't exist, evaluation will fail and an error will be raised.
- The operator precedence is as follows. Note that “and” and “or” have the same precedence:
- (), $( )
- <, >, <=, >=, =, !=, ~=
- And, Or
- Nested parenthesis are allowed.
- Literals can be surrounded by quotes, although quotes are not required.
- Quotes, leading, and trailing white space are stripped off literal values.
- Invalid expressions will cause an exception to be thrown.
Variables (used in <ifdef ?> and <ifndef ?>)
For example: <?ifdef [variable] ?>
For <ifdef ?>, if the variable has been defined, this statement will be true. <ifndef ?> works in the exact opposite way.
Note that these examples will actually each be a no-op because there aren’t any tags between the if and endif tags.
<?define myValue = "3"?> <?define system32=$(env.windir)\system32 ?> <?define B = "good var" ?> <?define C =3 ?> <?define IExist ?> <?if $(var.Iexist) ?><?endif?> <!-- true --> <?if $(var.myValue) = 6 ?><?endif?> <!-- false --> <?if $(var.myValue)!=3 ?><?endif?> <!-- false --> <?if not "x"= "y"?> <?endif?> <!-- true --> <?if $(env.systemdrive)=a?><?endif?> <!-- false --> <?if 3 < $(var.myValue)?> <?endif?> <!-- false --> <?if $(var.B) = "good VAR"?> <?endif?> <!-- false --> <?if $(var.A) and not $(env.MyEnvVariable) ?> <?endif?> <!-- false --> <?if $(var.A) Or ($(var.B) And $(var.myValue) >=3)?><?endif?> <!-- true --> <?ifdef IExist ?> <!-- true --> <?else?> <!-- false --> <?endif?>
Errors and Warnings
You can use the preprocessor to show meaningful error and warning messages using, <?error error-message ?> and <?warning warning-message?>. When one of these preprocessor instructions is encountered the preprocessor will either display an error and stop the compile or display a warning and continue.
<?ifndef RequiredVariable ?> <?error RequiredVariable must be defined ?> <?endif?>
There is a single iteration statement, <?foreach variable-name in semi-colon-delimited-list ?> <?endforeach?>. When this occurs the preprocessor will
- create a private copy of the variable context
- set the variable in the foreach statement to an iteration on the semicolon delimited list
- generate a fragment with the variable substituted
The effect of this process is that the fragment is used as a template by the preprocessor in order to generate a series of fragments. The variable name in the ?foreach statement can be preceded by "var.". When a variable is used inside the text of the fragment, it must be preceded by "var."
An few examples:
<?foreach LCID in 1033;1041;1055?> <Fragment Id='Fragment.$(var.LCID)'> <DirectoryRef Id='TARGETDIR'> <Component Id='MyComponent.$(var.LCID)' /> </DirectoryRef> </Fragment> <?endforeach?>
<?define LcidList=1033;1041;1055?> <?foreach LCID in $(var.LcidList)?> <Fragment Id='Fragment.$(var.LCID)'> <DirectoryRef Id='TARGETDIR'> <Component Id='MyComponent.$(var.LCID)' /> </DirectoryRef> </Fragment> <?endforeach?>
filename: ExtentOfLocalization.wxi <Include> <?define LcidList=1033;1041;1055?> </Include>
<?include ExtentOfLocalization.wxi ?> <?foreach LCID in $(var.LcidList)?> <Fragment Id='Fragment.$(var.LCID)'> <DirectoryRef Id='TARGETDIR'> <Component Id='MyComponent.$(var.LCID)' /> </DirectoryRef> </Fragment> <?endforeach?>
An alternative to the foreach process would be to write the template WiX fragment into a separate file and have another process generate the authoring that will be passed to WiX. The greatest merit of this alternative is that it's easier to debug.
The preprocessor treats the $ character in a special way if it is followed by a $ or (. If you want to use a literal $$, use $$$$ instead. Every two $ characters will be replaced with one. For example, $$$$$ will be replaced with $$$.
The preprocessor supports the following functions:
- Gets an auto generated version number using the same scheme as .NET AssemblyVersion attribute. The parameters x.y specify the major and minor verion number, the build is set to the number of days since 1/1/2000 and revision to the number of seconds since midnight divided by 2. Both values are calculated using UTC.
WiX has support for preprocessor extensions via the PreprocessorExtension class. The PreprocessorExtension can provide callbacks with context at foreach initialization, variable evaluation, function definitions, and the last call before invoking the compiler (for full custom preprocessing).