using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.ServiceProcess; using System.Text; using Microsoft.Win32; namespace JJServiceManager { /// Windows 서비스 조회/제어 public static class ServiceOps { public static string StatusText(ServiceControllerStatus? s) => s switch { ServiceControllerStatus.Running => "실행 중", ServiceControllerStatus.Stopped => "중지됨", ServiceControllerStatus.StartPending => "시작 중", ServiceControllerStatus.StopPending => "중지 중", ServiceControllerStatus.Paused => "일시 중지됨", ServiceControllerStatus.PausePending => "일시 중지 중", ServiceControllerStatus.ContinuePending => "계속 중", null => "서비스 없음", _ => s.ToString() }; public static string StartTypeText(ServiceStartMode? m, bool delayed = false) => m switch { ServiceStartMode.Automatic => delayed ? "자동(지연된 시작)" : "자동", ServiceStartMode.Manual => "수동", ServiceStartMode.Disabled => "사용 안 함", ServiceStartMode.Boot => "부팅", ServiceStartMode.System => "시스템", null => "", _ => m.ToString() }; /// 상태 조회. 서비스가 없으면 null public static ServiceControllerStatus? GetStatus(string serviceName) { try { using var sc = new ServiceController(serviceName); return sc.Status; } catch { return null; } } /// 서비스 시작 후 실행 상태가 될 때까지 대기 public static void Start(string serviceName, TimeSpan timeout) { using var sc = new ServiceController(serviceName); sc.Refresh(); if (sc.Status == ServiceControllerStatus.Running) throw new InfoException("이미 실행 중입니다."); if (sc.Status == ServiceControllerStatus.Paused) sc.Continue(); else if (sc.Status != ServiceControllerStatus.StartPending) sc.Start(); sc.WaitForStatus(ServiceControllerStatus.Running, timeout); } /// 서비스 중지 후 중지 상태가 될 때까지 대기 public static void Stop(string serviceName, TimeSpan timeout) { using var sc = new ServiceController(serviceName); sc.Refresh(); if (sc.Status == ServiceControllerStatus.Stopped) throw new InfoException("이미 중지되어 있습니다."); if (!sc.CanStop && sc.Status == ServiceControllerStatus.Running) throw new InvalidOperationException("이 서비스는 중지를 허용하지 않습니다."); if (sc.Status != ServiceControllerStatus.StopPending) sc.Stop(); sc.WaitForStatus(ServiceControllerStatus.Stopped, timeout); } /// 예외를 사람이 읽기 쉬운 메시지로 public static string Describe(Exception ex) { if (ex is System.ServiceProcess.TimeoutException) return "제한 시간 안에 상태가 바뀌지 않았습니다."; var inner = ex; while (inner.InnerException != null) inner = inner.InnerException; if (inner is Win32Exception w && !ReferenceEquals(inner, ex)) return $"{ex.Message} ({w.Message})"; return ex.Message; } /// Status 버튼용 상세 정보 public static List GetDetails(string serviceName) { var lines = new List(); using var sc = new ServiceController(serviceName); sc.Refresh(); var reg = ReadRegistry(serviceName); lines.Add($"표시 이름 : {sc.DisplayName}"); lines.Add($"서비스명 : {sc.ServiceName}"); lines.Add($"상태 : {StatusText(sc.Status)}"); ServiceStartMode? mode = null; try { mode = sc.StartType; } catch { } lines.Add($"시작 유형 : {StartTypeText(mode, reg.DelayedStart)}"); int pid = TryGetProcessId(sc); if (pid > 0) { string pname = ""; try { using var p = Process.GetProcessById(pid); pname = p.ProcessName; var started = p.StartTime; lines.Add($"프로세스 : {pname} (PID {pid}), 시작 {started:yyyy-MM-dd HH:mm:ss}"); } catch { lines.Add($"프로세스 : PID {pid}"); } } if (!string.IsNullOrEmpty(reg.ImagePath)) lines.Add($"실행 파일 : {reg.ImagePath}"); if (!string.IsNullOrEmpty(reg.Account)) lines.Add($"로그온 계정 : {reg.Account}"); try { var deps = sc.ServicesDependedOn; if (deps.Length > 0) { var names = new List(); foreach (var d in deps) { names.Add(d.ServiceName); d.Dispose(); } lines.Add($"필요한 서비스 : {string.Join(", ", names)}"); } } catch { } if (!string.IsNullOrEmpty(reg.Description)) lines.Add($"설명 : {reg.Description}"); return lines; } // ───────────────────────── 레지스트리 ───────────────────────── public class RegInfo { public string Description = ""; public string ImagePath = ""; public string Account = ""; public bool DelayedStart; } public static RegInfo ReadRegistry(string serviceName) { var info = new RegInfo(); try { using var key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Services\" + serviceName); if (key == null) return info; info.Description = ResolveIndirect(key.GetValue("Description") as string); info.ImagePath = key.GetValue("ImagePath") as string ?? ""; info.Account = key.GetValue("ObjectName") as string ?? ""; info.DelayedStart = (key.GetValue("DelayedAutostart") is int d) && d == 1; } catch { } return info; } [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] private static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved); /// "@%SystemRoot%\...dll,-123" 같은 간접 문자열을 실제 문구로 변환 private static string ResolveIndirect(string value) { if (string.IsNullOrEmpty(value)) return ""; if (!value.StartsWith("@")) return value; try { var sb = new StringBuilder(2048); if (SHLoadIndirectString(value, sb, sb.Capacity, IntPtr.Zero) == 0) return sb.ToString(); } catch { } return ""; } // ───────────────────────── PID 조회 ───────────────────────── [StructLayout(LayoutKind.Sequential)] private struct SERVICE_STATUS_PROCESS { public int dwServiceType; public int dwCurrentState; public int dwControlsAccepted; public int dwWin32ExitCode; public int dwServiceSpecificExitCode; public int dwCheckPoint; public int dwWaitHint; public int dwProcessId; public int dwServiceFlags; } [DllImport("advapi32.dll", SetLastError = true)] private static extern bool QueryServiceStatusEx(SafeHandle hService, int infoLevel, ref SERVICE_STATUS_PROCESS buffer, int bufSize, out int bytesNeeded); private static int TryGetProcessId(ServiceController sc) { try { var ssp = new SERVICE_STATUS_PROCESS(); if (QueryServiceStatusEx(sc.ServiceHandle, 0, ref ssp, Marshal.SizeOf(), out _)) return ssp.dwProcessId; } catch { } return 0; } } /// 오류가 아닌 안내성 메시지 (이미 실행 중 등) public class InfoException : Exception { public InfoException(string message) : base(message) { } } /// 서비스 선택 화면의 한 줄 public class ServiceInfo : ObservableObject { private bool _isSelected; public string ServiceName { get; init; } = ""; public string DisplayName { get; init; } = ""; public string Description { get; init; } = ""; public string StatusText { get; init; } = ""; public string StartTypeText { get; init; } = ""; public bool IsRegistered { get; init; } public bool IsSelected { get => _isSelected; set => Set(ref _isSelected, value); } public string Tooltip => $"서비스명: {ServiceName}" + (IsRegistered ? " (이미 등록됨)" : ""); public static List LoadAll(ISet registered) { var result = new List(); ServiceController[] all; try { all = ServiceController.GetServices(); } catch { return result; } foreach (var sc in all) { try { var reg = ServiceOps.ReadRegistry(sc.ServiceName); ServiceStartMode? mode = null; try { mode = sc.StartType; } catch { } ServiceControllerStatus? st = null; try { st = sc.Status; } catch { } bool isReg = registered.Contains(sc.ServiceName); result.Add(new ServiceInfo { ServiceName = sc.ServiceName, DisplayName = sc.DisplayName, Description = reg.Description, StatusText = ServiceOps.StatusText(st), StartTypeText = ServiceOps.StartTypeText(mode, reg.DelayedStart), IsRegistered = isReg, IsSelected = isReg }); } catch { } finally { sc.Dispose(); } } return result; } } }