获取提供程序元数据

提供程序使用检测清单来标识自身、定义其写入的事件以及其他组件(如通道、任务和关键字)。 若要在运行时访问此信息,请调用 EvtOpenPublisherMetadata 函数以获取发布者元数据的句柄。

元数据在发布者元数据和事件元数据之间划分。 发布者元数据包含标识发布者的元数据及其定义的任何元数据,例如通道、级别、任务、操作码和关键字。 有关可以检索的提供程序元数据的列表,请参阅 EVT_PUBLISHER_METADATA_PROPERTY_ID 枚举。 若要获取元数据,请调用 EvtGetPublisherMetadataProperty 函数。 此函数返回用于标识通道、级别、任务、操作码和关键字组件的提供程序和句柄的属性的属性值。 若要访问这些组件的元数据,请调用 EvtGetObjectArrayProperty 函数。

若要获取事件元数据,请调用 EvtOpenEventMetadataEnum 函数以获取提供程序定义的事件列表。 然后在循环中调用 EvtNextEventMetadata 函数以枚举事件。 有关可以为事件获取的元数据列表,请参阅 EVT_EVENT_METADATA_PROPERTY_ID 枚举。 若要获取元数据,请调用 EvtGetEventMetadataProperty 函数。 这些属性返回事件定义的每个元素的整数值。 对于事件 ID 和版本,显示整数值是可以的,但对于其他元素(如通道和级别),显示整数值对用户可能没有任何意义。 这些元素的整数值是 value 属性的值。 可以使用整数值映射到提供程序的元数据中,并获取元素的消息字符串或名称值,并将其改为显示给用户。

有关演示如何枚举计算机上已注册的提供程序的示例,请参阅 枚举已注册的提供程序

有关演示如何获取已注册提供程序的元数据的示例,请参阅 获取提供程序的元数据

枚举已注册的提供程序

以下示例演示如何枚举计算机上注册的提供程序。

#include <windows.h>
#include <stdio.h>
#include <winevt.h>

#pragma comment(lib, "wevtapi.lib")

void main(void)
{
    EVT_HANDLE hProviders = NULL;
    LPWSTR pwcsProviderName = NULL;
    LPWSTR pTemp = NULL;
    DWORD dwBufferSize = 0;
    DWORD dwBufferUsed = 0;
    DWORD status = ERROR_SUCCESS;

    // Get a handle to the list of providers.
    hProviders = EvtOpenPublisherEnum(NULL, 0);
    if (NULL == hProviders)
    {
        wprintf(L"EvtOpenPublisherEnum failed with %lu\n", GetLastError());
        goto cleanup;
    }

    wprintf(L"List of registered providers:\n\n");

    // Enumerate the providers in the list.
    while (true)
    {
        // Get a provider from the list. If the buffer is not big enough
        // to contain the provider's name, reallocate the buffer to the required size.
        if  (!EvtNextPublisherId(hProviders, dwBufferSize, pwcsProviderName, &dwBufferUsed))
        {
            status = GetLastError();
            if (ERROR_NO_MORE_ITEMS == status)
            {
                break;
            }
            else if (ERROR_INSUFFICIENT_BUFFER == status)
            {
                dwBufferSize = dwBufferUsed;
                pTemp = (LPWSTR)realloc(pwcsProviderName, dwBufferSize * sizeof(WCHAR));
                if (pTemp)
                {
                    pwcsProviderName = pTemp;
                    pTemp = NULL;
                    EvtNextPublisherId(hProviders, dwBufferSize, pwcsProviderName, &dwBufferUsed);
                }
                else
                {
                    wprintf(L"realloc failed\n");
                    goto cleanup;
                }
            }

            if (ERROR_SUCCESS != (status = GetLastError()))
            {
                wprintf(L"EvtNextPublisherId failed with %d\n", status);
                goto cleanup;
            }
        }

        wprintf(L"%s\n", pwcsProviderName);

        RtlZeroMemory(pwcsProviderName, dwBufferUsed * sizeof(WCHAR));
    }

cleanup:

    if (pwcsProviderName)
        free(pwcsProviderName);

    if (hProviders)
        EvtClose(hProviders);
}

获取提供程序的元数据

以下示例演示如何获取提供程序及其定义的事件的元数据。

#include <windows.h>
#include <stdio.h>
#include <winevt.h>

#pragma comment(lib, "wevtapi.lib")
#pragma comment(lib, "ole32.lib")

// Contains the value and message string for a type, such as
// an opcode or task that the provider defines or uses. If the
// type does not specify a message string, the message member
// contains the value of the type's name attribute.
typedef struct _msgstring
{
    union
    {
        DWORD dwValue;  // Value attribute for opcode, task, and level
        UINT64 ullMask; // Mask attribute for keyword
    };
    LPWSTR pwcsMessage; // Message string or name attribute
} MSG_STRING, *PMSG_STRING;

// Header for the block of value/message string pairs. The dwSize member
// is the size, in bytes, of the block of MSG_STRING structures to which 
// the pMessage member points.
typedef struct _messages
{
    DWORD dwSize;
    PMSG_STRING pMessage;
} MESSAGES, *PMESSAGES;


DWORD PrintProviderEvents(EVT_HANDLE hMetadata);
DWORD PrintEventProperties(EVT_HANDLE hEvent);
DWORD PrintEventProperty(EVT_HANDLE hMetadata, int Id, PEVT_VARIANT pProperty);
LPWSTR GetPropertyName(EVT_HANDLE hMetadata, PMESSAGES pMessages, DWORD dwValue);
LPWSTR GetOpcodeName(EVT_HANDLE hMetadata, PMESSAGES pMessages, DWORD dwOpcodeValue, DWORD dwTaskValue);
LPWSTR GetKeywordName(EVT_HANDLE hMetadata, PMESSAGES pMessages, UINT64 ullKeyword);
DWORD PrintProviderProperties(EVT_HANDLE hMetadata);
DWORD PrintProviderProperty(EVT_HANDLE hMetadata, int Id, PEVT_VARIANT pProperty);
LPWSTR GetMessageString(EVT_HANDLE hMetadata, DWORD dwMessageId);
DWORD DumpChannelProperties(EVT_HANDLE hChannels, DWORD dwIndex, PMESSAGES pMessages);
DWORD DumpLevelProperties(EVT_HANDLE hLevels, DWORD dwIndex, PMESSAGES pMessages);
DWORD DumpTaskProperties(EVT_HANDLE hTasks, DWORD dwIndex, PMESSAGES pMessages);
DWORD DumpOpcodeProperties(EVT_HANDLE hOpcodes, DWORD dwIndex, PMESSAGES pMessages);
DWORD DumpKeywordProperties(EVT_HANDLE hKeywords, DWORD dwIndex, PMESSAGES pMessages);
void FreeMessages(PMESSAGES pMessages);
PEVT_VARIANT GetProperty(EVT_HANDLE handle, DWORD dwIndex, EVT_PUBLISHER_METADATA_PROPERTY_ID PropertyId);

