SkillAgentSearch skills...

property-patterns

MSBuild property definition patterns: conditional defaults, composition/concatenation, path normalization, trailing-slash handling, TFM detection helpers, and evaluation order.

Install / Use

npx skills add dotnet/skills --skill property-patterns

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

87/100

Supported Platforms

Universal

Tags

Our assessment of property-patterns

property-patterns scores 87/100 on our quality scale, 870th of 2,398 Development & Engineering skills we index (top 37%).

Its SKILL.md is 6.6 KB long, well organised into 13 sections with 9 code examples: a thorough specification that gives an agent plenty to work with.

With 5,471 GitHub stars, it is one of the more widely adopted skills in the catalogue.

Substance
29/30
Structure
20/20
Description
15/15
Adoption
16/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated 2 days ago, so property-patterns is actively maintained.
  • It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
  • Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

property-patterns compared with similar skills

All 4 of these similar skills score higher than property-patterns; compare them before choosing.

SkillScoreStarsUpdatedFormat
property-patterns (this skill)by dotnet875.5k2d agoSKILL.md
ai-job-searchby MadsLorentzen10044.0k5d agoCLAUDE.md
claude-howtoby luongnv8910041.7ktodayCLAUDE.md
algorithmic-artby anthropics100177.9k4d agoSKILL.md
pptxby anthropics100177.9k4d agoSKILL.md

Frequently asked questions

How do I install property-patterns?
Run npx skills add dotnet/skills --skill property-patterns. The install tabs above show the steps for each supported agent.
Which AI agents does property-patterns work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is property-patterns safe to use?
It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is property-patterns still maintained?
The repository was last updated 2 days ago, so property-patterns is actively maintained.

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:

<PropertyGroup>
  <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
  <Platform Condition="'$(Platform)' == ''">AnyCPU</Platform>
  <BuildInParallel Condition="'$(BuildInParallel)' == ''">true</BuildInParallel>
</PropertyGroup>

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:

<PropertyGroup Condition="$(TargetFramework.StartsWith('net4'))">
  <DefineConstants>$(DefineConstants);FEATURE_APARTMENT_STATE</DefineConstants>
  <DefineConstants>$(DefineConstants);FEATURE_APM</DefineConstants>
  <FeatureAppDomain>true</FeatureAppDomain>
</PropertyGroup>

<PropertyGroup Condition="'$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))' == '.NETCoreApp'">
  <NetCoreBuild>true</NetCoreBuild>
  <DefineConstants>$(DefineConstants);RUNTIME_TYPE_NETCORE</DefineConstants>
</PropertyGroup>

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:

<PropertyGroup>
  <DefineConstants>$(DefineConstants);MY_FEATURE</DefineConstants>
  <NoWarn>$(NoWarn);NU5131;IDE0005</NoWarn>
  <LibraryTargetFrameworks>$(FullFrameworkTFM);$(LatestDotNetCoreForMSBuild);netstandard2.0</LibraryTargetFrameworks>
</PropertyGroup>

Path Normalization and Trailing Slashes

<!-- Ensure trailing slash on directories -->
<PropertyGroup>
  <OutDir Condition="'$(OutDir)' != '' and !HasTrailingSlash('$(OutDir)')">$(OutDir)\</OutDir>
</PropertyGroup>

<!-- Normalize paths for cross-platform -->
<PropertyGroup>
  <TargetRefPath>$([MSBuild]::NormalizePath('$(TargetDir)', 'ref', '$(TargetFileName)'))</TargetRefPath>
</PropertyGroup>

<!-- Make relative path absolute -->
<PropertyGroup>
  <MSBuildProjectExtensionsPath
      Condition="'$([System.IO.Path]::IsPathRooted('$(MSBuildProjectExtensionsPath)'))' == 'false'">
    $([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))
  </MSBuildProjectExtensionsPath>
</PropertyGroup>

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

<!-- Get TFM identifier -->
<PropertyGroup Condition="'$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))' == '.NETCoreApp'">
  <NetCoreBuild>true</NetCoreBuild>
</PropertyGroup>

<!-- Check TFM compatibility -->
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net472'))">
  <UseFrozenVersions>true</UseFrozenVersions>
</PropertyGroup>

<!-- OS detection -->
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('windows'))">
  <DefineConstants>$(DefineConstants);TEST_ISWINDOWS</DefineConstants>
</PropertyGroup>

Guard Properties

Mark that a file has been imported to prevent double-imports:

<!-- At the end of MySDK.props -->
<PropertyGroup>
  <MySDKPropsImported>true</MySDKPropsImported>
</PropertyGroup>

<!-- At the top of MySDK.targets -->
<Import Project="MySDK.props" Condition="'$(MySDKPropsImported)' != 'true'" />

Feature Gating by MSBuild Version

<PropertyGroup Condition="$([MSBuild]::AreFeaturesEnabled('17.10'))">
  <UseNewBehavior>true</UseNewBehavior>
</PropertyGroup>

Fallback Chains

Set via primary source first, then fall back:

<PropertyGroup>
  <TlbExpPath>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToDotNetFrameworkSdkFile('tlbexp.exe'))</TlbExpPath>
  <TlbExpPath Condition="'$(TlbExpPath)' == ''">$(_NetFxToolsDir)TlbExp.exe</TlbExpPath>
</PropertyGroup>

Last Write Wins — Evaluation Order

MSBuild evaluates properties top-to-bottom. The last assignment wins:

<!-- File 1 (imported first) -->
<MyProp>value1</MyProp>        <!-- set to value1 -->
<!-- File 2 (imported second) -->
<MyProp>value2</MyProp>        <!-- overwritten to value2 -->
<!-- File 3 (imported third) -->
<MyProp Condition="'$(MyProp)' == ''">value3</MyProp>  <!-- NOT set — already value2 -->

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 Condition on defaults makes properties non-overridable. Add Condition="'$(Prop)' == ''" for values meant to be defaults.

Related Skills

View on GitHub
GitHub Stars5.5k
CategoryDevelopment
Updated2d ago
Forks418

Languages

C#

Trust signals

100/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

No cautions