Generated banner for Msbuild
Generated logo for Msbuild

Msbuild

.NET (Microsoft)OfficialPublished by .NET (Microsoft)

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Use before MSBuild custom-target or incremental-generation repairs, XML/import/hook review, .binlog capture/replay, or assessing supported/unsupported build-speedup claims and controlled timings, even without a checkout. For MSBuild authoring itself, activate before reviewing/editing .proj/.props/.targets/.csproj/.fsproj/.vbproj or Directory.Build files, including no-change import reviews. Framework/API/.NET/ASP.NET upgrade plans and working non-SDK-to-SDK/packages.config conversions are migrations, even if they mention .csproj/NuGet: DO NOT activate. A failing assertion after a successful build is code/test debugging: DO NOT activate. Check Import/Exists, required and NuGet build/buildTransitive/nuspec imports, conditions, Inputs/Outputs, stale outputs and incorrect skips. Build checks use copies; source inputs stay read-only. Diagnose failed dotnet build/restore/pack/publish or slow evaluation/compilation/copying. Exclude SDK installation alone, C# refactoring, runtime CPU profiling and non-MSBuild builds.
— the description by .NET (Microsoft)

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured methodology for investigating MSBuild failures, performance bottlenecks, and authoring issues. It emphasizes evidence-based analysis using binary logs and incremental build checks, ensuring that repairs are targeted and minimal. The skill guides users through diagnosing failed builds, verifying incremental build contracts, and optimizing build performance without resorting to broad cleanups or unverified assumptions.

What it does

  • Analyzes binary logs to identify root causes of build failures.
  • Validates incremental build inputs, outputs, and skip conditions.
  • Diagnoses performance bottlenecks in evaluation, compilation, and copying.
  • Reviews MSBuild project files for anti-patterns and incorrect import contracts.
  • Provides a framework for controlled performance experiments.
  • Ensures build authoring changes preserve existing contracts and dependencies.

When to use it

  • When a build fails and you need to identify the specific project instance or task responsible.
  • When outputs are stale or build targets are incorrectly skipped.
  • When you need to investigate slow build evaluation or compilation times.
  • When reviewing or editing .proj, .props, .targets, or Directory.Build files.
  • When you need to verify that custom build targets correctly handle incremental builds.

Advantages

  • Promotes minimal, targeted repairs rather than broad, risky cleanups.
  • Uses evidence-based analysis via binary logs instead of guesswork.
  • Provides clear, repeatable patterns for performance benchmarking.
  • Ensures build integrity by respecting existing contracts and evaluation order.
  • Reduces build noise by correctly identifying cascading failures.

Usage examples

“My build is failing with an obscure error. How do I investigate?”
The agent will guide you to capture a binary log, replay it to isolate the specific failing task, and identify the root cause within the project structure.
“Why is my custom target running even when inputs haven't changed?”
The agent will help you audit the target's Inputs and Outputs, check for missing file registrations, and ensure input-set membership is correctly tracked.
“How can I prove that changing a specific MSBuild property improves build time?”
The agent will define a controlled two-arm experiment, comparing the baseline against the modified setting while keeping all other variables constant.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/dotnet/msbuild/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip msbuild.zip -d .agents/skills

Documentation by .NET (Microsoft)

MSBuild troubleshooting

For advice or static review, answer from the supplied XML, package facts, or measurements when they settle the question: no reference reads, checkout discovery, commands, or invented verification. For requested work, start at the supplied project or artifact; discover only what the next decision needs. Read a reference only for a specific unresolved question. References are not invocable skills.

Start with the task and evidence

Before editing, follow repository instructions and inspect the project/imports. Preserve the original command, working directory, SDK, configuration, target framework, runtime identifier, global properties, and target. Ask only for consequential information that cannot be discovered.

An existing .binlog can be the only available artifact. Analyze it before requesting a new build; paths inside it do not prove a checkout exists locally.

For a mixed request, fix correctness before measuring successful builds, and keep authoring review separate from a performance experiment. A failing test assertion after a successful build is not an MSBuild failure.

