Click here to Skip to main content
Click here to Skip to main content

About Windows Services

, 15 Nov 2001 CPOL
Rate this:
Please Sign up or sign in to vote.
A discussion on Windows services with examples
<!-- Download Links --> <!-- Main HTML starts here -->

Introduction

Writing server side program is little bit different then normal program. Because you have to deal a lot of issues, such as security. Microsoft recommends that server side application should be written in the form of service.

Service are also windows program. The difference is that System runs services rather than user. The other major difference is that services have no user interface. So service program can be start by either main or WinMain. Service Control Program that comes with windows is responsible to run, register and perform operations of the entire windows services. All the information of services is stored in windows registry under the key

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services

It is not recommended to access this registry key directly. To access the information about the use the windows APIs which manipulate the information about all the services. First you have to open the SCM (Service Control Manager) by using OpenSCManager, which have three parameters first is computer name on which you want to open SCM. In case of local computer just pass NULL. Second parameter is database name, to get the active database just pass NULL. Third parameter is desired Access.

Here is small console based program to show this

Program 1

#include &ltwindows.h>
#include &ltiostream.h>

void ErrorDescription(DWORD p_dwError);

int main()
{
    SC_HANDLE hHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (NULL == hHandle) {
        ErrorDescription(GetLastError());
        return -1;
    }
    else {
        cout << "Open SCM sucessfully" << endl;
    }

    if (!CloseServiceHandle(hHandle)) {
        ErrorDescription(GetLastError());
    }
    else {
        cout << "Close SCM sucessfully" << endl;
    }

    return 0;
}

// get the description of error
void ErrorDescription(DWORD p_dwError) {
    
    HLOCAL hLocal = NULL;
    
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, p_dwError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(LPTSTR)&hLocal, 
        0, NULL);
    
    MessageBox(NULL, (LPCTSTR)LocalLock(hLocal), TEXT("Error"), MB_OK | MB_ICONERROR);
    LocalFree(hLocal);
}

To get the information about all the services EnumServicesStatus is used. This function takes 8 paramaters. First one is handle of SCM. Second is Service Type whether you want to see Win32 service, device driver or both. Third is Service state whose value can be SERVICE_ACTIVE, SERVICE_INACTIVE or SERVICE_STATE_ALL to get information about active, inactive or all services respectively. Fourth one is a structure of ENUM_SERVICE_STATUS.

ENUM_SERVICE_STATUS is define in WinSvc.h file as

typedef struct _ENUM_SERVICE_STATUSW {
    LPWSTR         lpServiceName;
    LPWSTR         lpDisplayName;
    SERVICE_STATUS ServiceStatus;
} ENUM_SERVICE_STATUSW, *LPENUM_SERVICE_STATUSW;

First member of this structure is Service name and second one is its display name. Third parameter is also structure define in same fine WinSvc.h as

//
// Service Status Structure
//

typedef struct _SERVICE_STATUS {
    DWORD   dwServiceType;
    DWORD   dwCurrentState;
    DWORD   dwControlsAccepted;
    DWORD   dwWin32ExitCode;
    DWORD   dwServiceSpecificExitCode;
    DWORD   dwCheckPoint;
    DWORD   dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;

Fifth paramater is the size of buffer to store the information about services. Here the size is sizeof(ENUM_SERVICE_STATUS). The last three parameters are output variable return the no of bytes needed, no of service entries, and next service entry respectively.

I add little bit code in last program to get the information about services

Program 2

#include &ltwindows.h>
#include &ltiostream.h>

void ErrorDescription(DWORD p_dwError);

int main()
{
    SC_HANDLE hHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (NULL == hHandle) {
        ErrorDescription(GetLastError());
        return -1;
    }
    else {
        cout << "Open SCM sucessfully" << endl;
    }

    ENUM_SERVICE_STATUS service;
    
    DWORD dwBytesNeeded = 0;
    DWORD dwServicesReturned = 0;
    DWORD dwResumedHandle = 0;
    DWORD dwServiceType = SERVICE_WIN32 | SERVICE_DRIVER;

    // Query services
    BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL, 
        &service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
        &dwResumedHandle);

    if (!retVal) {
        ErrorDescription(GetLastError());
    }

    if (!CloseServiceHandle(hHandle)) {
        ErrorDescription(GetLastError());
    }
    else {
        cout << "Close SCM sucessfully" << endl;
    }

    return 0;
}

