{*******************************************************}
{                                                       }
{       Tocsg.Service                                   }
{                                                       }
{       Copyright (C) 2022 kkuzil                       }
{                                                       }
{*******************************************************}

unit Tocsg.Service;

interface

uses
  System.SysUtils, Winapi.Messages, Winapi.Windows, Winapi.WinSvc;

type
  TInstallServiceDll = procedure(bSilent: BOOL; dwSvcType, dwSvcStart: DWORD); stdcall;
  TUninstallServiceDll = procedure(bSilent: BOOL); stdcall;

function InstallService(const sSvcName, sBinaryPath, sDisplayName: String;
                        dwServiceType, nStartMode: Integer): Boolean;
function UninstallService(const sSvcName: String): Boolean;

function InstallServiceDll(const sDllPath: String; dwSvcType, dwSvcStart: DWORD): Boolean;
function UninstallServiceDll(const sDllPath: String): Boolean;

function ServiceExists(const sSvcName: String): Boolean;

function SetServiceStartType(const sSvcName: String; dwMode: DWORD): Boolean;
function SetServiceState(const sSvcName: String; dwStatus: DWORD): Boolean;

function GetServiceStatus(const sSvcName: String): DWORD;

function ServiceStart(const sSvcName: String; dwDesiredAccess: DWORD = SERVICE_ALL_ACCESS): Boolean;
function ServiceStop(const sSvcName: String; nTmSec: Integer = 0): Boolean;
function ServicePause(const sSvcName: String): Boolean;
function ServiceContinue(const sSvcName: String): Boolean;
function GetServicePid(sSvcName: String): DWORD;

procedure SetVisibleService(sSvcName: String; bVisible: Boolean);


implementation

uses
  Tocsg.Registry, Tocsg.Shell, Tocsg.Trace, Tocsg.Exception;

function InstallService(const sSvcName, sBinaryPath, sDisplayName: String;
                        dwServiceType, nStartMode: Integer): Boolean;
var
  hScm,
  hSvc: SC_HANDLE;
begin
  Result := false;

  hScm := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
  if hScm <> 0 then
  begin
    hSvc := OpenService(hScm, PChar(sSvcName), SERVICE_ALL_ACCESS);
    if (hSvc = 0) and (GetLastError = ERROR_SERVICE_DOES_NOT_EXIST) then
    begin
			hSvc := CreateService(hScm,
                            PChar(sSvcName),
                            PChar(sDisplayName),
                            SERVICE_ALL_ACCESS,
                            dwServiceType,
                            nStartMode,
                            SERVICE_ERROR_NORMAL,
                            PChar(sBinaryPath),
                            nil, nil, nil, nil, nil);

    end;

    Result := hSvc <> 0;

    if hSvc <> 0 then
      CloseServiceHandle(hSvc);

    CloseServiceHandle(hScm);
  end;
end;

function UninstallService(const sSvcName: String): Boolean;
var
  hScm,
  hSvc: SC_HANDLE;
begin
  Result := true;

  hScm := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
  if hScm <> 0 then
  begin
    hSvc := OpenService(hScm, PChar(sSvcName), SERVICE_ALL_ACCESS);
    if hSvc <> 0 then
    begin
      Result := DeleteService(hSvc);
      CloseServiceHandle(hSvc);
    end;
    CloseServiceHandle(hScm);
  end;
end;

function InstallServiceDll(const sDllPath: String; dwSvcType, dwSvcStart: DWORD): Boolean;
var
  h: THandle;
  fnInstSvc: TInstallServiceDll;
begin
  Result := false;
  if not FileExists(sDllPath) then
    exit;

  h := LoadLibrary(PChar(sDllPath));
  if h = 0 then
    exit;

  try
    try
      fnInstSvc := GetProcAddress(h, 'InstallServices_dll');
      if @fnInstSvc = nil then
        exit;

      fnInstSvc(true, dwSvcType, dwSvcStart);
    except
      on E: EOSError do
      begin
        Result := E.ErrorCode = 1073;
        exit;
      end;

      on E: Exception do
        exit;
    end;
    Result := true;
  finally
    FreeLibrary(h);
  end;
