Resolve Windows metadata from .NET SDK ref pack on hosts with no registered Windows SDK#2486
Open
nmetulev wants to merge 3 commits into
Open
Resolve Windows metadata from .NET SDK ref pack on hosts with no registered Windows SDK#2486nmetulev wants to merge 3 commits into
nmetulev wants to merge 3 commits into
Conversation
The shipped MSBuild targets default `CsWinRTWindowsMetadata` to a bare Windows SDK version (`$(WindowsSDKVersion)`/`$(TargetPlatformVersion)`), which `cswinrt.exe` can only resolve via the registry (`HKLM\...\Windows Kits\Installed Roots\KitsRoot10`). On hosts with no registered Windows SDK (clean CI agents, containers, SDK-less dev machines) that lookup throws "Could not find the Windows SDK in the registry", cascading into downstream (e.g. XAML) build failures. Add `CsWinRTResolveWindowsMetadataFromSdkRefPack`, which - only when no Windows SDK is registered and the value is still the bare-version default - repoints `CsWinRTWindowsMetadata` at the `\winmd` folder of the `Microsoft.Windows.SDK.NET.Ref` projection ref pack the .NET SDK already restores for `net*-windows10.0.x` TFMs (resolved robustly via `@(ResolvedTargetingPack)`, versioned to `$(TargetPlatformVersion)`). Registered -SDK builds (Visual Studio, SDK-installed CI) and explicit `CsWinRTWindowsMetadata` values are unaffected. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f8b6648-c42b-4fca-98a7-2025142b37bd
manodasanW
reviewed
Jul 18, 2026
Document (in the targets comment) that @(ResolvedTargetingPack) resolves the actually-restored Microsoft.Windows.SDK.NET.Ref pack via the SDK's own targeting-pack resolution, so it tolerates ref-pack versions that do not string-match $(TargetPlatformVersion) (e.g. a 10.0.26100.67-preview pack for a project targeting 10.0.26100.0) - which an exact-version lookup would miss. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f8b6648-c42b-4fca-98a7-2025142b37bd
Member
|
Thanks for the change, kicked off CI. |
Co-authored-by: Manodasan Wignarajah <manodasan@hotmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2485
Problem
On hosts with no registered Windows SDK (clean CI agents, containers, SDK-less/VS-less developer machines), C#/WinRT projection and authoring fail early with:
which cascades into downstream failures (e.g. WinUI/WMC XAML compilation).
Root cause
nuget/Microsoft.Windows.CsWinRT.targetsdefaultsCsWinRTWindowsMetadatato a bare Windows SDK version, sourced from$(WindowsSDKVersion)then$(TargetPlatformVersion)(e.g.10.0.19041.0). That bare version is passed tocswinrt.exeas-input <version>. Insrc/cswinrt/cmd_reader.h, thefiles()resolver treats a bareN.N.N.Nversion by callingget_sdk_path()->open_sdk(), which readsHKLM\SOFTWARE\Microsoft\Windows Kits\Installed Roots\KitsRoot10from the registry and throws when no SDK is installed.The tool is not at fault: it already accepts
-input <folder>,-input <file>, and-input local. The fragility is that the targets pick a default that only the registry can resolve.Fix
Add a gated target
CsWinRTResolveWindowsMetadataFromSdkRefPack. When the bare-version default would otherwise be used and no Windows SDK is registered, it repointsCsWinRTWindowsMetadataat the\winmdfolder of theMicrosoft.Windows.SDK.NET.Refprojection ref pack that the .NET SDK already auto-restores fornet*-windows10.0.xTFMs. Those winmds are versioned to$(TargetPlatformVersion)(more correct thanlocal, which would yield the running OS's winmds).The ref-pack path is resolved robustly via
@(ResolvedTargetingPack)(filtered byNuGetPackageId == Microsoft.Windows.SDK.NET.Ref, then%(Path)\winmd) - not by globbing the NuGet cache. Setting the singleCsWinRTWindowsMetadataproperty covers both the projection response file (-input $(CsWinRTWindowsMetadata)) and the authoring source generator (which reads theCsWinRTWindowsMetadatacompiler-visible property).Why item-based resolution (not a version scan)
The ref-pack folder is resolved from the actually-restored targeting pack via
@(ResolvedTargetingPack), not by scanning for a pack whose version equals$(TargetPlatformVersion). Confirmed live on an SDK-less ARM64 host: the restored pack wasMicrosoft.Windows.SDK.NET.Ref 10.0.26100.67-previewwhile the project'sTargetPlatformVersionwas10.0.26100.0— they do not string-match. Because@(ResolvedTargetingPack)goes through the SDK's own targeting-pack resolution, it picks up preview/suffix-versioned packs correctly, whereas an exact-version lookup would miss them.Gating (conservative)
The override takes effect only when all of the following hold:
CsWinRTWindowsMetadatais still the bare-version default: non-empty, not one of the tool's own sentinels (local/sdk/sdk+), and does not point at an existing path (explicit user file/folder values are left untouched).KitsRoot10registry value is empty (read via[MSBuild]::GetRegistryValueFromView(...), mirroring the 32-bit-then-default view ordercswinrt.exeuses).Microsoft.Windows.SDK.NET.Refref pack was resolved and its\winmdfolder exists.As a result, machines with a registered Windows SDK (Visual Studio, SDK-installed CI) keep their existing behavior byte-for-byte; only SDK-less hosts change.
Design tradeoff considered
An alternative is to always prefer the ref-pack winmds for modern TFMs (not just when the SDK is unregistered). That is arguably more consistent (build metadata always matches the restored, versioned ref pack rather than whatever SDK happens to be installed), but it changes behavior for the large population of registered-SDK/VS builds and risks subtle regressions in existing pipelines. I chose the gated approach to fix the broken scenario with zero impact on working ones; the always-prefer option can be revisited separately if desired.
Validation
Validated on a genuine SDK-less host (Windows ARM64, .NET SDK 10.0.302,
KitsRoot10registry value empty, no Windows Kits installed).Tool level (shipped
cswinrt.exefrom theMicrosoft.Windows.CsWinRT2.2.0 package):-input 10.0.26100.0(bare version) ->error: Could not find the Windows SDK in the registry(exit 1) - reproduces the root cause.-input <Microsoft.Windows.SDK.NET.Ref>\winmd(the folder this fix resolves) -> exit 0, projection sources generated. Confirms the tool consumes the ref-pack folder with no registered SDK.End-to-end through the real package targets (a
net8.0-windows10.0.26100.0authoring component referencingMicrosoft.Windows.CsWinRT2.2.0, whose shipped defaultCsWinRTWindowsMetadatalogic is identical to this PR's):dotnet buildfails witherror MSB3073 ... cswinrt.exe ... exited with code 1/Could not find the Windows SDK in the registry(the targets invoke the tool with the bare version).Directory.Build.targets): the target fires, emitsno Windows SDK is registered; using Windows metadata from the .NET SDK projection ref pack: '...\microsoft.windows.sdk.net.ref\10.0.26100.57\winmd', the registry error is gone, projection runs, Build succeeded, and the authored.winmdis produced.Isolated MSBuild logic (three gate cases): SDK-less + bare default -> rewritten to the ref-pack
\winmd; simulated registered SDK (non-emptyKitsRoot10) -> bare version preserved (no regression); explicit user path -> preserved unchanged. Edited targets file XML confirmed well-formed.Not run: a full native
cswinrt.exe/ master-package build (the native tool is unchanged and its-inputfolder handling insrc/cswinrt/cmd_reader.his the same in 2.x and master, so the injected-target test on the 2.2.0 package is representative), and builds on a registered-SDK machine (the gate is specifically a no-op there - only SDK-less hosts are affected).Related
A complementary short-term shim is being added in the winapp CLI (auto-injecting
CsWinRTWindowsMetadataon SDK-less hosts). That shim is temporary - this upstream targets fix is the durable solution.