Capture only: choose an unused artifact path under the supplied workspace, unless the user specified another artifact directory. Do not put the only deliverable in a shared/global temporary directory where workspace artifact collection will miss it, or stage/commit the log. Append the binary logger to the exact supplied command, run it once, and save its original exit code before checking the new log is nonempty. Report the artifact path and original exit code, then stop: no replay, diagnosis, or repair. Read capture details only for an unresolved wrapper, logger-option, toolset, or privacy question.

1. Troubleshoot build issues

Goal: identify the independent root causes and make the smallest repair, not a broad cleanup.

SituationReference if neededAction
A build failed and a binary log existsbinlog-failure-analysisReplay the log with MSBuild; connect errors to the responsible project instance, target, and task. Separate root causes from cascading failures.
The failure needs evidence and no matching log existsbinlog-generation, then failure analysisCapture the original invocation once and verify the artifact. Analyze it only when requested.
A project/build file has incorrect conditions, items, properties, or output pathsmsbuild-antipatternsSelect the relevant catalog entries and check their exceptions before changing anything.
Imports or build hooks are missing, overwritten, or run in the wrong orderextension-pointsInspect the import contract, discovery order, and packed NuGet layout rather than hiding a required failure.
Outputs are stale or required work is skippedincremental-buildTrack input-set membership as well as file timestamps; apply the repair contract below.

For build-wiring-only work, generator and source inputs are read-only throughout, not restored after tests; edit the original only as the requested repair requires. Allocate disposable scratch as a uniquely named directory outside the project tree, normally the OS temporary directory from mktemp -d; a copy inside the project can be matched by SDK default globs and break the later confirming build. Print the path once and write every copy, log, and hash under it: no fixed leaf names, pointer files, pre-deleting, or globbing to recover it. After the checks, remove exactly that path (never a pattern) and keep it in the report. Copy the project/input tree and required imports; run every scenario that adds, deletes, empties, or retimes inputs, or deletes obj/outputs, with the copy as working directory. A backup is not a verification copy, nor is an original project already under a temporary path. Compare original hashes before/after; equal final hashes do not excuse intermediate mutation. A confirming build of the repaired original must not delete outputs. If the copy cannot reproduce the environment, report blocked checks.

For input-set changes, prefer a persisted membership list written with WriteLinesToFile and WriteOnlyWhenDifferent, plus actual files in Inputs. A path list is not a content hash. Verify first/unchanged builds, deletion, older addition, empty inputs, and missing-output recovery on the copy. End the repair report, however brief, with three labeled explanations, not only a checklist:

  • Cause: state both blind spots: a deleted input vanishes from the timestamp comparison; an added input older than the output never looks newer; either skips required regeneration.

  • Mechanism: identify the membership artifact and how its changes invalidate the output. Unchanged membership preserves skips; actual file timestamps still catch ordinary edits.

  • Checks: list passed, failed, and not-run separately with the command or path used. "None failed" requires every command to exit 0; an errored script, even if fixed and rerun, is listed failed with its cause. If the main scenario passed but a later assertion failed, report both; never summarize as verified.

After a repair, rerun the original failing scenario and check affected dependents. If only a log is available, provide an evidence-backed proposed fix and say it was not applied or rebuilt. Do not turn a targeted failure investigation into solution-wide authoring cleanup.

2. Diagnose and improve build performance

Goal: measure the original workload, not an easier replacement.

For advice, give the verdict, decisive facts, and a concrete next step with equivalent successful builds and required outputs. Do not re-prove a supplied timeline or read references to restate it. Do not infer warm/no-op state or assign any part of a confounded timing gap to restore, cache, or setting effects without isolating that cause.

Supplied evidenceDecisionSmallest useful follow-up
Parallel target totals include overlapping dependency waitsKeep required references and parallelism. Cumulative waits can exceed both wall time and the duration of one dependency task; they do not prove CPU utilization or healthy scheduling.Follow the supplied timeline to the dependency task holding up consumers. For Csc, inspect compiler inputs and invocation, analyzer/source-generator timing, and whether compilation was expected for this workload. Measure CPU/node utilization or I/O contention if unknown; do not infer them from wait totals. Validate any change with equivalent successful builds and required outputs.
Commands, restore policy, or starting input/output states differReject attribution to the setting, not the possibility of a real gain.Give one matched old-setting/new-setting experiment below, not a menu of different workloads or an invented setting name.
Repeated successful runs have matched conditions and outputsAccept the observed improvement for that workload; use the same summary statistic for both arms: 100 * (old - new) / old.Report sample count and observed elapsed-time ranges, not an invented percentage uncertainty interval. Then state residual limits: small sample, one workload/machine/SDK, what restored starting state does and does not control, and no independent rerun. Do not require a checkout to acknowledge the supplied result.

