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.wxs will be ignored.
<?if $(env.MySku) = Enterprise ?> <?include EnterpriseFeature.wxs ?> <?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
Custom variables <? define ?>
If you want to define custom variables, you need to use the <?define?> statement. 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 (Quotes are only required if it contains spaces):
<?define MyVariable = “Hello World” ?>
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 ?>
Conditional StatementsThere 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
- 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 used to check for existence
- 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.
More ExamplesNote 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?>
Iteration StatementsThere 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
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?>or
<?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?>or
filename: ExtentOfLocalization.wxi <Include> <?define LcidList=1033;1041;1055?> </Include> and <?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.
WiX has support for preprocessor extensions via the PreprocessorExtension class. The PreprocessorExtension can provide callbacks with context at foreach initialization, variable evaluation, and the last call before invoking the compiler (for full custom preprocessing). See the preprocessor.cs source file and the preprocessorextension.cs source file for more information.