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:
co-authored by
Claude Opus 4.8
parent
9392cc1fcc
commit
2dad00c42f
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user