A Windows PC that has seen a few years of .NET work carries several SDKs and more runtimes, and two
questions decide whether a build works: which SDK builds this project, and which runtime runs the
result. This part answers both with the commands, on a PC with four SDKs and six runtimes, pins and
unpins an SDK with global.json, breaks a build on purpose with NETSDK1045, runs an app
where its runtime is missing, and reads the event log of the morning Visual Studio replaced the whole
.NET 10 toolset underneath a running build.
dotnet --list-sdksanddotnet --list-runtimes, thendotnet --versionin an empty folder: the highest SDK wins when nothing pins itdotnet new globaljson --sdk-version 10.0.402in a project, then edit the version to one you do not have and read the error- Add
"rollForward": "latestFeature", then"latestMajor", and watch which SDKdotnet --versionpicks - Pin 9.0.317 and build a net10.0 project: NETSDK1045
- Build a net8.0 app and run its dll in the
sdk:10.0container, with and withoutDOTNET_ROLL_FORWARD=Major dotnet nuget locals all --listand the size of your global packages folderdotnet new tool-manifestand a localdotnet-efnext to the global one
What is installed, and which one answers
This PC lists four SDKs, 8.0.426, 9.0.317, a leftover 10.0.100 release candidate and 10.0.402, and
six runtime versions from 6.0.36 to 10.0.13. In an empty folder dotnet --version printed
10.0.402: with no global.json anywhere above the folder, the highest installed
SDK is used. The documentation says the same, "the highest installed SDK version is used (equivalent
to setting rollForward to latestMajor)", and adds the detail people miss: the search "starts from the
current working directory, which isn't necessarily the same as the project directory". Pin an SDK in
a parent folder and every project under it changes.
Pinning an SDK, and the error when it is not there
dotnet new globaljson --sdk-version 10.0.402 writes a file with only that version. Edit it
to 10.0.300, a version this PC does not have, and nothing works any more, not even
dotnet --version:
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: 10.0.300
global.json file: C:\...\SdkLab\global.json
Installed SDKs:
8.0.426 [C:\Program Files\dotnet\sdk]
9.0.317 [C:\Program Files\dotnet\sdk]
10.0.100-rc.1.25451.107 [C:\Program Files\dotnet\sdk]
10.0.402 [C:\Program Files\dotnet\sdk]
Install the [10.0.300] .NET SDK or update [C:\...\SdkLab\global.json] to match an installed SDK.
Why does 10.0.402 not satisfy a request for 10.0.300? Because a version with no
rollForward means the patch policy, which the documentation defines as "Uses
the specified version. If not found, rolls forward to the latest patch level. If not found, fails."
The feature band is the hundreds digit: 10.0.402 is band 4, the request was band 3, and patch
roll-forward stays inside the band. Pinning 10.0.400 with no policy picked 10.0.402, which is the
patch roll-forward working as described. The paths array that lets a global.json point at
local SDK folders is new in the .NET 10 SDK and was not tested here.
| global.json | dotnet --version picked |
|---|---|
| 10.0.300, no rollForward | fails, as above |
| 10.0.300 with latestFeature | 10.0.402 |
| 10.0.300 with latestMajor | 10.0.402 |
| 8.0.100 with latestFeature | 8.0.426 |
| 8.0.100 with latestMajor | 10.0.402 |
| 10.0.400, no rollForward | 10.0.402 |
| 9.0.317 | 9.0.317 in the folder, 10.0.402 one level up |
The 8.0.100 pair shows the difference between the two policies: latestFeature stays on the
requested major and minor, latestMajor takes the newest SDK on the machine. The last row is
the habit that keeps a team honest: a global.json in the repository root pins the build for
everyone, and only inside that tree.
The SDK is too old for the project: NETSDK1045
With 9.0.317 pinned and a project that targets net10.0, dotnet build stops during restore:
error NETSDK1045: The current .NET SDK does not support targeting .NET 10.0. Either target .NET 9.0 or lower, or use a version of the .NET SDK that supports .NET 10.0. Download the .NET SDK from https://aka.ms/dotnet/download
Removing the pin made the same build succeed on 10.0.402. The error has a Fixes post, and the message is a clue in itself: an SDK can build any older target framework, so when it refuses a newer one, the SDK is the thing to update or unpin, not the project.
Which runtime runs the app
The SDK is for building; the runtime is what runs the result, and an app asks for a specific one. A
net8.0 console app built on this PC carries "version": "8.0.0" for
Microsoft.NETCore.App in its runtimeconfig. On the host it ran on 8.0.32, the highest 8.0
patch installed, although 8.0.30 is also present; the documentation's rule is "The host chooses the
latest patch version installed on the machine." Copied into the sdk:10.0 container, where
only a 10.0 runtime exists, it did not run:
You must install or update .NET to run this application.
App: /app/RtLab.dll
Architecture: x64
Framework: 'Microsoft.NETCore.App', version '8.0.0' (x64)
.NET location: /usr/share/dotnet/
The following frameworks were found:
10.0.10 at [/usr/share/dotnet/shared/Microsoft.NETCore.App]
The default policy is Minor: roll forward within the same major, never across it. With
DOTNET_ROLL_FORWARD=Major in the container's environment the same dll ran and printed
Runtime: 10.0.10; on the host, the same variable changed nothing and the app still reported
8.0.32, because the Major policy only moves "if requested major version is missing". So the honest
production fix is still the one in the
Fixes post: install
the runtime the app asks for, and use roll-forward as a deliberate choice, not an accident.
Where the packages go
dotnet nuget locals all --list prints four folders. On this PC the global packages folder
is not the default under the profile but C:\Nuget, because the machine-level variable
NUGET_PACKAGES says so, and it holds 932 package folders, 146,962 files and 35.58 GB. Every
project on the machine restores into it, which is why a restore of a package you used last year is
instant and why that folder is the first place to look when the disk fills up.
dotnet nuget list source showed nuget.org and the Visual Studio offline packages folder,
the two sources a Visual Studio install leaves behind.
Global tools and local tools
dotnet tool list --global showed one tool, dotnet-ef 10.0.0. In a project folder,
dotnet new tool-manifest followed by dotnet tool install dotnet-ef installed
10.0.13 into a manifest, and inside that folder tree dotnet ef --version answered 10.0.13
while one folder up it still answered 10.0.0. The manifest travels with the repository, so every
developer and the build server get the same tool version; the global tool stays whatever each PC
happened to install. One detail of SDK 10.0.402: the manifest was written as
dotnet-tools.json in the folder itself, not under a .config folder as older
documentation shows.
The morning Visual Studio replaced .NET 10
On October 14, 2026 the Visual Studio 18.11 update ran on this PC, and the Application event log
records what it did to .NET, product by product. It removed the 10.0.12 runtimes and the 10.0.401 SDK
first, starting at 09:11, and installed 10.0.13 and 10.0.402 afterwards, from 09:18. The x64 .NET
runtime was absent for five and a half minutes; the SDK toolset for seven and a half; from the first
removal to the last core install was eleven minutes forty seconds, and every build started in that
window failed with messages that had nothing to do with the code. The documentation warns about
exactly this: "Visual Studio only ever installs a single copy of the .NET SDK. So if you upgrade your
Visual Studio version, it removes the previous version of the .NET SDK it had used". The log also
shows the update finding dotnet.exe held by a running dotnet process at 09:22
and deferring a restart. Two lessons: a global.json pinned to the exact SDK version that
Visual Studio installed will break on the next Visual Studio update, so pin with
latestFeature or install the SDK from the standalone installer; and stop your dev servers
before you let the installer run. Earlier the same morning Windows Update had moved 8.0.31 to 8.0.32
the other way round, install first and remove second, with no gap at all.
Frequently asked
- How does dotnet choose which SDK to use?
- It looks for a global.json from the current folder upwards. With none, the highest installed SDK is used. With a version and no rollForward, the patch policy applies: the exact version or a later patch in the same feature band, otherwise the error A compatible .NET SDK was not found. rollForward latestFeature allows a newer feature band of the same major and minor; latestMajor allows any newer SDK.
- What does NETSDK1045 mean?
- The SDK that is building the project is older than the TargetFramework the project asks for, for example SDK 9.0.317 building net10.0. An SDK can target older frameworks but never newer ones. Remove or change the global.json pin, or install the newer SDK. The error is raised during restore, before any code is compiled.
- Why does Visual Studio remove my .NET SDK?
- Visual Studio installs a single copy of the .NET SDK and, when it updates, removes the previous version before installing the new one, even across major versions. On this PC the October 2026 update left no .NET 10 runtime for about five minutes and no toolset for seven. Pin SDKs with rollForward latestFeature rather than an exact version, or install the SDK you depend on from the standalone installer.
Next: Part 2, WSL for .NET work: a throwaway
distribution, the SDK inside it, the speed difference between /mnt/c and the Linux file
system, and reaching a server in WSL from Windows.