Skip to content

Work around MSB4216 NET task host drive-casing failure in CodeQL build#9371

Closed
AlesProkop wants to merge 1 commit into
microsoft:mainfrom
AlesProkop:workaround/codeql-msb4216-drive-casing
Closed

Work around MSB4216 NET task host drive-casing failure in CodeQL build#9371
AlesProkop wants to merge 1 commit into
microsoft:mainfrom
AlesProkop:workaround/codeql-msb4216-drive-casing

Conversation

@AlesProkop

Copy link
Copy Markdown
Member

Problem

The CodeQL pipeline (and other .NET Framework MSBuild host scenarios on certain hosted CI agents) fails with MSB4216 when Arcade's InstallDotNetCore task launches the .NET task host:

error MSB4216: Could not run the "InstallDotNetCore" task because MSBuild could not
create or connect to a task host with runtime "NET" and architecture "x86".

Root cause (see dotnet/msbuild#14026): the task host handshake salt is a case-sensitive hash of the SDK tools directory. The .NET Framework host derives it from $(NetCoreSdkRoot) (drive-letter casing propagated verbatim from the environment, e.g. ADO's D:\a\_work\1\s), while the .NET task host child resolves its own path via Environment.ProcessPath, which on some agents reports the volume's canonical lowercase drive (d:). The salts then differ and the handshake fails. The failure is agent-specific, which is why it reproduces intermittently.

Fix (temporary)

An InitialTargets target in eng/Versions.props rewrites only the drive-letter casing of $(NetCoreSdkRoot) to match the casing the child task host will use. It learns that casing by launching the SDK's own dotnet host (dotnet.exe — the runtime host, which always runs and lives on the same volume as the SDK) and reading GetModuleFileNameW(NULL), the exact API behind the child's Environment.ProcessPath. The detected drive letter is adopted only when it is the same letter differing solely in case, so it can never change the actual drive — only its casing. Windows-only, cached, and a safe no-op on agents that already work.

Knobs:

  • DisableNetCoreSdkRootDriveCasingWorkaround=true — skip entirely.
  • NetCoreSdkRootDriveCasingOverride=lower|upper — force the casing without probing (emergency/testing).

Validation

Reproduced and validated on the actual failing agent type via the microsoft-testfx CodeQL pipeline: the probe resolved d:, the target rewrote NetCoreSdkRoot D:d:, and MSB4216 was eliminated (two independent runs that both landed on a "bad" agent went green; an Override=lower run also confirmed the salt reads the live, target-mutated property).

Temporary / follow-up

This is a stopgap. The permanent fixes are:

Remove this once both are in.

Temporary stopgap: rewrite the drive-letter casing of NetCoreSdkRoot to match
the .NET task host child, learned via the SDK's dotnet host. Remove once the
permanent fix (dotnet/arcade#17039, dotnet/msbuild#14027) flows in.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 23, 2026 10:47
@AlesProkop

Copy link
Copy Markdown
Member Author

@microsoft-github-policy-service agree company="Microsoft"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a temporary MSBuild-time workaround in eng/Versions.props to prevent intermittent MSB4216 failures in .NET Framework MSBuild task-host scenarios (notably CodeQL), by normalizing the drive-letter casing of $(NetCoreSdkRoot) to match what the child task host resolves.

Changes:

  • Adds InitialTargets="NormalizeNetCoreSdkRootCasing" so the normalization runs early in Full MSBuild runs.
  • Introduces an inline RoslynCodeTaskFactory task that probes the SDK’s dotnet.exe process path to learn the volume’s canonical drive casing and rewrites $(NetCoreSdkRoot) casing-only.
  • Adds opt-out / override properties (DisableNetCoreSdkRootDriveCasingWorkaround, NetCoreSdkRootDriveCasingOverride).

Comment thread eng/Versions.props
Comment on lines +199 to +203

string tasksDll = Path.Combine(sdkRoot, "Microsoft.Build.Tasks.Core.dll");
string tempDir = Path.Combine(Path.GetTempPath(), "sdkdrvprobe_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDir);
string proj = Path.Combine(tempDir, "probe.proj");
Comment thread eng/Versions.props
Comment on lines +235 to +256
var psi = new ProcessStartInfo
{
FileName = dotnetExe,
Arguments = "msbuild \"" + proj + "\" -nologo -nodeReuse:false -v:m -t:P",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WorkingDirectory = tempDir,
};

using (var p = Process.Start(psi))
{
string stdout = p.StandardOutput.ReadToEnd();
string stderr = p.StandardError.ReadToEnd();
if (!p.WaitForExit(120000))
{
try { p.Kill(); } catch { }
Log.LogMessage(MessageImportance.Low, "NormalizeSdkRootDriveCasing: probe timed out launching '{0}'.", dotnetExe);
return '\0';
}

@Evangelink

Copy link
Copy Markdown
Member

Closing for now, I'll wait for the arcade fix to flow.

@Evangelink Evangelink closed this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants