Search code examples
.netdllinno-setuppascalscript

Checking dependent services of a given service in Inno Setup


I'm currently working on an Inno Setup installer which checks for dependent Services of a given Service. According to the MSDN docs, I need the function EnumDependentServices().

However, when I pass parameters to the function, Inno Setup gives me status code 5, which stands for "Access Denied", although I execute my Setup as an Administrator.

I'm using the SC_MANAGER_ALL_ACCESS (0xF003F) right for starting the Service Handler, and it still denies my function call.

Both the SCManager and the opened Service (I tested it with "DnsCache") return non-zero values, and work with my other functions.

Here is the [Code] snippet from my .iss file:

#ifdef UNICODE
  #define AW "W"
#else
  #define AW "A"
#endif

type
    SERVICE_STATUS = record
    dwServiceType: DWORD;
    dwCurrentState: DWORD;
    dwControlsAccepted: DWORD;
    dwWin32ExitCode: DWORD;
    dwServiceSpecificExitCode: DWORD;
    dwCheckPoint: DWORD;
    dwWaitHint: DWORD;
  end;
    HANDLE = THandle;
    PDWord = DWORD;

const
    SERVICE_QUERY_CONFIG        = $1;
    SC_MANAGER_CONNECT          = $0001;
    SERVICE_QUERY_STATUS        = $0004;
    SERVICE_CHANGE_CONFIG       = $2;
    SERVICE_CURRENT_STATUS      = $0003;
    SERVICE_START               = $10;
    SERVICE_STOP                = $20;
    SERVICE_ALL_ACCESS          = $f01ff;
    SC_MANAGER_ALL_ACCESS       = $F003F;
    SERVICE_WIN32_OWN_PROCESS   = $10;
    SERVICE_WIN32_SHARE_PROCESS = $20;
    SERVICE_WIN32               = $30;
    SERVICE_INTERACTIVE_PROCESS = $100;
    SERVICE_BOOT_START          = $0;
    SERVICE_SYSTEM_START        = $1;
    SERVICE_AUTO_START          = $2;
    SERVICE_DEMAND_START        = $3;
    SERVICE_DISABLED            = $4;
    SERVICE_DELETE              = $10000;
    SERVICE_CONTROL_STOP        = $1;
    SERVICE_CONTROL_PAUSE       = $2;
    SERVICE_CONTROL_CONTINUE    = $3;
    SERVICE_CONTROL_INTERROGATE = $4;
    SERVICE_STOPPED             = $1;
    SERVICE_START_PENDING       = $2;
    SERVICE_STOP_PENDING        = $3;
    SERVICE_RUNNING             = $4;
    SERVICE_CONTINUE_PENDING    = $5;
    SERVICE_PAUSE_PENDING       = $6;
    SERVICE_PAUSED              = $7;

// #######################################################################################
// nt based service utilities
// #######################################################################################
function OpenSCManager(lpMachineName, lpDatabaseName: string; dwDesiredAccess :cardinal): HANDLE;
external 'OpenSCManager{#AW}@advapi32.dll stdcall';

function OpenService(hSCManager :HANDLE;lpServiceName: string; dwDesiredAccess :cardinal): HANDLE;
external 'OpenService{#AW}@advapi32.dll stdcall';

function CloseServiceHandle(hSCObject :HANDLE): boolean;
external 'CloseServiceHandle@advapi32.dll stdcall';

function CreateService(hSCManager :HANDLE;lpServiceName, lpDisplayName: string;dwDesiredAccess,dwServiceType,dwStartType,dwErrorControl: cardinal;lpBinaryPathName,lpLoadOrderGroup: String; lpdwTagId : cardinal;lpDependencies,lpServiceStartName,lpPassword :string): cardinal;
external 'CreateService{#AW}@advapi32.dll stdcall';

function DeleteService(hService :HANDLE): boolean;
external 'DeleteService@advapi32.dll stdcall';

function StartNTService(hService :HANDLE;dwNumServiceArgs : cardinal;lpServiceArgVectors : cardinal) : boolean;
external 'StartService{#AW}@advapi32.dll stdcall';

function ControlService(hService :HANDLE; dwControl :cardinal;var ServiceStatus :SERVICE_STATUS) : boolean;
external 'ControlService@advapi32.dll stdcall';

function QueryServiceStatus(hService :HANDLE;var ServiceStatus :SERVICE_STATUS) : boolean;
external 'QueryServiceStatus@advapi32.dll stdcall';

function QueryServiceStatusEx(hService :HANDLE;ServiceStatus :SERVICE_STATUS) : boolean;
external 'QueryServiceStatus@advapi32.dll stdcall';

function GetLastError() : cardinal;
external 'GetLastError@kernel32.dll stdcall';

