The Event Logging API

Of course, more important than the ability to use the Windows 2000 user interface to view events is the ability to add events to the event log from within a service application. Adding events to the event log can be a fairly complex process. Figure 5-5 shows this process at a very high level, without detailing the complexities.

click to view at full size.

Figure 5-5 The process for adding events to the event log.

The application does not write directly to the event log, but instead sends a request to the Windows 2000 EventLog service. The EventLog service in turn writes the message to the event log. Take another look at Figure 5-2. The message displayed in the event properties dialog is "The C: disk is at or near capacity. You may need to delete some files." Now take a look at Figure 5-3. We can see that the system event log is in a file named C:\Winnt\System32\Config\Sysevent.evt. Searching this file for the word "near" results in no match. You'll understand why this happens once I've explained the process involved in writing event log records.

Writing Events to the Event Log

An application cannot simply call the ReportEvent Win32 API function with a character pointer and have the text logged. Creating entries in the event log involves several steps:

  1. Create a message resource that can be linked into the application.
  2. Create a registry entry that identifies the application that will be the source of the events and the type of events that will be submitted.
  3. Call the RegisterEventSource Win32 API upon entry to the application.
  4. Call the ReportEvent Win32 API to report the event, passing only the replaceable parameters required by the message mask created for the particular event, as specified by the message string.

I'll go through these steps in more detail in the next few paragraphs.

Creating the Resource

To create the resource, we need to create a file that can be fed to the resource compiler to generate the message resource. The file needs to have one multiline entry for each possible message, as in the following example:

 MessageId=100 SymbolicName=EVMSG_INSTALLED Language=English The%1servicewasinstalled. . 

The MessageID is commonly specified only for the first entry, with each additional entry automatically assigned a MessageID value one greater than the entry before it. The program uses the symbolic name to internally identify the message. Finally, the language is specified, followed by the text to be used for that message in the specified language. Note that additional languages and messages could have been specified if multiple languages were to be supported. Note also that the message text uses replaceable parameters in the form of %n , where n is the number of the parameter. Because of the format of this file, you should follow a couple of commonsense rules to make your life easier:

  • Do not begin a text line with a period, as the message compiler uses a period at the start of a line to signal the end of an entry.
  • If an entry must contain a percent sign (%), you must use an additional percent sign as an escape character before the percent sign. For example, if the text means "Drive %1 is more than 95% full.", you need to say "Drive %1 is more than 95%% full."

All the examples in this book will follow these rules. Listing 5-1 shows CppSvcMsg.mc, a message file for use in the CppService class first introduced in Chapter 3. Listing 5-2 shows CppSvcMsg.h, one of the files created when CppSvcMsg.mc is run through the message compiler.

Listing 5-1

CppSvcMsg.mc

 MessageId=100 SymbolicName=EVMSG_INSTALLED Language=English The%1servicewasinstalled. . MessageId= SymbolicName=EVMSG_REMOVED Language=English The%1servicewasremoved. . MessageId= SymbolicName=EVMSG_STARTED Language=English Theservicewasstarted. . MessageId= SymbolicName=EVMSG_STOPPED Language=English The%1servicewasstopped. . MessageId= SymbolicName=EVMSG_DEBUG Language=English Debug:%1 . 

Listing 5-2

CppSvcMsg.h

 // //The32-bitvaluesarelaidoutasfollows: // //3322222222221111111111 //10987654321098765432109876543210 //+---+-+-+-----------------------+-------------------------------+ //SevCRFacilityCode //+---+-+-+-----------------------+-------------------------------+ // //where // //Sev-istheseveritycode // //00-Success //01-Informational //10-Warning //11-Error // //C-istheCustomercodeflag // //R-isareservedbit // //Facility-isthefacilitycode // //Code-isthefacility'sstatuscode // // //Definethefacilitycodes. // // //Definetheseveritycodes. // // //MessageId:EVMSG_INSTALLED // //MessageText: // //The%1servicewasinstalled. // #defineEVMSG_INSTALLED0x00000064L // //MessageId:EVMSG_REMOVED // //MessageText: // //The%1servicewasremoved. // #defineEVMSG_REMOVED0x00000065L // //MessageId:EVMSG_STARTED // //MessageText: // //Theservicewasstarted. // #defineEVMSG_STARTED0x00000066L // //MessageId:EVMSG_STOPPED // //MessageText: // //The%1servicewasstopped. // #defineEVMSG_STOPPED0x00000067L // //MessageId:EVMSG_DEBUG // //MessageText: // //Debug:%1 // #defineEVMSG_DEBUG0x00000068L 

The last message in the file, identified as EVMSG_DEBUG, is useful for debugging. This message takes a single parameter and will allow you to write arbitrary strings to the event log.

Creating a Registry Entry for the Event Source

Once the mc file, the rc file, and the h file have been added to the project, the project needs to be modified to create a registry entry identifying the application that will be creating the messages. The applications reading the event log use this entry to piece together the actual text of the message as intended by the developer, even though the actual verbiage is not captured in the event log file. The registry needs to have a key added under

 HKLM\SYSTEM\CurrentControlSet\Services\EventLog\Application 

The key needs to be the name of the application. Values under this key include the following:

  • EventMessageFile , which contains the name of the file containing the message resource, often the application's executable file.
  • TypesSupported , a bit flag specifying the types of messages that the application will generate, including EVENTLOG_ERROR_TYPE, EVENTLOG_WARNING_TYPE, and EVENTLOG_INFORMATION_TYPE.

Two additional values for TypesSupported are EVENTLOG_AUDIT_SUCCESS and EVENTLOG_AUDIT_FAILURE, which are commonly used when writing to the security log. Several less common registry entries can be added. Each is fully described in the Win32 SDK documentation. The registry entries need to be entered only once. Within a service, this is usually done during the installation process. Note that, commonly, the entries are not deleted during uninstallation, since the events that exist in the log cannot be properly displayed if the registry entries are missing. This implies that the service application generating event log entries will remain on the disk in the location specified in the EventLog registry entry.

