from __future__ import annotations import subprocess from typing import Any class WindowsServiceExecutor: def execute(self, params: dict[str, Any]) -> tuple[bool, str, dict[str, Any], dict[str, Any]]: service_name = str(params["service_name"]) action = str(params.get("action", "status")).lower() if action == "status": return self._query_status(service_name, action) if action == "start": status_before = self._query_service_status(service_name) if status_before == "RUNNING": return True, "service already running", self._build_data(service_name, action, status_before), {} self._run_command(["sc.exe", "start", service_name]) return self._query_status(service_name, action) if action == "stop": status_before = self._query_service_status(service_name) if status_before == "STOPPED": return True, "service already stopped", self._build_data(service_name, action, status_before), {} self._run_command(["sc.exe", "stop", service_name]) return self._query_status(service_name, action) if action == "restart": stop_success, stop_message, stop_data, stop_evidence = self.execute({"service_name": service_name, "action": "stop"}) if not stop_success: return stop_success, stop_message, stop_data, stop_evidence start_success, start_message, start_data, start_evidence = self.execute({"service_name": service_name, "action": "start"}) start_data["previous_action"] = "stop" start_evidence["stop"] = stop_evidence return start_success, start_message, start_data, start_evidence return False, f"unsupported action: {action}", self._build_data(service_name, action, None), {} def _query_status(self, service_name: str, action: str) -> tuple[bool, str, dict[str, Any], dict[str, Any]]: result = self._run_command(["sc.exe", "query", service_name], check=False) service_status = self._parse_service_status(result.stdout + "\n" + result.stderr) success = result.returncode == 0 and service_status not in {None, "NOT_FOUND"} message = "service status queried" if success else (result.stderr.strip() or result.stdout.strip() or "service query failed") return success, message, self._build_data(service_name, action, service_status), {"raw_output": (result.stdout + "\n" + result.stderr).strip()} def _query_service_status(self, service_name: str) -> str | None: result = self._run_command(["sc.exe", "query", service_name], check=False) return self._parse_service_status(result.stdout + "\n" + result.stderr) def _run_command(self, command: list[str], check: bool = True) -> subprocess.CompletedProcess[str]: return subprocess.run(command, capture_output=True, text=True, check=check) def _parse_service_status(self, text: str) -> str | None: normalized = text.upper() if "FAILED 1060" in normalized or "DOES NOT EXIST" in normalized: return "NOT_FOUND" for candidate in ("RUNNING", "STOPPED", "START_PENDING", "STOP_PENDING", "PAUSED"): if candidate in normalized: return candidate return None def _build_data(self, service_name: str, action: str, service_status: str | None) -> dict[str, Any]: return { "service_name": service_name, "action": action, "service_status": service_status, }