function EnumDependentServices(
  hService :HANDLE; 
  ServiceStatus : LongInt; 
  lpServices: DWORD; 
  cbBufSize: DWORD; 
  var pcbBytesNeeded: DWORD; 
  var lpServicesReturned : DWORD) : boolean;
external 'EnumDependentServices{#AW}@advapi32.dll stdcall';

function HasServiceDependencies(const ServiceName: string) : boolean;
var  
  hSCM: HANDLE;
  hService: HANDLE;
  pcbBytesNeeded : DWORD;
  lpServicesReturned : DWORD;
begin
    hSCM := OpenSCManager('', '', SC_MANAGER_ALL_ACCESS);
    Log(Format('%d', [hSCM]));
    if hSCM <> 0 then begin
        hService := OpenService(hSCM, ServiceName, SERVICE_QUERY_STATUS);
        Log(Format('%d', [hService]));
        if hService <> 0 then begin

            EnumDependentServices( hService, SERVICE_CURRENT_STATUS, 0, 0, pcbBytesNeeded, lpServicesReturned);
            
            MsgBox(SysErrorMessage(DLLGetLastError()), mbError, mb_Ok);

            CloseServiceHandle(hService)
            end;
        CloseServiceHandle(hSCM)
    end;
    Result := false;
end;

function InitializeSetup(): Boolean;
begin
  Log('InitializeSetup called');
  Result := HasServiceDependencies('Dnscache');
  if Result = False then
    MsgBox('InitializeSetup:' #13#13 'Ok, bye bye.', mbInformation, MB_OK);
end;

I tried modifying the Status and Query Codes to other ones, but that didn't help, either.


Solution

  • Per the EnumDependentServices() documentation:

    [in] hService

    A handle to the service. This handle is returned by the OpenService or CreateService function, and it must have the SERVICE_ENUMERATE_DEPENDENTS access right. For more information, see Service Security and Access Right

    ...

    The following error codes may be set by the service control manager. Other error codes may be set by the registry functions that are called by the service control manager.

    Return code Description
    ERROR_ACCESS_DENIED The handle does not have the SERVICE_ENUMERATE_DEPENDENTS access right.

    When you are opening your hService handle, you are not specifying the SERVICE_ENUMERATE_DEPENDENTS right, only the SERVICE_QUERY_STATUS right (which is used only by QueryServiceStatus/Ex() and NotifyServiceStatusChange(), not by EnumDependentServices()).

    Also, SC_MANAGER_ALL_ACCESS is too many rights to ask for. Don't ask for more rights than you actually need. In this case, you only need SC_MANAGER_CONNECT.

    Also, SERVICE_CURRENT_STATUS is not a valid symbol defined by the Win32 API. You are declaring it as $0003, which is the same value as SERVICE_STATE_ALL, which is the actual symbol that EnumDependentServices() defines.

    Try this instead:

    const
      ...
      SERVICE_STATE_ALL = $0003;
      SERVICE_ENUMERATE_DEPENDENTS = $0008;
      ERROR_MORE_DATA = 234;
      ...
    
    procedure LogError(const FuncName: string);
    var
      ErrCode : DWORD;
      ErrMsg : string; 
    begin
      ErrCode := GetLastError();
      ErrMsg := Format('%s failed: (%d) %s', [FuncName, ErrCode, SysErrorMessage(ErrCode)]);
      Log(ErrMsg);
      MsgBox(ErrMsg, mbError, mb_Ok);
    end;
    
    function HasServiceDependencies(const ServiceName: string) : boolean;
    var  
      hSCM: HANDLE;
      hService: HANDLE;
      dwBytesNeeded : DWORD;
      dwServicesReturned : DWORD;
    begin
      Result := False;
      hSCM := OpenSCManager('', '', SC_MANAGER_CONNECT);
      if hSCM = 0 then begin
        LogError('OpenSCManager');
      end
      else begin
        hService := OpenService(hSCM, ServiceName, SERVICE_ENUMERATE_DEPENDENTS);
        if hService = 0 then begin
          LogError('OpenService');
        end
        else begin
          // specifying a nil buffer, so will return True if there are no dependencies,
          // otherwise will return False and set LastError=ERROR_MORE_DATA if
          // dependency data is available...
          if EnumDependentServices(hService, SERVICE_STATE_ALL, 0, 0, dwBytesNeeded, dwServicesReturned) then begin
            Log(Format('%s has no dependencies', [ServiceName]));
          end
          else if GetLastError() <> ERROR_MORE_DATA then begin
            LogError('EnumDependentServices');
          end
          else begin
            Log(Format('%s has dependencies', [ServiceName]));
          end;
          CloseServiceHandle(hService);
        end;
        CloseServiceHandle(hSCM);
      end;
    end;