The same query syntax is used by Structured Log Viewer search and by BinlogMcp.
Queries are made from space-separated terms. Terms are combined with AND: every term must match unless you use a scoped expression such as under(...) or not(...).
Unquoted words are case-insensitive substring matches.
Matches nodes containing both Copying and file.
Use quotes when the words must appear together.
Matches the literal phrase.
Quotes around one word turn off substring matching.
Matches Csc exactly, not every longer string containing those letters.
Start with a $kind token when you already know what kind of thing you want. This keeps broad text searches from drowning you in messages.
| Token | Use it for |
|---|---|
| $project | Project execution nodes. The same project can appear multiple times for different target frameworks, target lists, or MSBuild task calls. |
| $projectevaluation | Evaluation nodes. Use these to inspect evaluated properties, items, imports, and preprocessed XML. |
| $target | MSBuild targets. |
| $task | Task invocations. Combine with a task name, for example $task Csc. |
| $error, $warning, $message | Build diagnostics and log messages. |
| $property, $item, $metadata | Name/value nodes from evaluation or execution. |
| $additem, $removeitem | Item mutations that happened while targets were executing. |
| $import, $noimport | Resolved imports and imports that were skipped or failed. |
| $csc, $rar | Shortcuts for $task Csc and $task ResolveAssemblyReference. |
Field filters are most useful for properties, items, metadata, skipped targets, and project-reference graph height.
The name contains Configuration.
The name equals Configuration.
Both filters must match.
Use skipped=false to exclude skipped targets.
Most build questions are about context. Scope tells search where a match must be found in the tree.
| Form | Meaning | Example |
|---|---|---|
| under(QUERY) | Match only nodes whose ancestors include something matching QUERY. | $csc under($project Core) |
| notunder(QUERY) | Exclude nodes under a matching ancestor. | $warning notunder($task Csc) |
| project(QUERY) | Like under(), but only checks the nearest parent project. | $error project(MyApp) |
| not(QUERY) | Exclude nodes that themselves match the nested query. | $task not(Csc) |
Nested expressions are allowed. The parser is whitespace tolerant, so under ($project Foo) and under($project Foo) both work.
project(A) checks the nearest parent project. under($project A) can also match work done by projects that A invoked through an MSBuild task. In the viewer, right-click a project, target, or task and choose Search in subtree to generate an under(...) query for that exact subtree.
There are two separate ideas: filter by a start/end timestamp, or annotate and sort by time.
Adds duration and sorts slowest first. $duration is an alias.
Good first move for performance investigations.
Use $end for end time.
Timestamps must be quoted and parseable by .NET.
These searches use analysis indexes built from the binlog instead of only scanning visible tree text.
| Index | Best for | Examples |
|---|---|---|
| $copy | Why a file was copied, where it came from, or which target/task copied it. Directory searches are grouped into Incoming and Outgoing copies. | $copy Foo.dll $copy directory\path $copy C:\full\path\Foo.dll |
| $nuget | Package dependency chains from embedded project.assets.json files. Search by package name, version, dependency, or file from a package. | $nuget project(MyApp.csproj) $nuget project(MyApp.csproj) Package.Name $nuget project(MyApp.csproj) File.dll $nuget project(.csproj) 13.0.3 |
| $projectreference | Project-to-project dependency graph. Scoped to one project, it is bidirectional. Use height=0, height=1, or height=max to find projects by graph depth. | $projectreference project(App.csproj) $project height=max |
$copy filename finds copy operations by file-name substring. $copy directory\path shows files copied into and out of a directory. $copy full\file\path focuses on one specific file. If the file came from a NuGet package, the result can show which package and dependency chain brought it in. If the copy came from a None or Content item with CopyToOutputDirectory, that is shown too.
Use project(.) or project(.csproj) to search all projects, which is useful but can be slower on large logs. In the viewer, right-click a project and choose Search project.assets.json to inspect that project's NuGet dependencies.
The SDK version is usually in the embedded path, such as C:\Program Files\dotnet\sdk\10.0.202\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.props. Different SDK versions explain many build differences before anything else does.
Pick the full source or destination path from the result, then search again with that full path. The expanded tree usually shows whether the file came from RAR, copy-local, NuGet content, project references, publish, or an explicit item with copy metadata.
If you need the full lifetime of every assignment, rebuild with MSBUILDLOGPROPERTYTRACKING=15.
First search for the target in the project:
If it ran or skipped, inspect its node and messages. If it is absent, inspect the preprocessed XML and imports to see whether the target was actually in scope.
Capture a clean build and then an immediate no-change rebuild. The second binlog is the interesting one.
MSBuild logs this when a target's up-to-date check failed. Cross-check with $task $time to focus on work that actually costs time.
The compact source material for this page lives here: