Choosing and Combining the Tools

This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its dotnet CLI) and NuGet 7.9, as published in the MSBuild, .NET and NuGet documentation on learn.microsoft.com, which is the reference these pages are written and verified against. Material about .NET 11 / NuGet 8.0 is still in preview and is always flagged as such — never presented as baseline.

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

dotnet CLI (.NET SDK)

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

msbuild.exe (Visual Studio, Build Tools)

Projects that need the .NET Framework MSBuild: non-SDK .csproj, C++/CLI, WPF/WinForms projects with Visual Studio components, build tasks compiled for .NET Framework. Also what Visual Studio itself runs.

Windows

nuget.exe

packages.config projects (restore, install, update), .nuspec-only packing, a few commands without a dotnet equivalent (mirror/replicate, init). Runs on macOS/Linux only under Mono, with limitations.

Windows (Mono elsewhere)

Visual Studio / Package Manager UI

Interactive package management, design-time builds, publish profiles.

Windows

Package Manager Console (PMC)

PowerShell commands (Install-Package, Update-Package) inside Visual Studio; does not work in other PowerShell hosts.

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

dotnet restore

msbuild -t:restore; nuget restore (also packages.config)

automatic on open/build

Build

dotnet build

msbuild -restore (default target Build)

Build > Build Solution

Test

dotnet test

msbuild -t:VSTest (VSTest mode)

Test Explorer

Pack

dotnet pack

msbuild -t:Pack; nuget pack x.nuspec

Pack on the project

Publish an app

dotnet publish

msbuild -t:Publish

Publish with a profile

Add a package

dotnet package add X (.NET 10; formerly dotnet add package X)

nuget install X (packages.config only)

Package Manager UI; Install-Package X

Update a package

dotnet package update X (.NET 10)

nuget update

UI; Update-Package X

List packages

dotnet package list [--outdated|--vulnerable]

 — 

UI; Get-Package

Push

dotnet nuget push

nuget push

 — 

Sources

dotnet nuget add|list|enable|disable|update|remove source

nuget sources

Tools > Options > NuGet Package Manager > Package Sources

Clear caches

dotnet nuget locals all --clear

nuget locals all -clear

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

.csproj (MSBuild XML) + project SDK

pom.xml / build.gradle(.kts)

pyproject.toml

Shared configuration

Directory.Build.props/.targets

parent POM / convention plugins

none (per project)

Dependency declaration

PackageReference, versions in Directory.Packages.props

<dependency> / dependencies {}, BOMs, version catalogs

dependencies / [tool.poetry.dependencies]

Resolution rule

lowest applicable version, nearest wins

Maven: nearest wins; Gradle: highest wins

pip/Poetry: a full resolver (backtracking)

Lock file

packages.lock.json (opt-in)

Gradle dependency locking; none in Maven

pylock.toml / poetry.lock

Package and repository

.nupkg on nuget.org or a NuGet feed

.jar on Maven Central or a repository

wheel/sdist on PyPI or an index

Build lifecycle

targets (Restore, Build, Pack, Publish)

phases (Maven) / tasks (Gradle)

PEP 517 backend hooks

Wrapper / version pin

global.json

mvnw / gradlew

requires-python, poetry version

Migrating old projects

Two migrations are usually needed before a .NET Framework code base can use the dotnet CLI, and they are independent:

  1. packages.config → PackageReference. Visual Studio’s Migrate packages.config to PackageReference command (on the packages.config file 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 on install.ps1 scripts or content transformations) are not fully compatible, and allowedVersions constraints have no PackageReference equivalent. The step-by-step procedure and the limitations are on nuget.exe, Visual Studio and packages.config.

  2. 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 explicit Compile list and AssemblyInfo.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 to net10.0 is 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 restore and dotnet restore in one repository — they can resolve differently (packages.config vs PackageReference); pick one per project format.

  • Assuming msbuild.exe and dotnet build load the same tasks — a task built for net10.0 cannot load in msbuild.exe; one built for .NET Framework cannot load in dotnet build.

  • Scripts that call nuget.exe on Linux agents — requires Mono; use the dotnet nuget commands.

  • Expecting Package Manager Console commands in a normal PowerShell — they exist only inside Visual Studio.

  • Using dotnet add package in new scripts — it still works but is the older, verb-first form; .NET 10 documents dotnet package add.

Release notes

  • .NET 8: dotnet pack and dotnet publish default to the Release configuration (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.