evtx

Read binary Windows event logs (.evtx, the files in C:\Windows\System32\winevt\Logs) straight from the file, with no Windows needed. Each event becomes the same kind of record the logs library makes for Windows exports: EventID, time, computer, channel, provider, the EventData fields by name (TargetUserName, IpAddress, CommandLine...), plus action/description/user/ip/success worked out the same way, so the log-triage rules and Sigma rules run unchanged. Parses the binary XML (chunks, templates, substitutions) itself. Times are read as UTC and shown in local time like logs does.

import evtx

Example

import evtx

# apps/data/sample.evtx is a small made-up Windows Security log.
let records = evtx.read("apps/data/sample.evtx")
say(f"{len(records)} events")

for r in records.slice(0, 5) {
  say(f"{r.when}  {r.EventID}  {r.action}  {r.user}")
}
say(evtx.summary(records))

Functions

read(path, options?)

returns list

Every event in an .evtx file, as a list of records.

Options

  • limit stop after this many events.
  • xml true adds the event's XML as "xml".
  • strict true fails on a broken record. Without it, a broken record is kept as a placeholder with "broken": true.
  • recover true also carves old or deleted records out of the chunks' slack space. They come back with "recovered": true; Windows itself no longer shows them.

Fields of each record

  • "time", "when"
  • "event_id", "action", "description"
  • "user", "ip", "success"
  • "computer", "channel", "provider", "level_name", "record_id" (Windows' EventRecordID), "record_number" (position in the file)
  • "task_name", "opcode_name" when known (Security task categories, standard opcodes).
  • "event_data" a map of the EventData fields. Every EventData field is also its own key.
  • "partial" true when parts of the event used binary XML this reader doesn't know.
  • "raw" the XML.

A .gz or one-file .zip of an .evtx file is unpacked as it is read.

Errors

  • more than 500,000 events (without a smaller limit): use evtx.each().
  • a .gz or .zip that unpacks to over 2 GiB, or over 100x its packed size.
Visualizehow evtx.read works

evtx.read("apps/data/sample.evtx", {"limit": 3}) gives [{"time": 1773302401, "when": "2026-03-12 08:00:01", "when_utc": "2026-03-12 08:00:01", "…

Drawing…

Real result: worked out by running this example with TB while the site was built.

parse(data, options?)

returns list

Like evtx.read() for EVTX bytes already in memory.

Visualizehow evtx.parse works

evtx.parse(data, {"limit": 2}) gives [{"time": 1773302401, "when": "2026-03-12 08:00:01", "when_utc": "2026-03-12 08:00:01", "…

evtx.parse: inputs flowing into the function and the resultdataEVTX bytes"ElfFile" …options?limit2evtx.parseresult · listevent_idactioncomputer4624loginWS-FINANCE-0…4672special_priv…WS-FINANCE-0…

Real result: worked out by running this example with TB while the site was built.

each(path, fn, options?)

returns number

Call fn(record) for every event, one at a time, streaming the file from disk a chunk at a time, so logs of any size use little memory. Takes the same options as read(). Return false from fn to stop early. Gives back how many events fn saw.

Visualizehow evtx.each works

evtx.each("apps/data/sample.evtx", r => ids.push(r.event_id)) gives 24

evtx.each: inputs flowing into the function and the resultpathapps/data/sample.evtxone event at a timeevent 1 → fn… event 24 → fnfnr => ids.push(r.event_id)options?limit, start...evtx.eachresult · how many events24

Real result: worked out by running this example with TB while the site was built.

summary(path_or_records)

returns map

An overview of an event log. Give it a path or the records from evtx.read().

Fields

  • "records", "first_when", "last_when"
  • "event_ids" a map of id -> count.
  • "actions", "channels", "computers", "providers"
  • "failures", "users", "ips"
  • "chunks", "version", "dirty", "checksums_ok"
Visualizehow evtx.summary works

evtx.summary("apps/data/sample.evtx") gives {"records": 24, "broken": 0, "first": 1773302401, "last": 1773302542.1, "first_when": "20…

evtx.summary: inputs flowing into the function and the resultpath_or_recordsapps/data/sample.evtxevtx.summaryresult · an overview: counts per event id and actionrecords24first_when"2026-03-12 08:00:01"last_when"2026-03-12 08:02:22"event_ids{4625: 12, 4688: 4, 4624: 2, 1: …actions{"failed_login": 12, "process_cr…

Real result: worked out by running this example with TB while the site was built.

info(path)

returns map

The file's own bookkeeping.

Fields

  • "version", "chunk_count", "next_record_id"
  • "dirty" true if the file wasn't closed cleanly.
  • "full", "checksum_ok", "records", "checksums_ok"
  • "chunks" a list of maps, one per chunk, with:
    • "index", "first_record_id", "last_record_id", "records"
    • "header_checksum_ok", "records_checksum_ok"
Visualizehow evtx.info works

evtx.info("apps/data/sample.evtx") gives {"first_chunk": 0, "last_chunk": 0, "next_record_id": 25, "version": "3.1", "chunk_count"…

evtx.info: inputs flowing into the function and the resultpathapps/data/sample.evtxfile headerElfFile ... chunks ... checksumsevtx.inforesult · mapversion"3.1"chunk_count1next_record_id25dirtyfalsefullfalsechecksums_oktrue

Real result: worked out by running this example with TB while the site was built.

is_evtx(path)

returns bool

True if the file starts with the EVTX signature (ElfFile).

Visualizehow evtx.is_evtx works

evtx.is_evtx("apps/data/sample.evtx") gives true

evtx.is_evtx: inputs flowing into the function and the resultpathapps/data/sample.evtxevtx.is_evtxresult · booltruepathapps/data/sample.pcapevtx.is_evtxresult · boolfalse

Real result: worked out by running this example with TB while the site was built.

to_json(record)

returns text

One record as a JSON line (the same shape Get-WinEvent | ConvertTo-Json gives), handy for feeding other tools.

Visualizehow evtx.to_json works

evtx.to_json(r) gives "{\"time\": 1773302401, \"when\": \"2026-03-12 08:00:01\", \"when_utc\": \"2026-03-12 08:…

evtx.to_json: inputs flowing into the function and the resultrecord{event_id: 4624, action: login, computer: WS-FINANCE-07 ...}evtx.to_jsonresult · one JSON line{"time": 1773302401, "when": "2026-03-1208:00:01", "when_utc": "2026-03-1208:00:01", "zone": "UTC", "format":"windows", "message": "An account wassuccessfully logged on", "event_id": 4624,"EventID": 4624, "action": "login","description": "An account was successfully logged on", "success": true, "user":… 75 more lines

Real result: worked out by running this example with TB while the site was built.

Values

NameValueWhat it is
evtx.levels{0: "LogAlways", 1: "Critical", 2: "Error", 3: "Warning", 4: "Information", 5...

Level numbers to names: 1 Critical, 2 Error, 3 Warning, 4 Information, 5 Verbose.

Visualizehow evtx.levels works

evtx.levels gives {0: "LogAlways", 1: "Critical", 2: "Error", 3: "Warning", 4: "Information", 5: …

The value evtx.levelsevtx.levels0"LogAlways"1"Critical"2"Error"3"Warning"4"Information"5"Verbose"what it isLevel numbers to names: 1 Critical, 2Error, 3 Warning, 4 Information, 5 Verbose.

No inputs: call it as it is.

Real value, read from TB while the site was built.