// Global variables.
EVT_HANDLE g_hMetadata = NULL;
MESSAGES g_ChannelMessages = {0, NULL};
MESSAGES g_LevelMessages = {0, NULL};
MESSAGES g_TaskMessages = {0, NULL};
MESSAGES g_OpcodeMessages = {0, NULL};
MESSAGES g_KeywordMessages = {0, NULL};


void main(void)
{
    LPWSTR pwszPublisherName = L"<name of the publisher goes here>";
    DWORD status = ERROR_SUCCESS;

    // Get a handle to the provider's metadata. You can specify the provider's name
    // if the provider is registered on the computer or the full path to an archived 
    // log file (archived log files contain the provider's metadata). Specify the locale 
    // identifier if you want the localized strings returned in a locale other than
    // the locale of the current thread.
    g_hMetadata = EvtOpenPublisherMetadata(NULL, 
        pwszPublisherName,
        NULL,
        0, //MAKELCID(MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH), 0), 
        0);
    if (NULL == g_hMetadata)
    {
        wprintf(L"EvtOpenPublisherMetadata failed with %d\n", GetLastError());
        goto cleanup;
    }

    wprintf(L"Provider Metadata for %s\n\n", pwszPublisherName);
    status = PrintProviderProperties(g_hMetadata);

    wprintf(L"\nEvent Metadata for %s\n\n", pwszPublisherName);
    status = PrintProviderEvents(g_hMetadata);

cleanup:

    if (g_hMetadata)
        EvtClose(g_hMetadata);

    if (g_ChannelMessages.pMessage)
        FreeMessages(&g_ChannelMessages);

    if (g_LevelMessages.pMessage)
        FreeMessages(&g_LevelMessages);

    if (g_TaskMessages.pMessage)
        FreeMessages(&g_TaskMessages);

    if (g_OpcodeMessages.pMessage)
        FreeMessages(&g_OpcodeMessages);

    if (g_KeywordMessages.pMessage)
        FreeMessages(&g_KeywordMessages);
}

// Free the memory for each message string in the messages block
// and then free the messages block.
void FreeMessages(PMESSAGES pMessages)
{
    DWORD dwCount = pMessages->dwSize / sizeof(MSG_STRING);

    for (DWORD i = 0; i < dwCount; i++)
    {
        free(((pMessages->pMessage)+i)->pwcsMessage);
        ((pMessages->pMessage)+i)->pwcsMessage = NULL;
    }

    free(pMessages->pMessage);
}

// Get an enumerator to the provider's events and enumerate them.
// Call this function after first calling the PrintProviderProperties
// function to get the message strings that this section uses.
DWORD PrintProviderEvents(EVT_HANDLE hMetadata)
{
    EVT_HANDLE hEvents = NULL;
    EVT_HANDLE hEvent = NULL;
    DWORD status = ERROR_SUCCESS;

    // Get a handle to the provider's events.
    hEvents = EvtOpenEventMetadataEnum(hMetadata, 0);
    if (NULL == hEvents)
    {
        wprintf(L"EvtOpenEventMetadataEnum failed with %lu\n", GetLastError());
        goto cleanup;
    }

    // Enumerate the events and print each event's metadata.
    while (true)
    {
        hEvent = EvtNextEventMetadata(hEvents, 0);
        if (NULL == hEvent)
        {
            if (ERROR_NO_MORE_ITEMS != (status = GetLastError()))
            {
                wprintf(L"EvtNextEventMetadata failed with %lu\n", status);
            }

            break;
        }

        if (status = PrintEventProperties(hEvent))
            break;

        EvtClose(hEvent);
        hEvent = NULL;
    }


cleanup:

    if (hEvents)
        EvtClose(hEvents);

    if (hEvent)
        EvtClose(hEvent);

    return status;
}


// Print the metadata for the event.
DWORD PrintEventProperties(EVT_HANDLE hEvent)
{
    PEVT_VARIANT pProperty = NULL;  // Contains a metadata value
    PEVT_VARIANT pTemp = NULL;
    DWORD dwBufferSize = 0;
    DWORD dwBufferUsed = 0;
    DWORD status = ERROR_SUCCESS;

    // Use the EVT_EVENT_METADATA_PROPERTY_ID's enumeration values to loop
    // through all the metadata for the event.
    for (int Id = 0; Id < EvtEventMetadataPropertyIdEND; Id++)
    {
        // Get the specified metadata property. If the pProperty buffer is not big enough, reallocate the buffer.
        if  (!EvtGetEventMetadataProperty(hEvent, (EVT_EVENT_METADATA_PROPERTY_ID)Id, 0, dwBufferSize, pProperty, &dwBufferUsed))
        {
            status = GetLastError();
            if (ERROR_INSUFFICIENT_BUFFER == status)
            {
                dwBufferSize = dwBufferUsed;
                pTemp = (PEVT_VARIANT)realloc(pProperty, dwBufferSize);
                if (pTemp)
                {
                    pProperty = pTemp;
                    pTemp = NULL;
                    EvtGetEventMetadataProperty(hEvent, (EVT_EVENT_METADATA_PROPERTY_ID)Id, 0, dwBufferSize, pProperty, &dwBufferUsed);
                }
                else
                {
                    wprintf(L"realloc failed\n");
                    status = ERROR_OUTOFMEMORY;
                    goto cleanup;
                }
            }

            if (ERROR_SUCCESS != (status = GetLastError()))
            {
                wprintf(L"EvtGetEventMetadataProperty failed with %d\n", GetLastError());
                goto cleanup;
            }
        }

        if (status = PrintEventProperty(g_hMetadata, Id, pProperty))
            break;

        RtlZeroMemory(pProperty, dwBufferUsed);
    }

cleanup:

    if (pProperty)
        free(pProperty);

    return status;
}


