Diagnosing MSBuild Evaluation Performance
Evaluation is the work MSBuild does before any target runs — reading project
files, processing imports, expanding globs. This skill helps you find and
confirm evaluation bottlenecks. Measure first; recommend a change only when a
measurement proves it is warranted.
Confirm the problem before changing anything
Engage only when evaluation is measurably the bottleneck. Do NOT act when:
- The slowness is during compilation or target execution, not evaluation.
That is not an evaluation problem — use
build-perf-diagnostics instead.
- The complaint is "rebuilds too much" / incremental build. Use
incremental-build instead.
- You have no measurement. If no binlog or timing summary shows evaluation
is slow, gather one first (see below). Do not guess from reading project files.
- A pattern below appears but evaluation is already fast. Broad globs, deep
imports, or
EnableDefaultItems are only worth flagging when the numbers show
they cost real time. A project that evaluates quickly needs no change.
When a pattern is present but unmeasured, report it as an observation and let
the user decide — do not rewrite working configuration to match a "best
practice" without evidence it costs measurable evaluation time. Prefer the
smallest, most targeted change; never disable SDK defaults as a first move.
MSBuild Evaluation Phases
For a comprehensive overview of MSBuild's evaluation and execution model, see Build process overview.
- Initial properties: environment variables, global properties, reserved properties
- Imports and property evaluation: process
<Import>, evaluate <PropertyGroup> top-to-bottom
- Item definition evaluation:
<ItemDefinitionGroup> metadata defaults
- Item evaluation:
<ItemGroup> with Include, Remove, Update, glob expansion
- UsingTask evaluation: register custom tasks
Key insight: evaluation happens BEFORE any targets run. Slow evaluation = slow build start even when nothing needs compiling.
Diagnosing Evaluation Performance
Primary: binlog MCP (preferred)
Use the binlog MCP server (Microsoft.AITools.BinlogMcp, exposed under the binlog MCP namespace) to analyze evaluation performance:
- Use the evaluations tool to list all evaluations and their durations
- Use evaluation_global_properties to check for multiple evaluations with differing global properties
- Use evaluation_properties to inspect evaluated properties for a specific project+TFM
- Use imports tool to analyze the import chain depth and structure
- Use properties tool to check for expensive property function evaluations
Fallback: text-log replay and preprocessing (when MCP is unavailable)
Using binlog
- Replay the binlog:
dotnet msbuild build.binlog -noconlog -fl -flp:v=diag;logfile=full.log
- Search for evaluation events:
grep -i 'Evaluation started\|Evaluation finished' full.log
- Multiple evaluations for the same project = overbuilding
- Look for "Project evaluation started/finished" messages and their timestamps
Using /pp (preprocess)
dotnet msbuild -pp:full.xml MyProject.csproj
- Shows the fully expanded project with ALL imports inlined
- Use to understand: what's imported, import depth, total content volume
- Large preprocessed output (>10K lines) = heavy evaluation
Using /clp:PerformanceSummary
- Add to build command for timing breakdown
- Shows evaluation time separately from target/task execution
Expensive Glob Patterns
Only pursue these remedies once a measurement shows item evaluation is slow and
the globs are the cause; a custom glob that isn't walking large trees is fine.
- Globs like
**/*.cs walk the entire directory tree
- Default SDK globs are optimized, but custom globs may not be
- Problem: globbing over
node_modules/, .git/, bin/, obj/ — millions of files
- Remedy: use
<DefaultItemExcludes> to exclude large directories
- Remedy: be specific with glob paths:
src/**/*.cs instead of **/*.cs
- Remedy: use
<EnableDefaultItems>false</EnableDefaultItems> only as a last resort (loses SDK defaults) — prefer the two options above first
- Check: grep for Compile items in the diagnostic log → if Compile items include unexpected files, globs are too broad
Import Chain Analysis
- Deep import chains (>20 levels) slow evaluation
- Each import: file I/O + parse + evaluate
- Common causes: NuGet packages adding .props/.targets, framework SDK imports, Directory.Build chains
- Diagnosis:
/pp output → search for <!-- Importing comments to see import tree
- Remedy (only if the chain is measurably costly): reduce transitive package imports where possible, consolidate imports
Multiple Evaluations
- A project evaluated multiple times = wasted work
- Common causes: referenced from multiple other projects with different global properties
- Each unique set of global properties = separate evaluation
- Diagnosis:
grep 'Evaluation started.*ProjectName' full.log → if count > 1, check for differing global properties
- Fix: normalize global properties, use graph build (
/graph)
TreatAsLocalProperty
- Prevents property values from flowing to child projects via MSBuild task
- Overuse: declaring many TreatAsLocalProperty entries adds evaluation overhead
- Correct use: only when you genuinely need to override an inherited property
Property Function Cost
- Property functions execute during evaluation
- Most are cheap (string operations)
- Expensive:
$([System.IO.File]::ReadAllText(...)) during evaluation — reads file on every evaluation
- Expensive: network calls, heavy computation
- Rule: property functions should be fast and side-effect-free
Optimization Checklist
1---2name: eval-performance3description: Guide for diagnosing and improving MSBuild project evaluation performance. USE FOR: builds slow before any compilation starts, high evaluation time in binlog analysis, expensive glob patterns walking large directories (node_modules, .git, bin/obj), deep import chains (>20 levels), preprocessed output >10K lines indicating heavy evaluation, property functions with file I/O ($([System.IO.File]::ReadAllText(...))), multiple evaluations per project. Covers the 5 MSBuild evaluation phases, glob optimization via DefaultItemExcludes, import chain analysis with /pp preprocessing. DO NOT USE FOR: compilation-time slowness (use build-perf-diagnostics), incremental build issues (use incremental-build), non-MSBuild build systems.4license: MIT5---6
7# Diagnosing MSBuild Evaluation Performance
8
9Evaluation is the work MSBuild does *before* any target runs — reading project
10files, processing imports, expanding globs. This skill helps you **find and
11confirm** evaluation bottlenecks. Measure first; recommend a change only when a
12measurement proves it is warranted.
13
14## Confirm the problem before changing anything
15
16Engage only when evaluation is *measurably* the bottleneck. Do NOT act when:
17
18- **The slowness is during compilation or target execution, not evaluation.**
19 That is not an evaluation problem — use `build-perf-diagnostics` instead.
20- **The complaint is "rebuilds too much" / incremental build.** Use
21 `incremental-build` instead.
22- **You have no measurement.** If no binlog or timing summary shows evaluation
23 is slow, gather one first (see below). Do not guess from reading project files.
24- **A pattern below appears but evaluation is already fast.** Broad globs, deep
25 imports, or `EnableDefaultItems` are only worth flagging when the numbers show
26 they cost real time. A project that evaluates quickly needs no change.
27
28When a pattern is present but unmeasured, **report it as an observation and let
29the user decide** — do not rewrite working configuration to match a "best
30practice" without evidence it costs measurable evaluation time. Prefer the
31smallest, most targeted change; never disable SDK defaults as a first move.
32
33## MSBuild Evaluation Phases
34
35For a comprehensive overview of MSBuild's evaluation and execution model, see [Build process overview](https://learn.microsoft.com/en-us/visualstudio/msbuild/build-process-overview).
36
371. **Initial properties**: environment variables, global properties, reserved properties
382. **Imports and property evaluation**: process `<Import>`, evaluate `<PropertyGroup>` top-to-bottom
393. **Item definition evaluation**: `<ItemDefinitionGroup>` metadata defaults
404. **Item evaluation**: `<ItemGroup>` with `Include`, `Remove`, `Update`, glob expansion
415. **UsingTask evaluation**: register custom tasks
42
43Key insight: evaluation happens BEFORE any targets run. Slow evaluation = slow build start even when nothing needs compiling.
44
45## Diagnosing Evaluation Performance
46
47### Primary: binlog MCP (preferred)
48
49Use the **binlog MCP server** (`Microsoft.AITools.BinlogMcp`, exposed under the `binlog` MCP namespace) to analyze evaluation performance:
50
511. Use the evaluations tool to list all evaluations and their durations
522. Use evaluation_global_properties to check for multiple evaluations with differing global properties
533. Use evaluation_properties to inspect evaluated properties for a specific project+TFM
544. Use imports tool to analyze the import chain depth and structure
555. Use properties tool to check for expensive property function evaluations
56
57### Fallback: text-log replay and preprocessing (when MCP is unavailable)
58
59### Using binlog
60
611. Replay the binlog: `dotnet msbuild build.binlog -noconlog -fl -flp:v=diag;logfile=full.log`
622. Search for evaluation events: `grep -i 'Evaluation started\|Evaluation finished' full.log`
633. Multiple evaluations for the same project = overbuilding
644. Look for "Project evaluation started/finished" messages and their timestamps
65
66### Using /pp (preprocess)
67
68- `dotnet msbuild -pp:full.xml MyProject.csproj`
69- Shows the fully expanded project with ALL imports inlined
70- Use to understand: what's imported, import depth, total content volume
71- Large preprocessed output (>10K lines) = heavy evaluation
72
73### Using /clp:PerformanceSummary
74
75- Add to build command for timing breakdown
76- Shows evaluation time separately from target/task execution
77
78## Expensive Glob Patterns
79
80Only pursue these remedies once a measurement shows item evaluation is slow and
81the globs are the cause; a custom glob that isn't walking large trees is fine.
82
83- Globs like `**/*.cs` walk the entire directory tree
84- Default SDK globs are optimized, but custom globs may not be
85- Problem: globbing over `node_modules/`, `.git/`, `bin/`, `obj/` — millions of files
86- Remedy: use `<DefaultItemExcludes>` to exclude large directories
87- Remedy: be specific with glob paths: `src/**/*.cs` instead of `**/*.cs`
88- Remedy: use `<EnableDefaultItems>false</EnableDefaultItems>` only as a last resort (loses SDK defaults) — prefer the two options above first
89- Check: grep for Compile items in the diagnostic log → if Compile items include unexpected files, globs are too broad
90
91## Import Chain Analysis
92
93- Deep import chains (>20 levels) slow evaluation
94- Each import: file I/O + parse + evaluate
95- Common causes: NuGet packages adding .props/.targets, framework SDK imports, Directory.Build chains
96- Diagnosis: `/pp` output → search for `<!-- Importing` comments to see import tree
97- Remedy (only if the chain is measurably costly): reduce transitive package imports where possible, consolidate imports
98
99## Multiple Evaluations
100
101- A project evaluated multiple times = wasted work
102- Common causes: referenced from multiple other projects with different global properties
103- Each unique set of global properties = separate evaluation
104- Diagnosis: `grep 'Evaluation started.*ProjectName' full.log` → if count > 1, check for differing global properties
105- Fix: normalize global properties, use graph build (`/graph`)
106
107## TreatAsLocalProperty
108
109- Prevents property values from flowing to child projects via MSBuild task
110- Overuse: declaring many TreatAsLocalProperty entries adds evaluation overhead
111- Correct use: only when you genuinely need to override an inherited property
112
113## Property Function Cost
114
115- Property functions execute during evaluation
116- Most are cheap (string operations)
117- Expensive: `$([System.IO.File]::ReadAllText(...))` during evaluation — reads file on every evaluation
118- Expensive: network calls, heavy computation
119- Rule: property functions should be fast and side-effect-free
120
121## Optimization Checklist
122
123- [ ] Check preprocessed output size: `dotnet msbuild -pp:full.xml`
124- [ ] Verify evaluation count: should be 1 per project per TFM
125- [ ] Exclude large directories from globs
126- [ ] Avoid file I/O in property functions during evaluation
127- [ ] Minimize import depth
128- [ ] Use graph build to reduce redundant evaluations
129- [ ] Check for unnecessary UsingTask declarations