I was setting up an Azure Pipelines build for a client on a self-hosted agent. The solution is a mix of old-style .NET Framework projects and newer SDK-style projects that want the .NET 10 SDK, and it uses one of the new .slnx solution files. All of that needs Visual Studio 2026. So Visual Studio 2026 went on the agent, I queued the build, and the restore step spat out a wall of these:
warning NU1503: Skipping restore for project 'C:\agent\_work\1\s\Something\Something.csproj'. The project file may be invalid or missing targets required for restore.
One for every SDK-style project in the solution. And then the build carried on without them, because NU1503 is a warning, not an error. The old-style projects built. The new ones just...weren't there.
The Short Version
NU1503: Skipping restore for projecton SDK-style projects is a warning, so the build keeps going without those projects. Don't miss it.- The
VSBuild@1task withvsVersion: latestrunsvswhere -version [17.0,18.0). That range means "newest Visual Studio 2022." VS 2026 is 18.x and never gets considered. - VS 2022's MSBuild can't load the .NET 10 SDK, so every project that needs it gets skipped.
- Fix: locate MSBuild yourself with
vswhere.exein a PowerShell step and call it directly. Printmsbuild -versionso the log shows which one ran. - This recurs at every Visual Studio major release. Owning the lookup is the durable fix.
What NU1503 Actually Means Here
The message says "the project file may be invalid or missing targets required for restore." The project files were fine. What it really meant was "the MSBuild that's running can't load the SDK these projects ask for, so I'm pretending they don't exist."
The build was running under Visual Studio 2022's MSBuild. Visual Studio 2022 (17.x) can't load the .NET 10 SDK. VS 2026 was right there on the box, but nobody was using it.
Why the VSBuild Task Picked VS 2022
The pipeline used the standard VSBuild@1 task with vsVersion: latest. "Latest" sounds like it means "the newest one you can find." Look at what the task actually ran, though. It's right there in the log:
vswhere.exe -version [17.0,18.0) -latest -format json
That's a version range. [17.0,18.0) means "17.0 or higher, but less than 18.0." In other words, "the newest Visual Studio 2022." Visual Studio 2026 is 18.x, so it's outside the range and the task never considers it. "Latest" has a ceiling and the ceiling is whatever the task knew about when it shipped.
This isn't a bug so much as lag. The build tasks have to be updated to know about each new Visual Studio major, and if you're on Azure DevOps Server (on-prem) you get task updates when the server gets patched, not when Microsoft publishes them. So there's a window after every new Visual Studio release where the tasks are behind. Right now we're in that window for VS 2026.
(For what it's worth, the MSBuild@1 task uses the same helper library, so switching tasks doesn't help.)
The Fix: Find MSBuild Yourself
Stop depending on the task to locate MSBuild. Visual Studio ships a tool called vswhere.exe that's built for exactly this and it's always at the same path. Two PowerShell steps replace the VSBuild task:
- task: PowerShell@2
displayName: Find MSBuild (VS 2026)
inputs:
targetType: inline
pwsh: true
script: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$msbuild = & $vswhere -latest -prerelease -products * -version '[18.0,19.0)' ` -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe' |
Select-Object -First 1
if (-not $msbuild) { throw "No Visual Studio 2026 (18.x) with MSBuild found on this agent" }
"Using $msbuild"
& $msbuild -version
Write-Host "##vso[task.setvariable variable=msbuildPath]$msbuild"
- task: PowerShell@2
displayName: Build
inputs:
targetType: inline
pwsh: true
script: |
& "$(msbuildPath)" MySolution.slnx -restore -p:RestorePackagesConfig=true ` -p:Configuration=Release "-p:Platform=Any CPU" -m -nologo
if ($LASTEXITCODE -ne 0) { throw "msbuild exited with $LASTEXITCODE" }
A few notes on that:
-version '[18.0,19.0)'pins it to VS 2026. When VS 2027 (or whatever they call it) comes out, you'll change that one string. If you'd rather it always take the newest, drop the-versionargument entirely.-products *makes it look at Community, Professional, Enterprise and Build Tools. Without it, Build Tools installs get skipped.-prereleaselets it find a Preview install. Take it out if you only want release builds of Visual Studio.- The first step prints
msbuild -version, so the log tells you which one ran. That's the line I wish I'd had on the first run. -restoreplus-p:RestorePackagesConfig=truerestores bothPackageReferenceand oldpackages.configprojects in one go, which matters if you've got a mixed solution like I did.dotnet buildcan't do thepackages.confighalf.
Once that was in, all the SDK-style projects restored and built. Same agent, same solution, same code. The only thing that changed was which MSBuild got called.
How to Tell This Is Your Problem
If you're seeing NU1503 on SDK-style projects and you're not sure this is it:
- Find the
vswhere.exeline in the build log and look at the-versionrange. - Find the
msbuild.exeline right after it and look at the path. If it says\2022\and you were expecting 2026, that's it. - On the agent, run
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -all -products * -property installationPathto see every Visual Studio the box actually has.
Summary
- Installing Visual Studio 2026 on your build agent isn't enough. The
VSBuild@1task (andMSBuild@1) searches a hard-coded version range of[17.0,18.0)and will keep picking Visual Studio 2022 until the task is updated. - When VS 2022's MSBuild meets SDK-style projects that need the .NET 10 SDK, you get
NU1503: Skipping restorewarnings, not errors, and those projects silently drop out of the build. - Locate MSBuild yourself with
vswhere.exein a PowerShell step, then call it directly. Printmsbuild -versionso the log shows which one ran. - This will happen again at the next Visual Studio major. Owning the MSBuild lookup is the durable fix.
I hope this helps.
-Ben
If you're trying to get an old codebase building on a modern pipeline, that's the kind of work I do. Let's talk.