--- description: 'Catalog of MSBuild anti-patterns with detection rules and fix recipes. Only activate in MSBuild/.NET build context. USE FOR: reviewing, auditing, or cleaning up .csproj, .vbproj, .fsproj, .props, .targets, or .proj files. Each anti-pattern has a symptom, explanation, and concrete BAD→GOOD transformation. Covers Exec-instead-of-built-in-task, unquoted conditions, hardcoded paths, restating SDK defaults, scattered package versions, and more. DO NOT USE FOR: non-MSBuild build systems (npm, Maven, CMake, etc.), project migration to SDK-style (use msbuild-modernization).' metadata: github-path: plugins/dotnet-msbuild/skills/msbuild-antipatterns github-pinned: v1.0.0 github-ref: refs/tags/v1.0.0 github-repo: https://github.com/dotnet/skills github-tree-sha: b0d1c5220d44eee7df2c6bc04b26ed1284a88835 name: msbuild-antipatterns --- # 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: `` for Operations That Have Built-in Tasks **Smell**: ``, ``, `` **Why it's bad**: Built-in tasks are cross-platform, support incremental build, emit structured logging, and handle errors consistently. `` is opaque to MSBuild. ```xml ``` **Built-in task alternatives:** | Shell Command | MSBuild Task | |--------------|--------------| | `mkdir` | `` | | `copy` / `cp` | `` | | `del` / `rm` | `` | | `move` / `mv` | `` | | `echo text > file` | `` | | `touch` | `` | | `xcopy /s` | `` 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. ```xml true true ``` **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. ```xml C:\tools\mytool\mytool.exe $(MSBuildThisFileDirectory)tools\mytool\mytool.exe ``` **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. ```xml Library true true MyLib MyLib true net8.0 ``` --- ## AP-05: Manual File Listing in SDK-Style Projects **Smell**: ``, `` 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. ```xml ``` **Exception**: Non-SDK-style (legacy) projects require explicit file includes. If migrating, see `msbuild-modernization` skill. --- ## AP-06: Using `` with HintPath for NuGet Packages **Smell**: `` **Why it's bad**: This is the legacy `packages.config` pattern. It doesn't support transitive dependencies, version conflict resolution, or automatic restore. The `packages/` folder must be committed or restored separately. ```xml ..\packages\Newtonsoft.Json.13.0.3\lib\netstandard2.0\Newtonsoft.Json.dll ``` **Note**: `` without HintPath is still valid for .NET Framework GAC assemblies like `WindowsBase`, `PresentationCore`, etc. --- ## AP-07: Missing `PrivateAssets="all"` on Analyzer/Tool Packages **Smell**: `` without `PrivateAssets="all"`. **Why it's bad**: Without `PrivateAssets="all"`, analyzer and build-tool packages flow as transitive dependencies to consumers of your library. Consumers get unwanted analyzers or build-time tools they didn't ask for. See [`references/private-assets.md`](references/private-assets.md) for BAD/GOOD examples and the full list of packages that need this. --- ## AP-08: Copy-Pasted Properties Across Multiple .csproj Files **Smell**: The same `` block appears in 3+ project files. **Why it's bad**: Maintenance burden — a change must be made in every file. Inconsistencies creep in over time. ```xml latest enable true enable latest enable true enable ``` See `directory-build-organization` skill for full guidance on structuring `Directory.Build.props` / `Directory.Build.targets`. --- ## AP-09: Scattered Package Versions Without Central Package Management **Smell**: `` with different versions of the same package across projects. **Why it's bad**: Version drift — different projects use different versions of the same package, leading to runtime mismatches, unexpected behavior, or diamond dependency conflicts. ```xml ``` **Fix:** Use Central Package Management. See [https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management](https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management) for details. --- ## AP-10: Monolithic Targets (Too Much in One Target) **Smell**: A single `` with 50+ lines doing multiple unrelated things. **Why it's bad**: Can't skip individual steps via incremental build, hard to debug, hard to extend, and the target name becomes meaningless. ```xml ``` --- ## AP-11: Custom Targets Missing `Inputs` and `Outputs` **Smell**: `` with no `Inputs` / `Outputs` attributes. **Why it's bad**: The target runs on every build, even when nothing changed. This defeats incremental build and slows down no-op builds. See [`references/incremental-build-inputs-outputs.md`](references/incremental-build-inputs-outputs.md) for BAD/GOOD examples and the full pattern including FileWrites registration. See `incremental-build` skill for deep guidance on Inputs/Outputs, FileWrites, and up-to-date checks. --- ## AP-12: Setting Defaults in .targets Instead of .props **Smell**: `` with default values inside a `.targets` file. **Why it's bad**: `.targets` files are imported late (after project files). By the time they set defaults, other `.targets` files may have already used the empty/undefined value. `.props` files are imported early and are the correct place for defaults. ```xml 2.0 2.0 ``` **Rule**: `.props` = defaults and settings (evaluated early). `.targets` = build logic and targets (evaluated late). --- ## AP-13: Import Without `Exists()` Guard **Smell**: `` without a `Condition="Exists('...')"` check. **Why it's bad**: If the file doesn't exist (not yet created, wrong path, deleted), the build fails with a confusing error. Optional imports should always be guarded. ```xml ``` **Exception**: Imports that are *required* for the build to work correctly should fail fast — don't guard those. Guard imports that are optional or environment-specific (e.g., local developer overrides, CI-specific settings). --- ## AP-14: Using Backslashes in Paths (Cross-Platform Issue) **Smell**: `` with backslash separators in `.props`/`.targets` files meant to be cross-platform. **Why it's bad**: Backslashes work on Windows but fail on Linux/macOS. MSBuild normalizes forward slashes on all platforms. ```xml ``` **Note**: `$(MSBuildThisFileDirectory)` already ends with a platform-appropriate separator, so `$(MSBuildThisFileDirectory)tools/mytool` works on both platforms. --- ## AP-15: Unconditional Property Override in Multiple Scopes **Smell**: A property set unconditionally in both `Directory.Build.props` and a `.csproj` — last write wins silently. **Why it's bad**: Hard to trace which value is actually used. Makes the build fragile and confusing for anyone reading the project files. ```xml bin\custom\ bin\other\ bin\custom\ ``` --- For additional anti-patterns (AP-16 through AP-21) and a quick-reference checklist, see [additional-antipatterns.md](references/additional-antipatterns.md).