When a minimal attribution experiment is needed, return one two-arm table, not alternatives:

  1. Bind the treatment to the user's identified setting and old/new values. If unidentified, retain literal <setting>, <old>, <new> placeholders. Differing commands are confounds, not setting names. Never substitute restore policy, Build/Rebuild, or cache state for an unidentified setting.

  2. Expand the full original baseline command in both arms. Only an explicitly identified command-line treatment may change its own argument; otherwise vary its configured value.

ArmTreatment, supplied or placeholderControlled invocation and workload
A<setting> = <old>Full original baseline command; input and starting outputs exactly as supplied
B<setting> = <new>Same full command, input, and starting outputs; only the named treatment differs
  1. Match non-treatment SDK, machine, configuration, instrumentation, target, restore policy, and input/output state exactly as supplied. Add no source edit the original workload lacked (keep one it had in both arms). Matching both arms is not enough if both replace the original workload. When they are controls, retain supplied Rebuild and timed restore; do not prime, warm, or move work outside the timer unless the original workload already did.

  2. Alternate matched pairs; compare wall-time medians and observed ranges; check required outputs and diagnostics. Scope the result to that workload. The table is the only experiment; the last sentence states a validity check or residual limit, not an offer or variant.

For optimization work, establish the relevant successful baseline before changing one supported cause:

Unresolved measurementReference if needed
Cold-output, changed-input, no-change, restore/cache state, or Build versus Rebuildbuild-perf-baseline
Unknown task cost, compiler/analyzer/reference/restore work, or overlapping waitsbuild-perf-diagnostics
Pre-target globs, imports, property evaluation, or excess project instanceseval-performance
Copies/output I/O, unchanged copies, or a modified destinationcopy-to-output-directory
Unexpected regeneration/skips, added/removed inputs, or missing outputsincremental-build

Stop when the suspected bottleneck is not supported by evidence, results fall within noise, or correctness regresses. Report missing measurements rather than manufacturing a complete baseline.

3. Review build authoring

Goal: preserve build contracts, not implicitly migrate frameworks, packages, languages, or project systems.

Check evaluation phases, not just import order. Outside targets, properties/imports are evaluated before items. A framework set in the project body can be unavailable to an early property condition in Directory.Build.props but available to an item condition in that same file. Move only the premature property assignment after its dependency; do not relocate an intentionally project-owned framework or a working item declaration. Preserve F# compile order and inspect SDK defaults before adding switches to suppress supposedly duplicated items.

Requested changeReference if neededPreserve
Review/clean up project files, shared properties, item declarations, or custom targetsmsbuild-antipatternsIntentional overrides, F# source order, required imports, package assets, and actual input/output contracts.
Review/improve hooks, imports, shared-file discovery, or NuGet build extensionsextension-pointsPrior hooks, evaluation order, direct/transitive consumer behavior, and the packed layout.
Restructure generation or copying as part of cleanupincremental-build and copy-to-output-directory, as applicableFirst-build and no-change behavior, invalidation, generated-item registration, and output completeness.

Record the existing contract; a matching catalog pattern alone is not a defect. Leave correct code alone. Validate authorized changes against original configurations, consumers, and affected build behavior.

Keep optional and required imports distinct, including inside aggregation files:

<Import Project="optional.targets" Condition="Exists('optional.targets')" />
<Import Project="required.targets" />

The required import must load or fail, not silently skip. Explain why a condition on an entire mixed path list can suppress a valid required import; semicolon-separated imports themselves are supported. Check optional present/absent crossed with required present/absent, preserving order when both exist. When checks run, report the observed order and failure cases, not only "verification passed." Do not replace configurable paths with hard-coded values unnecessarily.

For a packaged forwarder, keep these bindings separate:

BindingMeaning
NuspecFileSelects the XML manifest, possibly outside the project; not a payload mapping.
NuspecBasePathBase directory for the manifest's file src lookups on disk; it neither locates nor selects the manifest.
Mapping src / targetReads the source file and emits its mapped archive path; does not implicitly ship the source path too.
Consumer importResolves from the importing file inside the package, not source, feed, or the consumer's framework.