// get the description of error
void ErrorDescription(DWORD p_dwError) {
    
    HLOCAL hLocal = NULL;
    
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, p_dwError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(LPTSTR)&hLocal, 
        0, NULL);
    
    MessageBox(NULL, (LPCTSTR)LocalLock(hLocal), TEXT("Error"), MB_OK | MB_ICONERROR);
    LocalFree(hLocal);
}

The output of this program is a Message Box with a message "More data is available". To get the reason of this just add two lines in the program after calling EnumServicesStatus.

cout << "Bytes needed for buffer" << dwBytesNeeded << endl;
cout << "Bytes given for buffer" << sizeof(ENUM_SERVICE_STATUS) << endl;

The output of these statement should be different to tell the reason why we got this error message. Now allocate the buffer of the requires size give by dwBytesNeeded and call this function again to get the correct results.

Add this code to display the service name and its display name.

    ENUM_SERVICE_STATUS service;
    
    DWORD dwBytesNeeded = 0;
    DWORD dwServicesReturned = 0;
    DWORD dwResumedHandle = 0;
    DWORD dwServiceType = SERVICE_WIN32 | SERVICE_DRIVER;

    // Query services
    BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL, 
        &service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
        &dwResumedHandle);

    if (!retVal) {
        // Need big buffer
        if (ERROR_MORE_DATA == GetLastError()) {
            // Set the buffer
            DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
            ENUM_SERVICE_STATUS* pServices = NULL;
            pServices = new ENUM_SERVICE_STATUS [dwBytes];

            // Now query again for services
            EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER, 
                SERVICE_STATE_ALL, pServices, dwBytes, &dwBytesNeeded, 
                &dwServicesReturned, &dwResumedHandle);

            // now traverse each service to get information
            for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
                
                cout << TEXT("Display Name") << (pServices + iIndex)->lpDisplayName << TEXT('\t');
                cout << TEXT("Service Name") << (pServices + iIndex)->lpServiceName << endl;                                    
            }

            delete [] pServices;
            pServices = NULL;

        }
        // there is any other reason
        else {
            ErrorDescription(GetLastError());
        }
    }

Here is a complete program to show the services name its display name.

Program 3

#include &ltwindows.h>
#include &ltiostream.h>

void ErrorDescription(DWORD p_dwError);

int main()
{
    SC_HANDLE hHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (NULL == hHandle) {
        ErrorDescription(GetLastError());
        return -1;
    }
    else {
        cout << "Open SCM sucessfully" << endl;
    }

    ENUM_SERVICE_STATUS service;
    
    DWORD dwBytesNeeded = 0;
    DWORD dwServicesReturned = 0;
    DWORD dwResumedHandle = 0;
    DWORD dwServiceType = SERVICE_WIN32 | SERVICE_DRIVER;

    // Query services
    BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL, 
        &service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
        &dwResumedHandle);

    if (!retVal) {
        // Need big buffer
        if (ERROR_MORE_DATA == GetLastError()) {
            // Set the buffer
            DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
            ENUM_SERVICE_STATUS* pServices = NULL;
            pServices = new ENUM_SERVICE_STATUS [dwBytes];

            // Now query again for services
            EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER, SERVICE_STATE_ALL, 
                pServices, dwBytes, &dwBytesNeeded, &dwServicesReturned, &dwResumedHandle);

            // now traverse each service to get information
            for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
                
                cout << TEXT("Display Name") << (pServices + iIndex)->lpDisplayName << TEXT('\t');
                cout << TEXT("Service Name") << (pServices + iIndex)->lpServiceName << endl;                                    
            }

            delete [] pServices;
            pServices = NULL;

        }
        // there is any other reason
        else {
            ErrorDescription(GetLastError());
        }
    }

    if (!CloseServiceHandle(hHandle)) {
        ErrorDescription(GetLastError());
    }
    else {
        cout << "Close SCM sucessfully" << endl;
    }

    return 0;
}

// get the description of error
void ErrorDescription(DWORD p_dwError) {
    
    HLOCAL hLocal = NULL;
    
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, p_dwError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(LPTSTR)&hLocal, 
        0, NULL);
    
    MessageBox(NULL, (LPCTSTR)LocalLock(hLocal), TEXT("Error"), MB_OK | MB_ICONERROR);
    LocalFree(hLocal);
}

We can get more information about Service such as its Path, Start type etc by QueryServiceConfig. To use this functoin first get the service handle by OpenService. OpenService takes three paramater, handle of SCM, Service name and desired access. To get handle of Service.

