Choosing and Combining the Tools
|
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. |
The .NET tool chain offers several front ends to the same engine. For a modern repository the answer is short — use the dotnet CLI — but code bases with .NET Framework projects, packages.config, Visual Studio-only workflows or
scripts written for nuget.exe still need the others, and knowing which command is equivalent to which avoids most
"works in Visual Studio, fails on CI" surprises. This page was written against MSBuild 18.9, .NET SDK 10.0.402 and
NuGet 7.9.0; nuget.exe 7.9.0 (2026-08-11, the latest on dist.nuget.org/tools.json) and Visual Studio 2026 18.11 are
described from their documentation and are not run here (no Windows machine).
Which tool for which job
| Tool | Use it for | Platforms |
|---|---|---|
|
Everything in an SDK-style repository: new, restore, build, test, pack, publish, run, tools, workloads, package and source management, push, signing. The default on developer machines and CI. |
Windows, macOS, Linux |
|
Projects that need the .NET Framework MSBuild: non-SDK |
Windows |
|
|
Windows (Mono elsewhere) |
Visual Studio / Package Manager UI |
Interactive package management, design-time builds, publish profiles. |
Windows |
Package Manager Console (PMC) |
PowerShell commands ( |
Windows (Visual Studio only) |
The NuGet documentation’s rule of thumb: target .NET or .NET Standard, or any SDK-style project → the dotnet CLI;
.NET Framework with non-SDK projects → nuget.exe; once such a project is migrated to PackageReference, switch to the
dotnet CLI. MSBuild can also restore and pack, "but MSBuild isn’t a general-purpose tool for working with NuGet"; it
is mainly useful on build servers.
Command equivalence
| Task | dotnet CLI |
msbuild.exe / nuget.exe |
Visual Studio / PMC |
|---|---|---|---|
Restore |
|
|
automatic on open/build |
Build |
|
|
Build > Build Solution |
Test |
|
|
Test Explorer |
Pack |
|
|
Pack on the project |
Publish an app |
|
|
Publish with a profile |
Add a package |
|
|
Package Manager UI; |
Update a package |
|
|
UI; |
List packages |
|
— |
UI; |
Push |
|
|
— |
Sources |
|
|
Tools > Options > NuGet Package Manager > Package Sources |
Clear caches |
|
|
Clear All NuGet Storage |
The equivalences are real, not approximate: dotnet pack is dotnet msbuild -restore -t:Pack, and both produce the
same package. For the Ledgerline library:
dotnet msbuild src/Ledgerline -restore -t:Pack -p:Configuration=Release -p:PackageOutputPath=$PWD/artifacts/msbuild-pack -tl:off -v:m
dotnet pack src/Ledgerline -c Release -o artifacts/dotnet-pack -tl:off -v:m
Determining projects to restore...
Restored ~/ledgerline/src/Ledgerline/Ledgerline.csproj (in 339 ms).
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Release/net8.0/Ledgerline.dll
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Release/netstandard2.0/Ledgerline.dll
Ledgerline -> ~/ledgerline/src/Ledgerline/bin/Release/net10.0/Ledgerline.dll
Successfully created package '~/ledgerline/artifacts/msbuild-pack/Ledgerline.1.0.0.nupkg'.
Successfully created package '~/ledgerline/artifacts/dotnet-pack/Ledgerline.1.0.0.nupkg'.
The differences that matter are the defaults the CLI adds (-restore, parallel nodes, the Terminal Logger, the
Release configuration for pack and publish since .NET 8) and which MSBuild runs: dotnet msbuild runs the
.NET MSBuild inside the SDK, msbuild.exe runs the .NET Framework MSBuild from Visual Studio, which can load tasks
compiled only for .NET Framework. A custom task should therefore target netstandard2.0 (as Ledgerline.Build does)
to work under both
(Writing Custom Tasks).
Coming from Maven, Gradle, pip or Poetry
Readers who know the JVM tools (issue #300) or the Python tools (issue #305) can map the concepts as follows. The biggest difference is that .NET has one build engine and one package format: the choice is between front ends, not between ecosystems.
| Concept | .NET | Maven / Gradle | pip / Poetry |
|---|---|---|---|
Build description |
|
|
|
Shared configuration |
|
parent POM / convention plugins |
none (per project) |
Dependency declaration |
|
|
|
Resolution rule |
lowest applicable version, nearest wins |
Maven: nearest wins; Gradle: highest wins |
pip/Poetry: a full resolver (backtracking) |
Lock file |
|
Gradle dependency locking; none in Maven |
|
Package and repository |
|
|
wheel/sdist on PyPI or an index |
Build lifecycle |
targets ( |
phases (Maven) / tasks (Gradle) |
PEP 517 backend hooks |
Wrapper / version pin |
|
|
|
Migrating old projects
Two migrations are usually needed before a .NET Framework code base can use the dotnet CLI, and they are independent:
-
packages.config→PackageReference. Visual Studio’s Migrate packages.config to PackageReference command (on thepackages.configfile or the References node) moves the dependencies into the project file, keeping only the top-level packages, and backs up the old files. The documented limitations: migration "is not currently available for C++ and ASP.NET projects", migrated projects need Visual Studio 2017 or later, some packages (for example those that rely oninstall.ps1scripts or content transformations) are not fully compatible, andallowedVersionsconstraints have noPackageReferenceequivalent. The step-by-step procedure and the limitations are on nuget.exe, Visual Studio and packages.config. -
Non-SDK project → SDK-style project. Replace the project body by
<Project Sdk="Microsoft.NET.Sdk">with<TargetFramework>net48</TargetFramework>(or a multi-target list), delete the explicitCompilelist andAssemblyInfo.cs, and keep only what differs from the defaults (SDK-Style Project Files). An SDK-style project can still target .NET Framework, so this step does not force a runtime change; retargeting tonet10.0is a third, separate step.
Microsoft’s .NET Upgrade Assistant automates parts of both; it is listed on Ecosystem and Learning Resources.
Common mistakes
-
Mixing
nuget.exe restoreanddotnet restorein one repository — they can resolve differently (packages.config vs PackageReference); pick one per project format. -
Assuming
msbuild.exeanddotnet buildload the same tasks — a task built fornet10.0cannot load inmsbuild.exe; one built for .NET Framework cannot load indotnet build. -
Scripts that call
nuget.exeon Linux agents — requires Mono; use thedotnet nugetcommands. -
Expecting Package Manager Console commands in a normal PowerShell — they exist only inside Visual Studio.
-
Using
dotnet add packagein new scripts — it still works but is the older, verb-first form; .NET 10 documentsdotnet package add.
Release notes
-
.NET 8:
dotnet packanddotnet publishdefault to theReleaseconfiguration (breaking change). -
.NET 10: noun-first commands (
dotnet package add/list/remove/update,dotnet reference add); the verb-first forms remain as aliases. -
nuget.exe 7.9.0 (2026-08-11) is the current recommended
nuget.exe.