A Condition attribute makes an MSBuild element take part only when an
expression is true. It is a small language of its own: strings and numbers compared with
== and friends, combined with and, or and
!, with two functions. MSBuild reads it with a scanner and a recursive-descent
parser (Scanner.cs and Parser.cs in its source); this page
states what they accept.
Where a condition may be written
On every element that does something: PropertyGroup, each property,
ItemGroup, each item and each item metadata element,
ItemDefinitionGroup, Target, each task,
Output, OnError, Import,
ImportGroup, UsingTask and When (where it is
required: it is how a Choose picks its branch). Choose and
Otherwise take none. The attribute on Project itself is not
checked by MSBuild.
A condition is evaluated while the file is evaluated: properties and items it reads are those defined earlier in the evaluation order, not those further down the file.
Grammar
condition = or-expr
or-expr = and-expr { "or" and-expr }
and-expr = relational { "and" relational }
relational = factor [ ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) factor ]
factor = argument
| function "(" [ argument { "," argument } ] ")"
| "(" or-expr ")"
| "!" factor
argument = quoted-string | number | word | property | metadata | item-list
and binds tighter than or, and both are left-associative. A
relational operator takes one operand on each side and cannot be chained:
a == b == c is an error. and and or are written in
any case (AND, Or) and, as MSBuild’s own comment notes, need no
spaces around them: '$(A)'==''and '$(B)'=='' is valid.
Tokens
| Token | Written as | Notes |
|---|---|---|
| Quoted string | 'text' | Single quotes only; there is no way to put a quote inside one. It may hold $(…), @(…), %(…) and %XX escapes, which are expanded first. An @(…) inside a string is skipped as a whole, so it may contain quotes of its own (@(A, '_')). |
| Number | 12, -3.5, 1.2.3, 0x1F | Decimal with optional sign and several dots (a version), or hexadecimal with 0x. |
| Word | Debug, true | An unquoted run of letters, digits and _ starting with a letter or _: Debug, x64. A -, ., :, / or \ ends it and is an error (Foo-bar == a fails; quote it). and and or are operators, not words; a word followed by ( is a function name. |
| Property | $(Name) | Any property expression, including property functions. Its parentheses nest. |
| Metadata | %(Name), %(Item.Name) | Item metadata. Where it is allowed depends on the element: see item lists and metadata. |
| Item list | @(Name), @(Name->'%(Filename)') | Quotes inside toggle, so a replacement with a quote-bearing separator is skipped as a unit. Where it is allowed depends on the element: see item lists and metadata. |
| Punctuation | ( ) , | Grouping, and the argument separator of functions. |
| Operators | == != < <= > >= ! and or | A single = is an error. In XML, write < and > as < and > in an attribute value. |
Operators
| Operator | Meaning | Operands |
|---|---|---|
== | Equal | Strings compare without regard to case. When both sides are numbers (decimal or 0x hexadecimal) or both are versions, they compare as numbers or versions, so '1.0' == '1.00' is true. |
!= | Not equal | As ==. |
< <= > >= | Order | Numbers or versions only. A string that is neither is an error when the condition is evaluated (MSB4086): 'a' < 'b' fails, $(MSBuildToolsVersion) >= 15.0 works, and so does '1.2.3' < '1.10.0' (versions compare part by part). |
! | Not | Applies to the factor after it: !Exists('x'), !($(A) == 1). |
and | Both true | Each side is a boolean (see below). |
or | Either true |
What counts as a boolean
The condition as a whole, and each operand of and, or and
!, must evaluate to a boolean: the words true,
false, on, off, yes and
no, in any case, with or without quotes. Anything else is an error when
the condition is evaluated: Condition="x" reports that the condition
evaluates to x instead of a boolean (MSB4113), and so does
Condition="$(Undefined)", which evaluates to the empty string.
Item lists and metadata
Inside a target, a condition may read item lists and item metadata. Outside a target (during evaluation, before any item list is final) MSBuild refuses them in some conditions. Each row was found by running the tool.
| Element outside a target | Item list @(…) | Item metadata %(…) |
|---|---|---|
PropertyGroup, a property | refused, MSB4099 | refused: custom MSB4191, built-in MSB4190 |
Import, ImportGroup, When | refused, MSB4099 | not tested |
ItemGroup | allowed | refused: custom MSB4191, built-in MSB4190 |
| An item | allowed | custom metadata refused, MSB4191 |
| An item metadata element | allowed | allowed (this is how Update items batch on metadata) |
Target, and everything inside a target | allowed | allowed |
Functions
Two functions exist; the name is matched without regard to case, takes exactly one argument, and any other name is an error.
| Function | True when |
|---|---|
Exists('path') | The file or folder exists. A relative path is relative to the folder of the project file (the file being evaluated for an import). |
HasTrailingSlash('text') | The string ends in / or \. |
Property functions are different: they are written inside $(…) and have
their own page, expressions and functions;
$([MSBuild]::IsOSPlatform('Windows')) and
$([System.IO.File]::Exists('a')) are expressions that give a string (True or
False) which the condition then compares.
Examples
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<DefineConstants>$(DefineConstants);TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net48' and Exists('legacy')">
<Compile Include="legacy\**\*.cs" />
</ItemGroup>
<Import Project="local.props" Condition="Exists('local.props')" />
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Linux')) or '$(CI)' != ''">
<Deterministic>true</Deterministic>
</PropertyGroup>
<Target Name="Report" Condition="'@(Compile)' != '' and $(MSBuildToolsVersion) >= 15.0">
<Message Text="Compiling @(Compile->Count()) files" />
</Target>
Errors
A condition that cannot be parsed makes MSBuild refuse the file, with an error that gives the position (counted from 1) inside the condition. These are the errors of the scanner and parser, each checked against the tool; the explorer reports the same ones, with the position.
| Code | Condition | Meaning |
|---|---|---|
MSB4092 | A ==, == A, A B, (A, A), !, A and, A == B == C | An unexpected token: the parser found something the grammar does not allow there, or the end of the condition too soon. |
MSB4105 | A = B | An unexpected character: a single =, where == was meant. |
MSB4090 | #, %A | An unexpected character that starts no token. |
MSB4101 | 'a | A quoted string with no closing quote. |
MSB4109 | $(A | A property reference with no closing parenthesis. |
MSB4110 | $ (A) | A $ not followed by (. |
MSB4106 | @(A | An item list with no closing parenthesis. |
MSB4091 | Foo('a') | A call to a function other than Exists and HasTrailingSlash. |
MSB4089 | Exists(), Exists('a','b') | The wrong number of arguments. |
MSB4086 | 'a' < 'b' | A numeric comparison of something that is not a number. |
MSB4113 | x, $(Empty) | The condition evaluates to something that is not a boolean. |
MSB4099, MSB4190, MSB4191 | '@(A)' != '' on a PropertyGroup outside a target | An item list or item metadata where the table above refuses it. |
Expressions that are malformed inside a value rather than a condition are not
errors: $(, @( and $() in a property value or an
item’s Include are left as plain text.
In the explorer
Open a project in the explorer and its Conditions tab lists every condition with its
line, the element it is on, how the parser read it (for example
((a == 1) or ((b == 2) and (c == 3)))) and whether it parses. In the
Structure tree a condition is taken apart: each operator, quoted string, function and
expression is a node of its own, and a syntax error is marked on the part of the
condition where MSBuild would stop.
Not described
Known, not yet covered
- Reference: the exact conversion rules for comparing mixed numbers, versions and strings (for example a value with trailing text against a number) are not listed beyond the cases above; they are in
ConditionEvaluator.csand the expression nodes of MSBuild’s source.
Sources
MSBuild source (dotnet/msbuild,
src/Build/Evaluation/Conditionals/Scanner.cs and Parser.cs):
tokens, grammar, function names. The error codes and their conditions were found by
running dotnet msbuild of the .NET SDK on each case. Microsoft Learn,
MSBuild
conditions.