walderich
walderich

Reputation: 626

Reading Task Scheduler events from Event Log

I want to list all executed runs of custom task of the Task Scheduler in C/C++. Therefore I access the Event Log and try to extract the TaskScheduler log entries, like so (removed all error handling for simplicity):

HANDLE hEv = OpenEventLogA(NULL, "Microsoft-Windows-TaskScheduler/Operational");
DWORD nrRead = 0x10000, status = ERROR_SUCCESS, nrMin = 0, nrDone;
PBYTE buf = (PBYTE) malloc(nrRead);
while (status == ERROR_SUCCESS) {
  if (!ReadEventLog(hEv, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 
    0, buf, nrRead, &nrDone, &nrMin)) status = GetLastError();

  for (PBYTE pRec = buf, pEnd = buf + nrRead; pRec < pEnd;) {
    (void) (pRec + sizeof(EVENTLOGRECORD));     // Store record
    pRec += ((PEVENTLOGRECORD) pRec)->Length;
    if (((PEVENTLOGRECORD) pRec)->Length == 0) break; // Avoid endless loop
  }
}

Actually I am able to read events from the log (e.g. the WiFi log). But I cannot open the TaskScheduler log. It then does as described in the documentation and falls back to the Application log.

I tried different strings for the log's name:

None of it seems to work. So how can I open the TaskScheduler log? Is the log name localized and needs to be adjusted according to the current Operating System language? Is there another way to retrieve the TaskScheduler executions?

Upvotes: 0

Views: 1037

Answers (2)

walderich
walderich

Reputation: 626

Thank you @zett42 for pointing me in the right direction and @Drake Wu for the detailed code example. But as I don't need any future event or asynchronous retrieval, I now implemented a simple synchronous function:

#define EVT_SIZE 10

int GetEvents(LPCWSTR query) {
    DWORD xmlLen = 0;
    LPCWSTR xml = NULL;
    EVT_HANDLE hQuery = EvtQuery(NULL, NULL, query, EvtQueryChannelPath | EvtQueryTolerateQueryErrors));

    while (true) {
        EVT_HANDLE hEv[EVT_SIZE];
        DWORD dwReturned = 0;
        if (!EvtNext(hQuery, EVT_SIZE, hEv, INFINITE, 0, &dwReturned)) return 0;

        // Loop over all events
        for (DWORD i = 0; i < dwReturned; i++) {
            DWORD nrRead = 0, nrProps = 0;

            if (!EvtRender(NULL, hEv[i], EvtRenderEventXml, xmlLen, xml, &nrRead, &nrProps)) {
                if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
                    xmlLen = nrRead;
                    xml = (LPWSTR) realloc(xml, xmlLen);
                    if (xml) {
                        EvtRender(NULL, hEv[i], EvtRenderEventXml, xmlLen, xml, &nrRead, &nrProps);
                    } else {
                        return -1;
                    }
                }
                if (GetLastError() != ERROR_SUCCESS) return -1;
            }

            // Store event data

            EvtClose(hEv[i]);
            hEv[i] = NULL;
        }
    }
    return 0;
}

Again I removed most error handling for simplifying the example. The Evt* functions indeed work for the retrieval of the TaskScheduler data (independently from the language).

Upvotes: 0

Drake Wu
Drake Wu

Reputation: 7170

I have tried your code, seem like OpenEventLog can only open some log that frequently-used(not sure). However, there is another way to list TaskScheduler event:

use EvtSubscribe() to add the callback function, When a record is queried, print it out in XML format. Here is the code example:

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

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

const int SIZE_DATA = 4096;
TCHAR XMLDataCurrent[SIZE_DATA];
TCHAR XMLDataUser[SIZE_DATA];

#define ARRAY_SIZE 10
#define TIMEOUT 1000  // 1 second; Set and use in place of INFINITE in EvtNext call

DWORD PrintEvent(EVT_HANDLE hEvent); // Shown in the Rendering Events topic
DWORD WINAPI SubscriptionCallback(EVT_SUBSCRIBE_NOTIFY_ACTION action, PVOID pContext, EVT_HANDLE hEvent);

