Installing and Selecting SDKs
|
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. |
Every dotnet command that builds, tests or packs runs inside one specific .NET SDK, and a machine usually has
several. Which one runs decides the C# compiler, the MSBuild version, the NuGet client and the defaults of every
property on this site, so a repository should state it rather than inherit whatever is newest on each machine. This
page installs SDKs side by side and pins one with global.json, using .NET SDK 10.0.402 (the baseline) and SDK
9.0.319 installed next to it.
Installing SDKs
| Method | Use it for |
|---|---|
Operating-system packages (Windows installer, |
Developer machines. One SDK per feature band is typical; updates come through the OS. |
The |
CI and non-admin installs: any version ( |
Container images ( |
Builds in containers; the tag pins the SDK. |
|
A user-level toolchain manager that installs SDKs and runtimes, follows channels, and can install the SDK requested
by the nearest |
SDKs install side by side under one dotnet root (sdk/<version>/), sharing the runtimes in shared/. This page
added SDK 9.0.319 to an existing 10.0.402 installation with the install script:
./dotnet-install.sh --version 9.0.319 --install-dir ~/.dotnet --no-path
dotnet --list-sdks
dotnet-install: Installation finished successfully.
9.0.319 [~/.dotnet/sdk]
10.0.402 [~/.dotnet/sdk]
The script does not verify that the operating system’s native dependencies are present (it says so in its output),
and it does not configure PATH with --no-path. In CI, prefer the setup action of the CI system (for example
actions/setup-dotnet), which runs the same script and reads global.json
(CI Builds and Caching).
|
Run the install scripts only from |
How the SDK is selected
dotnet is a small native muxer. For an SDK command it searches for global.json from the current working
directory upwards; without one, it runs the highest installed SDK, including previews. With one, it applies the
file’s version and roll-forward policy.
from the current folder upwards?} find -- no --> latest[Use the highest installed SDK] find -- yes --> valid{File valid?} valid -- no --> ignored[Ignore it; use the highest installed SDK] valid -- yes --> ver{sdk.version set?} ver -- no --> latest ver -- yes --> paths[Search the folders in sdk.paths,
or the dotnet root by default] paths --> roll{A version matching
version + rollForward?} roll -- yes --> run([Run that SDK]) roll -- no --> fail([Fail: errorMessage, or
A compatible .NET SDK was not found])
A loop over the global.json variants, run in an empty folder on the machine above:
dotnet --version
for g in '{"sdk":{"version":"9.0.319"}}' \
'{"sdk":{"version":"9.0.300","rollForward":"latestPatch"}}' \
'{"sdk":{"version":"9.0.100","rollForward":"latestFeature"}}' \
'{"sdk":{"version":"8.0.100","rollForward":"latestMajor"}}' \
'{"sdk":{"version":"10.0.402","rollForward":"disable"}}'; do
echo "$g" > global.json; echo "$g -> $(dotnet --version)"
done
10.0.402
{"sdk":{"version":"9.0.319"}} -> 9.0.319
{"sdk":{"version":"9.0.300","rollForward":"latestPatch"}} -> 9.0.319
{"sdk":{"version":"9.0.100","rollForward":"latestFeature"}} -> 9.0.319
{"sdk":{"version":"8.0.100","rollForward":"latestMajor"}} -> 10.0.402
{"sdk":{"version":"10.0.402","rollForward":"disable"}} -> 10.0.402
When nothing matches — 9.0.100 with the default policy (no 9.0.1xx SDK is installed), 8.0.100 without
latestMajor, or 10.0.400 with disable — every command fails before MSBuild starts:
The command could not be loaded, possibly because:
* You intended to execute a .NET application:
The application '--version' does not exist or is not a managed .dll or .exe.
* You intended to execute a .NET SDK command:
A compatible .NET SDK was not found.
Requested SDK version: 9.0.100
global.json file: ~/sel/global.json
Installed SDKs:
9.0.319 [~/.dotnet/sdk]
10.0.402 [~/.dotnet/sdk]
Install the [9.0.100] .NET SDK or update [~/sel/global.json] to match an installed SDK.
Learn about SDK resolution:
https://aka.ms/dotnet/sdk-not-found
Every global.json key
| Key | Since | Meaning |
|---|---|---|
|
3.0 |
The full SDK version ( |
|
3.0 |
How far a later SDK may be used; values below. Default |
|
3.0 |
Whether previews are considered. Default |
|
10.0 |
Folders to search, in order, relative to |
|
10.0 |
Replaces the "A compatible .NET SDK was not found" text. |
|
8.0.400 |
Pins the workload set that |
|
— |
Versions for MSBuild project SDKs such as |
|
10.0 |
|
The rollForward values, for a version written x.y.znn (major, minor, feature band z, patch nn):
| Value | Behaviour |
|---|---|
|
The version, else the latest patch in the same feature band; else fail. |
|
The latest patch in the same band, else the next higher band of the same |
|
As |
|
As |
|
The highest patch |
|
The highest band and patch |
|
The highest minor, band and patch |
|
The highest installed SDK |
|
Exactly the version. |
The latest* values always take the newest match; the others take the nearest match and move further only when
nothing nearer is installed. Ledgerline uses 10.0.402 with latestPatch: every developer and CI agent runs a
10.0.4xx SDK at or above 402, and nobody moves to 10.0.5xx by accident. The documentation adds that repositories using
NuGet lock files should consider disable, so that the SDK (whose pruning data affects the restore graph) and the
lock file change together (Lock
Files and Locked Mode).
global.json accepts // and /* */ comments. dotnet new globaljson --sdk-version 10.0.402 --roll-forward
latestPatch writes one.
Source: global.json schema, sdk, msbuild-sdks, test, Comments in global.json, Control roll-forward behavior.
A custom error message
errorMessage tells a contributor what to do instead of printing a version mismatch:
{
"sdk": {
"version": "10.0.500",
"rollForward": "latestFeature",
"errorMessage": "Ledgerline needs the .NET SDK 10.0.500 or later. Run ./eng/install-sdk.sh."
}
}
The command could not be loaded, possibly because:
* You intended to execute a .NET application:
The application '--version' does not exist or is not a managed .dll or .exe.
* You intended to execute a .NET SDK command:
Ledgerline needs the .NET SDK 10.0.500 or later. Run ./eng/install-sdk.sh.
Source: global.json: sdk: errorMessage.
Invalid files are ignored, not rejected
A global.json that cannot be used does not stop the build. "version": "10.0" and "version": "ten" both
built with the latest SDK and no warning; only dotnet --info reports it:
global.json file:
Invalid [~/sel3/global.json]
Version '10.0' is not valid for the 'sdk/version' value
Invalid global.json is ignored for SDK resolution.
A JSON syntax error is worse: a trailing comma ({"sdk":{"version":"10.0.402",}}) crashed dotnet build with
System.TypeInitializationException: The type initializer for 'Microsoft.DotNet.Cli.Parser' threw an exception and a
JsonException about the trailing comma. Check dotnet --version in CI right after the SDK is installed, and fail
the job if it is not the expected version.
Source: global.json overview.
Testing a prerelease SDK locally
paths (SDK 10.0 and later muxers) lets a repository use an SDK that is not installed globally — for example a .NET
11 preview — by installing it into a repository folder:
{
"sdk": {
"version": "9.0.300",
"rollForward": "latestFeature",
"paths": [ ".dotnet", "$host$" ],
"errorMessage": "Run ./eng/install-sdk.sh to install the SDK into .dotnet/."
}
}
The folder must be a complete dotnet root, with the runtime the SDK runs on. With only .dotnet/sdk/9.0.319/
present, the muxer selected the SDK and then failed:
You must install or update .NET to run this application.
App: ~/.dotnet/sdk/9.0.319/dotnet.dll
Architecture: x64
Framework: 'Microsoft.NETCore.App', version '9.0.21' (x64)
.NET location: ~/paths/.dotnet/
No frameworks were found.
After installing the SDK into .dotnet with dotnet-install.sh --install-dir .dotnet, the repository runs it while
the rest of the machine keeps 10.0.402:
dotnet --version
dotnet --info | sed -n '/^.NET SDK:/,/Base Path/p'
9.0.319
.NET SDK:
Version: 9.0.319
Commit: 9de5af5aa0
Workload version: 9.0.300-manifests.f33e7c40
MSBuild version: 17.14.60+43b635718
Runtime Environment:
OS Name: ubuntu
OS Version: 24.04
OS Platform: Linux
RID: linux-x64
Base Path: ~/paths/.dotnet/sdk/9.0.319/
paths affects SDK commands only (dotnet build, dotnet run, …). An app started with its apphost or with
dotnet app.dll still resolves runtimes from the normal locations. Add .dotnet/ to .gitignore.
Checking for updates
dotnet sdk check
.NET SDKs:
Version Status
----------------------------------------------------
9.0.319 .NET 9.0 is going out of support soon.
10.0.402 Up to date.
.NET Runtimes:
Name Version Status
---------------------------------------------------------------------------------
Microsoft.NETCore.App 8.0.32 .NET 8.0 is going out of support soon.
Microsoft.AspNetCore.App 9.0.21 .NET 9.0 is going out of support soon.
Microsoft.NETCore.App 9.0.21 .NET 9.0 is going out of support soon.
Microsoft.AspNetCore.App 10.0.13 Up to date.
Microsoft.NETCore.App 10.0.13 Up to date.
Source: dotnet sdk check.
NETSDK1141
The MSBuild SDK resolver — used when Visual Studio or msbuild.exe loads a project — reports SDK-selection failures
as NETSDK1141: "Unable to resolve the .NET SDK version as specified in the global.json located at …". Its causes
are the same as the muxer failure above: a malformed version, an SDK that is not installed, or a wrong path. It did
not appear from the dotnet CLI in this environment. The muxer fails first when the current folder’s global.json
cannot be satisfied, and a project in a subfolder with an unsatisfiable global.json built with the SDK the muxer had
already chosen — even though the global.json page says the project SDK resolver starts its search from the solution
or project folder. Run dotnet commands from the repository root, where the global.json is.
Common mistakes
-
No
global.json— every machine builds with its newest SDK, including previews. -
A partial version (
10.0) — silently ignored. -
rollForwardleft at the defaultpatchwith a version from another band — fails as soon as that band is not installed. -
latestMajor"to be safe" — the next major SDK changes analyzers, defaults and warnings without a commit. -
A
.dotnetfolder with onlysdk/— "No frameworks were found". -
Running commands from a subfolder with its own
global.json— a different SDK than CI.