Calling RegisterEventSource

Once the registry entry is made, the Win32 API function RegisterEventSource can be called. This function has the following prototype:

 HANDLERegisterEventSource(LPCTSTRlpUNCServerName,//Servernameforsource LPCTSTRlpSourceName//Sourcenameforregisteredhandle); 

The first parameter, lpUNCServerName , is the Universal Naming Convention (UNC) name of the server. UNC names are names in the form of \\Server\Share\Path. In this case, the UNC name needs only to reference the server name. If this parameter is NULL, the event source is registered on the local machine. The second parameter, lpSourceName , is the event source name and must correspond to the name of the registry key created to represent the application in the registry entry mentioned earlier. If the source name is not found, entries are still made to the application event log. However, their later interpretation will be much more difficult. Upon success, this function returns a handle that can be used with the ReportEvent Win32 API function described in the next section. A corresponding Win32 API function DeregisterEventSource takes as its only parameter the handle returned from RegisterEventSource .

Calling ReportEvent

As needed, the application can now call ReportEvent , which is a function with the following prototype:

 BOOLReportEvent(HANDLEhEventLog,//HandlereturnedbyRegisterEventSource WORDwType,//Thetypeofeventtolog WORDwCategory,//Eventcategory DWORDdwEventID,//Eventidentifier PSIDlpUserSid,//Usersecurityidentifier(optional) WORDwNumStrings,//Numberofstringstomergewithmessage DWORDdwDataSize,//Sizeofbinarydata,inbytes LPCTSTR*lpStrings,//Arrayofstringstomergewithmessage LPVOIDlpRawData//Addressofbinarydata); 

The first parameter is the handle returned by RegisterEventSource . The second parameter, wType , is the type of event being logged. A more accurate name for this parameter might have been severity . The values include EVENTLOG_ERROR_TYPE, EVENTLOG_WARNING_TYPE, EVENTLOG_INFORMATION_TYPE, EVENTLOG_AUDIT_SUCCESS, and EVENTLOG_AUDIT_FAILURE. The wCategory parameter specifies the event category, which can have any value and is commonly set to 0. The fourth parameter, dwEventID , is the event identifier, which corresponds to one of the values set in the message file. This parameter will almost always be a value that you define in your application. If the user generating the event is important, the next parameter, lpUserSid , must be the Security ID (SID) of the user. This can be NULL if user information is not required. In a service application run under the System account, the user is rarely specified unless the service is using impersonation.

The final four parameters allow the program to send the strings used as replicable parameters in the message strings, as well as any raw data that could be useful to further understand the event. The wNumStrings parameter contains the number of strings passed in the lpStrings parameter, which should be at least the highest placeholder number in the message string. For instance, if a message string referred to by the dwEventID parameter is "Drive %1 has only %2 bytes of free space out of %3 total bytes on the volume", there should be three strings in the array of strings pointed to by lpStrings. The dwDataSize parameter contains the number of bytes pointed to by lpRawData . Even if the value of wNumStrings or dwDataSize is 0, lpStrings and lpRawData must either be valid pointers or NULL.

ReportEvent returns a nonzero value if the event entry was written to the log or 0 if an error occurred. As with virtually all Win32 functions, if an error occurs, calling GetLastError will return the error code for the last failed call.

Adding Event Logging to CPPService

I introduced the CPPService class in Chapter 3 along with a simple example that built upon the framework named CPPBeepService . The base class had a simple method named logError for logging events to a standard MS-DOS sequential file. This method took as its single parameter a character pointer to the message to write to the file. The updated version of this class will add a method named LogEvent , which will accept seven parameters. The first is wType , a WORD value that corresponds to the type or severity of the event. The second parameter is dwEventID , the event identifier from the message file. The remaining five parameters are strings with default values of NULL. These will be the values passed to ReportError to fill in the placeholders in the messages. (Passing five individual strings rather than passing an array of character pointers is a concession to ease of use.) Of course, if the need arose for more than the five replaceable parameters, they could be added to the class with minimum effort.

The steps required for adding event logging are the four steps listed earlier in "Writing Events to the Event Log". First the message script is added to the project. The message compiler is a command line tool that generates the header file containing the event IDs and the constants defined for the C++ program. In this example, the command line to generate CppSvcMsg.h is

 mccppsvcmsg.mc 

The resulting header file is included in the stdafx.h file as follows:

 //...othercode #include<stdio.h> #include<windows.h> #include"CPPService.h" //AddedforChapter5 #include"CppSvcMsg.h" //...morecode 

Note that this example is used throughout the rest of the book. Changes to the code are noted in the chapter in which they are added. As you look at more complete examples later in the book, you can identify where each feature was added.

The next step is to create the appropriate registry entries to register the event source. As I mentioned in the service discussion, this is commonly done as the service is installed. Rather than doing this in the OnInstall method of each descendent class, the base class handles the registry entries in the Install method. The code to do so (shown below) is added to the Install method of CPPService .

 //AddedforChapter5 strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\"); strcat(szKey,ServiceName()); if(::RegCreateKey(HKEY_LOCAL_MACHINE, szKey,&hKey)!=ERROR_SUCCESS) { returnFALSE; } //AddtheeventIDmessagefile(inthiscase,theEXEfile). ::RegSetValueEx(hKey, "EventMessageFile", 0, REG_EXPAND_SZ, (CONSTBYTE*)szPath, strlen(szPath)+1); //Setthesupportedtypesflags. DWORDdwData=EVENTLOG_ERROR_TYPEEVENTLOG_WARNING_TYPE EVENTLOG_INFORMATION_TYPE; ::RegSetValueEx(hKey, "TypesSupported", 0, REG_DWORD, (CONSTBYTE*)&dwData, sizeof(DWORD)); ::RegCloseKey(hKey); //EndaddedforChapter5 

The code uses the standard Win32 Registry API functions, which are documented in the Win32 SDK. First the key is created, and then two values are addeda REG_EXPAND_SZ or NULL-terminated string for the EventMessageFile value, and a REG_DWORD four-byte unsigned value for TypesSupported .

Once the registry entries are in place, you can call RegisterEventSource . You can call RegisterEventSource within the Initialize method, but it makes a bit more sense to integrate it into the LogEvent method. In this chapter's version of the CPPBeepService service, I first added the declaration for the LogEvent method to the CPPService class declaration and then added the implementation to CPPService.cpp. The implementation of LogEvent follows:

 //AddedforChapter5 BOOLCPPService::LogEvent(WORDwType,DWORDdwEventID, constchar*ps1, constchar*ps2, constchar*ps3, constchar*ps4, constchar*ps5) { WORDwNumStrings; constchar*pStrings[5]; if(m_EventHandle==0) { m_EventHandle=::RegisterEventSource(NULL, ServiceName()); } if(m_EventHandle!=0) { wNumStrings=0; if(ps1!=0) { pStrings[wNumStrings]=ps1; wNumStrings++; if(ps2!=0) { pStrings[wNumStrings]=ps2; wNumStrings++; if(ps3!=0) { pStrings[wNumStrings]=ps3; wNumStrings++; if(ps4!=0) { pStrings[wNumStrings]=ps4; wNumStrings++; if(ps5!=0) { pStrings[wNumStrings]=ps5; wNumStrings++; } } } } } ::ReportEvent(m_EventHandle,wType,0,dwEventID,NULL, wNumStrings,0,pStrings,NULL); returntrue; } returnfalse; } //EndaddedforChapter5 

If m_EventHandle is 0, RegisterEventSource is called. If m_EventHandle is not 0, the function enters a loop that builds the pStrings array based on the values passed to the function. The logError method is then remapped to a call to LogEvent as follows:

 VOIDCPPService::logError(char*text) { //AddedforChapter5 LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG, (constchar*)text); //RemovedforChapter5 //log=fopen("c:\\service.log","a+t"); //if(log) //{ //fprintf(log,"%s\n",text); //fclose(log); //} return; } 

Once this is done, the service, when restarted, will begin logging events to the event log rather than to the simple MS-DOS sequential file of the previous version. The complete source for the modified CPPService class is presented in Listings 5-3 (CppService.cpp) and 5-4 (CppService.h). The complete project is on the companion CD-ROM.

Listing 5-3

CppService.cpp

 #include"Stdafx.h" CPPService*CPPService::m_this=NULL; VOIDStartThisService(char*serviceName) { SERVICE_STATUSssStatus; DWORDdwOldCheckPoint; SC_HANDLEschSCManager= OpenSCManager(NULL,//Localmachine NULL,//ServicesActivedatabase SC_MANAGER_ALL_ACCESS);//Fullaccess printf("\nStartingService...\n"); SC_HANDLEschService=OpenService(schSCManager,//SCMdatabase TEXT(serviceName),//Servicename SERVICE_ALL_ACCESS); if(schService==NULL) { printf("\nCan'tOpenService...LastError=%d", GetLastError()); return; } if(!StartService(schService,//Handletoservice 0,//Numberofarguments NULL))//Noarguments { printf("\nStartServicefailure..."); return; } else { printf("Servicestartpending\n"); } //Checkthestatusuntiltheserviceisnolongerstartpending. if(!QueryServiceStatus(schService,//Handletoservice &ssStatus))//Addressofstatusinformation { printf("\nErrorinQueryServiceStatus"); return; } //Someservicestakealongtimetostart,sothereis //anelaboratemechanismtohandlethat.Thisisnot //veryusefulwiththeexampleservicesprovided,but //couldbemoresoformorecomplexservices. while(ssStatus.dwCurrentState==SERVICE_START_PENDING) { //Savethecurrentcheckpoint. dwOldCheckPoint=ssStatus.dwCheckPoint; //Waitforthespecifiedinterval. Sleep(ssStatus.dwWaitHint); if(!QueryServiceStatus(//Checkthestatusagain. schService,//Handletoservice &ssStatus))//Addressofstatusinformation { break; } //Breakifthecheckpointhasnotbeenincremented. if(dwOldCheckPoint>=ssStatus.dwCheckPoint) { break; } } if(ssStatus.dwCurrentState==SERVICE_RUNNING) { printf("\nServiceStarted\n"); } else { printf("CurrentState:%d\n",ssStatus.dwCurrentState); printf("ExitCode:%d\n",ssStatus.dwWin32ExitCode); printf("ServiceSpecificExitCode:%d\n", ssStatus.dwServiceSpecificExitCode); printf("CheckPoint:%d\n",ssStatus.dwCheckPoint); printf("WaitHint:%d\n",ssStatus.dwWaitHint); } CloseServiceHandle(schService); CloseServiceHandle(schSCManager); } CPPService::CPPService(char*tsvcName,char*tdispName, DWORDtsvcStartType) { strncpy(serviceName,tsvcName,128); strncpy(serviceDisplayName,tdispName,128); m_svcStartType=tsvcStartType; m_scmHandle=OpenSCManager(NULL,SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); if(m_scmHandle==NULL) { printf("\nOpenSCManagererror%d",GetLastError()); } m_scHandle=0; m_this=this; m_isRunning=false; m_isPaused=false; //AddedforChapter5 m_EventHandle=0; ServiceStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS; ServiceStatus.dwCurrentState=SERVICE_START_PENDING; ServiceStatus.dwControlsAccepted=SERVICE_ACCEPT_STOP SERVICE_ACCEPT_PAUSE_CONTINUE; ServiceStatus.dwWin32ExitCode=0; ServiceStatus.dwServiceSpecificExitCode=0; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; } CPPService::~CPPService() { if(m_scmHandle!=0) { ::CloseHandle(m_scmHandle); } if(m_scHandle!=0) { ::CloseHandle(m_scHandle); } } BOOLCPPService::ParseArguments(intargc,char**argv) { BOOLresult=false; if(argc>1&&!(strnicmp(argv[1],"-i",2))) { if((Install())) { printf("\n%sinstalled!",ServiceName()); } else { printf("\n%sNOTinstalled!Lasterror%d", ServiceName(),GetLastError()); } result=true; } elseif(argc>1&&!(strnicmp(argv[1],"-u",2))) { if((UnInstall())) { printf("\n%sUninstalled!",ServiceName()); } else { printf("\n%sNOTuninstalled!Lasterror%d", ServiceName(),GetLastError()); } result=true; } returnresult; } VOIDCPPService::logError(char*text) { //AddedforChapter5 LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_DEBUG, (constchar*)text); //RemovedforChapter5 //log=fopen("c:\\service.log","a+t"); //if(log) //{ //fprintf(log,"%s\n",text); //fclose(log); //} return; } BOOLCPPService::Install() { charszPath[MAX_PATH]; charszBuffer[MAX_PATH+2]; //AddedforChapter5 charszKey[256]; HKEYhKey=NULL; //EndaddedforChapter5 BOOLresult=true;//Defaulttosuccess GetModuleFileName(GetModuleHandle(NULL),szPath,MAX_PATH); //Whydowedothis?Thepathmaycontainspaces. //Ifitdoes,thepathmustbequoted. strcpy(szBuffer,"\""); strcat(szBuffer,szPath); strcat(szBuffer,"\""); printf("\nInstallingservice%s",szPath); m_scHandle=CreateService(m_scmHandle,serviceName, serviceDisplayName,SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, m_svcStartType, SERVICE_ERROR_NORMAL,szBuffer, NULL,NULL,NULL,NULL,NULL); if(m_scHandle==NULL)//Processerror { DWORDlastError; lastError=GetLastError(); if(lastError==1073)//Servicealreadyinstalled { sprintf(m_diag,"CreateServiceerror=" "ServiceAlreadyInstalled(1073)\n"); } else { sprintf(m_diag,"CreateServiceerror=%d\n", lastError); } printf("%s\n",m_diag); logError(m_diag); result=false; } //AddedforChapter5 strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\" "EventLog\\Application\\"); strcat(szKey,ServiceName()); if(::RegCreateKey(HKEY_LOCAL_MACHINE,szKey, &hKey)!=ERROR_SUCCESS) { returnFALSE; } //AddtheeventIDmessagefile(inthiscase,theEXEfile). ::RegSetValueEx(hKey, "EventMessageFile", 0, REG_EXPAND_SZ, (CONSTBYTE*)szPath, strlen(szPath)+1); //Setthesupportedtypesflags. DWORDdwData=EVENTLOG_ERROR_TYPEEVENTLOG_WARNING_TYPE EVENTLOG_INFORMATION_TYPE; ::RegSetValueEx(hKey, "TypesSupported", 0, REG_DWORD, (CONSTBYTE*)&dwData, sizeof(DWORD)); ::RegCloseKey(hKey); //EndaddedforChapter5 OnInstall(); returnresult; } BOOLCPPService::StartService() { SERVICE_TABLE_ENTRYDispatchTable[]={ {serviceName,CPPService::ServiceMain}, {NULL,NULL} }; BOOLb=::StartServiceCtrlDispatcher(DispatchTable); //AddedforChapter5 if(b) { //Nostringsneededforthismessage... LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_STARTED); } //EndaddedforChapter5 returnb; } BOOLCPPService::UnInstall() { m_scHandle=OpenService(m_scmHandle,serviceName, SERVICE_ALL_ACCESS); if(m_scHandle!=NULL) { DeleteService(m_scHandle); printf("\n%suninstalled...",serviceName); CloseHandle(m_scHandle); m_scHandle=0; //AddedforChapter5 LogEvent(EVENTLOG_INFORMATION_TYPE,EVMSG_REMOVED, ServiceName()); //EndaddedforChapter5 OnUninstall(); return(true); } printf("\nOpenServiceerror:%d",GetLastError()); return(false); } voidCPPService::SetStatus(DWORDdwControl) { ServiceStatus.dwCurrentState=dwControl; ServiceStatus.dwCheckPoint=0; ServiceStatus.dwWaitHint=0; if(!SetServiceStatus(ServiceStatusHandle,&ServiceStatus)) { m_status=GetLastError(); sprintf(m_diag, "%sSetServiceStatuserror%lddwControl=%d\n", serviceName,m_status,dwControl); logError(m_diag); } } VOIDWINAPICPPService::ServiceMain(DWORDargc,LPTSTR*argv) { BOOLinitStat; DWORDspecificError; CPPService*thisPtr; thisPtr=m_this; #ifdef_WIN2K_COMPAT charszContext[255]; strcpy(szContext,thisPtr->ServiceName()); thisPtr->ServiceStatusHandle= RegisterServiceCtrlHandlerEx(thisPtr->ServiceName, HandlerEx, &szContext); #else thisPtr->ServiceStatusHandle= RegisterServiceCtrlHandler(thisPtr->serviceName, Handler); #endif if(thisPtr->ServiceStatusHandle==(SERVICE_STATUS_HANDLE)0) { sprintf(thisPtr->m_diag, "RegisterServiceCtrlHandlerfailed%d", GetLastError()); thisPtr->logError(thisPtr->m_diag); return; } thisPtr->SetStatus(SERVICE_START_PENDING); //Initializationcodegoeshere. initStat=thisPtr->OnInit(&specificError); //Handleerrorcondition if(initStat==false) { thisPtr->SetStatus(SERVICE_STOPPED); sprintf(thisPtr->m_diag,"OnInitfailedinitStat=%d...", initStat); thisPtr->logError(thisPtr->m_diag); return; } thisPtr->SetStatus(SERVICE_RUNNING); thisPtr->m_isRunning=true; sprintf(thisPtr->m_diag,"Justbeforeprocessingloop...\n"); thisPtr->logError(thisPtr->m_diag); //Thisiswheretheservicedoesitswork. thisPtr->Run(); //Sendcurrentstatus. thisPtr->SetStatus(SERVICE_STOPPED); return; } #ifdef_WIN2K_COMPAT DWORDWINAPICPPService::HandlerEx(DWORDdwControl, DWORDdwEventType,LPVOIDlpEventData,LPVOIDlpContext) { DWORDstatus=SERVICE_RUNNING; CPPService*thisPtr; thisPtr=m_this; sprintf(thisPtr->m_diag,"INtoserviceControl...\n"); thisPtr->logError(thisPtr->m_diag); switch(dwControl) { caseSERVICE_CONTROL_PAUSE: //Dowhateverittakestopausehere. sprintf(thisPtr->m_diag,"Pausing...\n"); thisPtr->logError(thisPtr->m_diag); if(!(thisPtr->m_isPaused) { thisPtr->m_isPaused=true; thisPtr->OnPause(); status=SERVICE_PAUSED; } break; caseSERVICE_CONTROL_CONTINUE: //Dowhateverittakestocontinuehere. sprintf(thisPtr->m_diag,"Continuing...\n"); thisPtr->logError(thisPtr->m_diag); if(thisPtr->m_isPaused) { thisPtr->OnContinue(); thisPtr->m_isPaused=false; status=SERVICE_RUNNING; } break; caseSERVICE_CONTROL_STOP: //Dowhateverittakestostophere. sprintf(thisPtr->m_diag,"Stopping...\n"); thisPtr->logError(thisPtr->m_diag); if(thisPtr->m_isRunning) { thisPtr->OnStop(); status=SERVICE_STOP_PENDING; thisPtr->m_isRunning=false; } thisPtr->SetStatus(status); sprintf(thisPtr->m_diag, "BeeperServiceLeavingHandler\n",0); thisPtr->logError(thisPtr->m_diag); return; caseSERVICE_CONTROL_PARAMCHANGE: thisPtr->OnParamChange(); break; caseSERVICE_CONTROL_NETBINDADD: thisPtr->OnNetBindAdd(); break caseSERVICE_CONTROL_NETBINDDELETE: thisPtr->OnNetBindDelete(); break caseSERVICE_CONTROL_NETBINDREMOVE: thisPtr->OnNetBindRemove(); break caseSERVICE_CONTROL_NETBINDENABLE: thisPtr->OnNetBindEnable(); break caseSERVICE_CONTROL_NETBINDDISABLE: thisPtr->OnNetBindDisable(); break caseSERVICE_CONTROL_DEVICEEVENT: thisPtr->OnDeviceEvent(dwEventType,lpEventData); break; caseSERVICE_CONTROL_POWEREVENT: thisPtr->OnPowerEvent(dwEventType,lpEventData); break; caseSERVICE_CONTROL_HARDWAREPROFILECHANGE: thisPtr->OnHardwareProfileChange(dwEventType); break; caseSERVICE_CONTROL_INTERROGATE: //Fallthroughtosendcurrentstatus. break; default: if(dwControl>=SERVICE_CONTROL_USER) { if(!thisPtr->OnUserControl(dwControl)) { sprintf(thisPtr->m_diag, "%sUnhandledcontrolcode%ld\n", thisPtr->ServiceName(), dwControl); thisPtr->logError(thisPtr->m_diag); } } else { sprintf(thisPtr->m_diag, "%sUnrecognizedcontrolcode%ld\n", thisPtr->ServiceName(), dwControl); thisPtr->logError(thisPtr->m_diag); } break; } //Sendcurrentstatus. thisPtr->SetStatus(status); return0; } #else VOIDWINAPICPPService::Handler(DWORDdwControl) { DWORDstatus=SERVICE_RUNNING; CPPService*thisPtr; thisPtr=m_this; sprintf(thisPtr->m_diag,"INtoserviceControl...\n"); thisPtr->logError(thisPtr->m_diag); switch(dwControl) { caseSERVICE_CONTROL_PAUSE: //Dowhateverittakestopausehere. sprintf(thisPtr->m_diag,"Pausing...\n"); thisPtr->logError(thisPtr->m_diag); if(!(thisPtr->m_isPaused)) { thisPtr->m_isPaused=true; thisPtr->OnPause(); status=SERVICE_PAUSED; } break; caseSERVICE_CONTROL_CONTINUE: //Dowhateverittakestocontinuehere. sprintf(thisPtr->m_diag,"Continuing...\n"); thisPtr->logError(thisPtr->m_diag); if(thisPtr->m_isPaused) { thisPtr->OnContinue(); thisPtr->m_isPaused=false; status=SERVICE_RUNNING; } break; caseSERVICE_CONTROL_STOP: //Dowhateverittakestostophere. //RemovedforChapter5 //sprintf(thisPtr->m_diag,"Stopping...\n"); //thisPtr->logError(thisPtr->m_diag); if(thisPtr->m_isRunning) { thisPtr->OnStop(); //AddedforChapter5 thisPtr->LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_STOPPED, thisPtr->ServiceName()); //EndaddedforChapter5 status=SERVICE_STOP_PENDING; thisPtr->m_isRunning=false; } thisPtr->SetStatus(status); sprintf(thisPtr->m_diag, "BeeperServiceLeavingHandler\n",0); thisPtr->logError(thisPtr->m_diag); return; caseSERVICE_CONTROL_PARAMCHANGE: thisPtr->OnParamChange(); break; caseSERVICE_CONTROL_NETBINDADD: thisPtr->OnNetBindAdd(); break; caseSERVICE_CONTROL_NETBINDDISABLE: thisPtr->OnNetBindDisable(); break; caseSERVICE_CONTROL_NETBINDREMOVE: thisPtr->OnNetBindRemove(); break; caseSERVICE_CONTROL_NETBINDENABLE: thisPtr->OnNetBindEnable(); break; caseSERVICE_CONTROL_INTERROGATE: //Fallthroughtosendcurrentstatus. break; default: if(dwControl>=SERVICE_CONTROL_USER) { if(!thisPtr->OnUserControl(dwControl)) { sprintf(thisPtr->m_diag, "%sUnhandledcontrolcode%ld\n", thisPtr->ServiceName(), dwControl); thisPtr->logError(thisPtr->m_diag); } } else { sprintf(thisPtr->m_diag, "%sUnrecognizedcontrolcode%ld\n", thisPtr->ServiceName(), dwControl); thisPtr->logError(thisPtr->m_diag); } break; } //Sendcurrentstatus. thisPtr->SetStatus(status); return; } #endif //Virtualfunctions,generallyoverriddenifneeded. BOOLCPPService::OnUserControl(DWORDdwControl) { logError("OnContinue"); returnfalse; } voidCPPService::OnContinue() { logError("OnContinue"); return; } voidCPPService::OnPause() { logError("OnPause"); return; } voidCPPService::OnStop() { logError("OnStop"); return; } voidCPPService::OnInstall() { logError("OnInstall"); return; } voidCPPService::OnUninstall() { logError("OnUninstall"); return; } BOOLCPPService::OnInit(DWORD*status) { logError("OnInit"); *status=0; returntrue; } voidCPPService::OnParamChange() { logError("OnParamChange"); return; } voidCPPService::OnNetBindAdd() { logError("OnNetBindAdd"); return; } voidCPPService::OnNetBindRemove() { logError("OnNetBindRemove"); return; } voidCPPService::OnNetBindEnable() { logError("OnNetBindEnable"); return; } voidCPPService::OnNetBindDisable() { logError("OnNetBindDisable"); return; } voidCPPService::Run() { while(m_isRunning) { Sleep(1000); } return; } #ifdef_WIN2K_COMPAT voidOnDeviceEvent(DWORDdwEventType,LPVOIDpvEventData) { logError("OnDeviceEvent"); return; } voidOnPowerEvent(DWORDdwEventType,LPVOIDpvEventData) { logError("OnPowerEvent"); return; } voidOnHardwareProfileChange(DWORDdwEventType) { logError("OnHardwareProfileChange"); return; } #endif //AddedforChapter5 BOOLCPPService::LogEvent(WORDwType,DWORDdwEventID, constchar*ps1, constchar*ps2, constchar*ps3, constchar*ps4, constchar*ps5) { WORDwNumStrings; constchar*pStrings[5]; if(m_EventHandle==0) { m_EventHandle=::RegisterEventSource(NULL, ServiceName()); } if(m_EventHandle!=0) { wNumStrings=0; if(ps1!=0) { pStrings[wNumStrings]=ps1; wNumStrings++; if(ps2!=0) { pStrings[wNumStrings]=ps2; wNumStrings++; if(ps3!=0) { pStrings[wNumStrings]=ps3; wNumStrings++; if(ps4!=0) { pStrings[wNumStrings]=ps4; wNumStrings++; if(ps5!=0) { pStrings[wNumStrings]=ps5; wNumStrings++; } } } } } ::ReportEvent(m_EventHandle,wType,0,dwEventID,NULL, wNumStrings,0,pStrings,NULL); returntrue; } returnfalse; } //EndaddedforChapter5 

Listing 5-4

CppService.h

 #defineSERVICE_CONTROL_USER128 VOIDStartThisService(char*serviceName); classCPPService{ public: //AddedforChapter5 BOOLLogEvent(WORDwType,DWORDdwEventID, constchar*ps1=0, constchar*ps2=0, constchar*ps3=0, constchar*ps4=0, constchar*ps5=0); CPPService(char*tsvcName,char*tdispName, DWORDtsvcStart=SERVICE_DEMAND_START); virtual~CPPService(); BOOLIsInstalled(); BOOLInstall(); BOOLUnInstall(); BOOLStartService(); voidSetStatus(DWORDdwControl); BOOLInitialize(); BOOLParseArguments(intargc,char*argv[]); char*ServiceName() { returnserviceName; } voidSetUserAndPassword(char*user=NULL, char*pwd=NULL); virtualvoidOnInstall(); virtualvoidOnUninstall(); virtualBOOLOnInit(DWORD*status); virtualvoidRun(); virtualvoidOnStop(); virtualvoidOnPause(); virtualvoidOnContinue(); virtualBOOLOnUserControl(DWORDdwControl); virtualvoidOnParamChange(); virtualvoidOnNetBindAdd(); virtualvoidOnNetBindRemove(); virtualvoidOnNetBindEnable(); virtualvoidOnNetBindDisable(); #ifdef_WIN2K_COMPAT virtualvoidOnDeviceEvent(DWORDdwEventType, LPVOIDpvEventData); virtualvoidOnPowerEvent(DWORDdwEventType, LPVOIDpvEventData); virtualvoidOnHardwareProfileChange(DWORDdwEventType); #endif protected: SC_HANDLEm_scmHandle; SC_HANDLEm_scHandle; //AddedforChapter5 HANDLEm_EventHandle; SERVICE_STATUSServiceStatus; SERVICE_STATUS_HANDLEServiceStatusHandle; staticCPPService*m_this; charserviceName[128]; charserviceDisplayName[128]; VOIDlogError(char*msg); FILE*log; charm_diag[255]; DWORDm_svcStartType; DWORDm_status; BOOLm_isRunning; BOOLm_isPaused; #ifdef_WIN2K_COMPAT staticDWORDWINAPIHandlerEx(DWORDdwControl, DWORDdwEventType, LPVOIDlpEventData,LPVOIDlpContext); #else staticVOIDWINAPIHandler(DWORDdwControl); #endif staticVOIDWINAPIServiceMain(DWORDdwArgc, LPTSTR*lpszArgv); }; 

NOTE
Once the service is restarted, it is possible that rather than the properly formatted message, you will see a message indicating that the message file cannot be found, such as "The description for event ID (<####>) in Source (<application name>) could not be found. It contains the following insertion string(s):". This could be because the resources have not been properly added to the project. However, even if you've done everything correctly, this message can still appear. The solution is to stop and restart the event logging service. This restart is required because the event logging service caches the DLLs it loads for event sources.

Reading Events from the Event Log

Reading events from the event log is also a bit more complicated than just reading a log file. Figure 5-6 shows the general structure.

click to view at full size.

Figure 5-6 The process used to read events to the event log.

As I mentioned previously, when an event is written to the log, the actual text of the message as it appears in the Event Viewer is not stored. Instead, only the parameters that get filled in to the message text are saved in the log. So, to actually read the message in the same way that the event viewer application does, you must take several steps:

  1. Open the event log using the OpenEventLog Win32 API function and read the events using the ReadEventLog Win32 API function.
  2. Read the registry to determine the event source for the event that was read.
  3. Load the message text from the event source application, and then format the string using FormatMessage .

OpenEventLog is a simple function that accepts two parameters:

 HANDLEOpenEventLog(LPCTSTRlpUNCServerName,//Pointertoservername LPCTSTRlpSourceName//Pointertosourcename); 

The lpUNCServerName parameter is either the name of the server or NULL for the local machine. The lpSourceName parameter is the name of the log to read, which is often "Application". This function returns a handle that is used in a call to ReadEventLog . ReadEventLog has the following prototype:

 BOOLReadEventLog(HANDLEhEventLog,//Handletoeventlog DWORDdwReadFlags,//Howtoreadlog DWORDdwRecordOffset,//Offsetoffirstrecord LPVOIDlpBuffer,//Bufferforreaddata DWORDnNumberOfBytesToRead,//Bytestoread DWORD*pnBytesRead,//Numberofbytesread DWORD*pnMinNumberOfBytesNeeded//Bytesrequired); 

The first parameter, hEventLog , is the handle returned by OpenEventLog . The parameter dwReadFlags specifies how the log is read. EVENTLOG_SEEK_READ allows a specific log record to be specified in the dwRecordOffset parameter, while EVENTLOG_SEQUENTIAL_READ reads sequentially from the last record read. One of two additional flags can be specified: EVENTLOG_FORWARDS_READ, which causes the log to be read in chronological order, and EVENTLOG_BACKWARDS_READ, which causes the log to be read in reverse chronological order. The lpBuffer parameter points to the buffer that will be filled with an EVENTLOGRECORD structure. The nNumberOfBytesToRead parameter specifies the number of bytes contained in the buffer. ReadEventLog will place as many complete EVENTLOGRECORD structures as will fit into the buffer passed on each call. The pnBytesRead parameter points to the number of bytes actually read when the function returns. If the function returns 0 (indicating an error has occurred) and GetLastError returns ERROR_INSUFFICENT_BUFFER, the last parameter points to the number of bytes required to read the next log entry.

Once the buffer passed to ReadEventLog is filled in and no error has occurred, there is at least one event record in the buffer. Thus, there is a while loop that is controlled by the return from ReadEventLog and an inner loop controlled by the reading of the buffer. The following code snippet shows the general process used:

 while(ReadEventLog(h, EVENTLOG_FORWARDS_READ EVENTLOG_SEQUENTIAL_READ, 0,//Ignoredforsequentialreads pevlr, BUFFER_SIZE, &dwBytesRead, &dwMinNumberOfBytesNeeded)) { //Loopthroughthereturnedbuffer... while(dwBytesRead>0) { charszPath[255]; DWORDcbData; //Codeforprocessingeachrecord //omittedforclarity. dwBytesRead-=pevlr->Length; pevlr=(EVENTLOGRECORD*) ((LPBYTE)pevlr+pevlr->Length); } pevlr=(EVENTLOGRECORD*)&bBuffer; } 

The outer loop terminates when there are no more records to read. The inner loop terminates when dwBytesRead drops to 0.

Each record is processed when it is placed into the EVENTLOGRECORD. For the simple example that follows, the processing involves displaying some of the raw datafor instance, the event ID, the event source, and the source. With the source, the DLL or executable file that contains the resource for the message text can be read. The registry reads in this code are the opposite of some of the registry code used during the installation of CPPService .

 memset(&szKey[0],'\0',255); strcpy(szKey,"SYSTEM\\CurrentControlSet\\" "Services\\EventLog\\Application\\"); strcat(szKey,(LPSTR)((LPBYTE)pevlr+ sizeof(EVENTLOGRECORD))); if(::RegOpenKey(HKEY_LOCAL_MACHINE,szKey, &hKey)!=ERROR_SUCCESS) { printf("\nGetLastError()=%d",GetLastError()); return; } cbData=255; //ReadtheeventIDmessagefile(inthiscase,theEXEfile). ::RegQueryValueEx(hKey, "EventMessageFile", 0, NULL, (unsignedchar*)szPath, &cbData); 

The registry key that contains information about the event source is opened and read. Specifically, EventMessageFile is read. Next, the path returned from this entry is passed to LoadLibraryEx , a Win32 API function that loads a DLL or EXE file, with two parameters not used by LoadLibrary . This is required because the third parameter allows LoadLibraryEx to not resolve DLL references, which are not necessary to get at the message resource contained in the file.

NOTE
All examples in this text are on the local machine. I cannot overemphasize the difficulty of doing this on a remote machine. From the standpoint of the local machine, the path to the DLL or EXE file containing the message text is a local path. To use LoadLibraryEx from a remote machine, you must find a way to get the UNC path to the module. Although there is no perfect way to do this, Chapter 14 offers a possible solution.

FormatMessage is commonly used to format error messages based on the return from GetLastError :

 DWORDFormatMessage(DWORDdwFlags,//Sourceandprocessingoptions LPCVOIDlpSource,//Pointertomessagesource DWORDdwMessageId,//Requestedmessageidentifier DWORDdwLanguageId,//Languageidentifierfor //requestedmessage LPTSTRlpBuffer,//Pointertomessagebuffer DWORDnSize,//Maximumsizeofmessagebuffer va_list*Arguments//Pointertoarrayofmessageinserts); 

In Listing 5-5, dwFlags is set to FORMAT_MESSAGE_FROM_HMODULE to indicate that the message should be formatted from a handle to a module andif there are insertion stringsORed with FORMAT_MESSAGE_ARGUMENT_ARRAY. The lpSource parameter is a handle to a module, returned from LoadLibraryEx . The dwMessageId parameter is set to the EventID element of the EVENTLOGRECORD structure. The dwLanguageId parameter is a DWORD that is created in this example by using the MAKELANGID macro. If this parameter is set to 0, specific rules in the Win32 SDK indicate how the language will be resolved. The lpBuffer parameter points to the buffer that will receive the formatted message, and nSize contains the size of the buffer. In Listing 5-5, the buffer used is a local buffer, but flags to FormatMessage tell it to allocate the buffer to the appropriate size and set the passed pointer to that value. Arguments is an array of pointers that contains the strings used in conjunction with the message string, allowing FormatMessage to replace %n with the appropriate argument.

Listing 5-5

ReadEvents.cpp

 //ReadEvents.cpp:ReadeventsfromtheApplicationeventlog. // #include"stdafx.h" #defineBUFFER_SIZE1024 voidDisplayEvents() { HANDLEh; HMODULEhm; EVENTLOGRECORD*pevlr; BYTEbBuffer[BUFFER_SIZE]; DWORDdwBytesRead; DWORDdwMinNumberOfBytesNeeded DWORDdwThisRecord=0; charszKey[255]; HKEYhKey=NULL; //OpentheApplicationeventlog. h=OpenEventLog(NULL,"Application"); if(h==NULL) { printf("CouldnotopentheApplicationeventlog."); return; } pevlr=(EVENTLOGRECORD*)&bBuffer; //Openingtheeventlogpositionsthefilepointerforthis //handleatthebeginningofthelog.Readtherecords //sequentiallyuntiltherearenomore. //Asinglereadcangetmorethanasinglerecord. while(ReadEventLog(h, EVENTLOG_FORWARDS_READ EVENTLOG_SEQUENTIAL_READ, 0,//Ignoredforsequentialreads pevlr, BUFFER_SIZE, &dwBytesRead, &dwMinNumberOfBytesNeeded)) { //Loopthroughthereturnedbuffer... while(dwBytesRead>0) { charszPath[255]; DWORDcbData; printf("%04dEventID:0x%08X", dwThisRecord++,pevlr->EventID); printf("EventType:%dSource:%s\n", pevlr->EventType,(LPSTR)((LPBYTE)pevlr+ sizeof(EVENTLOGRECORD))); //Nowweneedtoactuallygetthetextsothatwe //canactuallydisplaythemessagetext. memset(&szKey[0],'\0',255); strcpy(szKey,"SYSTEM\\CurrentControlSet\\" "Services\\EventLog\\Application\\"); strcat(szKey,(LPSTR)((LPBYTE)pevlr+ sizeof(EVENTLOGRECORD))); if(::RegOpenKey(HKEY_LOCAL_MACHINE, szKey,&hKey)!=ERROR_SUCCESS) { printf("\nGetLastError()=%d",GetLastError()); return; } cbData=255; //ReadtheeventIDmessagefile //(inthiscase,theEXEfile). ::RegQueryValueEx(hKey, "EventMessageFile", 0, NULL, (unsignedchar*)szPath, &cbData); printf("\t%s\n",szPath); hm=LoadLibraryEx(szPath,NULL, DONT_RESOLVE_DLL_REFERENCES); if(hm!=0) { charbuffer[1024]; char*pStr; char*pStrs[255]; DWORDdwFlags=FORMAT_MESSAGE_FROM_HMODULE; if(pevlr->NumStrings) { intloop; pStr=(char*)pevlr+pevlr->StringOffset; for(loop=0;loop<pevlr->NumStrings;loop++) { pStrs[loop]=pStr; pStr=strchr(pStr,'\0')+1; } dwFlags=FORMAT_MESSAGE_ARGUMENT_ARRAY; } FormatMessage(dwFlags,hm,pevlr->EventID, MAKELANGID(LANG_ENGLISH,SUBLANG_DEFAULT), buffer,1024,pStrs); printf("Message:%s\n",buffer); FreeLibrary(hm); } dwBytesRead-=pevlr->Length; pevlr=(EVENTLOGRECORD*) ((LPBYTE)pevlr+pevlr->Length); ::RegCloseKey(hKey); } pevlr=(EVENTLOGRECORD*)&bBuffer; } CloseEventLog(h); } intmain(intargc,char*argv[]) { printf("HelloRegistryWorld!\n"); DisplayEvents(); return0; } 


Inside Server-Based Applications
Inside Server-Based Applications (DV-MPS General)
ISBN: 1572318171
EAN: 2147483647
Year: 1999
Pages: 91

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net