Conditions
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
A Condition attribute makes an element take effect only when its expression is true. Conditions are how one project
file serves several configurations, target frameworks, operating systems and CI systems, and they are also a common
source of silent mistakes, because a condition that is false for the wrong reason produces no message at all. This page
covers the syntax and its traps with MSBuild 18.9 (.NET SDK 10.0.402).
Operators
| Operator | Meaning |
|---|---|
|
String equality, case-insensitive. Quotes are needed around anything that can be empty or contains spaces. |
|
Numeric comparison of decimal, hexadecimal ( |
|
A file or folder exists (no wildcard expansion). |
|
The string ends with |
|
Logic. |
|
Boolean literals ( |
|
Anything that evaluates to a Boolean string: |
Source: MSBuild conditions.
A demonstration
<Project>
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<OutputPath>out</OutputPath>
<!-- String comparison is case-insensitive. -->
<IsDebug Condition="'$(Configuration)' == 'DEBUG'">true</IsDebug>
<!-- Exists and HasTrailingSlash. -->
<HasJournals Condition="Exists('journals')">true</HasJournals>
<OutputPath Condition="!HasTrailingSlash('$(OutputPath)')">$(OutputPath)/</OutputPath>
<!-- Numeric and version comparisons. -->
<NumericOk Condition="10 > 9">true</NumericOk>
<VersionOperator Condition="'1.1' < '1.1.0'">1.1 < 1.1.0 (System.Version)</VersionOperator>
<VersionFunction Condition="$([MSBuild]::VersionLessThan('1.1', '1.1.0'))">1.1 < 1.1.0 (VersionLessThan)</VersionFunction>
<VersionFunction Condition="'$(VersionFunction)' == ''">1.1 == 1.1.0 (VersionLessThan is false)</VersionFunction>
<!-- "true" checks: != 'false' is also true when the property is unset. -->
<StrictCheck Condition="'$(Strict)' == 'true'">on</StrictCheck>
<LooseCheck Condition="'$(Strict)' != 'false'">on</LooseCheck>
</PropertyGroup>
<Choose>
<When Condition="'$(Configuration)' == 'Release'">
<PropertyGroup><Optimize>true</Optimize></PropertyGroup>
</When>
<Otherwise>
<PropertyGroup><Optimize>false</Optimize></PropertyGroup>
</Otherwise>
</Choose>
<Target Name="Show">
<Message Importance="high" Text="IsDebug=$(IsDebug) HasJournals=$(HasJournals) OutputPath=$(OutputPath) NumericOk=$(NumericOk)" />
<Message Importance="high" Text="VersionOperator=$(VersionOperator)" />
<Message Importance="high" Text="VersionFunction=$(VersionFunction)" />
<Message Importance="high" Text="StrictCheck=[$(StrictCheck)] LooseCheck=[$(LooseCheck)] Optimize=$(Optimize)" />
</Target>
<Target Name="Precedence">
<Message Importance="high" Condition="'$(A)' == '1' or '$(B)' == '1' and '$(C)' == '1'" Text="matched" />
</Target>
</Project>
dotnet msbuild cond.proj -t:Show -nologo -v:m
dotnet msbuild cond.proj -t:Show -nologo -v:m -p:Configuration=Release | tail -1
IsDebug=true HasJournals=true OutputPath=out/ NumericOk=true
VersionOperator=1.1 < 1.1.0 (System.Version)
VersionFunction=1.1 == 1.1.0 (VersionLessThan is false)
StrictCheck=[] LooseCheck=[on] Optimize=false
StrictCheck=[] LooseCheck=[on] Optimize=true
Three lines deserve a second look:
-
'$(Configuration)' == 'DEBUG'matchedDebug: comparisons ignore case. -
Versions. The relational operators parse four-part versions with
System.Version, for which1.1is older than1.1.0— a missing part is-1, not0. The$([MSBuild]::Version…)functions use SemVer-compatible rules, for which they are equal. Use the functions for anything that came from a version string. -
Booleans. MSBuild has no Boolean type.
'$(Strict)' != 'false'is true whenStrictis unset, soLooseCheckturned on without anyone asking. Test for the value that enables a feature (== 'true').
Three errors and a warning
Precedence. Mixing and and or without parentheses is evaluated correctly (and first) but warned about,
because older MSBuild versions got it wrong:
dotnet msbuild cond.proj -t:Precedence -nologo -v:m -p:A=1
~/cond/cond.proj(37,32): warning MSB4130: The condition "'$(A)' == '1' or '$(B)' == '1' and '$(C)' == '1'" may have been evaluated incorrectly in an earlier version of MSBuild. Please verify that the order of the AND and OR clauses is written as intended. To avoid this warning, add parentheses to make the evaluation order explicit.
matched
Quoting. A value with a space must be quoted, or the parser stops at the second word:
<Ok Condition="$(Title) == Trial Balance">true</Ok>
~/cond/quote.proj(4,9): error MSB4092: An unexpected token "Balance" was found at character position 19 in condition "$(Title) == Trial Balance".
Quote both sides as a habit: '$(Title)' == 'Trial Balance'. An empty property then compares as '' instead of
disappearing from the expression.
Items in the wrong place. Item lists (@) and metadata (%) are expanded in conditions only where items exist:
in item groups and in targets. A property condition outside a target runs before any item:
<PropertyGroup>
<HasAny Condition="'@(Journal)' != ''">true</HasAny>
</PropertyGroup>
~/cond/itemcond.proj(6,13): error MSB4099: A reference to an item list at position 2 is not allowed in this condition "'@(Journal)' != ''".
Move the check into a target, or into an item group (<HasJournals Include="x" Condition="'@(Journal)' != ''" />).
Undefined properties, by contrast, never error: they expand to an empty string, so a misspelled property name in a
condition makes it silently false (or, with !=, silently true).
Where conditions are allowed
Condition is accepted on Import, ImportGroup, Item (and ItemGroup, ItemDefinitionGroup, item metadata),
Property and PropertyGroup, Target, task elements, Output, OnError, UsingTask and When. It is not
accepted on Project, Choose, Otherwise or ProjectExtensions.
A target’s Condition is evaluated when the target is about to run. When it is false the target is skipped and its
DependsOnTargets are not run, but targets that name it in BeforeTargets or AfterTargets still run, and a
skipped target can still run later if another target requests it and the condition has become true
(Targets and Build Order).
Choose, When and Otherwise
Choose selects the first When whose condition is true, or Otherwise. Each branch may contain PropertyGroup,
ItemGroup and nested Choose elements. It reads better than repeating a condition on many properties, but it is
evaluated like any other property and item group: properties in a Choose take part in the property pass, items in the
item pass. It cannot contain targets.
Source: MSBuild conditional constructs.
Common mistakes
-
Unquoted operands —
MSB4092, or an empty property vanishing from the expression. -
!= 'false'as a "true" test — true when the property is unset. -
Version strings compared with
<—1.1vs1.1.0,17.10vs17.9as four-part versions; use theVersion…functions. -
@(…)in property conditions outside targets —MSB4099. -
Conditions on
$(TargetFramework)inDirectory.Build.props— empty there except in multi-target inner builds (Repository-Wide Configuration). -
Case-sensitive expectations —
==ignores case.