Merge nucleic/olive-jade-civet-rznt into dev
This commit is contained in:
+25
-9
@@ -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
|
||||
```
|
||||
|
||||
**If it reports missing components** (`VirtualMachinePlatform`, `WslPackage`), the machine cannot
|
||||
run wslc yet: `wsl --install`, reboot for the Virtual Machine Platform feature, and run again.
|
||||
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
|
||||
in that state instead of failing.
|
||||
**If it reports missing components**, the machine cannot run wslc yet, and the two components are
|
||||
NOT fixed the same way — the tool prints the specific remedy for each:
|
||||
|
||||
| Missing | Fix |
|
||||
| --- | --- |
|
||||
| `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
|
||||
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
|
||||
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
|
||||
and member names — including the `ABI.` shadow types and a service that reports missing components
|
||||
and throws `0x80040154` — so a failure on your machine is a finding about wslc, not about this
|
||||
tool.
|
||||
`--probe` has been **run clean on real hardware** (Windows 11 amd64, WSL 2.9.3): 54/54 `ok`,
|
||||
no missing components. Every other path is exercised against a stand-in assembly carrying the
|
||||
observed 2.9.3 shape — the `ABI.` shadow types, a service reporting missing components, an
|
||||
`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 —
|
||||
which now means the package moved under us, not that we guessed wrong.
|
||||
|
||||
@@ -74,10 +74,10 @@ internal static class Program
|
||||
|
||||
var missing = CheckAssumptions(types);
|
||||
|
||||
// Components are probed BEFORE anything else that touches the service. On a machine
|
||||
// where WSL container support isn't installed, every live call fails with
|
||||
// REGDB_E_CLASSNOTREG (0x80040154) — "Class not registered" — and reporting that as a
|
||||
// string of mysterious COM errors would bury the one fact that explains them all.
|
||||
// Components are probed BEFORE anything else that touches the service, because that one
|
||||
// call explains every other failure: nothing installed answers REGDB_E_CLASSNOTREG
|
||||
// (0x80040154), while an installed-but-too-old WSL answers ERROR_NOT_SUPPORTED
|
||||
// (0x80070032). Reporting those as unexplained COM errors buries the actual finding.
|
||||
var componentsMissing = probe ? ProbeStatics(types) : null;
|
||||
if (session)
|
||||
{
|
||||
@@ -85,7 +85,7 @@ internal static class Program
|
||||
{
|
||||
Console.WriteLine();
|
||||
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
|
||||
{
|
||||
@@ -97,9 +97,11 @@ internal static class Program
|
||||
if (componentsMissing is { Count: > 0 })
|
||||
{
|
||||
Console.WriteLine($"RESULT: this machine cannot run wslc yet — missing "
|
||||
+ $"{string.Join(", ", componentsMissing)}. Install with `wsl --install` (the "
|
||||
+ "Virtual Machine Platform component needs a reboot), then run this again.");
|
||||
Console.WriteLine(" This is the §8 onboarding condition, not a defect.");
|
||||
+ $"{string.Join(", ", componentsMissing)}.");
|
||||
foreach (var component in componentsMissing)
|
||||
Console.WriteLine($" {component}: {Remedy(component)}");
|
||||
Console.WriteLine(" Then run this again. This is the §8 onboarding condition, "
|
||||
+ "not a defect.");
|
||||
}
|
||||
else if (missing == 0)
|
||||
{
|
||||
@@ -324,15 +326,17 @@ internal static class Program
|
||||
{
|
||||
componentsMissing = list.Cast<object?>().Select(v => v?.ToString() ?? "?").ToList();
|
||||
if (componentsMissing.Count > 0)
|
||||
Console.WriteLine(" → WSL container support is NOT installed here. Every "
|
||||
+ "call below that reaches the service will fail with 0x80040154.");
|
||||
Console.WriteLine(" → wslc is not usable here yet. Calls that reach the "
|
||||
+ "service will fail (0x80040154 when nothing is installed, "
|
||||
+ "0x80070032 when WSL is present but too old).");
|
||||
}
|
||||
}
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
var inner = e.InnerException;
|
||||
var expected = componentsMissing is { Count: > 0 }
|
||||
&& inner is COMException { HResult: unchecked((int)0x80040154) };
|
||||
// Any COM failure is expected while components are missing — pinning it to one
|
||||
// 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)}"
|
||||
+ (expected ? " ← expected: the components above are missing" : ""));
|
||||
}
|
||||
@@ -455,18 +459,96 @@ internal static class Program
|
||||
{
|
||||
if (e is null) return "an unknown error";
|
||||
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",
|
||||
string s => $"\"{s}\"",
|
||||
System.Collections.IEnumerable e and not string =>
|
||||
"[" + string.Join(", ", e.Cast<object?>().Select(Render)) + "]",
|
||||
_ => value.ToString() ?? "?",
|
||||
"[" + string.Join(", ", e.Cast<object?>().Select(v => Render(v, depth + 1))) + "]",
|
||||
_ => 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)
|
||||
{
|
||||
var i = Array.IndexOf(args, flag);
|
||||
|
||||
Reference in New Issue
Block a user