SC_HANDLE hService = OpenService(hHandle, (pServices + iIndex)->lpServiceName, 
    SERVICE_ALL_ACCESS);

// if can not get handle of service
if (!hService) {
    ErrorDescription(GetLastError());
}

After getting the service handle use QueryServiceConfig to get more information about the service. QueryServiceConfig takes four parameters. First one is handle of service, second is address of QUERY_SERVICE_CONFIG which is define in WinSvc.h as

typedef struct _QUERY_SERVICE_CONFIGA {
    DWORD   dwServiceType;
    DWORD   dwStartType;
    DWORD   dwErrorControl;
    LPSTR   lpBinaryPathName;
    LPSTR   lpLoadOrderGroup;
    DWORD   dwTagId;
    LPSTR   lpDependencies;
    LPSTR   lpServiceStartName;
    LPSTR   lpDisplayName;
} QUERY_SERVICE_CONFIGA, *LPQUERY_SERVICE_CONFIGA;
typedef struct _QUERY_SERVICE_CONFIGW {
    DWORD   dwServiceType;
    DWORD   dwStartType;
    DWORD   dwErrorControl;
    LPWSTR  lpBinaryPathName;
    LPWSTR  lpLoadOrderGroup;
    DWORD   dwTagId;
    LPWSTR  lpDependencies;
    LPWSTR  lpServiceStartName;
    LPWSTR  lpDisplayName;
} QUERY_SERVICE_CONFIGW, *LPQUERY_SERVICE_CONFIGW;
#ifdef UNICODE
typedef QUERY_SERVICE_CONFIGW QUERY_SERVICE_CONFIG;
typedef LPQUERY_SERVICE_CONFIGW LPQUERY_SERVICE_CONFIG;
#else
typedef QUERY_SERVICE_CONFIGA QUERY_SERVICE_CONFIG;
typedef LPQUERY_SERVICE_CONFIGA LPQUERY_SERVICE_CONFIG;
#endif // UNICODE

Third parameter is buffer size and last one is output parameter returns the bytes needed. Add this to display the path of service.

// now traverse each service to get information
for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
    
    SC_HANDLE hService = OpenService(hHandle, (pServices + iIndex)->lpServiceName, 
        SERVICE_ALL_ACCESS);

    // if can not get handle of service
    if (!hService) {
        ErrorDescription(GetLastError());
        continue;
    }

    QUERY_SERVICE_CONFIG sc;
    DWORD dwBytesNeeded = 0;

    // Try to get information about the query
    BOOL bRetVal = QueryServiceConfig(hService, &sc, sizeof(QUERY_SERVICE_CONFIG),
        &dwBytesNeeded);

    if (!bRetVal) {
        ErrorDescription(GetLastError());
    }

    cout << TEXT("Display Name") << (pServices + iIndex)->lpDisplayName << TEXT('\t');
    cout << TEXT("Service Name") << (pServices + iIndex)->lpServiceName << TEXT('\t');
    cout << TEXT("Path Name") << sc.lpBinaryPathName << endl;
}

The output of this program is an Error. The description of that error is "The data area passed to a system call is too small". The same problem here. Solution is same allocate the buffer according to required bytes and call this function again.

Here is a complete console based program which shows the service path along with its name and display name.

Program 4

#include &ltwindows.h>
#include &ltiostream.h>

void ErrorDescription(DWORD p_dwError);

QUERY_SERVICE_CONFIG* g_psc = NULL;

