microsoft/building-projects
> Build tool selection and orchestration for .NET projects during modernization upgrades. Use this skill whenever the agent modifies code files, project files, packages, or configuration and needs to validate the build — which is virtually every upgrade task. solution builds, resolving MSBuild errors (MSB3086, MSB4019, NETSDK1005, etc.), handling multi-targeting scenarios, resource generation failures with .resx files, WPF/WinForms/XAML compilation issues, SDK-style vs legacy csproj differences, and recovering when a build fails. Also use when diagnosing why a project compiles under one tool but not another, or when deciding build scope (single project vs full solution) during iterative migration work.
npx skills add https://github.com/microsoft/upgrade-agent-plugins --skill building-projects
During .NET modernization, the choice of build tool is not trivial. dotnet build and msbuild.exe
are not interchangeable — they have different capabilities, different SDK resolution paths, and
different behavior with legacy project features. This skill encodes the expert decision logic so
the agent picks the right tool on the first attempt and recovers intelligently when a build fails.
| Action | Skill |
|--------|-------|
| Convert legacy project files to SDK-style format | converting-to-sdk-style |
| Add/update/remove target frameworks | managing-target-frameworks |
| Add/update package references or resolve NuGet version conflicts | managing-package-references |
| Modify project properties | modifying-project-properties |
Defer to these skills for project format conversion, TFM changes, package management, and property edits.
If the repository contains a custom build skill (e.g., in .github/skills/, .copilot/skills/,
or a project-level BUILD.md with build instructions), defer to that skill for project-specific
build decisions. This skill provides general .NET modernization build logic and should serve as
a fallback when no repo-specific guidance exists.
If the agent is running inside an IDE (Visual Studio, VS Code, Rider) that exposes build-related
tools — such as build_project, build_solution or similar MCP/Copilot tools —
prefer those over shelling out to the command line. IDE build tools respect the user's local
configuration and surface diagnostics where the user can click-to-navigate.
Fall back to this skill's CLI patterns when the IDE doesn't expose a build tool, the IDE build
tool fails with insufficient error output, the agent needs fine-grained control (targeting a
specific TFM, adjusting verbosity, choosing between dotnet build and msbuild.exe), or the
agent needs to capture structured build output for automated validation.
Track progress using this checklist:
Task Progress:
- [ ] Step 1: Determine the right build tool
- [ ] Step 2: Execute the build
- [ ] Step 3: Diagnose and recover from failures (if any)
- [ ] Step 4: Validate the build output
dotnet buildFor SDK-style projects targeting modern .NET only, dotnet build is the correct default. It
ships with the .NET SDK, handles NuGet restore implicitly, and works cross-platform.
msbuild.exeEscalate to the full Visual Studio MSBuild (msbuild.exe) when ANY of these conditions apply:
.resx files containing images, icons, or binary resources.dotnet build uses a portable resource generator that cannot process embedded images.
Symptom: MSB3086, MSB3552, or LC.exe errors.
net*-windows with XAML.WPF's markup compiler requires the full Windows SDK targets that only ship with VS MSBuild.
Symptom: MC1000, XDG0008, or missing Microsoft.WinFX.targets.
The ResGen.exe that ships with the .NET SDK may lack support for System.Drawing types
referenced in .resx files.
net472 (or any netXXX <= net48) TFM.Building classic .NET Framework TFMs requires reference assemblies and the full MSBuild
toolset. dotnet build can handle this IF Microsoft.NETFramework.ReferenceAssemblies
is in the project or Directory.Build.props, but frequently fails with NETSDK1005.
These rely on VS-specific targets/tasks that the .NET SDK MSBuild doesn't include.
vcxproj) projects.The .NET CLI cannot build these at all.
If the project still uses <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets">
rather than <Project Sdk="Microsoft.NET.Sdk">, only msbuild.exe will work.
msbuild.exe (full VS). Stop.Microsoft.NETFramework.ReferenceAssemblies present?dotnet build first, fall back to msbuild.exe on failure.msbuild.exe (or add the ReferenceAssemblies package first).msbuild.exe if ANY of the following apply, otherwise use dotnet build:.resx with embedded images/iconsDuring modernization, NuGet packages change constantly — versions get bumped, packages get
swapped, new ones added, old ones removed. A stale obj/project.assets.json is one of the
most common causes of phantom build failures.
Default behavior: always let restore run.
dotnet build: restore runs by default — do not add --no-restore during migration.Package references change frequently, and skipping restore causes phantom build failures.
msbuild.exe: use msbuild /restore (or /r) to run NuGet restore before building.This is especially important when already using msbuild.exe for the build — mixing
dotnet restore as a separate step with msbuild /build can cause resolver mismatches.
When restore can be skipped: Only when the current migration step modified nothing but
.cs files — no *proj, Directory.Build.props, Directory.Packages.props, or
nuget.config was touched. If in doubt, restore. The cost is a few seconds; the cost of
a stale restore is a misleading build result.
Building the entire solution on every change is too slow for iterative migration work.
Use two tiers:
Targeted project build (default during iterative work):
When the agent modifies a project or its files, build only that specific project:
dotnet build <specific-project.csproj> -r
msbuild.exe <specific-project.csproj> /restore /t:Build /p:Configuration=Release
This gives fast feedback (seconds, not minutes) on whether the migration change compiled.
The agent is iterating — it needs signal on its change, not the entire dependency graph.
Full solution build (final validation only):
Build the entire solution when:
public API changes, package updates consumed by multiple projects)
dotnet build <solution.sln> -r
msbuild.exe <solution.sln> /restore /t:Build /p:Configuration=Release
Do NOT default to solution builds for every change. Reserve them as a gate check.
Simple SDK build (happy path):
dotnet build <project.csproj> -r
MSBuild build:
msbuild.exe <project.csproj> /restore /t:Build /p:Configuration=Release /v:minimal
Clean build (when incremental build is suspect):
dotnet build <project.csproj> --no-incremental
Or for MSBuild:
msbuild.exe <project.csproj> /restore /t:Clean;Build /p:Configuration=Release
Debugging restore failures in isolation:
When restore itself needs diagnosing, run it separately:
dotnet restore <project.csproj> --verbosity detailed
Then build without restore to isolate the compilation phase:
dotnet build <project.csproj> --no-restore
This is a diagnostic pattern only — not the default workflow.
After determining the right build tool for a project, save the decision to
scenario-instructions.md so it doesn't need to be re-derived on every build.
Add or update a ## Build Tool Decisions section:
## Build Tool Decisions
- **MyWebApp.csproj**: msbuild.exe (non-SDK-style, System.Web references)
- **Common.csproj**: dotnet build (SDK-style, no special requirements)
- **Tests.csproj**: dotnet build (SDK-style after conversion)
On subsequent builds, check this section first:
When a build fails, analyze the error codes and take targeted action rather than blindly retrying.
Load ref/error-codes.md for the full error catalog
with specific recovery actions per error code.
Key recovery principles:
-v:detailed or /v:diag on retry for more info)and NuGet errors (NU\*)
Do NOT:
/nowarn without the user's explicit approvalTreatWarningsAsErrors without flagging itAfter a successful build during migration, verify:
bin/ pathnet10.0/), not the old TFM
When the decision is msbuild.exe, the agent must locate it reliably.
Priority 1 — VSINSTALLDIR environment variable:
If the agent is running inside Visual Studio or was launched from a VS Developer Command
Prompt, the VSINSTALLDIR environment variable is already set and points to the correct
VS installation. Use it directly:
"%VSINSTALLDIR%\MSBuild\Current\Bin\MSBuild.exe"
This is the preferred approach — it matches the user's active VS context and avoids
picking a different installation than the one they're working with.
Priority 2 — vswhere.exe:
If VSINSTALLDIR is not set (agent running outside VS), use vswhere.exe:
"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" ^
-latest -requires Microsoft.Component.MSBuild ^
-find MSBuild\**\Bin\MSBuild.exe
Priority 3 — well-known paths:
If neither is available, fall back to well-known paths under Program Files.
Full msbuild.exe is generally not available outside Windows+VS. If the project requires it,
flag it as a Windows-only build requirement or suggest restructuring to remove the dependency
(e.g., convert .resx to use LogicalName with precompiled resources, or extract the WPF layer).
Projects that multi-target (e.g., <TargetFrameworks>net472;net10.0</TargetFrameworks>) during
a transitional migration phase need special handling:
dotnet build builds ALL TFMs by default. To build just one: dotnet build -f net10.0
to confirm backward compatibility.
<TargetFrameworks> — defer to the managing-target-frameworks skill for the actual edit.
dotnet build to msbuild.exe (or vice versa) without understanding the root cause of the failure/nowarn or downgrading TreatWarningsAsErrors without explicit user approvalbin/ pathTake microsoft/building-projects from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.