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;
}
}
}