// Print the event property.
DWORD PrintEventProperty(EVT_HANDLE hMetadata, int Id, PEVT_VARIANT pProperty)
{
    DWORD status = ERROR_SUCCESS;
    static DWORD dwOpcode = 0;
    LPWSTR pName = NULL;       // The event's name property
    LPWSTR pMessage = NULL;    // The event's message string

    switch(Id)
    {
        case EventMetadataEventID:
            wprintf(L"ID: %lu\n", pProperty->UInt32Val);
            break;

        case EventMetadataEventVersion:
            wprintf(L"Version: %lu\n", pProperty->UInt32Val);
            break;

        // The channel property contains the value of the channel's value attribute.
        // Instead of printing the value attribute, use it to find the channel's
        // message string or name and print it.
        case EventMetadataEventChannel:
            if (pProperty->UInt32Val > 0)
            {
                pName = GetPropertyName(hMetadata, &g_ChannelMessages, pProperty->UInt32Val);
                wprintf(L"Channel: %s\n", pName);
            }
            else
            {
                wprintf(L"Channel: \n");
            }
            break;

        // The level property contains the value of the level's value attribute.
        // Instead of printing the value attribute, use it to find the level's
        // message string or name and print it.
        case EventMetadataEventLevel:
            if (pProperty->UInt32Val > 0)
            {
                pName = GetPropertyName(hMetadata, &g_LevelMessages, pProperty->UInt32Val);
                wprintf(L"Level: %s\n", pName);
            }
            else
            {
                wprintf(L"Level: \n");
            }
            break;

        // The opcode property contains the value of the opcode's value attribute.
        // Instead of printing the value attribute, use it to find the opcode's
        // message string or name and print it.
        // The opcode value contains the opcode in the high word. If the opcode is 
        // task-specific, the opcode value will contain the task value in the low word.
        // Save the opcode in a static variable and print it when we get the task
        // value, so we can decide if the opcode is task-specific.
        case EventMetadataEventOpcode:
            dwOpcode = pProperty->UInt32Val;
            break;

        // The task property contains the value of the task's value attribute.
        // Instead of printing the value attribute, use it to find the task's
        // message string or name and print it.
        case EventMetadataEventTask:
            if (pProperty->UInt32Val > 0)
            {
                pName = GetPropertyName(hMetadata, &g_TaskMessages, pProperty->UInt32Val);
                wprintf(L"Task: %s\n", pName);
            }
            else
            {
                wprintf(L"Task: \n");
            }

            // Now that we know the task, get the opcode string and print it.
            if (dwOpcode > 0)
            {
                pName = GetOpcodeName(hMetadata, &g_OpcodeMessages, dwOpcode, pProperty->UInt32Val);
                wprintf(L"Opcode: %s\n", pName);
            }
            else
            {
                wprintf(L"Opcode: \n");
            }
            break;

        // The keyword property contains a bit mask of all the keywords.
        // Instead of printing the value attribute, use it to find the 
        // message string or name associated with each keyword and print them (space delimited).
        case EventMetadataEventKeyword:
            // The upper 8 bits can contain reserved bit values, so do not include them
            // when checking to see if any keywords are set.
            if ((pProperty->UInt32Val & 0x00FFFFFFFFFFFFFF) > 0)
            {
                pName = GetKeywordName(hMetadata, &g_KeywordMessages, pProperty->UInt32Val);
                wprintf(L"Keyword: %s\n", pName);
                if (pName)
                    free(pName);
            }
            else
            {
                wprintf(L"Keyword: \n");
            }
            break;

        // If the message string is not specified, the value is -1.
        case EventMetadataEventMessageID:
            if (-1 == pProperty->UInt32Val)
            {
                wprintf(L"Message string: \n");
            }
            else
            {
                pMessage = GetMessageString(hMetadata, pProperty->UInt32Val);
                wprintf(L"Message string: %s\n", (pMessage) ? pMessage : L"");
                if (pMessage)
                {
                    free(pMessage);
                }
            }
            break;

        // When you define the event, the template attribute contains the template
        // identifier; however, the template metadata contains an XML string of the 
        // template (includes the data items, not the UserData section).
        case EventMetadataEventTemplate:
            wprintf(L"Template: %s\n\n", pProperty->StringVal);
            break;

        default:
            wprintf(L"Unknown property Id: %d\n", Id);
    }

    return status;
}


// Used to get the message string or name for levels, tasks, and channels.
// Search the messages block sequentially for an item that has the same value
// and return a pointer to the message string.
LPWSTR GetPropertyName(EVT_HANDLE hMetadata, PMESSAGES pMessages, DWORD dwValue)
{
    UNREFERENCED_PARAMETER(hMetadata);

    LPWSTR pMessage = NULL;
    DWORD dwCount = pMessages->dwSize / sizeof(MSG_STRING);

    for (DWORD i = 0; i < dwCount; i++)
    {
        if (dwValue == ((pMessages->pMessage)+i)->dwValue)
        {
            pMessage = ((pMessages->pMessage)+i)->pwcsMessage;
            break;
        }
    }

    return pMessage;
}