Do not substitute the manifest path for a mapping's src. For a no-change verdict, explain how NuspecFile selection, NuspecBasePath src lookup, mapping, packed target, and relative import connect, and why guarding a required import would hide a broken package. If supplied package facts settle this, stop: no Exists guard, reorganization, or confirmatory rebuild/repack. Inspect artifacts only when verification is requested or contents remain unknown.

Shared evidence and safety

  • For analysis, use MSBuild replay, never binary-file text parsing. Replay success and replay duration are not the recorded build's result or duration.

  • Resolve these files relative to this skill. Do not load the original plugin's skills or search other installations/checkouts. After a failed reference read, allow one listing of references to distinguish a missing bundled file from a present-but-unreadable tool integration failure. Report the error and reduced coverage; do not retry through shell commands or other readers, and do not pretend unread guidance was followed. Never work around a policy denial.

  • Do not clean outputs/caches, stop shared build servers, disable diagnostics, change SDKs, or serialize builds as generic fixes. Scope any necessary destructive experiment to approved, owned outputs. Keep binary and replayed logs local unless sharing is authorized.

  • Distinguish recorded facts from hypotheses. Missing source, log detail, tools, or platform support must be reported explicitly, not replaced with assumed values or invented verification.

Completion

Advice: verdict, decisive evidence, necessary next measurement; no manufactured verification. Repository work: cause/decision, minimal change (or no change), and exact successful, failed, and not-run checks. Performance claims also need comparable measurements and uncertainty. Scale length to the task, but never drop a required report field.

Files

references/incremental-build.md

Optimizing MSBuild Incremental Builds

Source: original incremental-build skill.

How MSBuild Incremental Build Works

MSBuild can skip targets whose outputs are up to date, reducing work on subsequent ordinary builds.

  • Inputs and Outputs: file-producing targets use file timestamps to determine freshness. Outputs must exist and be at least as new as the relevant inputs. One-to-one item transforms can support partial incremental execution rather than requiring every output to be rebuilt.

  • Without file-based checks: a target with neither attribute has no such freshness test when reached. It can still be skipped by conditions or because it already ran for that project instance. Cheap orchestration or discovery targets may correctly execute on every invocation that reaches them.

  • No Incremental target attribute: Incremental="false" is not a valid MSBuild Target attribute. Do not add it to force execution.

  • Timestamps, not hashes: touching an input can invalidate the target even without content changes. Conversely, unchanged timestamps can hide changes.

<!-- A one-to-one input/output mapping -->
<Target Name="Transform"
        Inputs="@(TransformFiles)"
        Outputs="@(TransformFiles->'$(OutputPath)%(Filename).out')">
  <!-- Existing transformation task goes here -->
</Target>

<!-- No file-based freshness check when this target is invoked -->
<Target Name="PrintMessage">
  <Message Text="This runs when the target is reached" />
</Target>

Give transformed outputs distinct paths if inputs can have duplicate basenames. Do not add fictitious output files merely to skip necessary work.

Why Incremental Builds Break (Top Causes)

  1. Missing Inputs/Outputs on file-producing custom targets. Declare meaningful file dependencies for expensive repeatable work, not indiscriminately for every orchestration target.

  2. Volatile output paths. Timestamps, build counters, or GUIDs in a path prevent finding the prior output.

  3. Writes outside declared Outputs. A target may skip even when an undeclared generated file is missing or stale; track all required outputs.

  4. Missing FileWrites registration. Standard Clean tracking may miss custom generated files. Merely putting a file under obj does not register it.

  5. Glob changes. Additions and removals alter item membership, but general timestamp checks do not remember the previous set. Removing an input, or adding an older input, need not invalidate a custom target. Use appropriate input-set tracking and obsolete-output cleanup when the output depends on membership.

  6. Property changes. MSBuild does not automatically compare prior property values used by a generator. Track relevant values explicitly. Changed configuration/TFM/RID paths can select missing outputs, but returning to an already-built configuration can reuse its isolated output set.

  7. NuGet package updates. Assets and resolved reference changes can correctly invalidate compilation and resolution work. Preserve necessary invalidation.

  8. Compiler-server state. A recycled VBCSCompiler can make a compilation that executes slower. It does not by itself explain why an up-to-date CoreCompile ran; separate execution decisions from compiler warm-up.

