Using Visual Basic Classes to Query Windows NT Services

Steve Kirk
MSDN Content Development Group

March 1997

Click to open or copy the files in the QRYSVC sample application for this technical article.

Abstract

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.

Object Model

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.

Using the Underlying Win32 API

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.

Getting the Services

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.