{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/property-patterns",
  "version": "1.0.0",
  "name": "property-patterns",
  "description": "MSBuild property definition patterns: conditional defaults, composition/concatenation, path normalization, trailing slash handling, TFM detection helpers, and property evaluation order. Only activate in MSBuild/.NET build context. USE FOR: diagnosing and fixing MSBuild property definition issues in .props or .csproj files, reviewing and fixing shared property configuration anti-patterns, fixing DefineConstants or NoWarn being overwritten instead of appended, fixing unconditional property assignments that prevent project-level overrides, fixing unquoted conditions that fail when properties are empty, fixing hardcoded paths that break cross-platform builds, setting property defaults that can be overridden, understanding 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...",
  "system_prompt_fragment": "# MSBuild Property Patterns\n\nCanonical property definition and manipulation patterns from the MSBuild repository.\n\n## Conditional Defaults — The Foundational Pattern\n\nSet a property **only if not already set**, allowing callers to override:\n\n```xml\n<PropertyGroup>\n  <Configuration Condition=\"'$(Configuration)' == ''\">Debug</Configuration>\n  <Platform Condition=\"'$(Platform)' == ''\">AnyCPU</Platform>\n  <BuildInParallel Condition=\"'$(BuildInParallel)' == ''\">true</BuildInParallel>\n</PropertyGroup>\n```\n\n### Rules\n\n- Always quote both sides: `'$(Prop)' == ''`\n- In `.props`: creates overridable defaults. In `.targets`: creates fallbacks.\n- Properties without the condition **cannot be overridden** by earlier imports.\n\n## Nested Conditional Groups\n\nGroup related properties under a shared condition:\n\n```xml\n<PropertyGroup Condition=\"$(TargetFramework.StartsWith('net4'))\">\n  <DefineConstants>$(DefineConstants);FEATURE_APARTMENT_STATE</DefineConstants>\n  <DefineConstants>$(DefineConstants);FEATURE_APM</DefineConstants>\n  <FeatureAppDomain>true</FeatureAppDomain>\n</PropertyGroup>\n\n<PropertyGroup Condition=\"'$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))' == '.NETCoreApp'\">\n  <NetCoreBuild>true</NetCoreBuild>\n  <DefineConstants>$(DefineConstants);RUNTIME_TYPE_NETCORE</DefineConstants>\n</PropertyGroup>\n```\n\nUse the outer `Condition` on `PropertyGroup` to avoid repeating the same condition on every property.\n\n> **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.\n\n## Composition — Semicolon Concatenation\n\nProperties that hold lists use semicolons. Always include the existing value when appending:\n\n```xml\n<PropertyGroup>\n  <DefineConstants>$(DefineConstants);MY_FEATURE</DefineConstants>\n  <NoWarn>$(NoWarn);NU5131;IDE0005</NoWarn>\n  <LibraryTargetFrameworks>$(FullFrameworkTFM);$(LatestDotNetCoreForMSBuild);netstandard2.0</LibraryTargetFrameworks>\n</PropertyGroup>\n```\n\n## Path Normalization and Trailing Slashes\n\n```xml\n<!-- Ensure trailing slash on directories -->\n<PropertyGroup>\n  <OutDir Condition=\"'$(OutDir)' != '' and !HasTrailingSlash('$(OutDir)')\">$(OutDir)\\</OutDir>\n</PropertyGroup>\n\n<!-- Normalize paths for cross-platform -->\n<PropertyGroup>\n  <TargetRefPath>$([MSBuild]::NormalizePath('$(TargetDir)', 'ref', '$(TargetFileName)'))</TargetRefPath>\n</PropertyGroup>\n\n<!-- Make relative path absolute -->\n<PropertyGroup>\n  <MSBuildProjectExtensionsPath\n      Condition=\"'$([System.IO.Path]::IsPathRooted('$(MSBuildProjectExtensionsPath)'))' == 'false'\">\n    $([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))\n  </MSBuildProjectExtensionsPath>\n</PropertyGroup>\n```\n\n### Preferred path functions\n\n| Function | Purpose |\n|---|---|\n| `$([MSBuild]::NormalizePath(...))` | Combine and normalize (cross-platform) |\n| `$([System.IO.Path]::Combine(...))` | Combine path segments |\n| `$([System.IO.Path]::IsPathRooted(...))` | Check if absolute |\n| `HasTrailingSlash(...)` | Check for trailing slash |\n| `$([MSBuild]::GetDirectoryNameOfFileAbove(...))` | Walk up directory tree |\n| `$(MSBuildThisFileDirectory)` | Directory of current file |\n\n## Target Framework Detection Helpers\n\n```xml\n<!-- Get TFM identifier -->\n<PropertyGroup Condition=\"'$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))' == '.NETCoreApp'\">\n  <NetCoreBuild>true</NetCoreBuild>\n</PropertyGroup>\n\n<!-- Check TFM compatibility -->\n<PropertyGroup Condition=\"$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net472'))\">\n  <UseFrozenVersions>true</UseFrozenVersions>\n</PropertyGroup>\n\n<!-- OS detection -->\n<PropertyGroup Condition=\"$([MSBuild]::IsOSPlatform('windows'))\">\n  <DefineConstants>$(DefineConstants);TEST_ISWINDOWS</DefineConstants>\n</PropertyGroup>\n```\n\n## Guard Properties\n\nMark that a file has been imported to prevent double-imports:\n\n```xml\n<!-- At the end of MySDK.props -->\n<PropertyGroup>\n  <MySDKPropsImported>true</MySDKPropsImported>\n</PropertyGroup>\n\n<!-- At the top of MySDK.targets -->\n<Import Project=\"MySDK.props\" Condition=\"'$(MySDKPropsImported)' != 'true'\" />\n```\n\n## Feature Gating by MSBuild Version\n\n```xml\n<PropertyGroup Condition=\"$([MSBuild]::AreFeaturesEnabled('17.10'))\">\n  <UseNewBehavior>true</UseNewBehavior>\n</PropertyGroup>\n```\n\n## Fallback Chains\n\nSet via primary source first, then fall back:\n\n```xml\n<PropertyGroup>\n  <TlbExpPath>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetPathToDotNetFrameworkSdkFile('tlbexp.exe'))</TlbExpPath>\n  <TlbExpPath Condition=\"'$(TlbExpPath)' == ''\">$(_NetFxToolsDir)TlbExp.exe</TlbExpPath>\n</PropertyGroup>\n```\n\n## Last Write Wins — Evaluation Order\n\nMSBuild evaluates properties top-to-bottom. The last assignment wins:\n\n```xml\n<!-- File 1 (imported first) -->\n<MyProp>value1</MyProp>        <!-- set to value1 -->\n<!-- File 2 (imported second) -->\n<MyProp>value2</MyProp>        <!-- overwritten to value2 -->\n<!-- File 3 (imported third) -->\n<MyProp Condition=\"'$(MyProp)' == ''\">value3</MyProp>  <!-- NOT set — already value2 -->\n```\n\nProperties in `.targets` (imported late) override properties in `.props` (imported early) and the project file.\n\n## Common Pitfalls\n\n- **Unquoted conditions** (`$(X)==true`) fail when the property is empty. Always quote both sides.\n- **Overwriting DefineConstants** (`<DefineConstants>MY_CONST</DefineConstants>`) drops all prior constants. Always append with `$(DefineConstants);`.\n- **Hardcoded absolute paths** break portability. Use `$(MSBuildThisFileDirectory)` or `$([MSBuild]::NormalizePath(...))`.\n- **Missing `Condition` on defaults** makes properties non-overridable. Add `Condition=\"'$(Prop)' == ''\"` for values meant to be defaults.",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/property-patterns"
  ],
  "tags": [
    "dotnet-msbuild",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-msbuild/skills/property-patterns/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-msbuild/skills/property-patterns/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}