---
name: property-patterns
description: "MSBuild property definition patterns: conditional defaults, composition/concatenation, path normalization, trailing-slash handling, TFM detection helpers, and evaluation order. USE FOR: diagnosing and fixing property definition issues and shared-property anti-patterns in .props/.csproj; DefineConstants or NoWarn overwritten instead of appended; unconditional assignments that block project-level overrides; unquoted conditions that fail on empty properties; hardcoded paths that break cross-platform builds; setting overridable defaults; property evaluation order and last-write-wins semantics. DO NOT USE FOR: props vs targets placement (use directory-build-organization), item operations (use item-management), target structure (use target-authoring), general anti-patterns (use msbuild-antipatterns), non-MSBuild build systems."
license: MIT
---
# 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:
```xml
Debug
AnyCPU
true
```
### 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:
```xml
$(DefineConstants);FEATURE_APARTMENT_STATE
$(DefineConstants);FEATURE_APM
true
true
$(DefineConstants);RUNTIME_TYPE_NETCORE
```
Use the outer `Condition` on `PropertyGroup` to avoid repeating the same condition on every property.
> **Warning:** `$(TargetFramework)` is empty in `.props` files for single-targeting projects until the project body is evaluated. Place `TargetFramework`-conditioned property groups in `.targets` files (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:
```xml
$(DefineConstants);MY_FEATURE
$(NoWarn);NU5131;IDE0005
$(FullFrameworkTFM);$(LatestDotNetCoreForMSBuild);netstandard2.0
```
## Path Normalization and Trailing Slashes
```xml
$(OutDir)\
$([MSBuild]::NormalizePath('$(TargetDir)', 'ref', '$(TargetFileName)'))
$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))
```
### Preferred path functions
| Function | Purpose |
|---|---|
| `$([MSBuild]::NormalizePath(...))` | Combine and normalize (cross-platform) |
| `$([System.IO.Path]::Combine(...))` | Combine path segments |
| `$([System.IO.Path]::IsPathRooted(...))` | Check if absolute |
| `HasTrailingSlash(...)` | Check for trailing slash |
| `$([MSBuild]::GetDirectoryNameOfFileAbove(...))` | Walk up directory tree |
| `$(MSBuildThisFileDirectory)` | Directory of current file |
## Target Framework Detection Helpers
```xml
true
true
$(DefineConstants);TEST_ISWINDOWS
```
## Guard Properties
Mark that a file has been imported to prevent double-imports:
```xml
true
```
## Feature Gating by MSBuild Version
```xml
true
```
## Fallback Chains
Set via primary source first, then fall back:
```xml
$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToDotNetFrameworkSdkFile('tlbexp.exe'))
$(_NetFxToolsDir)TlbExp.exe
```
## Last Write Wins — Evaluation Order
MSBuild evaluates properties top-to-bottom. The last assignment wins:
```xml
value1
value2
value3
```
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** (`MY_CONST`) drops all prior constants. Always append with `$(DefineConstants);`.
- **Hardcoded absolute paths** break portability. Use `$(MSBuildThisFileDirectory)` or `$([MSBuild]::NormalizePath(...))`.
- **Missing `Condition` on defaults** makes properties non-overridable. Add `Condition="'$(Prop)' == ''"` for values meant to be defaults.