Diagnosing "Why Did This Rebuild?"

Use binlogs to identify exactly why a target executed instead of skipping.

Step-by-Step Using Binlog
  1. Reuse matching first/second-build artifacts if available. Otherwise follow binlog generation and capture two ordinary builds:

    Choose unused explicit names for both logs; the names below are illustrative.

    dotnet build "-bl:first.binlog"
    if ($LASTEXITCODE -ne 0) { throw "First build failed; no valid incremental baseline." }
    dotnet build "-bl:second.binlog"

    Keep configuration, TFM/RID, properties, restore policy, and inputs unchanged. Analyze the second successful build. Rebuild and --no-incremental force full work and cannot establish a no-change skip result.

  2. Replay the second binlog locally with diagnostic verbosity and a performance summary, substituting second.binlog for build.binlog. The searches below use that command's full-01.log; substitute the actual path if you chose another name. Replay the first build to a different file when comparing.

  3. Find executed/skipped targets and the accompanying reasons:

    Select-String -Path .\full-01.log -Pattern 'Building target|Skipping target|was not skipped|is newer than output' -Context 0,5
  4. Interpret messages in context (... stands for the target name):

    • Building target "..." completely.: full target execution; read the reason instead of assuming all outputs were missing.

    • Building target "..." partially, because some output files are out of date with respect to their input files.: partial execution for an applicable input/output mapping.

    • Skipping target "..." because all output files are up-to-date with respect to the input files.: freshness checks permitted a skip; verify the declared dependency set is complete.

  5. Record the precise input/output path, timestamp, missing output, or condition responsible, for the correct project/global-property instance.

Additional Diagnostic Techniques
  • Compare the two replayed logs for changed properties, items, paths, and reasons.

  • Find expensive second-build targets:

    Select-String -Path .\full-01.log -Pattern 'Target Performance Summary|Task Performance Summary' -Context 0,30
  • Aggregate/nested timings are not additive wall time. Follow orchestration waits to actual task work using performance diagnostics.

  • A zero-duration target that runs is not automatically a defect. Inspect its dependencies and actual cost before changing it.

  • If evaluation dominates while compilation correctly skips, use evaluation performance, not a fake incremental shortcut.

FileWrites and Clean Build

FileWrites lets the standard Clean infrastructure track build-generated files. It is separate from target freshness and compiler inclusion.

  • FileWrites: register owned generated files so the normal Clean pipeline can discover them.

  • FileWritesShareable: do not assume reference-counted ownership or protection because another project still uses a file. Inspect the selected Clean targets, locations, and shared-file ownership before relying on it.

  • Missing registration: files can accumulate, leaving stale inputs or outputs. A directory named obj is not automatic per-file tracking.

Pattern for Registering Generated Files

For a generator with declared input files and generated line content:

<Target Name="MyGenerator"
        Inputs="$(MSBuildProjectFullPath);@(GeneratorInput)"
        Outputs="$(IntermediateOutputPath)generated.cs">
  <WriteLinesToFile File="$(IntermediateOutputPath)generated.cs"
                    Lines="@(GeneratedLines)"
                    Overwrite="true" />
  <ItemGroup>
    <FileWrites Include="$(IntermediateOutputPath)generated.cs" />
  </ItemGroup>
</Target>

Ensure the directory exists and that all settings/tool/template dependencies are modeled; project-file timestamps do not track arbitrary property values. Use Overwrite="true" when replacing generated content rather than appending it.

An ItemGroup inside a target with an up-to-date skip can still be evaluated by output inference. Thus the source pattern of registering FileWrites and Compile inside that target is valid when the paths/items can be inferred. Do not move it solely because the task skipped. If an item depends only on task output that cannot be inferred, make that item's definition available separately.

Use only authorized, scoped Clean operations. Never register user-authored files or another project's outputs merely to make cleanup more aggressive.

Visual Studio Fast Up-to-Date Check

