Offer the service on Win7+ by feature-detecting, not version-gating

Replaces the hardcoded "Windows 10 or newer" gate on the Service menu with a
runtime capability probe (ServiceCapability). It tries to load the .NET
service machinery (System.ServiceProcess) and offers the Service menu wherever
that succeeds -- including Windows 7 if the service layer loads there, which is
what Ed asked for ("install on Win7 or above"). If the assembly can't load on
an older/unsupported Windows, the probe catches it and the menu is simply
hidden, so such a machine can never be crashed by it.

Safety by construction: the reference to the service types lives only in the
NoInlining Probe method, so the assembly load is triggered by the CALL from
IsAvailable (inside its try/catch) and is catchable -- not during JIT of the
caller, which would be fatal. The startup path stays service-type-free via
ServiceEntry, so this probe runs only at window construction, never at launch.

Probe uses the parameterless ServiceController ctor: it forces the assembly to
load but touches no service and no SCM. (First cut read .ServiceName on a
made-up name, which actually queries the SCM and threw "service not found" --
the self-test caught that it was hiding the menu on Windows 11 too.)

New self-test "Service capability probe": available + cached + never throws on
the Win10/11 gate runner. Gate 30/30.

Note: this makes the service INSTALLABLE on Win7 wherever the layer loads; it
does not prove the service RUNS there (Session-0 capture on an unsupported
runtime) -- only a real test on the Win7 box can confirm that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-14 08:55:16 +01:00
co-authored by Claude Opus 4.8
parent 9392cc1fcc
commit 2dad00c42f
3 changed files with 72 additions and 6 deletions
+16
View File
@@ -80,6 +80,7 @@ internal static class SelfTest
RunStep(results, "Main window profile round-trip (controls load + save)", MainWindowProfileRoundTrip);
RunStep(results, "Auto-save non-read-only profiles (options + guard + silent timer)", AutoSaveNonReadOnlyProfiles);
RunStep(results, "Service verb gate (normal launch stays load-safe)", ServiceVerbGate);
RunStep(results, "Service capability probe (feature-detect, cached, safe)", ServiceCapabilityProbe);
var failed = results.Count(r => r.Status == "FAIL");
var skipped = results.Count(r => r.Status == "SKIP");
@@ -1313,6 +1314,21 @@ internal static class SelfTest
private static bool IsAssemblyLoaded(string simpleName) =>
AppDomain.CurrentDomain.GetAssemblies().Any(a => string.Equals(a.GetName().Name, simpleName, StringComparison.OrdinalIgnoreCase));
/// <summary>The Service menu is shown by FEATURE-DETECTING the Windows service machinery (so it can
/// appear on Win7 too if the .NET service layer loads there), not by a hardcoded Windows version. The
/// probe must be stable/cached and must never throw. On this Win10/11 gate runner the machinery loads,
/// so it must report available; the "can't load → hidden" path can only be exercised on an OS where the
/// assembly genuinely won't load, but the try/catch that guarantees it degrades safely is verified here
/// by the probe never throwing.</summary>
private static string? ServiceCapabilityProbe()
{
bool first = ServiceCapability.IsAvailable();
bool second = ServiceCapability.IsAvailable();
Check(first == second, "the capability result must be stable across calls (cached)");
Check(first, "the Windows service machinery must be detected as available on this Windows 10/11 test runner");
return "service machinery feature-detected as available, and the result is cached";
}
private static int CountControls(Control root, Func<Control, bool> predicate)
{
var n = 0;