// Used to get the message string or name for an opcode. Search the messages block sequentially 
// for an item that has the same opcode value (high word). Opcodes can be defined globally or 
// locally (task-specific). All global opcodes must be unique, but multiple tasks can specify the
// same opcode value, so we need to check the low word to see if the task on the event matches
// the task on the opcode.
LPWSTR GetOpcodeName(EVT_HANDLE hMetadata, PMESSAGES pMessages, DWORD dwOpcodeValue, DWORD dwTaskValue)
{
    UNREFERENCED_PARAMETER(hMetadata);

    LPWSTR pMessage = NULL;
    DWORD dwCount = pMessages->dwSize / sizeof(MSG_STRING);
    DWORD dwOpcodeIndex = 0;  // Points to the global opcode (low word is zero)
    BOOL fFound = FALSE;

    for (DWORD i = 0; i < dwCount; i++)
    {
        if (dwOpcodeValue == HIWORD(((pMessages->pMessage)+i)->dwValue))
        {
            if (0 == LOWORD(((pMessages->pMessage)+i)->dwValue))
            {
                dwOpcodeIndex = i;
            }
            else if (dwTaskValue == LOWORD(((pMessages->pMessage)+i)->dwValue))
            {
                pMessage = ((pMessages->pMessage)+i)->pwcsMessage;
                fFound = TRUE;
                break;
            }
        }
    }

    if (FALSE == fFound)
    {
        pMessage = ((pMessages->pMessage)+dwOpcodeIndex)->pwcsMessage;
    }

    return pMessage;
}

// Used to get the message strings or names for the keywords specified on the event. The event
// contains a bit mask that has bits set for each keyword specified on the event. Search the 
// messages block sequentially for items that have the same keyword bit set. Concatenate all the
// message strings.
LPWSTR GetKeywordName(EVT_HANDLE hMetadata, PMESSAGES pMessages, UINT64 ullKeywords)
{
    UNREFERENCED_PARAMETER(hMetadata);

    LPWSTR pMessage = NULL;
    LPWSTR pTemp = NULL;
    BOOL fFirstMessage = TRUE;
    size_t dwStringLen = 0;
    DWORD dwCount = pMessages->dwSize / sizeof(MSG_STRING);

    for (DWORD i = 0; i < dwCount; i++)
    {
        if (ullKeywords & ((pMessages->pMessage)+i)->ullMask)
        {
            dwStringLen += wcslen(((pMessages->pMessage)+i)->pwcsMessage) + 1 + 1;  // + space delimiter + null-terminator
            pTemp = (LPWSTR)realloc(pMessage, dwStringLen * sizeof(WCHAR));
            if (pTemp)
            {
                pMessage = pTemp;
                pTemp = NULL;

                if (fFirstMessage)
                {
                    *pMessage = L'\0';  // Need so first wcscat_s call works
                    fFirstMessage = FALSE;
                }
                else
                {
                    wcscat_s(pMessage, dwStringLen, L" ");  // Space delimiter
                }

                wcscat_s(pMessage, dwStringLen, ((pMessages->pMessage)+i)->pwcsMessage);
            }
            else
            {
                wprintf(L"realloc failed for GetKeywordName\n");
                if (pMessage)
                {
                    free(pMessage);
                    pMessage = NULL;
                }
                break;
            }
        }
    }

    return pMessage;
}


// Use the EVT_PUBLISHER_METADATA_PROPERTY_ID enumeration values to enumerate all the
// provider's metadata excluding event metadata. Enumerates the metadata for channels,
// tasks, opcodes, levels, keywords, and the provider.
DWORD PrintProviderProperties(EVT_HANDLE hMetadata)
{
    PEVT_VARIANT pProperty = NULL;  // Contains the metadata property
    PEVT_VARIANT pTemp = NULL;
    DWORD dwBufferSize = 0;
    DWORD dwBufferUsed = 0;
    DWORD status = ERROR_SUCCESS;

    for (int Id = 0; Id < EvtPublisherMetadataPropertyIdEND; Id++)
    {
        // Get the metadata property. If the buffer is not big enough, reallocate the buffer.
        if  (!EvtGetPublisherMetadataProperty(hMetadata, (EVT_PUBLISHER_METADATA_PROPERTY_ID)Id, 0, dwBufferSize, pProperty, &dwBufferUsed))
        {
            status = GetLastError();
            if (ERROR_INSUFFICIENT_BUFFER == status)
            {
                dwBufferSize = dwBufferUsed;
                pTemp = (PEVT_VARIANT)realloc(pProperty, dwBufferSize);
                if (pTemp)
                {
                    pProperty = pTemp;
                    pTemp = NULL;
                    EvtGetPublisherMetadataProperty(hMetadata, (EVT_PUBLISHER_METADATA_PROPERTY_ID)Id, 0, dwBufferSize, pProperty, &dwBufferUsed);
                }
                else
                {
                    wprintf(L"realloc failed\n");
                    status = ERROR_OUTOFMEMORY;
                    goto cleanup;
                }
            }

            if (ERROR_SUCCESS != (status = GetLastError()))
            {
                wprintf(L"EvtGetPublisherMetadataProperty failed with %d\n", GetLastError());
                goto cleanup;
            }
        }

        if (status = PrintProviderProperty(hMetadata, Id, pProperty))
            break;

        RtlZeroMemory(pProperty, dwBufferUsed);

        // Skip the type-specific IDs, so the loop doesn't fail. For channels, levels,
        // opcodes, tasks, and keywords, you use EvtGetPublisherMetadataProperty 
        // to get a handle to an array of those objects. You would then use the type
        // specific ID (for example, EvtPublisherMetadataLevelValue) to access the metadata from
        // the array. Do not call EvtGetPublisherMetadataProperty with a type specific ID or it 
        // will fail. The switch statement increments to the end of the type specific IDs for 
        // each type.
        switch (Id)
        {
            case EvtPublisherMetadataChannelReferences:
                Id = EvtPublisherMetadataChannelReferenceMessageID;
                break;

            case EvtPublisherMetadataLevels:
                Id = EvtPublisherMetadataLevelMessageID;
                break;

            case EvtPublisherMetadataOpcodes:
                Id = EvtPublisherMetadataOpcodeMessageID;
                break;

            case EvtPublisherMetadataTasks:
                Id = EvtPublisherMetadataTaskMessageID;
                break;

            case EvtPublisherMetadataKeywords:
                Id = EvtPublisherMetadataKeywordMessageID;
                break;
        }
    }

cleanup:

    if (pProperty)
        free(pProperty);

    return status;
}


