Professional Documents
Culture Documents
Using The Windows Event Log From Visual Foxpro
Using The Windows Event Log From Visual Foxpro
Using The Windows Event Log From Visual Foxpro
Overview
In this article, you will learn how to use the Windows Event Log from Visual FoxPro. While you can use Windows Scripting Host (WSH) to read and write to the Windows Event Log, WSH could be disabled due to today s security requirements. Therefore, it is necessary to use the Win32 API to access the log. Using several API calls, you will learn how to read, write, and maintain the log.
Logging Overview
Capturing error conditions is an important part of a robust application. Historically, Visual FoxPro applications have used tables (DBFs) or text files to capture error information. However, other types of logging are available. These include XML files or the Windows Event Log. Using the Windows Event Log is desirable because other file types use proprietary formats, different user interfaces for reporting, and they cannot be merged with logs from other applications. The Windows Event Log is a standard format, stored in a centralized location and single user interface can be used to view log information from a variety of programs. Also, third party software can be purchased that will report on entries stored in the Windows Event Log. In addition to error conditions, the Windows Event Log can be used to store information such as a user logging on, opening a database, starting a file transfer, or other information. The Windows Event Log should not be used as a tracing tool or to record transactional information such as database changes. The Windows Event Log actually contains several types of logs. The standard logs and what they are used for are listed in Table 1. Under Windows 2000 and later you may find User Defined types. I will talk more about these later in this document. Your programs will make registry entries under the Application or a User Defined type. The Windows Event Log is actually a group of files. Each file represents a different log type. The location of these files is specified by Registry entries.
Table 1. A listing of the standard log types and their locations. Log Type Description Security Security information such as logon, logoff, password changes, etc. This log is reserved by Windows. System Information from Windows. This log is reserved by Windows. Application Information from application software.
\WinNT\System32\Config\SysEvent.Evt \WinNT\System32\Config\AppEvent.Evt
Each entry in the event log contains the following information: Date Time Username: The user that wrote the entry. Computer: The name of the computer that wrote the error. Generally, you will use the local event log. However, if you have the proper rights, you can log information to a server or other computer. Source: The application, module, or component that wrote the event. Message Type: Can be Success, Error, Warning, Information, Audit Success or Audit Failure. Category: Specified by a message file. Message files are discussed later in this document Event ID: Specified by a message file. Description ID: Specified by a message file. Binary Data The primary way to view information from the event log is the Event Log Viewer (Figure 1). Under Windows 2000 or later, you access the Viewer from the Administrative Tools applet of Windows Control Panel.
Figure 1. The Event Viewer is used to view and manage the Windows Event Log.
When you double-click on an entry, the Event Properties dialog (Figure 2) for that event is displayed. You can see from Figure XX that not all the information is required. For example, the Category and User are not specified. The Description is actually loaded from a message file.
Create the Message Files Register the Event Source Call the Event Log API
Step three consists of several Win32 API calls and is discussed in detail later in this document.
To create a resource DLL you will need the Resource Compiler, Message Compiler, and Linker. These tools ship with Visual Studio, the Microsoft Platform SDK, and other developer tools. I did an Internet search and got hits for free tools that may give you the same functionality. The following steps walk you through creating a message file.
1.
Create a text file for the messages. This example uses FoxMessages.mc.
LanguageNames = ( English = 0x0409:Messages_ENU ) ;//////////////////////////////////////// ;// Eventlog categories ;// ;// These always have to be the first entries in a message file MessageId = 1 SymbolicName = CATEGORY_ONE Severity = Success Language = English First category event . MessageId = 2 SymbolicName = CATEGORY_TWO Severity = Success Language = English Second category event . ;//////////////////////////////////////// ;// Events MessageId = 1000 SymbolicName = HELLO Language = English Hello World! . MessageId = 1001 SymbolicName = GREETING Language = English Hello %1. How are you? . MessageId = 1002 SymbolicName = GENERIC1 Language = English %1 . MessageId = 1003 SymbolicName = GENERIC2 Language = English Message 1: %1 Message 2: %2
2.
In the Windows Command Interpreter, compile the message file using the Message Compiler.
mc FoxMessages.mc
The message compiler outputs the following files: FoxMessages.h: A C++ header file containing the event information FoxMessages.rc: A Resource source file. This will be the input for resource compiler. Messages_ENU.bin: A binary file for US English messages.
3.
In the Windows Command Interpreter, compile the resource file using the Resource Compiler.
rc FoxMessages.rc
The dll parameter tells the linker to create a .dll file (FoxMessages.dll) and noentry says there will not be entry point. This is required because the linker is not creating a Win32 dll, but rather a resource dll. The resource dll is now ready to use. The sample code that accompanies this article includes the batch file Make.bat that does all three steps for you.
application name. In this example, I m using HKEY_LOCAL_MACHINE\SYSTEM\ ControlSet001\Services\EventLog\Application\FoxMessages. Here are the steps I used:
1.
Run Regedit and drill down until you reach the above key (Figure 3)
Figure 3. The Registry Editor is used to view and manage entries in the Windows registry.
2.
Right-click on Application and select New | Key from the context menu. Enter FoxMessages as the key. Right-click on FoxMessages and select New | String Value. Enter EventMessageFile. Double-click EventMessageFile. The Edit String dialog (Figure 4) is displayed. Enter the path to your resource dll and click OK.
3. 4.
Figure 4. You can add or change an entry in the registry from the Edit String dialog.
Craig Berntson, Cole Gleave, 2004, 2005 Page 6 of 31
5. 6.
Right-click on FoxMessages and select New | DWORD Value. Enter TypesSupported. Double-click on TypesSupported. The Edit DWORD Value dialog (Figure 5) is displayed. Enter 7 for Value data so that the log entry can support all types. The types are listed in Table 2. Make sure Hexadecimal is selected, and then click OK.
Figure 5. You can add or edit a DWORD in the registry using the Edit DWORD Value dialog. Table 2. The Bitmask values for the TypesSupported registry entry. Description Hex Value EVENT_LOG_ERROR_TYPE 0x0001 EVENT_LOG_WARNING_TYPE 0x0002 EVENT_LOG_INFORMATION_TYP 0x0004 E
7. 8.
Binary Value 1 2 4
Add an entry for CategoryMessageFile. The Value will be the same path and file name you entered for EventMessageFile. Add another entry for CategoryCount. The Value will be 2, as that is the number of categories in the message file.
You have completed the registry entries needed for your application. Next, I ll show you how to write to the Windows Event Log.
Before you can write to an event source, you need to register it. The function returns a handle to the event source or NULL if an error occurs. Pass a NULL or empty string as the first parameter to use the Windows Event Log on the local computer.
DECLARE INTEGER RegisterEventSource IN WIN32API ; STRING UNCServerName, ;
STRING SourceName
When you finish with log, call DeregisterEventSource( ), which has a single parameter, hEventLog. This is the handle that was returned by RegisterEventSource( ).
DECLARE INTEGER DeregisterEventSource IN WIN32API ; INTEGER hEventLog
The ReportEvent( ) function actually records the event into the log. If the message is successfully recorded to the log, the return value is non-zero. If the function fails, a 0 is returned.
DECLARE INTEGER ReportEvent IN WIN32API ; INTEGER EventLog, ; INTEGER Type, ; INTEGER Category, ; INTEGER EventID, ; INTEGER UserSid, ; INTEGER NumStrings, ; INTEGER DataSize, ; STRING @Strings, ; INTEGER RawData
RawData
When reading the event log, the first thing you need to do is call OpenEventLog( ).
DECLARE INTEGER OpenEventLog IN WIN32API ; STRING UNCServerName, ; STRING UNCSourceName
If the function succeeds, it returns a handle to the event log. If it fails, it returns NULL. The parameters are listed in Table 5.
Table 5. A listing of the parameters for the OpenEventLog function. Parameters Description UNCServerName The server name where the event is logged. If logging to the local machine, pass an empty string. SourceName The source name that is saved to the Registry. Earlier, I named the source, FoxMessage .
When you finish using the event log, you should call the CloseEventLog( ) function.
DECLARE INTEGER CloseEventLog IN WIN32API ; INTEGER hEventLog
The single parameter, hEventLog, is the handle returned by OpenEventLog( ). CloseEventLog( ) will return a non-zero value if it succeeds or zero if it fails. Once the event log is open, it can be read. The event log API allows you to read a single entry at a time using the ReadEventLog( ) function.
DECLARE INTEGER ReadEventLog IN WIN32API ; INTEGER hEventLog, ; INTEGER ReadFlags, ; INTEGER RecordOffset, ; STRING @Buffer, ; INTEGER NumberOfBytesToRead, ; STRING @BytesRead, ; STRING @MinNumberOfBytesNeeded
If successful, a non-zero value is returned. Zero is returned if the function fails. The parameters are listed in Table 6.
Table 6. A listing parameters for the ReadEventLog function. Parameters Description hEventLog The handle to the event log. ReadFlags Specifies how to read the log. See the #DEFINEs bellow. RecordOffset The log entry number where the read operation should start. @Buffer A buffer for the data read. NumberOfBytesToRead The size, in bytes, of the buffer. @BytesRead The number of bytes actually read. @MinNumberOfBytesNeede The number of bytes required for the next log entry. d
Now that you ve seen the primary functions in the Event Log API, it s time to see some additional functions you can do in the Windows Event Log. The first tells you how many records are in the event log.
DECLARE INTEGER GetNumberOfEventLogRecords IN WIN32API ; INTEGER hEventLog, ; STRING @NumberOfRecords
The return value will be nonzero if the function succeeds or zero if it fails. Table 7 lists the parameters.
Table 7. A listing of parameters for the GetNumberOfEventLogRecords function. Parameters Description hEventLog The event log handle. @NumberOfRecords The number of records in the event log.
GetOldestEventLogRecord( ) gets the record number for the oldest event in the log. It returns a nonzero value if successful or zero if it fails. Table 8 lists the parameters.
DECLARE INTEGER GetOldestEventLogRecord IN WIN32API ; INTEGER hEventLog, ; STRING @OldestRecord Table 8. A listing parameters for the GetOldestEventLogRecord function. Parameters Description
hEventLog @OldestRecord
The event log handle. The record number of the oldest record in the event log.
That completes the actual event log API calls. There are some additional Win32 API functions that you ll need to use. The first of these is GlobalAlloc( ), which allocates memory on the heap. If the function succeeds, it returns a handle to the allocated memory. If it fails, it returns NULL.
DECLARE INTEGER GlobalAlloc IN WIN32API ; INTEGER Flags, ; INTEGER Bytes
The following line defines the value to pass for the Flags parameter:
#DEFINE GMEM_ZEROINIT 0x0040
Since memory is allocated, is must also be deallocated. That is the purpose of GlobalFree( ). The single parameter is the handle to the memory, as returned by GlobalAlloc( ). If GlobalFree( ) succeeds in deallocating the memory, it returns NULL. Any other value indicates failure.
DECLARE INTEGER GlobalFree IN WIN32API ; INTEGER Mem
The strings that are used for the @Strings parameter of the ReportEvent function need to be copied from Visual FoxPro memory into the allocated memory. Keep in mind that the Event API function requires a C++ array string. By copying the memory and doing some other hocus-pocus that I will explain later, the VFP string will look like a C++ array string. The CopyMemory( ) function will handle this for us.
DECLARE RtlMoveMemory IN WIN32API AS CopyMemory ; INTEGER Destination, ; STRING Source, ; INTEGER Length
This function has no return value. The parameters are listed in Table 10.
Table 10. A listing of parameters for the RtlMoveMemory function. Parameters Description Destination The starting address of the copied block s destination. Source The starting address of the block of memory to copy. Length The size, in bytes, of the block of memory to copy.
One of the optional parameters of the ReportEvent( ) function is UserSid, which is the User s Security ID. The LookupAccountSid( ) function gets that information for you.
DECLARE INTEGER LookupAccountSid IN WIN32API ; STRING SystemName, ; STRING @Sid, ; STRING @Name, ; STRING @cbName, ; STRING @ReferencedDomainName, ; STRING @cbReferencedDomainName, ; STRING @peUse
A nonzero return value indicates success, zero indicates failure. Table 11 lists the parameters.
Table 11. A listing of parameters for the LookupAccountSID function. Parameters Description SystemName The server name to query. Pass an empty string to query the local system. @Sid A SID structure for the account information to look up. @Name The account name corresponding to the @Sid parameter. @cbName The number of bytes contained in the @Name parameter. If the function fails because @Name is too small, this will contain the number of bytes needed. @ReferencedDomainName The domain where the account is found. @cbReferencedDomainNam The number of bytes contained in the @ReferencedDomainName e parameter. If the function fails because @ReferencedDomainName is too small, this will contain the number of bytes needed. @peUse Contains the type of account when the function returns.
The next function, LoadLibraryEx( ) maps an executable module into the address space of the calling process. For the Windows Event Log, it is used to load the message file. Here is the syntax:
DECLARE LONG LoadLibraryEx IN WIN32API ; STRING LibFileName, ; INTEGER RESERVED, ; INTEGER Flags
If the function call succeeds, the return value is a handle to the mapped executable module. If it fails, NULL is returned. Table 12 lists the parameters.
Table 12. A listing of parameters for the LoadLibraryEx function.
Description The name of the executable (DLL or EXE) module. Must be NULL The action to take when loading the module. For event log usage, the only value you need to use here is to load the library as a datafile.
#DEFINE LOAD_LIBRARY_AS_DATAFILE
0x00000002
After you finish using the library, you need to release it. The FreeLibrary( ) function will do this. It takes a single parameter, hLibModule, which is the return value of LoadLibraryEx( ). If the function succeeds, it returns a nonzero value. Any other return value indicates failure.
DECLARE INTEGER FreeLibrary IN WIN32API ; LONG hLibModule
You will need to format the message string. The FormatMessageString( ) function does this.
DECLARE INTEGER FormatMessage IN WIN32API ; INTEGER Flags, ; LONG Source, ; INTEGER MessageId, ; INTEGER LanguageId, ; STRING @Buffer, ; INTEGER BufferSize, ; STRING @Arguments
FormatMessage( ) returns the number of bytes in the output buffer. If it fails, it returns a zero. Table 13 lists the parameters.
Table 13. A listing parameters for the FormatMessage function. Parameters Description Flags A series of bit flags that specify aspects of the formatting process and how to interpret the Source parameter. See the FORMAT_MESSAGE constants below. Source Specifies the location of the message definition. This will be the resource file loaded by LoadLibraryEx. MessageId The Message Id from the Message file. LanguageId The language Id. You can use 0 if no language is specified. @Buffer Used to return the formatted message. BufferSize The maximum number of bytes that can be stored in @Buffer. @Arguments Values that are used as insert values in the message.
2048 8192
The last API function is ExpandEnvironmentStrings( ). This function expands environment variable strings and replaces them with their user-defined values.
DECLARE INTEGER ExpandEnvironmentStrings IN WIN32API ; STRING @Src, ; STRING @Dst, ; INTEGER nSize
If the function succeeds, the return value is the number of characters stored in the destination buffer. If the number of characters is greater than the size of the destination buffer, the return value is the size of the buffer required to hold the expanded strings. If the function fails, the return value is zero. Table 14 lists the parameters.
Table 14. A list of parameters for the ExpandEnvironmentStrings function. Parameters Description @Src The environment variable string to expand. Must be of the form: %variableName%. @Dst The buffer to receive the value specified by @Src. nSize The maximum number of bytes that can be held by @Dst.
That completes the Win32 API functions. However, there is still just a bit more to cover before looking at the full code. I haven t completely showed you how to do the conversion from a VFP string to the C++ array string. The following VFP functions will do this:
* Adapted from KB Article ID: Q181289 *-- The following function converts a long integer to an ASCII *-- character representation of the passed value in low-high format. ****************** FUNCTION LongToStr ****************** * Passed : 32-bit non-negative numeric value (lnLongval) * Returns : ascii character representation of passed value in low-high * format (lcRetstr) * Example : * m.long = "999999" * m.longstr = long2str(m.long) LPARAMETERS lnLongval LOCAL lnI, lcRetstr lcRetstr = "" FOR lnI = 24 TO 0 STEP -8 lcRetstr = CHR(INT(lnLongval / (2 ^ lnI))) + lcRetstr lnLongval = MOD(lnLongval, (2 ^ lnI)) NEXT RETURN lcRetstr
Craig Berntson, Cole Gleave, 2004, 2005 Page 15 of 31
*-- The following function converts a string in low-high format to a *-- long integer. ****************** FUNCTION StrToLong ****************** * Passed: 4-byte character string (lcLongstr) in low-high ASCII format * Returns: long integer value * Example: * m.longstr = "1111" * m.longval = str2long(m.longstr) LPARAMETERS lcLongstr LOCAL lnI, lnRetval lnRetval = 0 FOR lnI = 0 TO 24 STEP 8 lnRetval = lnRetval + (ASC(lcLongstr) * (2 ^ lnI)) lcLongstr = RIGHT(lcLongstr, LEN(lcLongstr) - 1) NEXT RETURN lnRetval
#define GMEM_ZEROINIT
DECLARE INTEGER GetLastError IN WIN32API DECLARE INTEGER RegisterEventSource IN WIN32API ; STRING UNCServerName, ; STRING SourceName DECLARE INTEGER DeregisterEventSource IN WIN32API ; INTEGER hEventLog DECLARE INTEGER ReportEvent IN WIN32API ; INTEGER EventLog, ; INTEGER Type, ; INTEGER Category, ; INTEGER EventID, ; INTEGER UserSid, ; INTEGER NumStrings, ; INTEGER DataSize, ;
Craig Berntson, Cole Gleave, 2004, 2005 Page 16 of 31
STRING @Strings, ; INTEGER RawData DECLARE INTEGER GlobalAlloc IN WIN32API ; INTEGER Flags, ; INTEGER Bytes DECLARE INTEGER GlobalFree IN WIN32API ; INTEGER Mem DECLARE RtlMoveMemory in WIN32API as CopyMemory ; INTEGER Destination, ; STRING Source, ; INTEGER Length LOCAL lnHandle, lcName, pName, lcStrings, lnRetVal, ; lcMessage1, lcMessage2, pMessage1, pMessage2, lcMessages lnHandle = RegisterEventSource("", "FoxMessages") IF lnHandle > 0 * No parameter lnRetVal = ReportEvent(lnHandle, EVENTLOG_SUCCESS, 1, 1000, 0, 0, 0, NULL, 0) IF lnRetVal = 0 MESSAGEBOX("ReportEvent Error: " + ALLTRIM(STR(GetLastError()))) ENDIF * Single parameter lcName = "Fred" pName = GlobalAlloc(GMEM_ZEROINIT, LEN(lcName) + 1) CopyMemory(pName, lcName, LEN(lcName)) lcStrings = LongToStr(pName) lnRetVal = ReportEvent(lnHandle, EVENTLOG_SUCCESS, 2, 1001, 0, 1, 0, @lcStrings, 0) IF lnRetVal = 0 MESSAGEBOX("ReportEvent Error: " + ALLTRIM(STR(GetLastError()))) ENDIF IF pName > 0 lnRet = GlobalFree(pName) ENDIF * Multiple parameters lcMessage1 = "This is the first message." lcMessage2 = "This is another message!" pMessage1 = GlobalAlloc(GMEM_ZEROINIT, LEN(lcMessage1) + 1) pMessage2 = GlobalAlloc(GMEM_ZEROINIT, LEN(lcMessage2) + 1) CopyMemory(pMessage1, lcMessage1, LEN(lcMessage1)) CopyMemory(pMessage2, lcMessage2, LEN(lcMessage2)) lcMessages = LongToStr(pMessage1) + LongToStr(pMessage2) lnRetVal = ReportEvent(lnHandle, EVENTLOG_SUCCESS, 1, 1003, 0, 2, 0, @lcMessages, 0) IF lnRetVal = 0 MESSAGEBOX("ReportEvent Error: " + ALLTRIM(STR(GetLastError()))) ENDIF IF pMessage1 > 0
lnRet = GlobalFree(pMessage1) ENDIF IF pMessage2 > 0 lnRet = GlobalFree(pMessage2) ENDIF lnRetVal = DeregisterEventSource(lnHandle) ELSE MESSAGEBOX("RegisterEventSource Error: " ; + ALLTRIM(STR(GetLastError()))) ENDIF RETURN ****************** * From KB Article ID: Q181289 ****************** FUNCTION LongToStr * The following function converts a long integer to an ASCII * character representation of the passed value in low-high format. * Passed: 32-bit non-negative numeric value (lnLongval) * Returns: ascii character representation of passed value in low-high * format LPARAMETERS lnLongval LOCAL lnI, lcRetstr lcRetstr = "" FOR lnI = 24 TO 0 STEP -8 lcRetstr = CHR(INT(lnLongval / (2 ^ lnI))) + lcRetstr lnLongval = MOD(lnLongval, (2 ^ lnI)) NEXT RETURN lcRetstr ****************** FUNCTION StrToLong * Convert a string in low-high format to a long integer. * Passed: 4-byte character string (lcLongstr) in low-high ASCII format * Returns: long integer value LPARAMETERS lcLongstr LOCAL lnI, lnRetval lnRetval = 0 FOR lnI = 0 TO 24 STEP 8 lnRetval = lnRetval + (ASC(lcLongstr) * (2 ^ lnI)) lcLongstr = RIGHT(lcLongstr, LEN(lcLongstr) - 1) NEXT RETURN lnRetval
As you can see from this example, the actual FoxPro code is fairly simple. The key to using the event log is defining the API calls and converting FoxPro data types to the C++ data types needed for the calls.
#DEFINE ERROR_INSUFFICIENT_BUFFER #DEFINE ERROR_HANDLE_EOF #DEFINE #DEFINE #DEFINE #DEFINE #DEFINE #DEFINE EVENTLOG_SUCCESS EVENTLOG_ERROR_TYPE EVENTLOG_WARNING_TYPE EVENTLOG_INFORMATION_TYPE EVENTLOG_AUDIT_SUCCESS EVENTLOG_AUDIT_FAILURE
DECLARE INTEGER GetLastError IN WIN32API DECLARE INTEGER OpenEventLog IN WIN32API ; STRING UNCServerName, ; STRING UNCSourceName DECLARE INTEGER CloseEventLog IN WIN32API ; INTEGER hEventLog DECLARE INTEGER GetNumberOfEventLogRecords IN WIN32API ; INTEGER hEventLog, ; STRING @NumberOfRecords DECLARE INTEGER GetOldestEventLogRecord IN WIN32API ; INTEGER hEventLog, ; STRING @OldestRecord DECLARE INTEGER ReadEventLog IN WIN32API ; INTEGER hEventLog, ; INTEGER ReadFlags, ; INTEGER RecordOffset, ; STRING @BUFFER, ; INTEGER NumberOfBytesToRead, ; STRING @BytesRead, ; STRING @MinNumberOfBytesNeeded DECLARE INTEGER LookupAccountSid IN WIN32API ;
DECLARE LONG LoadLibraryEx IN WIN32API ; STRING LibFileName, ; INTEGER RESERVED, ; INTEGER FLAGS DECLARE INTEGER FreeLibrary IN WIN32API ; LONG hLibModule DECLARE INTEGER FormatMessage IN WIN32API ; INTEGER FLAGS, ; LONG SOURCE, ; INTEGER MessageId, ; INTEGER LanguageId, ; STRING@ BUFFER, ; INTEGER BufferSize, ; STRING@ Arguments DECLARE INTEGER ExpandEnvironmentStrings IN WIN32API ; STRING@ Src, ; STRING@ Dst, ; INTEGER nSize DECLARE INTEGER GlobalAlloc IN WIN32API ; INTEGER FLAGS, ; INTEGER Bytes DECLARE INTEGER GlobalFree IN WIN32API ; INTEGER Mem DECLARE RtlMoveMemory IN WIN32API AS CopyMemory ; INTEGER Destination, ; STRING SOURCE, ; INTEGER LENGTH ************************************** LOCAL lcUNCSourceName, loReg, hEventLog, lcNumberOfRecords, lnRetVal, ; lcOldestRecord, lcBuffer, lcBytesRead, lcMinNumberOfBytesNeeded lcUNCSourceName = "C:\" SET PROCEDURE TO (ADDBS(HOME()) + "samples\classes\registry.prg") ADDITIVE loReg = CREATEOBJECT("FileReg") * lpUNCSourceName = "3MHIS" hEventLog = OpenEventLog(NULL, lcUNCSourceName) IF hEventLog = 0 MESSAGEBOX("OpenEventLog Error: " + ALLTRIM(STR(GetLastError()))) RETURN
ENDIF * Get the number of records in the event log lcNumberOfRecords = SPACE(4) lnRetVal = GetNumberOfEventLogRecords(hEventLog, @lcNumberOfRecords) IF lnRetVal = 0 MESSAGEBOX("GetNumberOfEventLogRecords Error: " + ALLTRIM(STR(GetLastError()))) ELSE MESSAGEBOX("Number of Event Log Records: " + ALLTRIM(STR(StrToLong(lcNumberOfRecords)))) ENDIF * Get the oldest record lcOldestRecord = SPACE(4) lnRetVal = GetOldestEventLogRecord(hEventLog, @lcOldestRecord) IF lnRetVal = 0 MESSAGEBOX("GetOldestEventLogRecord Error: " + ALLTRIM(STR(GetLastError()))) ELSE MESSAGEBOX("Oldest Event Log Record: " + ALLTRIM(STR(StrToLong(lcOldestRecord)))) ENDIF * Loop through messages DO WHILE .T. lcBuffer = SPACE(1) lcBytesRead = SPACE(4) lcMinNumberOfBytesNeeded = SPACE(4) lnRetVal = ReadEventLog(hEventLog, ; BITOR(EVENTLOG_SEQUENTIAL_READ, ; EVENTLOG_FORWARDS_READ), ; 0, ; @lcBuffer, ; LEN(lcBuffer), ; @lcBytesRead, ; @lcMinNumberOfBytesNeeded) IF GetLastError() = ERROR_INSUFFICIENT_BUFFER lcBuffer = SPACE(StrToLong(lcMinNumberOfBytesNeeded)) lcBytesRead = SPACE(4) lcMinNumberOfBytesNeeded = SPACE(4) lnRet = ReadEventLog(hEventLog, ; BITOR(EVENTLOG_SEQUENTIAL_READ, ; EVENTLOG_FORWARDS_READ), ; 0, ; @lcBuffer, ; LEN(lcBuffer), ; @lcBytesRead, ; @lcMinNumberOfBytesNeeded) IF lnRet = 0 MESSAGEBOX("ReadEventLog Error: " + ALLTRIM(STR(GetLastError()))) EXIT ELSE
lcMessage = ParseLogRecord(lcBuffer, lcUNCSourceName, loReg) IF MESSAGEBOX(lcMessage, 1) = 2 EXIT ENDIF ENDIF ELSE IF GetLastError() = ERROR_HANDLE_EOF MESSAGEBOX("End of File") EXIT ELSE MESSAGEBOX("ReadEventLog Error: " + ALLTRIM(STR(GetLastError()))) EXIT ENDIF ENDIF ENDDO lnRetVal = CloseEventLog(hEventLog) IF lnRetVal = 0 MESSAGEBOX("CloseEventLog Error: " + ALLTRIM(STR(GetLastError()))) ENDIF RETURN **************************************************** FUNCTION ParseLogRecord LPARAMETERS tcBuffer, tcUNCSourceName, toReg LOCAL lcMessage, lnCounter, lnDataLen, pBuffer lcMessage = "Length: " + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 1, 4)))) + CHR(13) + CHR(10) lcMessage = lcMessage + "Record Number: " + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 9, 4)))) + CHR(13) + CHR(10) lcMessage = lcMessage + "Time Generated: " + TTOC({^1969/12/31 18:00:00} + StrToLong(SUBSTR(tcBuffer, 13, 4))) + CHR(13) + CHR(10) lcMessage = lcMessage + "Time Written: " + TTOC({^1969/12/31 18:00:00} + StrToLong(SUBSTR(tcBuffer, 17, 4))) + CHR(13) + CHR(10) lcMessage = lcMessage + "Event ID: " + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 21, 2)))) + CHR(13) + CHR(10) lcMessage = lcMessage + GetEventType(tcBuffer) lcMessage = lcMessage + "Number of Strings: " + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 27, 2)))) + CHR(13) + CHR(10) lcMessage = lcMessage + "Source Name: " lcSource = "" lnCounter = 57 DO WHILE SUBSTR(tcBuffer, lnCounter, 1) <> CHR(0) lcSource = lcSource + SUBSTR(tcBuffer, lnCounter, 1) lnCounter = lnCounter + 1 ENDDO lcMessage = lcMessage + lcSource lcMessage = lcMessage + CHR(13) + CHR(10) lcMessage = lcMessage + GetEventCategory(tcBuffer, tcUNCSourceName, lcSource, toReg) lnCounter = lnCounter + 1
lcMessage = lcMessage + "Computer Name: " DO WHILE SUBSTR(tcBuffer, lnCounter, 1) <> CHR(0) lcMessage = lcMessage + SUBSTR(tcBuffer, lnCounter, 1) lnCounter = lnCounter + 1 ENDDO lcMessage = lcMessage + CHR(13) + CHR(10) lcMessage = lcMessage + GetUserInfo(tcBuffer) lcMessage = lcMessage + GetDescription(tcBuffer, tcUNCSourceName, lcSource, toReg) lnDataLen = StrToLong(SUBSTR(tcBuffer, 49, 4)) IF lnDataLen > 0 lcMessage = lcMessage + "Data: " pBuffer = StrToLong(SUBSTR(tcBuffer, 53, 4)) FOR lnCounter = 1 TO lnDataLen lcMessage = lcMessage + STR(ASC(SUBSTR(tcBuffer, pBuffer + lnCounter, lnDataLen))) NEXT lcMessage = lcMessage + CHR(13) + CHR(10) ENDIF RETURN lcMessage ************************************************* FUNCTION GetEventType LPARAMETERS tcBuffer LOCAL lcRetVal, lnEventType lnEventType = StrToLong(SUBSTR(tcBuffer, 25, 2)) DO CASE CASE lnEventType = EVENTLOG_SUCCESS lcRetVal = "Success" CASE lnEventType = EVENTLOG_ERROR_TYPE lcRetVal = "Error" CASE lnEventType = EVENTLOG_WARNING_TYPE lcRetVal = "Warning" CASE lnEventType = EVENTLOG_INFORMATION_TYPE lcRetVal = "Information" CASE lnEventType = EVENTLOG_AUDIT_SUCCESS lcRetVal = "Audit Success" CASE lnEventType = EVENTLOG_AUDIT_FAILURE lcRetVal = "Audit Failure" ENDCASE lcRetVal = "Event Type: " + lcRetVal + CHR(13) + CHR(10) RETURN lcRetVal ************************************ FUNCTION GetEventCategory LPARAMETERS tcBuffer, tcUNCSourceName, tcSource, toReg LOCAL lcRetVal, lnCategory, lcLookup, lnKey, lcCMF, lnKeyValue, lcCMFx, lnRet, ; hResource, lcBuffer, lnI lcRetVal = "" lnCategory = StrToLong(SUBSTR(tcBuffer, 29, 2))
IF lnCategory = 0 lcRetVal = "None" ELSE lcLookUp = "SYSTEM\CurrentControlSet\Services\EventLog\" + ALLTRIM(tcUNCSourceName) + "\" + tcSource lnKey = toReg.OpenKey(lcLookUp, HKEY_LOCAL_MACHINE, .F.) IF lnKey = 0 lcCMF = "" lnKeyValue = toReg.GetKeyValue("CategoryMessageFile", @lcCMF) IF lnKeyValue = 0 lcCMFx = SPACE(255) lnRet = ExpandEnvironmentStrings(@lcCMF, @lcCMFx, 255) hResource = LoadLibraryEx(ALLTRIM(lcCMFx), 0, LOAD_LIBRARY_AS_DATAFILE) IF hResource > 0 lcBuff = SPACE(100) lnRet = FormatMessage(BITOR(FORMAT_MESSAGE_FROM_HMODULE, FORMAT_MESSAGE_IGNORE_INSERTS), ; hResource, ; lnCategory, ; 0, ; @lcBuff, ; 100, ; NULL) IF lnRet > 0 FOR lnI = 1 TO lRet IF SUBSTR(tcBuffer, lnI, 1) = CHR(13) EXIT ELSE lcRetVal = lcRetVal + SUBSTR(tcBuffer, lnI, 1) ENDIF NEXT ELSE lcRetVal = lcRetVal + "(" + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 29, 2)))) + ")" ENDIF FreeLibrary(hResource) ELSE lcRetVal = lcRetVal + "(" + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 29, 2)))) + ")" ENDIF ELSE lcRetVal = lcRetVal + "(" + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 29, 2)))) + ")" ENDIF oReg.CloseKey() ELSE lcRetVal = lcRetVal + "(" + ALLTRIM(STR(StrToLong(SUBSTR(tcBuffer, 29, 2)))) + ")" ENDIF ENDIF lcRetVal = "Event Category: " + lcRetVal + CHR(13) + CHR(10) RETURN lcRetVal
************************************************** FUNCTION GetUserInfo LPARAMETERS tcBuffer LOCAL lnUserSIDLength, lnUserSIDOffset, lcRetVal, lcSID, lnI, lcName, cbName, ; lcReferencedDomainName, cbReferencedDomainName, peUse, lnRet lnUserSIDLength = StrToLong(SUBSTR(tcBuffer, 41, 4)) lnUserSIDOffset = StrToLong(SUBSTR(tcBuffer, 45, 4)) lcRetVal = "" IF lnUserSIDLength > 0 lcSid = "" FOR lnI = 1 TO lnUserSIDLength lcSid = lcSid + SUBSTR(tcBuffer, lnUserSIDOffset + lnI, 1) NEXT lcName = SPACE(1) cbName = LongToStr(1) lcReferencedDomainName = SPACE(1) cbReferencedDomainName = LongToStr(1) peUse = SPACE(1) lnRet = LookupAccountSid("", ; @lcSid, ; @lcName, ; @cbName, ; @lcReferencedDomainName, ; @cbReferencedDomainName, ; @peUse) lnRet = GetLastError() IF lnRet = ERROR_INSUFFICIENT_BUFFER lcName = SPACE(StrToLong(cbName)) lcReferencedDomainName = SPACE(StrToLong(cbReferencedDomainName)) lnRet = LookupAccountSid("", ; @lcSid, ; @lcName, ; @cbName, ; @lcReferencedDomainName, ; @cbReferencedDomainName, ; @peUse) IF lnRet = 1 lcRetVal = lcRetVal + lcName ELSE lcRetVal = lcRetVal + "N/A" ENDIF ELSE lcRetVal = lcRetVal + "N/A" ENDIF ELSE lcRetVal = lcRetVal + "N/A" ENDIF lcRetVal = "User: " + lcRetVal + CHR(13) + CHR(10) RETURN lcRetVal
*********************************************** PROCEDURE GetDescription LPARAMETERS tcBuffer, tcUNCSourceName, tcSource, toReg LOCAL lcLookup, lnRet, lcEMF, lcEMFx, lnNumStrings, pStr, lcPtrs, lnI, ; hResource, lcBuffer, lnEventNo, lcMessage, lcRetVal lcLookUp = "SYSTEM\CurrentControlSet\Services\EventLog\" + ALLTRIM(tcUNCSourceName) + "\" + tcSource lnRet = toReg.OpenKey(lcLookUp, HKEY_LOCAL_MACHINE, .F.) IF lnRet = 0 lcEMF = "" lnRet = oReg.GetKeyValue("EventMessageFile", @lcEMF) IF lnRet = 0 lnNumStrings = StrToLong(SUBSTR(tcBuffer, 27, 2)) pStr = StrToLong(SUBSTR(cBuffer, 37, 4)) + 1 lcMessage = "" lcPtrs = "" FOR lnI = 1 TO lnNumStrings DO WHILE .T. IF SUBSTR(tcBuffer, pStr, 1) <> CHR(0) lcMessage = lcMessage + SUBSTR(tcBuffer, pStr, 1) pStr = pStr + 1 ELSE lcPtrs = lcPtrs + LongToStr(GlobalAlloc(GMEM_ZEROINIT, LEN(lcMessage) + 1)) CopyMemory(StrToLong(RIGHT(lcPtrs, 4)), lcMessage, LEN(lcMessage)) pStr = pStr + 1 lcMessage = "" EXIT ENDIF ENDDO NEXT lcEMFx = SPACE(255) lnRet = ExpandEnvironmentStrings(@lcEMF, @lcEMFx, 255) hResource = LoadLibraryEx(ALLTRIM(lcEMFx), 0, LOAD_LIBRARY_AS_DATAFILE) IF hResource > 0 lcBuffer = SPACE(1000) lnRet = FormatMessage(BITOR(FORMAT_MESSAGE_FROM_HMODULE, FORMAT_MESSAGE_ARGUMENT_ARRAY, 60), ; hResource, ; StrToLong(SUBSTR(tcBuffer, 21, 2)), ; 0, ; @lcBuffer, ; 10000, ; @lcPtrs) IF lnRet = 0 lnEventNo = StrToLong(SUBSTR(tcBuffer, 21, 2)) lnRet = GetLastError()
ELSE lcRetVal = ALLTRIM(lcBuffer) ENDIF FreeLibrary(hResource) ENDIF FOR lnI = 1 TO lnNumStrings lnRet = GlobalFree(StrToLong(SUBSTR(lcPtrs, lnI * 4 - 3, 4))) NEXT ENDIF toReg.CloseKey() ENDIF lcRetVal = IIF(EMPTY(lcRetVal), "", "Description: " + lcRetVal) RETURN lcRetVal ****************** * From KB Article ID: Q181289 ****************** FUNCTION LongToStr * The following function converts a long integer to an ASCII * character representation of the passed value in low-high format. * Passed: 32-bit non-negative numeric value (lnLongval) * Returns: ascii character representation of passed value in low-high * format LPARAMETERS tnLongval LOCAL lnI, lcRetVal lcRetVal = "" FOR lnI = 24 TO 0 STEP -8 lcRetVal = CHR(INT(tnLongVal / (2 ^ lnI))) + lcRetVal lnLongval = MOD(tnLongVal, (2 ^ lnI)) NEXT RETURN lcRetVal ****************** FUNCTION StrToLong * Convert a string in low-high format to a long integer. * Passed: 4-byte character string (lcLongstr) in low-high ASCII format * Returns: long integer value LPARAMETERS tcLongStr LOCAL lcLongStr, lnI, lnRetval lcLongStr = tcLongStr lnRetVal = 0 FOR lnI = 0 TO 24 STEP 8 lnRetVal = lnRetVal + (ASC(lcLongStr) * (2 ^ lnI)) lcLongStr = RIGHT(lcLongStr, LEN(lcLongStr) - 1) NEXT RETURN lnRetVal
You can see that the code that actually reads the log is quite small. Most of the code is for formatting the output.
Open the Event Viewer (Figure 1). Right-click on Application and select Properties. The Application Properties dialog (Figure 7) is displayed.
Figure 7. The Application Properties dialog is used to maintain the Event Log.
3. 4. 5.
Use the Log size settings to maintain the size of the Event Log. Click Clear Log to clear all entries from the log Click OK to close the properties dialog.
You can also maintain the log programmatically. The following code shows how to backup the log:
Craig Berntson, Cole Gleave, 2004, 2005 Page 28 of 31
DECLARE INTEGER GetLastError IN WIN32API DECLARE INTEGER OpenEventLog IN WIN32API ; STRING UNCServerName, ; STRING UNCSourceName DECLARE INTEGER BackupEventLog IN WIN32API ; INTEGER hEventLog, ; STRING BackupLog DECLARE INTEGER CloseEventLog IN WIN32API ; INTEGER hEventLog *************************************** LOCAL hEventLog, lcBackupLog, lnRet lcUNCSourceName = "C:\" lcBackupLog = "C:\Temp\BackupLog.Evt" hEventLog = OpenEventLog(NULL, lcUNCSourceName) IF hEventLog = 0 MESSAGEBOX("OpenEventLog Error: " + ALLTRIM(STR(GetLastError()))) RETURN ENDIF ERASE (lcBackupLog) lnRet = BackupEventLog(hEventLog, lcBackupLog) IF lnRet = 0 MESSAGEBOX("BackupEventLog Error: " + ALLTRIM(STR(GetLastError()))) ENDIF lnRet = CloseEventLog(hEventLog) IF lnRet = 0 MESSAGEBOX("CloseEventLog Error: " + ALLTRIM(STR(GetLastError()))) ENDIF
The following code shows how to clear the Windows Event Log:
DECLARE INTEGER GetLastError IN WIN32API DECLARE INTEGER OpenEventLog IN WIN32API ; STRING UNCServerName, ; STRING UNCSourceName DECLARE INTEGER ClearEventLog IN WIN32API ; INTEGER hEventLog, ; STRING BackupLog DECLARE INTEGER CloseEventLog IN WIN32API ; INTEGER hEventLog ***************************************
LOCAL hEventLog, lcBackupLog, lnRet, lcNewBackup lcUNCSourceName = "C:\" lcBackupLog = "C:\Temp\Backup.Evt" lcNewBackup = NULL hEventLog = OpenEventLog(NULL, lcUNCSourceName) IF hEventLog = 0 MESSAGEBOX("OpenEventLog Error: " + ALLTRIM(STR(GetLastError()))) RETURN ENDIF IF !ISNULL(lcBackupLog) ERASE (lcBackupLog) ENDIF lnRet = ClearEventLog(hEventLog, lcBackupLog) IF lnRet = 0 MESSAGEBOX("ClearEventLog Error: " + ALLTRIM(STR(GetLastError()))) ENDIF lnRet = CloseEventLog(hEventLog) IF lnRet = 0 MESSAGEBOX("CloseEventLog Error: " + ALLTRIM(STR(GetLastError()))) ENDIF
Summary
It may seem that using the Windows Event Log is very complex, but you could create a class that will handle this for you and a generic message file, making the job easier. By using the Windows Event Log, you will centralize event reporting, use a format common to many other applications, and make event reporting easier for the end user.
Craig Berntson is a Microsoft Most Valuable Professional (MVP) for Visual FoxPro, a Microsoft Certified Solution Developer, and President of the Salt Lake City Fox User Group. He wrote the book CrysDev: A Developer s Guide to Integrating Crystal Reports , available from Hentzenwerke Publishing. He has also written for FoxTalk and the Visual FoxPro User Group (VFUG) newsletter. He has spoken at Advisor DevCon, Essential Fox, the Great Lakes Great Database Workshop, Southwest Fox, Microsoft DevDays and user groups around the country. Currently, Craig is a Senior Software Engineer at 3M Health Information Systems in Salt Lake City. You can reach him a craig@craigberntson.com or visit his website, www.craigberntson.com. Cole Gleave is a Senior Software Engineer at 3M Health Information Systems in Salt Lake City. Previously he was Vice-President of Technology at Convenient Automation, specializing in software for the retail industry. He has worked with FoxPro since version 2.6. You can reach him at cgleave@yahoo.com.
This document was created with Win2PDF available at http://www.daneprairie.com. The unregistered version of Win2PDF is for evaluation or non-commercial use only.