Steve Kirk
MSDN Content Development Group
March 1997
Click to open or copy the files in the QRYSVC sample application for this technical article.
This article demonstrates how to create a pair of classes in Microsoft® Visual Basic® version 5.0 that query Windows NT® services according to type and status and present the results as a collection of service objects. A detailed description of each service is provided through the properties of the service class. You can package these class modules as a Component Object Model (COM) server or just include them in your project.
These Visual Basic 5.0 classes provide an object interface for programs that need to know what Windows NT services are installed on a machine or that need detailed information about a particular service. A client using these classes can query system services by type or status and receive the results as a collection of service objects. Read on for a description of how to build the classes.
The administrative class CServiceAdmin provides a container for the Services collection as well as the supporting filter properties and the FillServices method. CServiceAdmin also provides the primary interface for client programs. See Table 1.
Table 1. CServiceAdmin Class
Property | Description |
Services | A collection of service objects that fit the filter specified by the ServiceType and ServiceState properties. The collection has a Count property and each member has a key of the service display name. |
ServiceType | A bit field that controls which services are included in the Services collection. The value supplied must be one or both of the following values that are defined by the server: SERVICE_WIN32 SERVICE_DRIVER |
ServiceState | A bit field that controls which services are included in the Services collection. The value supplied must be one or both of the following values that are defined by the server: SERVICE_ACTIVE SERVICE_INACTIVE |
Method | Description |
FillServices | Refreshes the Services collection based on the filter specified by the ServiceType and ServiceState properties. |
CserviceClass describes a service by name and publishes type and state information and control codes that the service will accept and process. See Table 2.
Table 2. CService Class
Property | Description |
ServiceName | The name of the service. |
DisplayName | The friendly name for the service. |
OwnProcess | True/False indicates the service runs in its own process. |
ShareProcess | True/False indicates the service runs in a process shared with other services. |
DeviceDriver | True/False indicates a Windows NT device driver. |
FileSystemDriver | True/False indicates a Windows NT file system driver. |
InteractsWithDesktop | True/False indicates a service process that can interact with the desktop. |
Stopped | True/False indicates the service is not running. |
StartPending | True/False indicates the service is starting. |
StopPending | True/False indicates the service is stopping. |
Running | True/False indicates the service is running. |
ContinuePending | True/False indicates the service continue is pending. |
PausePending | True/False indicates the service pause is pending. |
Paused | True/False indicates the service is paused. |
AcceptStop | True/False indicates the service can be stopped. |
AcceptPauseContinue | True/False indicates the service can be paused and continued. |
AcceptShutdown | True/False indicates the service is notified when system shutdown occurs. |
Win32ExitCode | Long specifies a Win32 error code that the service uses to report an error that occurs when it is starting or stopping. Indicates that the error code is contained at the ServiceSpecificExitCode property when set to ERROR_SERVICE_SPECIFIC_ERROR. |
ServiceSpecificExitCode | Long contains an error code returned by the service during stopping or starting if the Win32ExitCode property is set to ERROR_SERVICE_SPECIFIC_ERROR. |
The core Win32® function used is EnumServicesStatus. This function returns information about the services that match the supplied dwServiceType and dwServiceState parameters into an array of ENUM_SERVICE_STATUS structures that each describe a service.
Private Declare Function EnumServicesStatus Lib "advapi32.dll" _
Alias _"EnumServicesStatusA" _
(ByVal hSCManager As Long, _
ByVal dwServiceType As Long, _
ByVal dwServiceState As Long, _
ByVal lpBufAddress As Long, _
ByVal cbBufSize As Long, _
pcbBytesNeeded As Long, _
lpServicesReturned As Long, _
lpResumeHandle As Long) As Long
The ENUM_SERVICE_STATUS structure contains the service name and display name as well as a SERVICE_STATUS structure that contains type, status, and control descriptions.
Private Type ENUM_SERVICE_STATUS
lpServiceName As Long
lpDisplayName As Long
ServiceStatus As SERVICE_STATUS
End Type
Private Type SERVICE_STATUS
dwServiceType As Long
dwCurrentState As Long
dwControlsAccepted As Long
dwWin32ExitCode As Long
dwServiceSpecificExitCode As Long
dwCheckPoint As Long
dwWaitHint As Long
End Type
The Win32 OpenSCManager function opens the service control manager database and returns a handle that is used by EnumServiceStatus. CloseServiceHandle closes the database and releases the handle.
Private Declare Function OpenSCManager Lib "advapi32.dll" Alias "OpenSCManagerA" (ByVal lpMachineName As String, _
ByVal lpDatabaseName As String, _
ByVal dwDesiredAccess As Long) As Long _
Private Declare Function CloseServiceHandle Lib "advapi32.dll" _
(ByVal hSCObject As Long) As Long
You will also use other procedures from Win32, including lstrcpy and lstrlen. See the QRYSVC sample code for declarations of these more general procedures.
The FillServices method is the heart of the CServiceAdmin class. It uses EnumServicesStatus to get the services and place them in an array of ENUM_SERVICE_STATUS structures in a memory buffer. The following code segments from FillServices show how this is done.
Public Sub FillServices()
Dim vEnumServiceStatus As ENUM_SERVICE_STATUS
Dim vEnumServiceStatus() As ENUM_SERVICE_STATUS
Dim sDisplayName As String * 256
Dim sServiceName As String * 256
The first task is to initialize the Services collection.
Set m_services = Nothing
Set m_services = New Collection
The OpenSCManager function returns a handle to the Service Control Manager (SCM). Empty strings for sMachineName and sDatabaseName open the active database on the local computer. If OpenSCManager fails and returns a 0, report the error using Err.Raise.
hSCM = OpenSCManager(sMachineName, sDatabaseName, _
SC_MANAGER_ENUMERATE_SERVICE)
If hSCM = 0 Then
lLastError = GetLastError
On Error GoTo 0
Err.Raise vbObjectError + lLastError, Trim$(App.Title
Exit Sub
End If
It is impossible to know how many services will be returned, so you should dimension vEnumServices() to a reasonable size and let EnumServicesStatus return services at the rate of a bufferful at a time. You’ll stay in the following loop until all of the services have been retrieved from the SCM and added to the Services collection. If the buffer specified is too small for all of the services, EnumServiceStatus will return a successful lRetVal but will set the value that is returned by Err.LastDllError to ERROR_MORE_DATA. EnumServicesStatus keeps track of its place with lResumehandle, so it will pick up where it left off each time around.
lBytesNeeeded = 0
lServicesReturned = 0
lResumeHandle = 0
bResult = True
ReDim vEnumServiceStatus(256) As ENUM_SERVICE_STATUS
While bResult
lRetVal = EnumServicesStatus(hSCM, _
m_ServiceType, _
m_ServiceState, _
vEnumServiceStatus(0), _
256, _
lBytesNeeeded, _
lServicesReturned, _
lResumeHandle)
If there are more services to retrieve than fit in the buffer, Err.LastDllError will return ERROR_MODE_DATA and you will stay in the loop. If not, you can exit the loop.
GLE = Err.LastDllError
If lRetVal = 0 Then
If GLE = ERROR_MORE_DATA Then
bResult = True
Else
bResult = False
End If
Else
bResult = True
End If
This section walks through vEnumServiceStatus() and adds a service object to the Services collection for each service returned. Use lstrcpy to copy pointers to ServiceName and DisplayName into fixed-length string variables and then trim them to the null terminator with help from lstrlen. Then fill in the numerical properties of oService and add it to the Services collection.
For lNdx = 0 To lServicesReturned - 1
Set oService = New CService
'Resolve ServiceName and DisplayName from pointers.
lResult = lstrcpy(sServiceName, _
vEnumServiceStatus(lNdx).lpServiceName)
lResult = lstrcpy(sDisplayName, _
vEnumServiceStatus(lNdx).lpDisplayName)
oService.ServiceName = Mid$(sServiceName, 1, _
lstrlen(sServiceName))
oService.DisplayName = Mid$(sDisplayName, 1, _
lstrlen(sDisplayName))
'Resolve Service Status.
oService.ServiceType = _
vEnumServiceStatus(lNdx).ServiceStatus.dwServiceType
.
.
.
'Add the service to the services collection.
m_services.Add oService, oService.DisplayName
Set oService = Nothing
Next
Wend
'Close the service control manager.
CloseServiceHandle (hSCM)
End Sub
Done! The services collection has been filled with service objects that match the ServiceType and ServiceStatus properties. The rest of the class module code is straightforward. You can get that by downloading the qrysvc sample that accompanies this article.