// Print the metadata properties for the provider and the types that
// it defines or references.
DWORD PrintProviderProperty(EVT_HANDLE hMetadata, int Id, PEVT_VARIANT pProperty)
{
    UNREFERENCED_PARAMETER(hMetadata);
    
    DWORD status = ERROR_SUCCESS;
    WCHAR wszProviderGuid[50];
    DWORD dwArraySize = 0;
    DWORD dwBlockSize = 0;
    LPWSTR pMessage = NULL;

    switch(Id)
    {
        case EvtPublisherMetadataPublisherGuid:
            StringFromGUID2(*(pProperty->GuidVal), wszProviderGuid, sizeof(wszProviderGuid)/sizeof(WCHAR));
            wprintf(L"Guid: %s\n", wszProviderGuid);
            break;

        case EvtPublisherMetadataResourceFilePath:
            wprintf(L"ResourceFilePath: %s\n", pProperty->StringVal);
            break;

        case EvtPublisherMetadataParameterFilePath:
            wprintf(L"ParameterFilePath: %s\n", (EvtVarTypeNull == pProperty->Type) ? L"" : pProperty->StringVal);
            break;

        case EvtPublisherMetadataMessageFilePath:
            wprintf(L"MessageFilePath: %s\n", pProperty->StringVal);
            break;

        case EvtPublisherMetadataHelpLink:
            wprintf(L"HelpLink: %s\n", pProperty->StringVal);
            break;

        // The message string ID is -1 if the provider element does not specify the message attribute.
        case EvtPublisherMetadataPublisherMessageID:
            if (-1 == pProperty->UInt32Val)
            {
                wprintf(L"Message string: \n");
            }
            else
            {
                pMessage = GetMessageString(g_hMetadata, pProperty->UInt32Val);
                wprintf(L"Publisher message string: %s\n", (pMessage) ? pMessage : L"");
                if (pMessage)
                {
                    free(pMessage);
                }
            }
            break;

        // We got the handle to all the channels defined in the channels section
        // of the manifest. Get the size of the array of channel objects and 
        // allocate the messages block that will contain the value and
        // message string for each channel. The strings are used to retrieve
        // display names for the channel referenced in an event definition.
        case EvtPublisherMetadataChannelReferences:
            wprintf(L"Channels:\n");
            if (EvtGetObjectArraySize(pProperty->EvtHandleVal, &dwArraySize))
            {
                // You always get a handle to the array but the array can be empty.
                if (dwArraySize > 0)
                {
                    dwBlockSize = sizeof(MSG_STRING) * dwArraySize;
                    g_ChannelMessages.pMessage = (PMSG_STRING)malloc(dwBlockSize);
                    if (g_ChannelMessages.pMessage)
                    {
                        RtlZeroMemory(g_ChannelMessages.pMessage, dwBlockSize);
                        g_ChannelMessages.dwSize = dwBlockSize;

                        // For each channel, print its metadata.
                        for (DWORD i = 0; i < dwArraySize; i++)
                        {
                            if (status = DumpChannelProperties(pProperty->EvtHandleVal, i, &g_ChannelMessages))
                                break;
                        }
                    }
                    else
                    {
                        status = ERROR_OUTOFMEMORY;
                        wprintf(L"g_pChannelMessages allocation error\n");
                    }
                }
            }
            else
            {
                status = GetLastError();
            }

            EvtClose(pProperty->EvtHandleVal);
            break;

        // These are handled by the EvtPublisherMetadataChannelReferences case;
        // they are here for completeness but will never be exercised.
        case EvtPublisherMetadataChannelReferencePath:
        case EvtPublisherMetadataChannelReferenceIndex:
        case EvtPublisherMetadataChannelReferenceID:
        case EvtPublisherMetadataChannelReferenceFlags:
        case EvtPublisherMetadataChannelReferenceMessageID:
            break;

        // We got the handle to all the levels defined in the channels section
        // of the manifest. Get the size of the array of level objects and 
        // allocate the messages block that will contain the value and
        // message string for each level. The strings are used to retrieve
        // display names for the level referenced in an event definition.
        // References to the levels defined in Winmeta.xml are included in 
        // the list.
        case EvtPublisherMetadataLevels:
            wprintf(L"Levels:\n");
            if (EvtGetObjectArraySize(pProperty->EvtHandleVal, &dwArraySize))
            {
                // You always get a handle to the array but the array can be empty.
                if (dwArraySize > 0)
                {
                    dwBlockSize = sizeof(MSG_STRING) * dwArraySize;
                    g_LevelMessages.pMessage = (PMSG_STRING)malloc(dwBlockSize);
                    if (g_LevelMessages.pMessage)
                    {
                        RtlZeroMemory(g_LevelMessages.pMessage, dwBlockSize);
                        g_LevelMessages.dwSize = dwBlockSize;

                        // For each level, print its metadata.
                        for (DWORD i = 0; i < dwArraySize; i++)
                        {
                            if (status = DumpLevelProperties(pProperty->EvtHandleVal, i, &g_LevelMessages))
                                break;
                        }
                    }
                    else
                    {
                        status = ERROR_OUTOFMEMORY;
                        wprintf(L"g_pLevelMessages allocation error\n");
                    }
                }
            }
            else
            {
                status = GetLastError();
            }

            EvtClose(pProperty->EvtHandleVal);
            break;

        // These are handled by the EvtPublisherMetadataLevels case;
        // they are here for completeness but will never be exercised.
        case EvtPublisherMetadataLevelName:
        case EvtPublisherMetadataLevelValue:
        case EvtPublisherMetadataLevelMessageID:
            break;

        // We got the handle to all the tasks defined in the channels section
        // of the manifest. Get the size of the array of task objects and 
        // allocate the messages block that will contain the value and
        // message string for each task. The strings are used to retrieve
        // display names for the task referenced in an event definition.
        case EvtPublisherMetadataTasks:
            wprintf(L"Tasks:\n");
            if (EvtGetObjectArraySize(pProperty->EvtHandleVal, &dwArraySize))
            {
                // You always get a handle to the array but the array can be empty.
                if (dwArraySize > 0)
                {
                    dwBlockSize = sizeof(MSG_STRING) * dwArraySize;
                    g_TaskMessages.pMessage = (PMSG_STRING)malloc(dwBlockSize);
                    if (g_TaskMessages.pMessage)
                    {
                        RtlZeroMemory(g_TaskMessages.pMessage, dwBlockSize);
                        g_TaskMessages.dwSize = dwBlockSize;

                        // For each task, print its metadata.
                        for (DWORD i = 0; i < dwArraySize; i++)
                        {
                            if (status = DumpTaskProperties(pProperty->EvtHandleVal, i, &g_TaskMessages))
                                break;
                        }
                    }
                    else
                    {
                        status = ERROR_OUTOFMEMORY;
                        wprintf(L"g_pTaskMessages allocation error\n");
                    }
                }
            }
            else
            {
                status = GetLastError();
            }

            EvtClose(pProperty->EvtHandleVal);
            break;

        // These are handled by the EvtPublisherMetadataTasks case;
        // they are here for completeness but will never be exercised.
        case EvtPublisherMetadataTaskName:
        case EvtPublisherMetadataTaskEventGuid:
        case EvtPublisherMetadataTaskValue:
        case EvtPublisherMetadataTaskMessageID:
            break;

        // We got the handle to all the opcodes defined in the channels section
        // of the manifest. Get the size of the array of opcode objects and 
        // allocate the messages block that will contain the value and
        // message string for each opcode. The strings are used to retrieve
        // display names for the opcode referenced in an event definition.
        case EvtPublisherMetadataOpcodes:
            wprintf(L"Opcodes:\n");
            if (EvtGetObjectArraySize(pProperty->EvtHandleVal, &dwArraySize))
            {
                // You always get a handle to the array but the array can be empty.
                if (dwArraySize > 0)
                {
                    dwBlockSize = sizeof(MSG_STRING) * dwArraySize;
                    g_OpcodeMessages.pMessage = (PMSG_STRING)malloc(dwBlockSize);
                    if (g_OpcodeMessages.pMessage)
                    {
                        RtlZeroMemory(g_OpcodeMessages.pMessage, dwBlockSize);
                        g_OpcodeMessages.dwSize = dwBlockSize;

                        // For each opcode, print its metadata.
                        for (DWORD i = 0; i < dwArraySize; i++)
                        {
                            if (status = DumpOpcodeProperties(pProperty->EvtHandleVal, i, &g_OpcodeMessages))
                                break;
                        }
                    }
                    else
                    {
                        status = ERROR_OUTOFMEMORY;
                        wprintf(L"g_pOpcodeMessages allocation error\n");
                    }
                }
            }
            else
            {
                status = GetLastError();
            }

            EvtClose(pProperty->EvtHandleVal);
            break;

        // These are handled by the EvtPublisherMetadataOpcodes case;
        // they are here for completeness but will never be exercised.
        case EvtPublisherMetadataOpcodeName:
        case EvtPublisherMetadataOpcodeValue:
        case EvtPublisherMetadataOpcodeMessageID:
            break;

        // We got the handle to all the keywords defined in the channels section
        // of the manifest. Get the size of the array of keyword objects and 
        // allocate the messages block that will contain the value and
        // message string for each keyword. The strings are used to retrieve
        // display names for the keyword referenced in an event definition.
        case EvtPublisherMetadataKeywords:
            wprintf(L"Keywords:\n");
            if (EvtGetObjectArraySize(pProperty->EvtHandleVal, &dwArraySize))
            {
                // You always get a handle to the array but the array can be empty.
                if (dwArraySize > 0)
                {
                    dwBlockSize = sizeof(MSG_STRING) * dwArraySize;
                    g_KeywordMessages.pMessage = (PMSG_STRING)malloc(dwBlockSize);
                    if (g_KeywordMessages.pMessage)
                    {
                        RtlZeroMemory(g_KeywordMessages.pMessage, dwBlockSize);
                        g_KeywordMessages.dwSize = dwBlockSize;

                        // For each keyword, print its metadata.
                        for (DWORD i = 0; i < dwArraySize; i++)
                        {
                            if (status = DumpKeywordProperties(pProperty->EvtHandleVal, i, &g_KeywordMessages))
                                break;
                        }
                    }
                    else
                    {
                        status = ERROR_OUTOFMEMORY;
                        wprintf(L"g_pKeywordMessages allocation error\n");
                    }
                }
            }
            else
            {
                status = GetLastError();
            }

            EvtClose(pProperty->EvtHandleVal);
            break;

        // These are handled by the EvtPublisherMetadataKeywords case;
        // they are here for completeness but will never be exercised.
        case EvtPublisherMetadataKeywordName:
        case EvtPublisherMetadataKeywordValue:
        case EvtPublisherMetadataKeywordMessageID:
            break;

        default:
            wprintf(L"Unknown property Id: %d\n", Id);
    }
    
    return status;
}

