Home > Net >  Delphi 11 and WinAPI.WinSvc (function EnumServiceState)
Delphi 11 and WinAPI.WinSvc (function EnumServiceState)

Time:06-22

I used the code below to get a list of Windows services. It works well for Delphi 10.2.3 and earlier:

uses WinSvc;

//-------------------------------------
// Get a list of services
//
// return TRUE if successful
//
// sMachine:
//   machine name, ie: \SERVER
//   empty = local machine
//
// dwServiceType
//   SERVICE_WIN32,
//   SERVICE_DRIVER or
//   SERVICE_TYPE_ALL
//
// dwServiceState
//   SERVICE_ACTIVE,
//   SERVICE_INACTIVE or
//   SERVICE_STATE_ALL
//
// slServicesList
//   TStrings variable to storage
//
function ServiceGetList(
  sMachine : string;
  dwServiceType,
  dwServiceState : DWord;
  slServicesList : TStrings )
  : boolean;
const
  //
  // assume that the total number of
  // services is less than 4096.
  // increase if necessary
  cnMaxServices = 4096;

type
  TSvcA = array[0..cnMaxServices]
          of TEnumServiceStatus;
  PSvcA = ^TSvcA;
          
var
  //
  // temp. use
  j : integer;

  //
  // service control
  // manager handle
  schm          : SC_Handle;

  //
  // bytes needed for the
  // next buffer, if any
  nBytesNeeded,

  //
  // number of services
  nServices,

  //
  // pointer to the
  // next unread service entry
  nResumeHandle : DWord;

  //
  // service status array
  ssa : PSvcA;
begin
  Result := false;

  // connect to the service
  // control manager
  schm := OpenSCManager(
    PChar(sMachine),
    Nil,
    SC_MANAGER_ALL_ACCESS);

  // if successful...
  if(schm > 0)then
  begin
    nResumeHandle := 0;

    New(ssa);

    EnumServicesStatus(
      schm,
      dwServiceType,
      dwServiceState,
      ssa^[0],
      SizeOf(ssa^),
      nBytesNeeded,
      nServices,
      nResumeHandle );

    //
    // assume that our initial array
    // was large enough to hold all
    // entries. add code to enumerate
    // if necessary.
    //
    
    for j := 0 to nServices-1 do
    begin
      slServicesList.
        Add( StrPas(
          ssa^[j].lpDisplayName ) );
    end;

    Result := true;

    Dispose(ssa);

    // close service control
    // manager handle
    CloseServiceHandle(schm);
  end;
end;

To get a list of all Windows services into a ListBox named ListBox1:

ServiceGetList( '',
  SERVICE_WIN32,
  SERVICE_STATE_ALL,
  ListBox1.Items );

I tried to use the same code in Delphi 10.4 and Delphi 11, but there is a problem with the EnumServicesStatus function:

[dcc32 Error] Unit1.pas(145): E2010 Incompatible types: 'LPENUM_SERVICE_STATUSW' and '_ENUM_SERVICE_STATUSW'

When I tried LPENUM_SERVICE_STATUSW instead TEnumServiceStatus:

type
  TSvcA = array[0..cnMaxServices]
          of LPENUM_SERVICE_STATUSW;// instead TEnumServiceStatus;

I got an 'access violation' error.

Maybe the point is in the external function in Winapi.WinSvc.pas:

function EnumServicesStatus;    external advapi32 name 'EnumServicesStatusW';

CodePudding user response:

It is not a good idea to pre-allocate such a large array. You can't assume how many services the PC actually has installed. Let EnumServicesStatus() tell you how much to allocate for the array.

Also, you have to account for the possibility that you may have to call EnumServicesStatus() multiple times to get all of the statuses.

Now, regarding the compiler issue - the actual EnumServiceStatus() API wants a pointer to an array of ENUM_SERVICE_STATUS records (aliased as TEnumServiceStatus in the WinSvc unit), not an array of LPENUM_SERVICE_STATUS pointers. In earlier versions of Delphi, the Winsvc unit declared EnumServiceStatusW() to take a var reference to the 1st ENUM_SERVICE_STATUS in the array. But apparently it has since been re-declared to instead take a pointer to the 1st ENUM_SERVICE_STATUS, to match the actual API.

So, with that said, try something more like this:

uses
  WinSvc;

{$IFDEF CONDITIONALEXPRESSIONS}
  {$IF CompilerVersion >= 34}
    {$DEFINE lpServices_Param_Is_Pointer}
  {$IFEND}
{$ENDIF}

function ServiceGetList(
  const sMachine : string;
  dwServiceType,
  dwServiceState : DWord;
  slServicesList : TStrings )
  : Boolean;
var
  j : integer;
  schm : SC_Handle;
  nBytesNeeded,
  nServices,
  nResumeHandle : DWord;
  buffer : array of Byte;
  ssa, ss : PEnumServiceStatus;
begin
  Result := False;

  schm := OpenSCManager(
    PChar(sMachine),
    nil,
    SC_MANAGER_CONNECT or SC_MANAGER_ENUMERATE_SERVICE);

  if (schm <> 0) then
  try
    nResumeHandle := 0;
    if not EnumServicesStatus(
      schm,
      dwServiceType,
      dwServiceState,
      {$IFDEF lpServices_Param_Is_Pointer}nil{$ELSE}PEnumServiceStatus(nil)^{$ENDIF},
      0,
      nBytesNeeded,
      nServices,
      nResumeHandle) then
    begin
      if (GetLastError() <> ERROR_INSUFFICIENT_BUFFER) and (GetLastError() <> ERROR_MORE_DATA) then
      begin
        Exit;
      end;
      SetLength(buffer, nBytesNeeded);
      ssa := PEnumServiceStatus(buffer);
      if not EnumServicesStatus(
        schm,
        dwServiceType,
        dwServiceState,
        ssa{$IFNDEF lpServices_Param_Is_Pointer}^{$ENDIF},
        Length(buffer),
        nBytesNeeded,
        nServices,
        nResumeHandle) then
      begin
        Exit;
      end;
    end;
    if (nServices > 0) then
    begin
      ss := ssa;
      for j := 0 to nServices-1 do
      begin
        slServicesList.Add(ss.lpDisplayName);
        Inc(ss);
      end;
    end;
  finally
    CloseServiceHandle(schm);
  end;
  Result := True;
end;
  • Related