{*******************************************************************************
/// <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 = $00000001,
SERVICE_START_PENDING = $00000002,
SERVICE_STOP_PENDING = $00000003,
SERVICE_RUNNING = $00000004,
SERVICE_CONTINUE_PENDING = $00000005,
SERVICE_PAUSE_PENDING = $00000006,
SERVICE_PAUSED = $00000007 );
/// 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.