// Print the metadata for a channel. Capture the message string and value for use later.
DWORD DumpChannelProperties(EVT_HANDLE hChannels, DWORD dwIndex, PMESSAGES pMessages)
{
    LPWSTR pMessage = NULL;
    DWORD status = ERROR_SUCCESS;
    size_t dwStringLen = 0;
    PEVT_VARIANT pvBuffer = NULL;

    pvBuffer = GetProperty(hChannels, dwIndex, EvtPublisherMetadataChannelReferenceMessageID);
    if (pvBuffer)
    {
        // The value is -1 if the channel did not specify a message attribute.
        if (-1 != pvBuffer->UInt32Val)
        {
            pMessage = GetMessageString(g_hMetadata, pvBuffer->UInt32Val);
        }

        wprintf(L"\tChannel message string: %s\n", (pMessage) ? pMessage : L"");
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    // This is the channel name. You can use it to call the EvtOpenChannelConfig function
    // to get the channel's configuration information.
    pvBuffer = GetProperty(hChannels, dwIndex, EvtPublisherMetadataChannelReferencePath);
    if (pvBuffer)
    {
        wprintf(L"\tChannel path is %s\n", pvBuffer->StringVal);
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    // Capture the message string if the channel specified a message string; otherwise,
    // capture the channel's name.
    if (pMessage)
    {
        dwStringLen = wcslen(pMessage) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pMessage);
    }
    else
    {
        dwStringLen = wcslen(pvBuffer->StringVal) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pvBuffer->StringVal);
    }

    pvBuffer = GetProperty(hChannels, dwIndex, EvtPublisherMetadataChannelReferenceIndex);
    if (pvBuffer)
    {
        wprintf(L"\tChannel index is %lu\n", pvBuffer->UInt32Val);
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    // Capture the channel's value attribute, which is used to look up the channel's
    // message string.
    pvBuffer = GetProperty(hChannels, dwIndex, EvtPublisherMetadataChannelReferenceID);
    if (pvBuffer)
    {
        wprintf(L"\tChannel ID is %lu\n", pvBuffer->UInt32Val);
        ((pMessages->pMessage)+dwIndex)->dwValue = pvBuffer->UInt32Val;
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    pvBuffer = GetProperty(hChannels, dwIndex, EvtPublisherMetadataChannelReferenceFlags);
    if (pvBuffer)
    {
        if (EvtChannelReferenceImported == (EvtChannelReferenceImported & pvBuffer->UInt32Val))
            wprintf(L"\tChannel is imported\n");
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

cleanup:

    if (pvBuffer)
        free(pvBuffer);

    if (pMessage)
        free(pMessage);

    return status;
}


// Print the metadata for a level. Capture the message string and value for use later.
DWORD DumpLevelProperties(EVT_HANDLE hLevels, DWORD dwIndex, PMESSAGES pMessages)
{
    LPWSTR pMessage = NULL;
    DWORD status = ERROR_SUCCESS;
    size_t dwStringLen = 0;
    PEVT_VARIANT pvBuffer = NULL;

    pvBuffer = GetProperty(hLevels, dwIndex, EvtPublisherMetadataLevelMessageID);
    if (pvBuffer)
    {
        // The value is -1 if the level did not specify a message attribute.
        if (-1 != pvBuffer->UInt32Val)
        {
            pMessage = GetMessageString(g_hMetadata, pvBuffer->UInt32Val);
        }

        wprintf(L"\tLevel message string: %s\n", (pMessage) ? pMessage : L"");
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    pvBuffer = GetProperty(hLevels, dwIndex, EvtPublisherMetadataLevelName);
    if (pvBuffer)
    {
        wprintf(L"\tLevel name is %s\n", pvBuffer->StringVal);
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    // Capture the message string if the level specified a message string; otherwise,
    // capture the level's name.
    if (pMessage)
    {
        dwStringLen = wcslen(pMessage) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pMessage);
    }
    else
    {
        dwStringLen = wcslen(pvBuffer->StringVal) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pvBuffer->StringVal);
    }

    // Capture the level's value attribute, which is used to look up the level's
    // message string.
    pvBuffer = GetProperty(hLevels, dwIndex, EvtPublisherMetadataLevelValue);
    if (pvBuffer)
    {
        wprintf(L"\tLevel value is %lu\n", pvBuffer->UInt32Val);
        ((pMessages->pMessage)+dwIndex)->dwValue = pvBuffer->UInt32Val;
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

cleanup:

    if (pvBuffer)
        free(pvBuffer);

    if (pMessage)
        free(pMessage);

    return status;
}


// Print the metadata for a task. Capture the message string and value for use later.
DWORD DumpTaskProperties(EVT_HANDLE hTasks, DWORD dwIndex, PMESSAGES pMessages)
{
    LPWSTR pMessage = NULL;
    DWORD status = ERROR_SUCCESS;
    size_t dwStringLen = 0;
    PEVT_VARIANT pvBuffer = NULL;
    WCHAR wszEventGuid[50];

    pvBuffer = GetProperty(hTasks, dwIndex, EvtPublisherMetadataTaskMessageID);
    if (pvBuffer)
    {
        // The value is -1 if the task did not specify a message attribute.
        if (-1 != pvBuffer->UInt32Val)
        {
            pMessage = GetMessageString(g_hMetadata, pvBuffer->UInt32Val);
        }

        wprintf(L"\tTask message string: %s\n", (pMessage) ? pMessage : L"");
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    pvBuffer = GetProperty(hTasks, dwIndex, EvtPublisherMetadataTaskName);
    if (pvBuffer)
    {
        wprintf(L"\tTask name is %s\n", pvBuffer->StringVal);
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    // Capture the message string if the task specified a message string; otherwise,
    // capture the task's name.
    if (pMessage)
    {
        dwStringLen = wcslen(pMessage) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pMessage);
    }
    else
    {
        dwStringLen = wcslen(pvBuffer->StringVal) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pvBuffer->StringVal);
    }

    pvBuffer = GetProperty(hTasks, dwIndex, EvtPublisherMetadataTaskEventGuid);
    if (pvBuffer)
    {
        if (!IsEqualGUID(GUID_NULL, *(pvBuffer->GuidVal)))
        {
            StringFromGUID2(*(pvBuffer->GuidVal), wszEventGuid, sizeof(wszEventGuid)/sizeof(WCHAR));
            wprintf(L"\tTask event GUID is %s\n", wszEventGuid);
        }
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    // Capture the task's value attribute, which is used to look up the task's
    // message string.
    pvBuffer = GetProperty(hTasks, dwIndex, EvtPublisherMetadataTaskValue);
    if (pvBuffer)
    {
        wprintf(L"\tTask value is %lu\n", pvBuffer->UInt32Val);
        ((pMessages->pMessage)+dwIndex)->dwValue = pvBuffer->UInt32Val;
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

cleanup:

    if (pvBuffer)
        free(pvBuffer);

    if (pMessage)
        free(pMessage);

    return status;
}


// Print the metadata for a opcode. Capture the message string and value for use later.
DWORD DumpOpcodeProperties(EVT_HANDLE hOpcodes, DWORD dwIndex, PMESSAGES pMessages)
{
    LPWSTR pMessage = NULL;
    DWORD status = ERROR_SUCCESS;
    size_t dwStringLen = 0;
    PEVT_VARIANT pvBuffer = NULL;

    pvBuffer = GetProperty(hOpcodes, dwIndex, EvtPublisherMetadataOpcodeMessageID);
    if (pvBuffer)
    {
        // The value is -1 if the opcode did not specify a message attribute.
        if (-1 != pvBuffer->UInt32Val)
        {
            pMessage = GetMessageString(g_hMetadata, pvBuffer->UInt32Val);
        }

        wprintf(L"\tOpcode message string: %s\n", (pMessage) ? pMessage : L"");
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    pvBuffer = GetProperty(hOpcodes, dwIndex, EvtPublisherMetadataOpcodeName);
    if (pvBuffer)
    {
        wprintf(L"\tOpcode name is %s\n", pvBuffer->StringVal);
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    // Capture the message string if the opcode specified a message string; otherwise,
    // capture the opcode's name.
    if (pMessage)
    {
        dwStringLen = wcslen(pMessage) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pMessage);
    }
    else
    {
        dwStringLen = wcslen(pvBuffer->StringVal) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pvBuffer->StringVal);
    }

    // Capture the opcode's value attribute, which is used to look up the opcode's
    // message string.
    pvBuffer = GetProperty(hOpcodes, dwIndex, EvtPublisherMetadataOpcodeValue);
    if (pvBuffer)
    {
        wprintf(L"\tOpcode value is %hu (task: %hu)\n", HIWORD(pvBuffer->UInt32Val), LOWORD(pvBuffer->UInt32Val));
        ((pMessages->pMessage)+dwIndex)->dwValue = pvBuffer->UInt32Val;
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

cleanup:

    if (pvBuffer)
        free(pvBuffer);

    if (pMessage)
        free(pMessage);

    return status;
}


// Print the metadata for a keyword. Capture the message string and mask for use later.
DWORD DumpKeywordProperties(EVT_HANDLE hKeywords, DWORD dwIndex, PMESSAGES pMessages)
{
    LPWSTR pMessage = NULL;
    DWORD status = ERROR_SUCCESS;
    size_t dwStringLen = 0;
    PEVT_VARIANT pvBuffer = NULL;

    pvBuffer = GetProperty(hKeywords, dwIndex, EvtPublisherMetadataKeywordMessageID);
    if (pvBuffer)
    {
        // The value is -1 if the keyword did not specify a message attribute.
        if (-1 != pvBuffer->UInt32Val)
        {
            pMessage = GetMessageString(g_hMetadata, pvBuffer->UInt32Val);
        }

        wprintf(L"\tKeyword message string: %s\n", (pMessage) ? pMessage : L"");
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    pvBuffer = GetProperty(hKeywords, dwIndex, EvtPublisherMetadataKeywordName);
    if (pvBuffer)
    {
        wprintf(L"\tKeyword name is %s\n", pvBuffer->StringVal);
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

    // Capture the message string if the keyword specified a message string; otherwise,
    // capture the keyword's name.
    if (pMessage)
    {
        dwStringLen = wcslen(pMessage) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pMessage);
    }
    else
    {
        dwStringLen = wcslen(pvBuffer->StringVal) + 1;
        ((pMessages->pMessage)+dwIndex)->pwcsMessage = (LPWSTR)malloc(dwStringLen * sizeof(WCHAR));
        wcscpy_s(((pMessages->pMessage)+dwIndex)->pwcsMessage, dwStringLen, pvBuffer->StringVal);
    }

    // Capture the keyword's mask attribute, which is used to look up the keyword's
    // message string.
    pvBuffer = GetProperty(hKeywords, dwIndex, EvtPublisherMetadataKeywordValue);
    if (pvBuffer)
    {
        wprintf(L"\tKeyword value is %I64u\n", pvBuffer->UInt64Val);
        ((pMessages->pMessage)+dwIndex)->ullMask = pvBuffer->UInt32Val;
    }
    else
    {
        status = GetLastError();
        goto cleanup;
    }

cleanup:

    if (pvBuffer)
        free(pvBuffer);

    if (pMessage)
        free(pMessage);

    return status;
}


// Get the metadata property for an object in the array.
PEVT_VARIANT GetProperty(EVT_HANDLE handle, DWORD dwIndex, EVT_PUBLISHER_METADATA_PROPERTY_ID PropertyId)
{
    DWORD status = ERROR_SUCCESS;
    PEVT_VARIANT pvBuffer = NULL;
    DWORD dwBufferSize = 0;
    DWORD dwBufferUsed = 0;

    if (!EvtGetObjectArrayProperty(handle, PropertyId, dwIndex, 0, dwBufferSize, pvBuffer, &dwBufferUsed))
    {
        status = GetLastError();
        if (ERROR_INSUFFICIENT_BUFFER == status)
        {
            dwBufferSize = dwBufferUsed;
            pvBuffer = (PEVT_VARIANT)malloc(dwBufferSize);
            if (pvBuffer)
            {
                EvtGetObjectArrayProperty(handle, PropertyId, dwIndex, 0, dwBufferSize, pvBuffer, &dwBufferUsed);
            }
            else
            {
                wprintf(L"malloc failed\n");
                status = ERROR_OUTOFMEMORY;
                goto cleanup;
            }
        }

        if (ERROR_SUCCESS != (status = GetLastError()))
        {
            wprintf(L"EvtGetObjectArrayProperty failed with %d\n", status);
        }
    }

cleanup:

    return pvBuffer;
}


// Get the formatted message string.
LPWSTR GetMessageString(EVT_HANDLE hMetadata, DWORD dwMessageId)
{
    LPWSTR pBuffer = NULL;
    DWORD dwBufferSize = 0;
    DWORD dwBufferUsed = 0;
    DWORD status = 0;

    if (!EvtFormatMessage(hMetadata, NULL, dwMessageId, 0, NULL, EvtFormatMessageId, dwBufferSize, pBuffer, &dwBufferUsed))
    {
        status = GetLastError();
        if (ERROR_INSUFFICIENT_BUFFER == status)
        {
            dwBufferSize = dwBufferUsed;
            pBuffer = (LPWSTR)malloc(dwBufferSize * sizeof(WCHAR));

            if (pBuffer)
            {
                EvtFormatMessage(hMetadata, NULL, dwMessageId, 0, NULL, EvtFormatMessageId, dwBufferSize, pBuffer, &dwBufferUsed);
            }
            else
            {
                wprintf(L"malloc failed\n");
            }
        }
        else
        {
            wprintf(L"EvtFormatMessage failed with %u\n", status);
        }
    }

    return pBuffer;
}