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 <windows.h>
#include <iostream.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;
}
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
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 <windows.h>
#include <iostream.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;
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;
}
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;
BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL,
&service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
&dwResumedHandle);
if (!retVal) {
if (ERROR_MORE_DATA == GetLastError()) {
DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
ENUM_SERVICE_STATUS* pServices = NULL;
pServices = new ENUM_SERVICE_STATUS [dwBytes];
EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER,
SERVICE_STATE_ALL, pServices, dwBytes, &dwBytesNeeded,
&dwServicesReturned, &dwResumedHandle);
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;
}
else {
ErrorDescription(GetLastError());
}
}
Here is a complete program to show the services name its display name.
Program 3
#include <windows.h>
#include <iostream.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;
BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL,
&service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
&dwResumedHandle);
if (!retVal) {
if (ERROR_MORE_DATA == GetLastError()) {
DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
ENUM_SERVICE_STATUS* pServices = NULL;
pServices = new ENUM_SERVICE_STATUS [dwBytes];
EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER, SERVICE_STATE_ALL,
pServices, dwBytes, &dwBytesNeeded, &dwServicesReturned, &dwResumedHandle);
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;
}
else {
ErrorDescription(GetLastError());
}
}
if (!CloseServiceHandle(hHandle)) {
ErrorDescription(GetLastError());
}
else {
cout << "Close SCM sucessfully" << endl;
}
return 0;
}
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 (!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
Third parameter is buffer size and last one is output parameter returns the bytes needed. Add this to display the path of service.
for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
SC_HANDLE hService = OpenService(hHandle, (pServices + iIndex)->lpServiceName,
SERVICE_ALL_ACCESS);
if (!hService) {
ErrorDescription(GetLastError());
continue;
}
QUERY_SERVICE_CONFIG sc;
DWORD dwBytesNeeded = 0;
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 <windows.h>
#include <iostream.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;
BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL,
&service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
&dwResumedHandle);
if (!retVal) {
if (ERROR_MORE_DATA == GetLastError()) {
DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
ENUM_SERVICE_STATUS* pServices = NULL;
pServices = new ENUM_SERVICE_STATUS [dwBytes];
EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER, SERVICE_STATE_ALL,
pServices, dwBytes, &dwBytesNeeded, &dwServicesReturned, &dwResumedHandle);
for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
SC_HANDLE hService = OpenService(hHandle, (pServices + iIndex)->lpServiceName,
SERVICE_ALL_ACCESS);
if (!hService) {
ErrorDescription(GetLastError());
continue;
}
QUERY_SERVICE_CONFIG sc;
DWORD dwBytesNeeded = 0;
BOOL bRetVal = QueryServiceConfig(hService, &sc,
sizeof(QUERY_SERVICE_CONFIG), &dwBytesNeeded);
if (!bRetVal) {
DWORD retVal = GetLastError();
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;
}
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.
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.
#include <delayimp.h>
#pragma comment(lib, "Delayimp.lib")
#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