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…
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.
/msbuild-antipatternsContext 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
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
A numbered catalog of common MSBuild anti-patterns. Each entry follows the format:
Use this catalog when scanning project files for improvements.
---
**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 |
---
**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.
---
**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 |
---
**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>
---
**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.
---
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.
Repo: dotnet/skills
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…
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime,…
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…
Design, implement, optimize, and review SIMD code in .NET. USE FOR: vectorizing scalar loops with TensorPrimitives, Vector64/128/256/512, or platform hardware…
Guides technology selection and implementation of AI and ML features in .NET 8+ applications using ML.NET, Microsoft.Extensions.AI (MEAI), Microsoft Agent…
Configure OpenTelemetry distributed tracing, metrics, and logging in ASP.NET Core using the .NET OpenTelemetry SDK. Use when adding observability, setting up…