end;

function UninstallServiceDll(const sDllPath: String): Boolean;
var
  h: THandle;
  fnUninstSvc: TUninstallServiceDll;
  bRetry: Boolean;
  nReCnt: Integer;
Label
  LB_Retry;
begin
  Result := false;
  if not FileExists(sDllPath) then
    exit;

  h := LoadLibrary(PChar(sDllPath));
  if h = 0 then
    exit;

  try
    bRetry := false;
    nReCnt := 0;

    fnUninstSvc := GetProcAddress(h, 'UnInstallServices_dll');
    if @fnUninstSvc = nil then
      exit;
  LB_Retry :
    try
      fnUninstSvc(true);
    except
      on E: EOSError do
      begin
        if E.ErrorCode = 5 then
          bRetry := true
        else
          exit;
      end;

      on E: Exception do
        exit;
    end;

    if bRetry then
    begin
      Inc(nReCnt);
      if nReCnt > 5 then
        exit;
      Sleep(1000);
      goto LB_Retry;
    end;

    Result := true;
  finally
    FreeLibrary(h);
  end;
end;

function ServiceExists(const sSvcName: String): Boolean;
var
  hScm,
  hSvc: SC_HANDLE;
begin
  Result := false;

  hScm := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
  if hScm <> 0 then
  begin
    hSvc := OpenService(hScm, PChar(sSvcName), SERVICE_ALL_ACCESS);
    if hSvc <> 0 then
    begin
      Result := true;
      CloseServiceHandle(hSvc);
    end;
    CloseServiceHandle(hScm);
  end;
end;

function SetServiceStartType(const sSvcName: String; dwMode: DWORD): Boolean;
const
  REG_SERVICE_KEY = 'SYSTEM\CurrentControlSet\Services\';
begin
  Result := false;
  if not ServiceExists(sSvcName) then
    exit;

  Result := SetRegValueInteger(HKEY_LOCAL_MACHINE,
                               REG_SERVICE_KEY + sSvcName,
                               'Start',
                               dwMode);
end;

function SetServiceState(const sSvcName: String; dwStatus: DWORD): Boolean;
var
  hScm,
  hSvc: SC_HANDLE;
  st: SERVICE_STATUS;
begin
  Result := false;

  hScm := OpenSCManager(nil, nil, SC_MANAGER_CONNECT);
  if hScm <> 0 then
  begin
    hSvc := OpenService(hScm, PChar(sSvcName), SERVICE_ALL_ACCESS);
    if hSvc <> 0 then
    begin
      ZeroMemory(@st, sizeof(st));
      Result := ControlService(hSvc, dwStatus, st);
      CloseServiceHandle(hSvc);
    end;
    CloseServiceHandle(hScm);
  end;
end;

function GetServiceStatus(const sSvcName: String): DWORD;
var
  hScm, hSvc : SC_HANDLE;
  st         : SERVICE_STATUS;
begin
  Result := 0;

  hScm := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);

  if hScm <> 0 then
  begin
    hSvc := OpenService(hScm, PChar(sSvcName), SERVICE_ALL_ACCESS);
    if hSvc <> 0 then
    begin
      ZeroMemory(@st, sizeof(SERVICE_STATUS));
      if QueryServiceStatus(hSvc, st) then
      begin
        Result := st.dwCurrentState;

        (*
          >>>>>>> dwCurrentState
          {$EXTERNALSYM SERVICE_STOPPED}
          SERVICE_STOPPED                = $00000001;
          {$EXTERNALSYM SERVICE_START_PENDING}
          SERVICE_START_PENDING          = $00000002;
          {$EXTERNALSYM SERVICE_STOP_PENDING}
          SERVICE_STOP_PENDING           = $00000003;
          {$EXTERNALSYM SERVICE_RUNNING}
          SERVICE_RUNNING                = $00000004;
          {$EXTERNALSYM SERVICE_CONTINUE_PENDING}
          SERVICE_CONTINUE_PENDING       = $00000005;
          {$EXTERNALSYM SERVICE_PAUSE_PENDING}
          SERVICE_PAUSE_PENDING          = $00000006;
          {$EXTERNALSYM SERVICE_PAUSED}
          SERVICE_PAUSED                 = $00000007;
        *)
      end;
      CloseServiceHandle(hSvc);
    end;
    CloseServiceHandle(hScm);
  end;
