Merge nucleic/olive-jade-civet-rznt into dev

This commit is contained in:
2026-07-29 00:59:16 -07:00
parent e2a1d55528
commit 80f9f3667b
2 changed files with 123 additions and 25 deletions
+25 -9
View File
@@ -37,11 +37,20 @@ dotnet run -- --session # + create a session, then a SECOND one with the
dotnet run -- --all-types # include the ABI/marshalling plumbing in the dump dotnet run -- --all-types # include the ABI/marshalling plumbing in the dump
``` ```
**If it reports missing components** (`VirtualMachinePlatform`, `WslPackage`), the machine cannot **If it reports missing components**, the machine cannot run wslc yet, and the two components are
run wslc yet: `wsl --install`, reboot for the Virtual Machine Platform feature, and run again. NOT fixed the same way — the tool prints the specific remedy for each:
Everything that reaches the service fails with `REGDB_E_CLASSNOTREG` (0x80040154) until then, and
the tool says so rather than emitting a string of unexplained COM errors. `--session` is skipped | Missing | Fix |
in that state instead of failing. | --- | --- |
| `VirtualMachinePlatform` | `wsl --install`, then **reboot** (an OS optional feature). |
| `WslPackage` | `wsl --update --pre-release`, then `wsl --shutdown`. WSL is installed but older than the SDK, and **2.9.3 is pre-release-only — a plain `wsl --update` will not get there.** Confirm with `wsl --version`. |
| `SdkNeedsUpdate` | The NuGet pin is ahead of the installed service: update WSL further, or pin the package back. |
The distinction is worth knowing because the failures look alike but mean opposite things:
`REGDB_E_CLASSNOTREG` (0x80040154) is *nothing installed*, `ERROR_NOT_SUPPORTED` (0x80070032) is
*installed but too old*. The tool decodes both, along with the `WSLC_E_*` range from `wslc.idl`,
because these COM exceptions often carry an empty message and leave nothing but a hex code.
`--session` is skipped while components are missing rather than failing the same way.
Note that the assumption check reads the **`Microsoft.WSL.Containers`** namespace only. That is Note that the assumption check reads the **`Microsoft.WSL.Containers`** namespace only. That is
correctness, not tidiness: a C#/WinRT projection also exports `ABI.Microsoft.WSL.Containers.*` correctness, not tidiness: a C#/WinRT projection also exports `ABI.Microsoft.WSL.Containers.*`
@@ -54,10 +63,17 @@ It writes the full public object model to `wslc-api-dump.txt` (`--out` to reloca
It exits 0 even when assumptions fail — a mismatch is the product, not an error. Only a genuinely It exits 0 even when assumptions fail — a mismatch is the product, not an error. Only a genuinely
broken run (the assembly won't load) exits non-zero. broken run (the assembly won't load) exits non-zero.
Every path in it has been exercised against a stand-in assembly carrying the observed 2.9.3 type `--probe` has been **run clean on real hardware** (Windows 11 amd64, WSL 2.9.3): 54/54 `ok`,
and member names — including the `ABI.` shadow types and a service that reports missing components no missing components. Every other path is exercised against a stand-in assembly carrying the
and throws `0x80040154` — so a failure on your machine is a finding about wslc, not about this observed 2.9.3 shape — the `ABI.` shadow types, a service reporting missing components, an
tool. `0x80070032` with an empty message, a `ServiceVersion` with no `ToString()` override, and a
duplicate-name `Session` throwing `0x80040607` — so a failure on your machine is a finding about
wslc, not about this tool.
Note the `ToString()` one, because it bit: a WinRT projection class does not override
`ToString()`, so printing a returned object gives you its *type name*. Values are rendered by
their properties instead — `ServiceVersion { Major=2, Minor=9, Revision=3 }`, not
`Microsoft.WSL.Containers.ServiceVersion`.
**What to send back:** the console output, and `wslc-api-dump.txt` if anything is MISSING — **What to send back:** the console output, and `wslc-api-dump.txt` if anything is MISSING —
which now means the package moved under us, not that we guessed wrong. which now means the package moved under us, not that we guessed wrong.
+98 -16
View File
@@ -74,10 +74,10 @@ internal static class Program
var missing = CheckAssumptions(types); var missing = CheckAssumptions(types);
// Components are probed BEFORE anything else that touches the service. On a machine // Components are probed BEFORE anything else that touches the service, because that one
// where WSL container support isn't installed, every live call fails with // call explains every other failure: nothing installed answers REGDB_E_CLASSNOTREG
// REGDB_E_CLASSNOTREG (0x80040154) — "Class not registered" — and reporting that as a // (0x80040154), while an installed-but-too-old WSL answers ERROR_NOT_SUPPORTED
// string of mysterious COM errors would bury the one fact that explains them all. // (0x80070032). Reporting those as unexplained COM errors buries the actual finding.
var componentsMissing = probe ? ProbeStatics(types) : null; var componentsMissing = probe ? ProbeStatics(types) : null;
if (session) if (session)
{ {
@@ -85,7 +85,7 @@ internal static class Program
{ {
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("skipping --session: the components above are missing, so creating " Console.WriteLine("skipping --session: the components above are missing, so creating "
+ "a session would only fail with the same 'class not registered'."); + "a session would only fail the same way. Fix those first.");
} }
else else
{ {
@@ -97,9 +97,11 @@ internal static class Program
if (componentsMissing is { Count: > 0 }) if (componentsMissing is { Count: > 0 })
{ {
Console.WriteLine($"RESULT: this machine cannot run wslc yet — missing " Console.WriteLine($"RESULT: this machine cannot run wslc yet — missing "
+ $"{string.Join(", ", componentsMissing)}. Install with `wsl --install` (the " + $"{string.Join(", ", componentsMissing)}.");
+ "Virtual Machine Platform component needs a reboot), then run this again."); foreach (var component in componentsMissing)
Console.WriteLine(" This is the §8 onboarding condition, not a defect."); Console.WriteLine($" {component}: {Remedy(component)}");
Console.WriteLine(" Then run this again. This is the §8 onboarding condition, "
+ "not a defect.");
} }
else if (missing == 0) else if (missing == 0)
{ {
@@ -324,15 +326,17 @@ internal static class Program
{ {
componentsMissing = list.Cast<object?>().Select(v => v?.ToString() ?? "?").ToList(); componentsMissing = list.Cast<object?>().Select(v => v?.ToString() ?? "?").ToList();
if (componentsMissing.Count > 0) if (componentsMissing.Count > 0)
Console.WriteLine(" → WSL container support is NOT installed here. Every " Console.WriteLine(" → wslc is not usable here yet. Calls that reach the "
+ "call below that reaches the service will fail with 0x80040154."); + "service will fail (0x80040154 when nothing is installed, "
+ "0x80070032 when WSL is present but too old).");
} }
} }
catch (TargetInvocationException e) catch (TargetInvocationException e)
{ {
var inner = e.InnerException; var inner = e.InnerException;
var expected = componentsMissing is { Count: > 0 } // Any COM failure is expected while components are missing — pinning it to one
&& inner is COMException { HResult: unchecked((int)0x80040154) }; // code was wrong: a too-old WSL answers ERROR_NOT_SUPPORTED, not CLASSNOTREG.
var expected = componentsMissing is { Count: > 0 } && inner is COMException;
Console.WriteLine($" {name}() threw {Describe(inner)}" Console.WriteLine($" {name}() threw {Describe(inner)}"
+ (expected ? " ← expected: the components above are missing" : "")); + (expected ? " ← expected: the components above are missing" : ""));
} }
@@ -455,18 +459,96 @@ internal static class Program
{ {
if (e is null) return "an unknown error"; if (e is null) return "an unknown error";
var code = e is COMException com ? com.HResult : e.HResult; var code = e is COMException com ? com.HResult : e.HResult;
return $"{e.GetType().Name} (0x{code:X8}): {e.Message}"; // COM messages from this API are often empty, which leaves a bare hex code and no clue.
var message = string.IsNullOrWhiteSpace(e.Message) ? KnownHResult(code) : e.Message.Trim();
return $"{e.GetType().Name} (0x{code:X8}): {message}";
} }
private static string Render(object? value) => value switch /// The codes actually seen coming out of wslc, decoded. The WSLC_E_* range is documented in
/// `wslc.idl`; the other two are ordinary Windows errors that mean very different things and
/// are easy to confuse — "not registered" is *nothing installed*, "not supported" is
/// *installed but too old*, which is a completely different fix.
private static string KnownHResult(int code) => (uint)code switch
{
0x80040154 => "REGDB_E_CLASSNOTREG — the WSLC service class is not registered "
+ "(nothing to talk to)",
0x80070032 => "ERROR_NOT_SUPPORTED — the installed WSL does not implement this call "
+ "(almost always: WSL is older than the SDK)",
0x80070005 => "E_ACCESSDENIED",
0x80040601 => "WSLC_E_IMAGE_NOT_FOUND",
0x80040603 => "WSLC_E_CONTAINER_NOT_FOUND",
0x80040605 => "WSLC_E_CONTAINER_NOT_RUNNING",
0x80040607 => "WSLC_E_SESSION_RESERVED — that session name is already taken",
0x80040608 => "WSLC_E_INVALID_SESSION_NAME",
0x8004060B => "WSLC_E_SDK_UPDATE_NEEDED",
0x8004060C => "WSLC_E_CONTAINER_DISABLED",
0x8004060F => "WSLC_E_SESSION_NOT_FOUND",
_ => "no message",
};
/// What to actually DO about each missing component. These are not interchangeable, and the
/// difference cost a round trip: `wsl --install` fixes VirtualMachinePlatform and does nothing
/// for WslPackage, which needs an *update* — and specifically a pre-release one, because the
/// SDK's 2.9.3 is ahead of the Store channel.
private static string Remedy(string component) => component switch
{
"VirtualMachinePlatform" =>
"`wsl --install`, then REBOOT (this is an OS optional feature)",
"WslPackage" =>
"`wsl --update --pre-release` then `wsl --shutdown` — WSL is installed but older than "
+ "the SDK. 2.9.3 is pre-release-only, so a plain `wsl --update` will NOT get there. "
+ "Confirm with `wsl --version` (need >= 2.9.3).",
"SdkNeedsUpdate" =>
"the Microsoft.WSL.Containers pin is NEWER than the installed service — either update "
+ "WSL further or pin the package back",
_ => "see `wsl --help`",
};
private static string Render(object? value) => Render(value, depth: 0);
/// Render a value for the console.
///
/// The wrinkle worth knowing: a WinRT projection class does NOT override `ToString()`, so the
/// default gives you its type name and nothing else — `GetVersion()` printed
/// "Microsoft.WSL.Containers.ServiceVersion" instead of the version it had just fetched. When
/// `ToString()` is that unhelpful, dump the readable properties instead. `depth` bounds the
/// recursion, since a projection object graph can be cyclic.
private static string Render(object? value, int depth) => value switch
{ {
null => "null", null => "null",
string s => $"\"{s}\"", string s => $"\"{s}\"",
System.Collections.IEnumerable e and not string => System.Collections.IEnumerable e and not string =>
"[" + string.Join(", ", e.Cast<object?>().Select(Render)) + "]", "[" + string.Join(", ", e.Cast<object?>().Select(v => Render(v, depth + 1))) + "]",
_ => value.ToString() ?? "?", _ => Structured(value, depth),
}; };
private static string Structured(object value, int depth)
{
var type = value.GetType();
var text = value.ToString();
// A meaningful ToString() is one that isn't just the type's own name.
if (!string.IsNullOrEmpty(text) && text != type.FullName && text != type.Name) return text;
if (depth >= 2) return type.Name;
PropertyInfo[] properties;
try
{
properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && p.GetIndexParameters().Length == 0)
.OrderBy(p => p.Name, StringComparer.Ordinal)
.ToArray();
}
catch { return type.Name; }
if (properties.Length == 0) return text ?? type.Name;
var parts = properties.Select(p =>
{
try { return $"{p.Name}={Render(p.GetValue(value), depth + 1)}"; }
catch (Exception e) { return $"{p.Name}=<{Unwrap(e)?.GetType().Name}>"; }
});
return $"{type.Name} {{ {string.Join(", ", parts)} }}";
}
private static string? ArgValue(string[] args, string flag) private static string? ArgValue(string[] args, string flag)
{ {
var i = Array.IndexOf(args, flag); var i = Array.IndexOf(args, flag);