Ali Keshavarz's Website
RSS icon Home icon
  • Enumerating Windows services

    Posted on September 13th, 2011 Ali Keshavarz 3 comments

    A while ago, I had to enumerate all Windows services for a project. I was browsing the source code today, and thought maybe it would be a good idea to talk about this matter in a blog post, and publish a sample source code to achieve this.

    The easiest way to enumerate Windows services is using WMI. Of course using WMI requires some basic knowledge of MS COM technology which is tricky, but thanks to the great tool. Delphi WMI Class Generator by Rodrigo Ruz, you can have a ready Delphi class for obtaining information about installed services on a Windows machine without even writing a single line of code. All you have to know is that WMI class for Windows services is Win32_Service under root\CIMV2 namespace. Delphi WMI Class Generator will generate a unit named uWin32_Service.pas which contains a class named TWin32_Service. This class returns info about a single service for you. If you need to enumerate all installed services, you need to write a couple of codes to call its GetCollectionCount to retrieve number of available services, and SetCollectionIndex to set current item index for the class. Here is a sample code to retrieve names of all installed services on the local machine using TWin32_Service class:

    var
      i: Integer;
    begin
      with TWin32_Service.Create do
      try
        for i := 0 to GetCollectionCount - 1 do
        begin
          Writeln(DisplayName);
          SetCollectionIndex(i);
        end;
      finally
        Free;
      end;
    end;
    

    However, WMI is slow, and it relays on WMI service. If you need a faster way to enumerate Windows services, or if for whatever reason WMI service is disabled or not working on your target machine; then you have to relay on Windows API. Using Windows API for this purpose is not as easy as using the automatically generated TWin32_Service class. I am not aware of a single Windows API function that can enumerate Windows services for you and give you all the detailed information which WMI returns for each service. You have to call roughly a dozen of API functions to enumerate Windows services and retrieve some nice information for each service.

    First you need to open a handle to service manager on the target machine by calling OpenSCManager function, then you should call EnumServicesStatusEx function to enumerate available services, and retrieve their names and status. If you just need service names and status, you are done, but if you need to retrieve description and configuration for each service too, then you should open a handle for each service using OpenService function by giving it service name which you obtain through EnumServicesStatusEx call , and then call QueryServiceConfig and QueryServiceConfig2 functions to get the required configurations. At the end you should make sure you free all the allocated buffers, and close all handles. Delphi installation does not provide header translation for some of these API functions (particularly EnumServicesStatusEx and QueryServiceConfig2), but they are translated and available for Delphi by well-known Project JEDI, and their JEDI Windows API Headers which contain the majority of Windows headers. If you are a Delphi developer and have not heard about them yet (That would be weird!!), you can download the package from this link.

    Anyways, in the source code below, I wrapped all of those API calls into a class named TAkServices. This class holds an internal list containing information for each Windows service installed on a given machine. I tested it on both Delphi 2010 (Win32) and Delphi XE2 (Win64):

    {*******************************************************************************
    /// <author> Ali Keshavarz (vcldeveloper@gmail.com) </author>
    /// <date> 09/13/2011 </date>
    /// <license>
    ///  This work is licensed under the Creative Commons Attribution 3.0 Unported
    ///  License. To view a copy of this license, visit
    ///  http://creativecommons.org/licenses/by/3.0/
    ///  or send a letter to Creative Commons, 171 Second Street, Suite 300,
    ///  San Francisco, California, 94105, USA.
    /// </license>
    *******************************************************************************}
    
    unit uAkServices;
    
    interface
    
    uses
     Generics.Collections;
    
    type
     /// Service status values; these items are defined in Windows SDK. For more
     /// info refer to MSDN.
     TAkServiceState = (SERVICE_STOPPED          = $۰۰۰۰۰۰۰۱,
     SERVICE_START_PENDING    = $۰۰۰۰۰۰۰۲,
     SERVICE_STOP_PENDING     = $۰۰۰۰۰۰۰۳,
     SERVICE_RUNNING          = $۰۰۰۰۰۰۰۴,
     SERVICE_CONTINUE_PENDING = $00000005,
     SERVICE_PAUSE_PENDING    = $۰۰۰۰۰۰۰۶,
     SERVICE_PAUSED           = $۰۰۰۰۰۰۰۷ );
    
     /// Service startup mode values; these items are defined in Windows SDK. For
     /// more info refer to MSDN.
     TAkServiceStartMode = (SERVICE_BOOT_START,
     SERVICE_SYSTEM_START,
     SERVICE_AUTO_START,
     SERVICE_DEMAND_START,
     SERVICE_DISABLED );
    
     /// <summary>
     /// Service information structure. This structure is used by TAkServiceList
     /// class to save info for each individual service.
     /// </summary>
     TAkServiceInfo = record
     public
     /// A path to service executable file.
     BinaryPath : string;
     /// Current service state.
     CurrentState : TAkServiceState;
    
     Description : string;
     /// User-friendly name of service
     DisplayName : string;
     /// Service flags. It can be either zero or SERVICE_RUNS_IN_SYSTEM_PROCESS.
     Flags : Integer;
     /// Actual name of service which is also used as service key name in Registry.
     Name : string;
     /// Process identifier for a running service. If the service is not running
     /// or it is running in System process, then ProcessID will be zero.
     ProcessID : Cardinal;
     /// Startup mode of service.
     StartMode : TAkServiceStartMode;
     /// Username by which service logged into system.
     StartName : string;
     end;
    
     /// Holds a list of TAkServiceInfo reccords as the list of Windows services.
     TAkServiceList = TList<TAkServiceInfo>;
    
     TAkServiceInfoEnumerator = TEnumerator<TAkServiceInfo>;
    
     /// <summary>
     /// Provides a list of installed Windows services on a given machine.
     /// </summary>
     TAkServices = class
     private
     FLastUpdateTime : TDateTime;
     FList : TAkServiceList;
     FMachineName : string;
     function GetCount: Integer;
     function GetItem(Index: Integer): TAkServiceInfo;
     procedure GetServiceConfig(hSCManager: Cardinal; var Service: TAkServiceInfo);
     procedure SetMachineName(const Value: string);
     protected
     procedure InitializeList; virtual;
     procedure InternalRefresh; virtual;
     public
     /// <summary>
     /// Constructor for TAkServices.
     /// </summary>
     /// <param name="AMachineName">
     ///  (in) Name of the machine which its services should be listed. Default = ''
     /// </param>
     /// <param name="."></param>
     constructor Create(const AMachineName: string = '');
     destructor Destroy; override;
     function GetEnumerator: TAkServiceInfoEnumerator;
     /// <summary> Updates list of services. </summary>
     procedure Refresh;
     /// <summary>
     /// Indicates number of services. If the list is not updated yet, it will be
     /// updated first.
     /// </summary>
     property Count: Integer read GetCount;
     /// <summary>
     /// Returns item at the Index position of the list of services.
     /// If the list is not updated yet, it will be updated first.
     /// </summary>
     property Item[Index: Integer]: TAkServiceInfo read GetItem; default;
     /// <summary>
     /// Indicates last update time for the list. It will be updated each time
     /// Refresh method is called.
     /// </summary>
     property LastUpdateTime: TDateTime read FLastUpdateTime;
     /// <summary>Name of the machine which its services should be listed. </summary>
     property MachineName: string read FMachineName write SetMachineName;
     end;
    
    implementation
    
    uses
     SysUtils,
     Windows,
     JwaWinNT,
     JwaWinType,
     JwaWinSvc,
     JwaWinError;
    
    { TAkServices }
    
    constructor TAkServices.Create(const AMachineName: string);
    begin
     inherited Create;
     FMachineName := AMachineName;
    end;
    
    destructor TAkServices.Destroy;
    begin
     System.TMonitor.Enter(Self);
     try
     FList.Free;
     FList := nil;
     finally
     System.TMonitor.Exit(Self);
     end;
    
     inherited;
    end;
    
    function TAkServices.GetCount: Integer;
    begin
     InitializeList;
     Result := FList.Count;
    end;
    
    function TAkServices.GetEnumerator: TAkServiceInfoEnumerator;
    begin
     InitializeList;
     Result := FList.GetEnumerator;
    end;
    
    function TAkServices.GetItem(Index: Integer): TAkServiceInfo;
    begin
     InitializeList;
     Result := FList[Index];
    end;
    
    procedure TAkServices.InitializeList;
    begin
     if not Assigned(FList) then
     begin
     System.TMonitor.Enter(Self);
     try
     FList := TAkServiceList.Create;
     InternalRefresh;
     finally
     System.TMonitor.Exit(Self);
     end;
     end;
    end;
    
    procedure TAkServices.GetServiceConfig(hSCManager: Cardinal; var Service: TAkServiceInfo);
    var
     dwBufNeeded: Cardinal;
     dwBufSize: Cardinal;
     hService : Cardinal;
    
     /// <summary>
     /// Retrieves service binary path, start mode, and user name.
     /// </summary>
     procedure GetRequiredConfig(var Service: TAkServiceInfo);
     var
     lpServiceConfig : LPQUERY_SERVICE_CONFIG;
     begin
     dwBufSize := 0;
     dwBufNeeded := 0;
     /// Retrieve required buffer size.
     QueryServiceConfig(hService, nil, dwBufSize, dwBufNeeded);
     if GetLastError <> ERROR_INSUFFICIENT_BUFFER then
     RaiseLastOSError;
    
     /// Alocate enough buffer size.
     dwBufSize := dwBufNeeded;
     lpServiceConfig := AllocMem(dwBufSize);
     try
     /// Get service configuration
     if not QueryServiceConfig(hService, lpServiceConfig, dwBufSize, dwBufNeeded) then
     RaiseLastOSError;
    
     /// Save configuration data in the record.
     Service.BinaryPath := lpServiceConfig.lpBinaryPathName;
     Service.StartMode := TAkServiceStartMode(lpServiceConfig.dwStartType);
     Service.StartName := lpServiceConfig.lpServiceStartName;
     finally
     FreeMem(lpServiceConfig);
     end;
     end;
    
     /// <summary>
     /// Retrieves service description.
     /// </summary>
     procedure GetOptionalConfig(var Service: TAkServiceInfo);
     var
     lpServiceDescBuff : LPSERVICE_DESCRIPTION;
     begin
     dwBufSize := 0;
     dwBufNeeded := 0;
     /// Retrieve required buffer size.
     QueryServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, nil, dwBufSize, dwBufNeeded);
     if GetLastError <> ERROR_INSUFFICIENT_BUFFER then
     RaiseLastOSError;
    
     /// Alocate enough buffer size.
     dwBufSize := dwBufNeeded;
     lpServiceDescBuff := AllocMem(dwBufSize);
     try
     /// Retrieve service description
     if not QueryServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION,
                                PByte(lpServiceDescBuff), dwBufSize, dwBufNeeded) then
       RaiseLastOSError;
      /// Save service description in the record.
      if Assigned(lpServiceDescBuff.lpDescription) then
       SetString(Service.Description,
                 lpServiceDescBuff.lpDescription,
                 StrLen(lpServiceDescBuff.lpDescription));
       finally
         FreeMem(lpServiceDescBuff);
       end;
     end;
    
    begin
      Assert(hSCManager > 0, 'Service manager is not initialized.');
    
      /// Open a service handle to be used by QueryServiceConfig() and QueryServiceConfig2() functions.
      hService := OpenService(hSCManager,PChar(Service.Name),SERVICE_QUERY_CONFIG);
      if hService = 0 then
        RaiseLastOSError;
      try
        GetRequiredConfig(Service);
        GetOptionalConfig(Service);
      finally
        CloseServiceHandle(hService);
      end;
    end;
    
    procedure TAkServices.InternalRefresh;
    var
      dwBufNeeded: Cardinal;
      dwBufSize: Cardinal;
      dwNumOfServices: ULONG;
      hSCM: SC_HANDLE;
      lpResumeHandle: Cardinal;
      NewItem : TAkServiceInfo;
      pBuf: PBYTE;
      pInfo: LPENUM_SERVICE_STATUS_PROCESS;
      i: Integer;
    begin
      Assert(Assigned(FList),'Internal list is not assigned yet!');
    
      System.TMonitor.Enter(FList);
      try
       dwBufSize := 0;
       dwBufNeeded := 0;
       dwNumOfServices := 0;
       lpResumeHandle := 0;
       FList.Clear;
    
       /// We need an open service manager handle to be able to enumerate services.
       hSCM := OpenSCManager(PChar(FMachineName), nil,
       SC_MANAGER_ENUMERATE_SERVICE or SC_MANAGER_CONNECT);
       if (hSCM = 0) then
         RaiseLastOSError;
    
       try
         /// First check how much buffer is needed by passing dwBufSize as zero,
         /// and pBuf as nil.
         EnumServicesStatusEx(hSCM, SC_ENUM_PROCESS_INFO, SERVICE_WIN32,
                              SERVICE_STATE_ALL, nil, dwBufSize, dwBufNeeded,
                              dwNumOfServices, lpResumeHandle, nil);
         if (dwBufNeeded < 1) then
           RaiseLastOSError;
    
         /// Alocate enough space for the buffer.
         dwBufSize := dwBufNeeded;
         pBuf := AllocMem(dwBufSize);
         try
           /// Retrieve services list
           if not EnumServicesStatusEx(hSCM, SC_ENUM_PROCESS_INFO, SERVICE_WIN32,
                                       SERVICE_STATE_ALL, pBuf, dwBufSize, dwBufNeeded,
                                       dwNumOfServices, lpResumeHandle, nil) then
             RaiseLastOSError;
    
           /// Type casting pBuf to LPENUM_SERVICE_STATUS_PROCESS record is necessary
           ///  so that we can access each service info in the buffer as a record.
           pInfo := LPENUM_SERVICE_STATUS_PROCESS(pBuf);
           /// Write services list
           for i := 0 to dwNumOfServices-1 do
           begin
             NewItem.Name := pInfo.lpServiceName;
             NewItem.DisplayName := pInfo.lpDisplayName;
             NewItem.Flags := pInfo.ServiceStatusProcess.dwServiceFlags;
             NewItem.ProcessID := pInfo.ServiceStatusProcess.dwProcessId;
             NewItem.CurrentState := TAkServiceState(pInfo.ServiceStatusProcess.dwCurrentState);
             GetServiceConfig(hSCM, NewItem);
             FList.Add(NewItem);
    
             /// Go to next record in the buffer. Compiler will increment pInfo pointer
             ///  according to size LPENUM_SERVICE_STATUS_PROCESS record.
             Inc(pInfo);
           end;
           FLastUpdateTime := Now;
        finally
           FreeMem(pBuf);
        end;
      finally
        CloseServiceHandle(hSCM);
      end;
      finally
         System.TMonitor.Exit(FList);
      end;
    end;
    
    procedure TAkServices.Refresh;
    begin
     /// If FList is not created yet, InitializeList will create it and calls InternalRefresh
     /// automatically, but if the list is already created, just calling InternalRefresh
     /// is enough.
     if not Assigned(FList) then
     InitializeList
     else
     InternalRefresh;
    end;
    
    procedure TAkServices.SetMachineName(const Value: string);
    begin
     if not SameText(FMachineName, Value) then
     begin
        FMachineName := Value;
        Refresh;
      end;
    end;
    
    end.
    

    Here is also a demo Delphi application using TAkServices class to enumerate Windows services on the local machine, and show them in a ListView control. Download link for the source files (including uAkServices.pas and all required JEDI API header translations) is available at the end of this article:

    Demo application in action; enumerating Windows services using Windows API

    Download:

    Source code plus the demo app

    P.S. Oops, it seems my code highlighter is not keen on indentation in longer source codes. If you see the code without proper indentation, you can use this pastebin link to see the source formatted properly, or download the source file. I’m sorry for the inconvenience.

     

    3 responses to “Enumerating Windows services” RSS icon


    Leave a reply