Troubleshooting MSBuild
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
Most MSBuild problems are one of two kinds: a value is not what you expected (a property, an item list, an import), or a task failed. Both are solved faster with a fixed method than by editing the project and rebuilding. This page gives that method, then covers the failures that need specific knowledge, with MSBuild 18.9 (.NET SDK 10.0.402).
A diagnosis method
Work from the cheapest tool to the most detailed one, and stop as soon as you have the answer.
-
Ask for the value.
-getProperty,-getItemand-getTargetResultprint evaluated values as JSON without building (Command-Line Reference). -
Find where it was set.
-pp:<file>writes the whole project with every import inlined and a comment naming each imported file; search it for the property. -
Watch the build. A binary log (
-bl) records every evaluation, property assignment, target, task and message; the MSBuild Structured Log Viewer searches it (Logging and Binary Logs). -
Read everything.
-v:diag(or-v:d) in the console, when you cannot open a viewer — for example in a CI log.
A worked example
Ledgerline packs the library and the CLI, but not the test project or the sample. Why is IsPackable false for
Ledgerline.Tests?
for p in src/Ledgerline src/Ledgerline.Cli tests/Ledgerline.Tests; do
echo "$p $(dotnet msbuild $p -getProperty:IsPackable)"
done
dotnet msbuild src/Ledgerline.Cli -pp:cli.pp.xml
src/Ledgerline true
src/Ledgerline.Cli true
tests/Ledgerline.Tests false
Searching cli.pp.xml for <IsPackable and noting the file comment above each hit gives the whole story:
111 ~/ledgerline/Directory.Build.props <IsPackable>false</IsPackable>
1532 ~/ledgerline/src/Ledgerline.Cli/Ledgerline.Cli.csproj <IsPackable>true</IsPackable>
17075 NuGet.Build.Tasks.Pack.targets <IsPackable Condition="'$(IsPackable)'=='' AND ('$(IsTestProject)'=='true' OR '$(IsTestingPlatformApplication)'=='true')">false</IsPackable>
17076 NuGet.Build.Tasks.Pack.targets <IsPackable Condition="'$(IsPackable)'==''">true</IsPackable>
Directory.Build.props turns packing off for every project, and only the projects that should be packed turn it back
on. The NuGet defaults never apply in Ledgerline, because the property is never empty when they run. The same three
steps answer "why is this file compiled?", "why does this target run?" and "which .targets file defines this?".
Task failures
Every task failure names the file and line of the task element. Start there:
-
MSB4018 "task failed unexpectedly" — the task threw an exception; the stack trace follows in the log. The bug is in the task (or its input), not in the project.
-
MSB4181 "returned false but did not log an error" — the task failed silently; fix the task to log why (Writing Custom Tasks).
-
MSB6006 "<tool> exited with code N" — a
ToolTaskran an executable that failed without the task recognising its error format. Run the logged command line by hand, or raise verbosity to see the tool’s output. -
MSB3073 — the same for the
Exectask;Execprints the tool’s output above the error.
To debug a custom task, set MSBUILDDEBUGONSTART. On Windows 1 launches the JIT debugger; on any platform 2
prints the process ID and waits for a debugger to attach:
MSBUILDDEBUGONSTART=2 dotnet msbuild cw.proj -t:Show
Waiting for debugger to attach (~/.dotnet/dotnet PID 29340). Press enter to continue...
Build with -m:1 -nr:false while debugging, so that the task runs in the process you attached to and a stale copy of
the task assembly is not reused from an earlier node.
Assembly-reference conflicts
ResolveAssemblyReference (RAR) turns Reference items and the assemblies from packages into the files the compiler
references and the build copies. When two versions of one assembly meet, it picks one. The documentation describes warning
MSB3277, "Found conflicts between different versions of '…' that could not be resolved", for conflicts it cannot
settle. In the reproduction below — with both net10.0 and net48 — RAR settled the conflict the wrong way
without any warning, and the program failed at run time.
The reproduction: Ledger.A.dll was compiled against Ledger.Core 1.0, Ledger.B.dll against Ledger.Core 2.0, and
the application references both plus Ledger.Core 1.0 directly:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<Reference Include="Ledger.A" HintPath="../lib/A/Ledger.A.dll" />
<Reference Include="Ledger.B" HintPath="../lib/B/Ledger.B.dll" />
<Reference Include="Ledger.Core" HintPath="../lib/core1/Ledger.Core.dll" />
</ItemGroup>
</Project>
dotnet build reports no warning, even with -p:AutoUnifyAssemblyReferences=false or a net48 target. Running the
program fails:
Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly 'Ledger.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'Ledger.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null'
at Ledger.B.Use..ctor() in ~/rar/B/U.cs:line 1
The decision is in the log at detailed verbosity (dotnet build -v:d), under the RAR task:
There was a conflict between "Ledger.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" and "Ledger.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null".
"Ledger.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" was chosen because it was primary and "Ledger.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" was not.
References which depend on "Ledger.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" [~/rar/lib/core1/Ledger.Core.dll].
~/rar/lib/core1/Ledger.Core.dll
Project file item includes which caused reference "~/rar/lib/core1/Ledger.Core.dll".
Ledger.Core
Ledger.A
A primary reference (one named in the project) always wins over a dependency. The fix is to reference the version
the dependencies need (2.0), or — the usual case — to use PackageReference, where NuGet chooses one version for the
whole graph (Dependency Resolution).
Search a binary log for "There was a conflict"; set MSBUILDLOGVERBOSERARSEARCHRESULTS=1 to log every location RAR
probed.
Missing targeting packs
SDK-style projects targeting .NET Framework download the reference assemblies as the
Microsoft.NETFramework.ReferenceAssemblies package, so net48 builds on Linux and macOS. Turning that off (or a
classic project on a machine without the Developer Pack) gives MSB3644:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<AutomaticallyUseReferenceAssemblyPackages>false</AutomaticallyUseReferenceAssemblyPackages>
</PropertyGroup>
</Project>
~/.dotnet/sdk/10.0.402/Microsoft.Common.CurrentVersion.targets(1259,5): error MSB3644: The reference assemblies for .NETFramework,Version=v4.8 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks [~/fx/Fx.csproj]
Source: MSB3644.
File locks
Copy retries when the destination cannot be written. With MSBuild 18.9 a Copy with no Retries attribute
retried 10 times, 1000 ms apart (the Copy task page still says the task default is zero); the Retries and
RetryDelayMilliseconds parameters change that. Each retry is warning MSB3026; giving up is error MSB3027,
followed by MSB3021 with the operating-system reason. On Windows the usual cause is a running process (the
application under test, an antivirus scanner, or a build node from an earlier build) holding the output file:
warning MSB3026: Could not copy "journals/jan.ledger.csv" to "/proc/ledgerline/jan.ledger.csv". Beginning retry 1 in 100ms. Could not find file '/proc/ledgerline'.
error MSB3027: Could not copy "journals/jan.ledger.csv" to "/proc/ledgerline/jan.ledger.csv". Exceeded retry count of 2. Failed.
error MSB3021: Unable to copy file "journals/jan.ledger.csv" to "/proc/ledgerline/jan.ledger.csv". Could not find file '/proc/ledgerline'.
(This output comes from the Copy example in
Tasks and Built-in Tasks,
which forces the failure with an unwritable path and Retries="2".) Stop the process that holds the file, run
dotnet build-server shutdown to end reused nodes, and look for two projects writing the same output (BuildCheck
BC0101, BuildCheck, Change Waves and Best Practices).
Error table
Every message below was produced with MSBuild 18.9 (paths shortened); the "Shown in" column links the page with the reproduction.
| Code | Message | Usual cause and fix |
|---|---|---|
MSB1009 |
|
A typo in the project argument, or a relative path from the wrong folder. |
MSB1011 |
|
Name the file; see Solutions and .slnx. |
MSB3021 |
|
The final copy failure, with the OS reason. |
MSB3026 |
|
A locked or unwritable destination; see File locks. |
MSB3027 |
|
The retries ran out. |
MSB3073 |
|
An |
MSB3277 |
|
Two versions of one assembly; see Assembly-reference conflicts. |
MSB3644 |
|
A missing targeting pack; see Missing targeting packs. |
MSB4018 |
|
A bug in the task (Writing Custom Tasks). |
MSB4019 |
|
A wrong |
MSB4057 |
|
A misspelled |
MSB4062 |
|
A wrong |
MSB4181 |
|
A task that fails without logging. |
MSB4236 |
|
An MSBuild SDK without a version in the project or |
Common mistakes
-
Editing and rebuilding instead of asking —
-getPropertyanswers in a second. -
Reading only the last error — the first error, or a warning above it, usually names the cause.
-
Debugging a custom task with node reuse on — the attached process is not the one running the task.
-
Trusting a clean build for references — RAR conflicts can build silently; run the program and its tests.
-
Retrying a locked-file build without stopping the holder — the next build hits the same lock.