/msbuild-antipatterns
Detect and fix MSBuild anti-patterns in project and build files. USE WHEN asked to review, audit, lint, clean up, or code-review a .csproj/.vbproj/.fsproj/.props/.targets/.proj (or Directory.Build.props/.targets) file, when asked 'is this project file correct?' or 'what's wrong
$ npx -y skills add dotnet/skills --skill msbuild-antipatterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/msbuild-antipatterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
Detect and fix MSBuild anti-patterns in project and build files. USE WHEN asked to review, audit, lint, clean up, or code-review a .csproj/.vbproj/.fsproj/.props/.targets/.proj (or Directory.Build.props/.targets) file, when asked 'is this project file correct?' or 'what's wrong
SKILL.md
msbuild-antipatterns.SKILL.mdname: msbuild-antipatterns
description: "Detect and fix MSBuild anti-patterns in project and build files. USE WHEN asked to review, audit, lint, clean up, or code-review a .csproj/.vbproj/.fsproj/.props/.targets/.proj (or Directory.Build.props/.targets) file, when asked 'is this project file correct?' or 'what's wrong with my build file?', or when hunting subtle build bugs caused by how a project is authored. Each anti-pattern has a symptom and a concrete BAD→GOOD fix. DO NOT USE FOR: non-MSBuild build systems (npm, Maven, CMake), or migrating a project to SDK-style (use msbuild-modernization)."
license: MIT
MSBuild Anti-Pattern Catalog
A numbered catalog of common MSBuild anti-patterns. Each entry follows the format:
- **Smell**: What to look for
- **Why it's bad**: Impact on builds, maintainability, or correctness
- **Fix**: Concrete transformation
Use this catalog when scanning project files for improvements.
---
AP-01: `<Exec>` for Operations That Have Built-in Tasks
**Smell**: `<Exec Command="mkdir ..." />`, `<Exec Command="copy ..." />`, `<Exec Command="del ..." />`
**Why it's bad**: Built-in tasks are cross-platform, support incremental build, emit structured logging, and handle errors consistently. `<Exec>` is opaque to MSBuild.
<!-- BAD -->
<Target Name="PrepareOutput">
<Exec Command="mkdir $(OutputPath)logs" />
<Exec Command="copy config.json $(OutputPath)" />
<Exec Command="del $(IntermediateOutputPath)*.tmp" />
</Target>
<!-- GOOD -->
<Target Name="PrepareOutput">
<MakeDir Directories="$(OutputPath)logs" />
<Copy SourceFiles="config.json" DestinationFolder="$(OutputPath)" />
<Delete Files="@(TempFiles)" />
</Target>
**Built-in task alternatives:**
| Shell Command | MSBuild Task | |--------------|--------------| | `mkdir` | `<MakeDir>` | | `copy` / `cp` | `<Copy>` | | `del` / `rm` | `<Delete>` | | `move` / `mv` | `<Move>` | | `echo text > file` | `<WriteLinesToFile>` | | `touch` | `<Touch>` | | `xcopy /s` | `<Copy>` with item globs |
---
AP-02: Unquoted Condition Expressions
**Smell**: `Condition="$(Foo) == Bar"` — either side of a comparison is unquoted.
**Why it's bad**: If the property is empty or contains spaces/special characters, the condition evaluates incorrectly or throws a parse error. MSBuild requires single-quoted strings for reliable comparisons.
<!-- BAD -->
<PropertyGroup Condition="$(Configuration) == Release">
<Optimize>true</Optimize>
</PropertyGroup>
<!-- GOOD -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
</PropertyGroup>
**Rule**: Always quote **both** sides of `==` and `!=` comparisons with single quotes.
---
AP-03: Hardcoded Absolute Paths
**Smell**: Paths like `C:\tools\`, `D:\packages\`, `/usr/local/bin/` in project files.
**Why it's bad**: Breaks on other machines, CI environments, and other operating systems. Not relocatable.
<!-- BAD -->
<PropertyGroup>
<ToolPath>C:\tools\mytool\mytool.exe</ToolPath>
</PropertyGroup>
<Import Project="C:\repos\shared\common.props" />
<!-- GOOD -->
<PropertyGroup>
<ToolPath>$(MSBuildThisFileDirectory)tools\mytool\mytool.exe</ToolPath>
</PropertyGroup>
<Import Project="$(RepoRoot)eng\common.props" />
**Preferred path properties:**
| Property | Meaning | |----------|---------| | `$(MSBuildThisFileDirectory)` | Directory of the current .props/.targets file | | `$(MSBuildProjectDirectory)` | Directory of the .csproj | | `$([MSBuild]::GetDirectoryNameOfFileAbove(...))` | Walk up to find a marker file | | `$([MSBuild]::NormalizePath(...))` | Combine and normalize path segments |
---
AP-04: Restating SDK Defaults
**Smell**: Properties set to values that the .NET SDK already provides by default.
**Why it's bad**: Adds noise, hides intentional overrides, and makes it harder to identify what's actually customized. When defaults change in newer SDKs, the redundant properties may silently pin old behavior.
<!-- BAD: All of these are already the default -->
<PropertyGroup>
<OutputType>Library</OutputType>
<EnableDefaultItems>true</EnableDefaultItems>
<EnableDefaultCompileItems>true</EnableDefaultCompileItems>
<RootNamespace>MyLib</RootNamespace> <!-- matches project name -->
<AssemblyName>MyLib</AssemblyName> <!-- matches project name -->
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<!-- GOOD: Only non-default values -->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
---
AP-05: Manual File Listing in SDK-Style Projects
**Smell**: `<Compile Include="File1.cs" />`, `<Compile Include="File2.cs" />` in SDK-style projects.
**Why it's bad**: SDK-style projects automatically glob `**/*.cs` (and other file types). Explicit listing is redundant, creates merge conflicts, and new files may be accidentally missed if not added to the list.
<!-- BAD -->
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Services\MyService.cs" />
<Compile Include="Models\User.cs" />
</ItemGroup>
<!-- GOOD: Remove entirely — SDK includes all .cs files by default.
Only use Remove/Exclude when you need to opt out: -->
<ItemGroup>
<Compile Remove="LegacyCode\**" />
</ItemGroup>**Exception**: Non-SDK-style (legacy) projects require explicit file includes. If migrating, see `msbuild-modernization` skill.
**Exception (F# / `.fsproj`)**: F# compilation is order-dependent — the compiler processes `<Compile Include>` items sequentially and a file can only reference types/modules declared in files listed above it. `.fsproj` files must therefore list every source file explicitly, in dependency order (utility/leaf modules at the top, the entry point such as `Program.fs` at the bottom). If a `.fsi` signature file is used, it must appear **immediately before** its companion `.fs` implementation file.
---
AP-06: Using `<Reference>` with HintPath for NuGet Packages
Read more
name: msbuild-antipatterns description: "Detect and fix MSBuild anti-patterns in project and build files. USE WHEN asked to review, audit, lint, clean up, or code-review a .csproj/.vbproj/.fsproj/.props/.targets/.proj (or Directory.Build.props/.targets) file, when asked 'is this project file correct?' or 'what's wrong with my build file?', or when hunting subtle build bugs caused by how a project is authored. Each anti-pattern has a symptom and a concrete BAD→GOOD fix. DO NOT USE FOR: non-MSBuild build systems (npm, Maven, CMake), or migrating a project to SDK-style (use msbuild-modernization)." license: MIT
MSBuild Anti-Pattern Catalog
A numbered catalog of common MSBuild anti-patterns. Each entry follows the format:
- **Smell**: What to look for
- **Why it's bad**: Impact on builds, maintainability, or correctness
- **Fix**: Concrete transformation
Use this catalog when scanning project files for improvements.
---
AP-01: `<Exec>` for Operations That Have Built-in Tasks
**Smell**: `<Exec Command="mkdir ..." />`, `<Exec Command="copy ..." />`, `<Exec Command="del ..." />`
**Why it's bad**: Built-in tasks are cross-platform, support incremental build, emit structured logging, and handle errors consistently. `<Exec>` is opaque to MSBuild.
<!-- BAD --> <Target Name="PrepareOutput"> <Exec Command="mkdir $(OutputPath)logs" /> <Exec Command="copy config.json $(OutputPath)" /> <Exec Command="del $(IntermediateOutputPath)*.tmp" /> </Target> <!-- GOOD --> <Target Name="PrepareOutput"> <MakeDir Directories="$(OutputPath)logs" /> <Copy SourceFiles="config.json" DestinationFolder="$(OutputPath)" /> <Delete Files="@(TempFiles)" /> </Target>
**Built-in task alternatives:**
| Shell Command | MSBuild Task | |--------------|--------------| | `mkdir` | `<MakeDir>` | | `copy` / `cp` | `<Copy>` | | `del` / `rm` | `<Delete>` | | `move` / `mv` | `<Move>` | | `echo text > file` | `<WriteLinesToFile>` | | `touch` | `<Touch>` | | `xcopy /s` | `<Copy>` with item globs |
---
AP-02: Unquoted Condition Expressions
**Smell**: `Condition="$(Foo) == Bar"` — either side of a comparison is unquoted.
**Why it's bad**: If the property is empty or contains spaces/special characters, the condition evaluates incorrectly or throws a parse error. MSBuild requires single-quoted strings for reliable comparisons.
<!-- BAD --> <PropertyGroup Condition="$(Configuration) == Release"> <Optimize>true</Optimize> </PropertyGroup> <!-- GOOD --> <PropertyGroup Condition="'$(Configuration)' == 'Release'"> <Optimize>true</Optimize> </PropertyGroup>
**Rule**: Always quote **both** sides of `==` and `!=` comparisons with single quotes.
---
AP-03: Hardcoded Absolute Paths
**Smell**: Paths like `C:\tools\`, `D:\packages\`, `/usr/local/bin/` in project files.
**Why it's bad**: Breaks on other machines, CI environments, and other operating systems. Not relocatable.
<!-- BAD --> <PropertyGroup> <ToolPath>C:\tools\mytool\mytool.exe</ToolPath> </PropertyGroup> <Import Project="C:\repos\shared\common.props" /> <!-- GOOD --> <PropertyGroup> <ToolPath>$(MSBuildThisFileDirectory)tools\mytool\mytool.exe</ToolPath> </PropertyGroup> <Import Project="$(RepoRoot)eng\common.props" />
**Preferred path properties:**
| Property | Meaning | |----------|---------| | `$(MSBuildThisFileDirectory)` | Directory of the current .props/.targets file | | `$(MSBuildProjectDirectory)` | Directory of the .csproj | | `$([MSBuild]::GetDirectoryNameOfFileAbove(...))` | Walk up to find a marker file | | `$([MSBuild]::NormalizePath(...))` | Combine and normalize path segments |
---
AP-04: Restating SDK Defaults
**Smell**: Properties set to values that the .NET SDK already provides by default.
**Why it's bad**: Adds noise, hides intentional overrides, and makes it harder to identify what's actually customized. When defaults change in newer SDKs, the redundant properties may silently pin old behavior.
<!-- BAD: All of these are already the default --> <PropertyGroup> <OutputType>Library</OutputType> <EnableDefaultItems>true</EnableDefaultItems> <EnableDefaultCompileItems>true</EnableDefaultCompileItems> <RootNamespace>MyLib</RootNamespace> <!-- matches project name --> <AssemblyName>MyLib</AssemblyName> <!-- matches project name --> <AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath> </PropertyGroup> <!-- GOOD: Only non-default values --> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> </PropertyGroup>
---
AP-05: Manual File Listing in SDK-Style Projects
**Smell**: `<Compile Include="File1.cs" />`, `<Compile Include="File2.cs" />` in SDK-style projects.
**Why it's bad**: SDK-style projects automatically glob `**/*.cs` (and other file types). Explicit listing is redundant, creates merge conflicts, and new files may be accidentally missed if not added to the list.
<!-- BAD -->
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Services\MyService.cs" />
<Compile Include="Models\User.cs" />
</ItemGroup>
<!-- GOOD: Remove entirely — SDK includes all .cs files by default.
Only use Remove/Exclude when you need to opt out: -->
<ItemGroup>
<Compile Remove="LegacyCode\**" />
</ItemGroup>**Exception**: Non-SDK-style (legacy) projects require explicit file includes. If migrating, see `msbuild-modernization` skill.
**Exception (F# / `.fsproj`)**: F# compilation is order-dependent — the compiler processes `<Compile Include>` items sequentially and a file can only reference types/modules declared in files listed above it. `.fsproj` files must therefore list every source file explicitly, in dependency order (utility/leaf modules at the top, the entry point such as `Program.fs` at the bottom). If a `.fsi` signature file is used, it must appear **immediately before** its companion `.fs` implementation file.
---
AP-06: Using `<Reference>` with HintPath for NuGet Packages
This repository contains the .NET team's curated set of core skills and custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io. 📊 Dashboard - Accuracy and efficiency scoring trends for contained plugins (
Repo: dotnet/skills
Other skills on dotnet-skills.
- /csharp-scripts
Run file-based C# apps with the .NET CLI when the user explicitly wants C#/.NET code without creating a project. Use for C# language/API experiments, one-file C# apps, small multi-file C# apps composed with `#:include`/`#:exclude`, or C# file-based apps linked with `#:ref`. Do
Open skill - /dotnet-pinvoke
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime, SafeHandle, and cross-platform patterns. USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging
Open skill - /nuget-trusted-publishing
Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish, migrate from NuGet API key, NuGet/login, secure NuGet publishing. DO NOT USE FOR: publishing to
Open skill - /technology-selection
Guides technology selection and implementation of AI and ML features in .NET 8+ applications using ML.NET, Microsoft.Extensions.AI (MEAI), Microsoft Agent Framework (MAF), GitHub Copilot SDK, ONNX Runtime, and OllamaSharp. Covers the full spectrum from classic ML through modern
Open skill - /configuring-opentelemetry-dotnet
Configure OpenTelemetry distributed tracing, metrics, and logging in ASP.NET Core using the .NET OpenTelemetry SDK. Use when adding observability, setting up OTLP exporters, creating custom metrics/spans, or troubleshooting distributed trace correlation.
Open skill - /convert-blazor-server-to-webapp
Guides conversion of a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. USE FOR: migrating apps that use AddServerSideBlazor and MapBlazorHub to the AddRazorComponents/MapRazorComponents model, converting _Host.cshtml to an App.razor root component, replacing
Open skill