end;

function ServiceStart(const sSvcName: String; dwDesiredAccess: DWORD = SERVICE_ALL_ACCESS): Boolean;
var
  hScm,
  hSvc: SC_HANDLE;
  sServiceArg: PChar;
begin
  Result := false;

  sServiceArg := nil;

  hScm := OpenSCManager(nil, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
  if hScm <> 0 then
  begin
    hSvc := OpenService(hScm, PChar(sSvcName), dwDesiredAccess);
    if hSvc <> 0 then
    begin
      Result := StartService(hSvc, 0, sServiceArg);
      CloseServiceHandle(hSvc);
    end;
    CloseServiceHandle(hScm);
  end;
end;

function ServiceStop(const sSvcName: String; nTmSec: Integer = 0): Boolean;
begin
  if nTmSec > 0 then
  begin
    var nTm: Integer := nTmSec * 2;
    Result := SetServiceState(sSvcName, SERVICE_CONTROL_STOP);
    while not Result do
    begin
      Dec(nTm);
      if nTm = 0 then
        exit;
      Sleep(500);
      Result := SetServiceState(sSvcName, SERVICE_CONTROL_STOP);
    end;
  end else
    Result := SetServiceState(sSvcName, SERVICE_CONTROL_STOP);
end;

function ServicePause(const sSvcName: String): Boolean;
begin
  Result := SetServiceState(sSvcName, SERVICE_CONTROL_PAUSE);
end;

function ServiceContinue(const sSvcName: String): Boolean;
begin
  Result := SetServiceState(sSvcName, SERVICE_CONTROL_CONTINUE);
end;

function GetServicePid(sSvcName: String): DWORD;
var
  hScm, hSvc: SC_HANDLE;
  SvcProcStatus: SERVICE_STATUS_PROCESS;
  dwBufLen,
  dwNeedByte,
  dwInfoLevel: DWORD;
begin
  Result := 0;

  try
    hScm := OpenSCManager(nil, nil, SC_MANAGER_CONNECT);
    if hScm <> 0 then
    begin
      hSvc := OpenService(hScm, PChar(sSvcName), SERVICE_QUERY_STATUS);
      try
        if hSvc <> 0 then
        begin
          dwInfoLevel := 0;
          dwNeedByte := 0;
          dwBufLen := SizeOf(SvcProcStatus);
          ZeroMemory(@SvcProcStatus, dwBufLen);

          if QueryServiceStatusEx(hSvc, SC_STATUS_TYPE(dwInfoLevel), @SvcProcStatus,
                  dwBufLen, dwNeedByte) then
          begin
            Result := SvcProcStatus.dwProcessId;
          end;
        end;
      finally
        if hSvc <> 0 then
          CloseServiceHandle(hSvc);
        CloseServiceHandle(hScm);
      end;
    end;
  except
    on E: Exception do
      ETgException.TraceException(E, 'Fail .. GetServicePid()');
  end;
end;

// 서비스를 숨기면 윈도우 메이저 업데이트 시 서비스가 사라지는 문제가 있음 24_0105 08:18:24 kku
procedure SetVisibleService(sSvcName: String; bVisible: Boolean);
const
  PARAM_SVC_VISIBLE_TRUE = 'sdset %s D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)';
  PARAM_SVC_VISIBLE_FALSE = 'sdset %s D:(D;;DCLCWPDTSD;;;IU)(D;;DCLCWPDTSD;;;SU)(D;;DCLCWPDTSD;;;BA)(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)';
var
  sParam: String;
begin
// 숨기면 OpenService()로도 찾을 수 없다. 20_0413 16:33:22 sunk
  if bVisible then
    sParam := Format(PARAM_SVC_VISIBLE_TRUE, [sSvcName])
  else
    sParam := Format(PARAM_SVC_VISIBLE_FALSE, [sSvcName]);

  ExecutePath_hide('sc.exe', sParam);
end;

end.