Visual Studio's Fast Up-to-Date Check (FUTDC) is distinct from MSBuild's Inputs/Outputs: it can skip invoking MSBuild entirely.

  • Fast checks: the project system examines known inputs, outputs, copy items, and timestamps without a full build.

  • Custom work: custom targets, generated files, or nonstandard/dynamic items may not be visible to that check.

  • Diagnostic comparison: temporarily force MSBuild checks when investigating a Visual Studio-only discrepancy:

    <PropertyGroup>
      <DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
    </PropertyGroup>

    Do not leave FUTDC disabled as a default performance fix.

  • Logging: in Visual Studio's Tools > Options > Projects and Solutions > SDK-Style Projects, enable verbose up-to-date-check logging and inspect the Output window for the actual file/reason.

  • Common cases: unregistered custom build actions, items added only during target execution, and changed Content/None copy items. Diagnose FUTDC's decision separately from MSBuild target skip reasons.

Making Custom Targets Incremental

The following example combines C# templates under config\$(ConfigFlavor) into a generated compilation input. The small state file tracks both the selected flavor and input membership; a plain timestamp list would miss some changes to either. Adapt the generator step and declared dependencies to the repository's real tool.

<PropertyGroup>
  <ConfigFlavor Condition="'$(ConfigFlavor)' == ''">Default</ConfigFlavor>
</PropertyGroup>
<ItemGroup>
  <ConfigInput Include="config\$(ConfigFlavor)\*.cs.in" />
</ItemGroup>

<Target Name="WriteConfigInputs">
  <MakeDir Directories="$(IntermediateOutputPath)" />
  <WriteLinesToFile File="$(IntermediateOutputPath)config.inputs"
                    Lines="Flavor=$(ConfigFlavor);@(ConfigInput->'%(FullPath)')"
                    Overwrite="true"
                    WriteOnlyWhenDifferent="true" />
  <ItemGroup>
    <FileWrites Include="$(IntermediateOutputPath)config.inputs" />
  </ItemGroup>
</Target>

<Target Name="GenerateConfig"
        DependsOnTargets="WriteConfigInputs"
        Inputs="$(MSBuildProjectFullPath);@(ConfigInput);$(IntermediateOutputPath)config.inputs"
        Outputs="$(IntermediateOutputPath)config.generated.cs"
        BeforeTargets="CoreCompile">
  <ReadLinesFromFile File="%(ConfigInput.Identity)"
                     Condition="'@(ConfigInput)' != ''">
    <Output TaskParameter="Lines" ItemName="_ConfigLines" />
  </ReadLinesFromFile>
  <WriteLinesToFile File="$(IntermediateOutputPath)config.generated.cs"
                    Lines="@(_ConfigLines)"
                    Overwrite="true" />
  <ItemGroup>
    <FileWrites Include="$(IntermediateOutputPath)config.generated.cs" />
    <Compile Include="$(IntermediateOutputPath)config.generated.cs" />
  </ItemGroup>
</Target>

Key points:

  • The project file, actual input files, and state file participate in freshness. Also declare imported generator logic, tool binaries, templates, and relevant settings when they affect the real generator.

  • WriteOnlyWhenDifferent prevents the state file from causing churn on every build. Include every relevant property/input-set value in an unambiguous, stable representation. Materialize valid state even when the initial input set is empty; comparing two empty strings must not suppress a missing state file.

  • This state file records input identities and settings, not input contents. Keep the actual files in Inputs for normal timestamp-based edit detection. Do not describe a membership manifest as detecting content edits whose timestamps were preserved. Use content hashing only when that is the required contract; avoid a custom temporary-file/compare/copy scheme when WriteOnlyWhenDifferent already supplies the needed behavior.

  • Generated output is overwritten when regeneration is necessary. If a real generator preserves output timestamps for identical content, use appropriate completion-state tracking so newer inputs do not make it execute forever; still detect missing real outputs.

  • IntermediateOutputPath should isolate configurations/TFMs/RIDs; inspect custom overrides. FileWrites, not the directory name, supplies clean registration.

  • BeforeTargets="CoreCompile" makes the file available before compilation. Compile inclusion and FileWrites registration can be inferred on a skip.

  • Do not duplicate the generated item or rely on an evaluation-time glob to find a file that does not yet exist.

  • For per-input generated outputs, detect removal and clean obsolete owned outputs as well as updating the input-set state.