void main(void)
{
    DWORD status = ERROR_SUCCESS;
    EVT_HANDLE hResults = NULL;

    //hResults = EvtQuery(NULL, pwsPath, pwsQuery, EvtQueryChannelPath );// EvtQueryReverseDirection);
    hResults = EvtSubscribe(NULL, NULL, L"Microsoft-Windows-TaskScheduler/Operational", NULL, NULL, NULL, (EVT_SUBSCRIBE_CALLBACK)SubscriptionCallback, EvtSubscribeStartAtOldestRecord);
    if (NULL == hResults)
    {
        status = GetLastError();
        if (ERROR_EVT_CHANNEL_NOT_FOUND == status)
            wprintf(L"The channel was not found.\n");
        else if (ERROR_EVT_INVALID_QUERY == status)
            // You can call the EvtGetExtendedStatus function to try to get 
            // additional information as to what is wrong with the query.
            wprintf(L"The query is not valid.\n");
        else
            wprintf(L"EvtQuery failed with %lu.\n", status);
    }
    Sleep(1000);

cleanup:

    if (hResults)
        EvtClose(hResults);

}
// The callback that receives the events that match the query criteria. 
DWORD WINAPI SubscriptionCallback(EVT_SUBSCRIBE_NOTIFY_ACTION action, PVOID pContext, EVT_HANDLE hEvent)
{
    UNREFERENCED_PARAMETER(pContext);

    DWORD status = ERROR_SUCCESS;

    switch (action)
    {
        // You should only get the EvtSubscribeActionError action if your subscription flags 
        // includes EvtSubscribeStrict and the channel contains missing event records.
    case EvtSubscribeActionError:
        if (ERROR_EVT_QUERY_RESULT_STALE == (DWORD)hEvent)
        {
            wprintf(L"The subscription callback was notified that event records are missing.\n");
            // Handle if this is an issue for your application.
        }
        else
        {
            wprintf(L"The subscription callback received the following Win32 error: %lu\n", (DWORD)hEvent);
        }
        break;

    case EvtSubscribeActionDeliver:
        if (ERROR_SUCCESS != (status = PrintEvent(hEvent)))
        {
            goto cleanup;
        }
        break;

    default:
        wprintf(L"SubscriptionCallback: Unknown action.\n");
    }

cleanup:

    if (ERROR_SUCCESS != status)
    {
        // End subscription - Use some kind of IPC mechanism to signal
        // your application to close the subscription handle.
    }

    return status; // The service ignores the returned status.
}


DWORD PrintEvent(EVT_HANDLE hEvent)
{
    DWORD status = ERROR_SUCCESS;
    DWORD dwBufferSize = 0;
    DWORD dwBufferUsed = 0;
    DWORD dwPropertyCount = 0;
    LPWSTR pRenderedContent = NULL;

    if (!EvtRender(NULL, hEvent, EvtRenderEventXml, dwBufferSize, pRenderedContent, &dwBufferUsed, &dwPropertyCount))
    {
        if (ERROR_INSUFFICIENT_BUFFER == (status = GetLastError()))
        {
            dwBufferSize = dwBufferUsed;
            pRenderedContent = (LPWSTR)malloc(dwBufferSize);
            if (pRenderedContent)
            {
                EvtRender(NULL, hEvent, EvtRenderEventXml, dwBufferSize, pRenderedContent, &dwBufferUsed, &dwPropertyCount);
            }
            else
            {
                wprintf(L"malloc failed\n");
                status = ERROR_OUTOFMEMORY;
                goto cleanup;
            }
        }

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

    ZeroMemory(XMLDataCurrent, SIZE_DATA);
    lstrcpyW(XMLDataCurrent, pRenderedContent);

    wprintf(L"EvtRender data %s\n", XMLDataCurrent);

cleanup:

    if (pRenderedContent)
        free(pRenderedContent);

    return status;
}

Hope it could help you!

Upvotes: 1

Related Questions