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."); } }