2

I have a app that uses win32evtlog to get and display different events and I would like to limit the display to events of a specific level but win32evtlog doesn't return this. It seems that you can convert an event to XML and then pull this info but I can't work out how you get the event from a loop to XML.

I can get up to the following and use it to display data the LogObject has such as LogObject.TimeGenerated

Log = win32evtlog.OpenEventLog('localhost', 'Application')
while 1:
    LogObjects = winev32tlog.ReadEventLog(Log, win32evtlog.EVENTLOG_BACKWARDS_READ|wine32vtlog.EVENTLOG_SEQUENTIAL_READ, 0)
    if not LogObjects:
        break
    for LogObject in LogObjects:

I tried the convert using

LogObjectXML = win32evtlog.EvtRender(LogObject, 1)

This unfortunately returns

TypeError: The object is not a PyHANDLE object

So I know I need to get some sort of handle object that I can use to point the EvtRender at the correct event but can't work out how I do that.

This question is quite similar to How retrieve from Python win32evtlog rest of info? but the solution there didn't answer the critical bit of how we convert the object to XML.

--== Edited with information about the XML for CristiFati ==--

Below is an example of an Application event where the event message reads:-

Updated Windows Defender status successfully to SECURITY_PRODUCT_STATE_ON.

The XML as per event viewer is as below

- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
  <Provider Name="SecurityCenter" /> 
  <EventID Qualifiers="0">15</EventID> 
  <Level>4</Level> 
  <Task>0</Task> 
  <Keywords>0x80000000000000</Keywords> 
  <TimeCreated SystemTime="2017-05-23T07:36:27.627108000Z" /> 
  <EventRecordID>49419</EventRecordID> 
  <Channel>Application</Channel> 
  <Computer>Name.domain.here</Computer> 
  <Security /> 
  </System>
- <EventData>
  <Data>Windows Defender</Data> 
  <Data>SECURITY_PRODUCT_STATE_ON</Data> 
  </EventData>
  </Event>

1 Answer 1

3

Listing [GitHub]: mhammond/pywin32 - Python for Windows (pywin32) Extensions which is a Python wrapper over WinAPIs. Documentation (WiP) can be found at [GitHub.MHammond]: Python for Win32 Extensions Help (or [ME.TimGolden]: Python for Win32 Extensions Help).

ReadEventLog returns PyEventLogRecords (wrapper over [MS.Learn]: EVENTLOGRECORD structure (winnt.h)), while EvtRender expects (you need to work with) PyHANDLEs (PyEVT_HANDLEs (wrapper over EVT_HANDLE ([MS.Learn]: Windows Event Log Data Types) to be more precise)).
So, for getting XML data, you need to use the functions family that works with this type: e.g. EvtQuery, EvtNext.

code00.py:

#!/usr/bin/env python

import sys

import pywintypes
import win32evtlog


INFINITE = 0xFFFFFFFF
EVTLOG_READ_BUF_LEN_MAX = 0x7FFFF

STANDARD_LOG_NAMES = ["Application", "System", "Security"]


def get_record_data(eventlog_record):
    ret = {}
    for key in dir(eventlog_record):
        if "A" < key[0] < "Z":  # @TODO - cfati: Weak
            ret[key] = getattr(eventlog_record, key)
    return ret


def get_eventlogs(source_name="Application", buf_size=EVTLOG_READ_BUF_LEN_MAX, backwards=True):
    ret = []
    evt_log = win32evtlog.OpenEventLog(None, source_name)
    read_flags = win32evtlog.EVENTLOG_SEQUENTIAL_READ
    if backwards:
        read_flags |= win32evtlog.EVENTLOG_BACKWARDS_READ
    else:
        read_flags |= win32evtlog.EVENTLOG_FORWARDS_READ
    offset = 0
    eventlog_records = win32evtlog.ReadEventLog(evt_log, read_flags, offset, buf_size)
    while eventlog_records:
        ret.extend(eventlog_records)
        offset += len(eventlog_records)
        eventlog_records = win32evtlog.ReadEventLog(evt_log, read_flags, offset, buf_size)
    win32evtlog.CloseEventLog(evt_log)
    return ret