int main()
{
    SC_HANDLE hHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    
    if (NULL == hHandle) {
        ErrorDescription(GetLastError());
        return -1;
    }
    else {
        cout << "Open SCM sucessfully" << endl;
    }
    
    ENUM_SERVICE_STATUS service;
    
    DWORD dwBytesNeeded = 0;
    DWORD dwServicesReturned = 0;
    DWORD dwResumedHandle = 0;
    DWORD dwServiceType = SERVICE_WIN32 | SERVICE_DRIVER;
    
    // Query services
    BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL, 
        &service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
        &dwResumedHandle);
    
    if (!retVal) {
        // Need big buffer
        if (ERROR_MORE_DATA == GetLastError()) {
            // Set the buffer
            DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
            ENUM_SERVICE_STATUS* pServices = NULL;
            pServices = new ENUM_SERVICE_STATUS [dwBytes];
            
            // Now query again for services
            EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER, SERVICE_STATE_ALL, 
                pServices, dwBytes, &dwBytesNeeded, &dwServicesReturned, &dwResumedHandle);
            
            // now traverse each service to get information
            for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
                
                SC_HANDLE hService = OpenService(hHandle, (pServices + iIndex)->lpServiceName, 
                    SERVICE_ALL_ACCESS);
                
                // if can not get handle of service
                if (!hService) {
                    ErrorDescription(GetLastError());
                    continue;
                }
                
                QUERY_SERVICE_CONFIG sc;
                DWORD dwBytesNeeded = 0;
                
                // Try to get information about the query
                BOOL bRetVal = QueryServiceConfig(hService, &sc, 
                    sizeof(QUERY_SERVICE_CONFIG), &dwBytesNeeded);
                
                if (!bRetVal) {

                    DWORD retVal = GetLastError();
                    // buffer size is small. 
                    // Required size is in dwBytesNeeded
                    if (ERROR_INSUFFICIENT_BUFFER == retVal) {
                        
                        DWORD dwBytes = sizeof(QUERY_SERVICE_CONFIG) + dwBytesNeeded;
                        g_psc = new QUERY_SERVICE_CONFIG[dwBytesNeeded];
                        
                        bRetVal = QueryServiceConfig(hService, g_psc, dwBytes, 
                                              &dwBytesNeeded);
                        
                        if (!bRetVal) {
                            
                            ErrorDescription(GetLastError());
                            
                            delete [] g_psc;
                            g_psc = NULL;
                            break;
                        }
                        
                    }
                    
                    cout << TEXT("Display Name:") << (pServices + iIndex)->lpDisplayName << TEXT('\t');
                    cout << TEXT("Service Name:") << (pServices + iIndex)->lpServiceName << TEXT('\t');
                    cout << TEXT("Path Name:") << g_psc->lpBinaryPathName << endl;
                }
            }

            delete [] pServices;
            pServices = NULL;
                
        }
    }
    
    if (!CloseServiceHandle(hHandle)) {
        ErrorDescription(GetLastError());
    }
    else {
        cout << "Close SCM sucessfully" << endl;
    }
    
    return 0;
}

// get the description of error
void ErrorDescription(DWORD p_dwError) {
    
    HLOCAL hLocal = NULL;
    
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, p_dwError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(LPTSTR)&hLocal, 
        0, NULL);
    
    MessageBox(NULL, (LPCTSTR)LocalLock(hLocal), TEXT("Error"), MB_OK | MB_ICONERROR);
    LocalFree(hLocal);
}

OpenSCManager can not only display local computers services but also display the information on remote computer. The first parameter of OpenSCManager is the name of computer. To get the name of all computers on the network NetServerEnum is used. This function is available only on Win NT/2000. So Program use this API run on Win 9x give this error message.

This message don't give any useful information to the user why this program is not running. It is better to check the version of operating system first and give the appropriate message.

// Get the type of operating system
OSVERSIONINFO osInfo;
osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

if (!GetVersionEx(&osInfo)) {
    ErrorDescription(GetLastError());
    return -1;
}

if (osInfo.dwPlatformId != VER_PLATFORM_WIN32_NT) {
    MessageBox(NULL, TEXT("This program runs on Win NT, 2000 or XP"),
        TEXT("Information"), MB_OK | MB_ICONINFORMATION);
}

This program again gives the same message box, because NetApi32.dll is implicitly load at the time of executing the program. There are two solution of this. First load the DLL dynamically using LoadLibrary and get the address of function by calling GetProcAddress. But you have to be careful when dll is in memory and when not. On the other hand linker of VC++ 6 introduce a new switch /delayload to do this. With the help of delayload we can tell the compiler that loads this dll when the function of dll is called. Using delayload is not very difficult.

// for delay load
#include <delayimp.h>
#pragma comment(lib, "Delayimp.lib")

// This file will be delay load
#pragma comment(linker, "/DelayLoad:NetAPI32.dll")
#pragma comment(linker, "/Delay:unload")

Now when run this program under Win 9x this will show this Message box.

I write a program which display the information about the services not only local but remote computer. Program is written in Completely C Language.

Main screen

Detail information about services

Information about network computers

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Zeeshan Amjad
Software Developer (Senior) Bloomberg LP
United States United States
Working as a Sr C++ Developer at Bloomberg LP

Comments and Discussions

 
GeneralRe: Ambitious PinsussAnonymous15-Aug-04 0:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 16 Nov 2001
Article Copyright 2001 by Zeeshan Amjad
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid