-
Enumerating Windows services
Posted on September 13th, 2011 3 commentsA 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:
Download:
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”

-
Thanx again…
-
Hello,
Thank you so much Mr Keshavarz.
For this case, it need to have administrator privileges. isn’t it?
Leave a reply
-


Shuhin Ashayeri September 15th, 2011 at 16:18