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
|
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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user