--- 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.