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 ?>
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:
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.
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:
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.
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:
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?>
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
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:
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).