Verify initial generation, an unchanged skip, ordinary edits, older-file additions, removals, missing outputs, and the contract's empty-input behavior. Run every destructive check on a disposable copy of the project and its required imports, never on the original inputs, even if they would be restored afterward. Make the copy byte-for-byte; Get-Content/Set-Content round-trips can change encodings and line endings. If the copy cannot reproduce the build, report those checks as blocked. Compare original hashes before and after as a final confirmation, not as permission to mutate them. Explain separately what the manifest tracks and what the file timestamps track.

Common Mistakes to Avoid
<!-- Expensive generation without file-based freshness checks -->
<Target Name="BadTarget" BeforeTargets="CoreCompile">
  <Exec Command="generate-code.exe" />
</Target>

<!-- Volatile output path: the next invocation cannot reuse it -->
<Target Name="BadTarget2"
        Inputs="@(Compile)"
        Outputs="$(OutputPath)gen_$([System.DateTime]::Now.Ticks).cs">
  <Exec Command="generate-code.exe" />
</Target>

<!-- Stable-output pattern; declare the real tool/configuration inputs too -->
<Target Name="GoodTarget"
        Inputs="$(MSBuildProjectFullPath);@(GeneratorInput)"
        Outputs="$(IntermediateOutputPath)generated.cs"
        BeforeTargets="CoreCompile">
  <Exec Command="generate-code.exe -o &quot;$(IntermediateOutputPath)generated.cs&quot;" />
  <ItemGroup>
    <FileWrites Include="$(IntermediateOutputPath)generated.cs" />
    <Compile Include="$(IntermediateOutputPath)generated.cs" />
  </ItemGroup>
</Target>

These external-tool patterns assume the actual generator exists and produces the declared file. Add property/input-set tracking when needed, as in the full example. Do not include generated outputs in their own input set.

Performance Summary and Preprocess

  • -clp:PerformanceSummary: identifies cumulative project/target/task costs:

    dotnet build -clp:PerformanceSummary

    Compare the relevant second-build work; inclusive durations are not exclusive CPU time.

  • -pp:preprocess.xml: expands imports to locate definitions and their origin:

    dotnet msbuild .\MyProject.csproj "-pp:preprocess.xml"

    It is not a dump of fully evaluated property/item values. Find the definitions of Inputs, Outputs, and imports, then inspect the matching build instance.

  • Use the summary for what ran and /pp for where it was defined, together with replayed skip reasons.

Common Fixes

  • Declare meaningful Inputs/Outputs for repeatable file-producing work, including missing-output recovery and all real dependencies.

  • Use stable intermediate paths while preserving configuration/TFM/RID isolation and avoiding output collisions.

  • Register owned generated files in FileWrites; validate the applicable scoped Clean behavior rather than assuming every intermediate file is tracked.

  • Avoid unnecessary volatile data. If per-build timestamps are required, preserve that contract and isolate their downstream impact. For Git revision state, a project-file timestamp or .git\HEAD alone is insufficient: branch refs can change without either changing. Use the repository's reliable state mechanism or a cheap probe that writes only changed content; report probe failures instead of freezing a stale value.

  • Use Returns for return-only item collections. These are alternative definitions, not two targets to install together:

    <!-- Outputs also participates in file-based freshness -->
    <Target Name="GetFiles" Outputs="@(DiscoveredFiles)" />
    
    <!-- Returns supplies items without a file-based freshness contract -->
    <Target Name="GetFiles" Returns="@(DiscoveredFiles)" />
  • Choose copy metadata deliberately using CopyToOutputDirectory modes, especially when consumers mutate copied files.

Validation

Compare equivalent ordinary builds using the baseline. In an authorized disposable/scoped workspace, verify: a no-change second build; input modification, addition, and removal; property/template/tool changes; deletion of a real generated output; downstream compilation and required diagnostics; relevant configuration/TFM/RID variants; and approved Clean behavior. For copied content, include destination mutation.

Report the target/project instance, exact reason/file evidence, the correction, before/after timings, and checks actually completed. A faster build that misses required regeneration, leaves stale outputs, or changes an agreed metadata contract is not a successful incremental-build fix.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Node.js

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
.NET (Microsoft)
Publisher
.NET (Microsoft) (Official)
Source
dotnet/skills · plugins/dotnet/skills/msbuild · commit 96c3d68
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by .NET (Microsoft) or .NET (Microsoft).

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download msbuild.zip