From d87de152e0f5b6dde371381aef6e84f51277cb01 Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Wed, 15 Apr 2026 09:27:54 +0200 Subject: [PATCH] feat(installer): add Stop/StartServiceStep sc.exe wrappers --- .../Steps/StartServiceStep.cs | 27 +++++++++++ .../Steps/StopServiceStep.cs | 48 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/ClaudeDo.Installer/Steps/StartServiceStep.cs create mode 100644 src/ClaudeDo.Installer/Steps/StopServiceStep.cs diff --git a/src/ClaudeDo.Installer/Steps/StartServiceStep.cs b/src/ClaudeDo.Installer/Steps/StartServiceStep.cs new file mode 100644 index 0000000..270c179 --- /dev/null +++ b/src/ClaudeDo.Installer/Steps/StartServiceStep.cs @@ -0,0 +1,27 @@ +using ClaudeDo.Installer.Core; + +namespace ClaudeDo.Installer.Steps; + +public sealed class StartServiceStep : IInstallStep +{ + private const string ServiceName = StopServiceStep.ServiceName; + + public string Name => "Start Worker Service"; + + public async Task ExecuteAsync(InstallContext ctx, IProgress progress, CancellationToken ct) + { + progress.Report($"Starting {ServiceName}..."); + + var (exit, output) = await ProcessRunner.RunAsync("sc.exe", $"start {ServiceName}", null, progress, ct); + if (exit == 0) return StepResult.Ok(); + + // Exit 1056 = already running — that's fine too. + if (output.Contains("1056", StringComparison.OrdinalIgnoreCase)) + { + progress.Report("Service was already running."); + return StepResult.Ok(); + } + + return StepResult.Fail($"sc.exe start failed with exit code {exit}"); + } +} diff --git a/src/ClaudeDo.Installer/Steps/StopServiceStep.cs b/src/ClaudeDo.Installer/Steps/StopServiceStep.cs new file mode 100644 index 0000000..be3c271 --- /dev/null +++ b/src/ClaudeDo.Installer/Steps/StopServiceStep.cs @@ -0,0 +1,48 @@ +using ClaudeDo.Installer.Core; + +namespace ClaudeDo.Installer.Steps; + +public sealed class StopServiceStep : IInstallStep +{ + public const string ServiceName = "ClaudeDoWorker"; + + public string Name => "Stop Worker Service"; + + public async Task ExecuteAsync(InstallContext ctx, IProgress progress, CancellationToken ct) + { + progress.Report($"Stopping {ServiceName} (if running)..."); + + // sc.exe query -> returns non-zero if the service does not exist; that's fine. + var (queryExit, queryOutput) = await ProcessRunner.RunAsync("sc.exe", $"query {ServiceName}", null, progress, ct); + if (queryExit != 0) + { + progress.Report("Service is not registered — nothing to stop."); + return StepResult.Ok(); + } + + if (queryOutput.Contains("STOPPED", StringComparison.OrdinalIgnoreCase)) + { + progress.Report("Service is already stopped."); + return StepResult.Ok(); + } + + var (stopExit, _) = await ProcessRunner.RunAsync("sc.exe", $"stop {ServiceName}", null, progress, ct); + if (stopExit != 0) + return StepResult.Fail($"sc.exe stop failed with exit code {stopExit}"); + + // Poll until stopped or timeout (up to 30s). + for (var i = 0; i < 30; i++) + { + ct.ThrowIfCancellationRequested(); + await Task.Delay(1000, ct); + var (e, o) = await ProcessRunner.RunAsync("sc.exe", $"query {ServiceName}", null, progress, ct); + if (e != 0 || o.Contains("STOPPED", StringComparison.OrdinalIgnoreCase)) + { + progress.Report("Service stopped."); + return StepResult.Ok(); + } + } + + return StepResult.Fail("Service did not stop within 30 seconds."); + } +}