MSBuild Property Patterns
Canonical property definition and manipulation patterns from the MSBuild repository.
Conditional Defaults — The Foundational Pattern
Set a property only if not already set, allowing callers to override:
Rules
- Always quote both sides:
'$(Prop)' == '' - In
.props: creates overridable defaults. In.targets: creates fallbacks. - Properties without the condition cannot be overridden by earlier imports.
Nested Conditional Groups
Group related properties under a shared condition:
Use the outer Condition on PropertyGroup to avoid repeating the same condition on every property.
Warning:
$(TargetFramework)is empty in.propsfiles for single-targeting projects until the project body is evaluated. PlaceTargetFramework-conditioned property groups in.targetsfiles (or the project file itself), where the value is always available.
Composition — Semicolon Concatenation
Properties that hold lists use semicolons. Always include the existing value when appending:
Path Normalization and Trailing Slashes
Preferred path functions
Target Framework Detection Helpers
Guard Properties
Mark that a file has been imported to prevent double-imports:
Feature Gating by MSBuild Version
Fallback Chains
Set via primary source first, then fall back:
Last Write Wins — Evaluation Order
MSBuild evaluates properties top-to-bottom. The last assignment wins:
Properties in .targets (imported late) override properties in .props (imported early) and the project file.
Common Pitfalls
- Unquoted conditions (
$(X)==true) fail when the property is empty. Always quote both sides. - Overwriting DefineConstants (
<DefineConstants>MY_CONST</DefineConstants>) drops all prior constants. Always append with$(DefineConstants);. - Hardcoded absolute paths break portability. Use
$(MSBuildThisFileDirectory)or$([MSBuild]::NormalizePath(...)). - Missing
Conditionon defaults makes properties non-overridable. AddCondition="'$(Prop)' == ''"for values meant to be defaults.