def get_events_xmls(channel_name="Application", events_batch_num=100, backwards=True):
    ret = []
    flags = win32evtlog.EvtQueryChannelPath
    if backwards:
        flags |= win32evtlog.EvtQueryReverseDirection
    try:
        query_results = win32evtlog.EvtQuery(channel_name, flags, None, None)
    except pywintypes.error as e:
        print(e)
        return ret
    events = win32evtlog.EvtNext(query_results, events_batch_num, INFINITE, 0)
    while events:
        for event in events:
            ret.append(win32evtlog.EvtRender(event, win32evtlog.EvtRenderEventXml))
        events = win32evtlog.EvtNext(query_results, events_batch_num, INFINITE, 0)
    return ret


def main(*argv):
    log_names = STANDARD_LOG_NAMES[:]
    log_names.append("Windows Powershell")  # !!! @TODO - cfati: This works on my machine
    for log_name in log_names:
        print(log_name)
        logs = get_eventlogs(source_name=log_name)
        xmls = get_events_xmls(channel_name=log_name)
        #print("\n", get_record_data(logs[0]))
        #print(xmls[0])
        #print("\n", get_record_data(logs[-1]))
        #print(xmls[-1])
        print(len(logs))
        print(len(xmls))


if __name__ == "__main__":
    print(
        "Python {:s} {:03d}bit on {:s}\n".format(
            " ".join(elem.strip() for elem in sys.version.split("\n")),
            64 if sys.maxsize > 0x100000000 else 32,
            sys.platform,
        )
    )
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

Output:

[cfati@CFATI-W10PC064:e:\Work\Dev\StackExchange\StackOverflow\q043911616]> "c:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32

Application
12068
12068
System
10015
10015
Security
22821
22821
Windows Powershell
2038
2038

Done.

Notes:

  • The 2 lists should have the same length. The nth entry in each of them should reference the same event (as long as both functions are called with same value for backwards argument (read below))

  • get_events_xmls:

    • Returns a list of XML blobs associated to the events

    • The error handling is not the best, you could wrap all API calls in try / except clauses (I didn't run into errors, so I'm not sure what are the situations where exception could be raised)

    • You can play a little bit with [MS.Learn]: EvtNext function (winevt.h)'s arguments (Timeout and EventsSize for performance fine tuning (for me, ~20k events were processed in a matter of < 10 seconds - out of which text printing and conversions took the most))

    • In Python 3, the XMLs are bytes ([Python 3.Docs]: Built-in Types - class bytes([source[, encoding[, errors]]])) rather than "normal" strings (I had to encode them because some contain some non-ASCII chars, and attempting to print them would raise UnicodeEncodeError)

    • Event filtering is possible, check [MS.Learn]: EvtQuery function (winevt.h)'s args (Query and Flags)

    • Note the backwards argument which allows traversing the events in reversed (chronological) order (default set to True).

  • get_record_data:

    • It's just a convenience function, it converts a PyEventLogRecord object into a Python dictionary

    • The conversion is based on the fact that fields that we care about start with a capital letter (EventID, ComputerName, TimeGenerated, ...), that's why it shouldn't be used in production

    • It doesn't convert the actual values (TimeGenerated's value is pywintypes.datetime(2017, 3, 11, 3, 46, 47))

  • get_eventlogs:

  • Since I'm storing all the data in the 2 lists (instead of inplace data processing), I am choosing speed over memory consumption. For ~20K events, the 2 lists are taking ~30MB of RAM (which nowadays I think it's decent enough)

Might also be interesting to read:



Update #0

According to [MS.Learn]: OpenEventLogW function (winbase.h):

If you specify a custom log and it cannot be found, the event logging service opens the Application log; however, there will be no associated message or category string file.

[MS.Learn]: Eventlog Key lists the 3 standard ones. So, that's why it opens the Application log. I've done some small changes to the script to test the sources. I don't know where MMC gets the Setup events from.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for that it's really helpful! When I get the event I also need to be able to display the text that explains what the event is about and I couldn't see a way to get that from the win32evtlog.EvtQuery method. Is there any way to get these details with this method?
Isn't all the data included in the XML blob? Is there any piece that's missing? Of course the blob must be parsed in order to get the data out. Here's an example of parsing XML: [SO]: Print all xml child node using python
I couldn't fit it into a comment so edited the original with the info from XML and the description I pulled the data from event viewer as it was quicker to give the example than with python. As you can see the XML doesn't contain the "Friendly" description.
Did you have the chance to test?
Let me know how it turns out :)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.