Service: --run-service host + registration CLI (install/uninstall/start/stop)
Local checkpoint - NOT for public release. - RemSoundService (ServiceBase): hosts ServiceSendHost.RunLoop on a worker thread, OnStop cancels + joins. Added System.ServiceProcess.ServiceController package. - ServiceControl: install (sc.exe create, auto-start, careful binPath quoting) / uninstall (stop + delete) / start / stop / status. Status query is unprivileged (menu can poll it); the mutating verbs self-elevate via ShellExecute runas. - Program.cs: early guards for --run-service (blocks in the SCM dispatcher) and the one-shot elevated verbs, before the single-instance lock (the service is a separate role and must never take the interactive lock). - Self-test "Service registration args" verifies the sc create binPath quoting survives a spaced exe path. Gate 20/20. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e648bee531
commit
5dbbe4dd71
@@ -64,6 +64,7 @@ internal static class SelfTest
|
||||
RunStep(results, "Lifecycle churn (modes, sources, pan/EQ, send/receive)", LifecycleChurn);
|
||||
RunStep(results, "Service app-yield token", ServiceInteractivePresence);
|
||||
RunStep(results, "Service send host (headless stream + yield)", ServiceSendHostStream);
|
||||
RunStep(results, "Service registration args", ServiceRegistrationArgs);
|
||||
RunStep(results, "v5 settings and shaping round-trip", V5ConfigRoundTrip);
|
||||
RunStep(results, "Profile save and reload", ProfileRoundTrip);
|
||||
RunStep(results, "What's-new update marker", WhatsNewMarkerRoundTrip);
|
||||
@@ -457,6 +458,23 @@ internal static class SelfTest
|
||||
catch { return 0; }
|
||||
}
|
||||
|
||||
/// <summary>The sc.exe "create" argument string quotes a spaced exe path correctly — a real footgun
|
||||
/// (a broken binPath silently installs a service that can't start). Pure/side-effect-free, so it
|
||||
/// never touches the SCM or needs admin.</summary>
|
||||
private static string? ServiceRegistrationArgs()
|
||||
{
|
||||
const string exe = @"C:\Program Files\RemSound\RemSound.exe";
|
||||
var args = ServiceControl.BuildCreateArgs(exe);
|
||||
Check(args.StartsWith($"create {ServiceControl.ServiceName} "), "must be a create for the named service");
|
||||
Check(args.Contains("start= auto"), "service must be auto-start");
|
||||
// The exe path must be wrapped in ESCAPED quotes inside the binPath value, followed by the run
|
||||
// verb, so a path with spaces survives sc.exe's parsing.
|
||||
Check(args.Contains("\\\"" + exe + "\\\" " + ServiceControl.RunVerb),
|
||||
$"exe path must be escaped-quoted with the run verb (got: {args})");
|
||||
Check(args.Contains($"DisplayName= \"{ServiceControl.DisplayName}\""), "must set the display name");
|
||||
return "sc create args quoted correctly for a spaced path";
|
||||
}
|
||||
|
||||
/// <summary>The lock-screen service's app-yield token: while a hold is active the service must see an
|
||||
/// interactive app present; once released (or on crash — the OS frees the mutex) it must see none.
|
||||
/// Uses a unique token name so the test is immune to a real RemSound running alongside the gate.</summary>
|
||||
|
||||
Reference in New Issue
Block a user