Curated events were re-read from the newest 25 entries per channel on every collect, and the server appended whatever arrived. One real event was therefore stored again on every cadence tick for as long as it stayed in that window, so event counts measured how long a box had been up rather than what had happened to it. The agent now keeps a per-channel watermark of the last Windows event record id it reported, persisted under ProgramData so a service restart does not replay history, and sends eventrecordid with each event. The server dedupes on (assetid, eventchannel, eventrecordid) behind a unique index, so a replay from an older agent cannot double-store either. Six inventory keys the agent had been sending all along - bios, disks, domain, memory, osinfo, processor - were parsed, logged as unpersisted, and dropped. They now have columns and are written on check-in, along with user context, pending reboot, and the cpupercent/memorypercent/ idleseconds fields that were declared in the contract but always null. Session-scoped data cannot be read from the agent's LocalSystem session 0 at all: mapped drives and user-owned listeners do not exist there. A probe runs in the interactive user's session and hands results back through a spool file. It is not yet producing output on a live endpoint; the column and panel are in place and read empty until that is fixed. Also bounds the user-context script wait. It was WaitForSingleObject with INFINITE, so a hung script pinned the command worker permanently.
54 lines
2.4 KiB
Python
54 lines
2.4 KiB
Python
"""Curated Windows event catalog: id -> human title and severity.
|
|
|
|
Single server-side source of truth shared by the recent-events asset panel and
|
|
the stability dashboard card, so a 4625 reads as "Failed logon (warning)" in
|
|
both places without either restating the mapping. The set of IDs here MUST stay
|
|
in step with CURATED_EVENT_IDS in plugin.py; a test asserts they match.
|
|
|
|
Severity vocabulary matches the dashboard card renderer: 'critical', 'warning',
|
|
'info'. The IDs marked critical are the ones an operator/tech should act on
|
|
without being asked - unexpected shutdown, crash, filesystem corruption, SCM
|
|
timeout. Logon activity is informational history unless it is a failure.
|
|
"""
|
|
|
|
# id -> (title, severity, channel)
|
|
EVENT_CATALOG = {
|
|
4624: ('Successful logon', 'info', 'Security'),
|
|
4625: ('Failed logon', 'warning', 'Security'),
|
|
4634: ('Logoff', 'info', 'Security'),
|
|
4672: ('Special privileges assigned', 'info', 'Security'),
|
|
4697: ('Service installed', 'warning', 'Security'),
|
|
4698: ('Scheduled task created', 'warning', 'Security'),
|
|
41: ('Unexpected shutdown', 'critical', 'System'),
|
|
4618: ('Monitored security event', 'warning', 'Security'),
|
|
55: ('File system corruption', 'critical', 'System'),
|
|
7011: ('Service timeout', 'critical', 'System'),
|
|
10016: ('DCOM permission error', 'warning', 'System'),
|
|
# Windows Error Reporting trio on the Application channel. 1000 is the
|
|
# faulting-application record (app name, version, faulting module, exception
|
|
# code) and is the one that says what actually broke; 1001 is the WER bucket
|
|
# record Windows writes alongside it; 1002 is a hang rather than a crash, so
|
|
# it is a warning - the app is unresponsive, not gone.
|
|
1000: ('Application error', 'critical', 'Application'),
|
|
1001: ('Application crash', 'critical', 'Application'),
|
|
1002: ('Application hang', 'warning', 'Application'),
|
|
}
|
|
|
|
# The IDs a tech should see surfaced fleet-wide on the dashboard stability card.
|
|
CRITICAL_EVENT_IDS = tuple(
|
|
eventid for eventid, (_t, severity, _c) in EVENT_CATALOG.items()
|
|
if severity == 'critical'
|
|
)
|
|
|
|
|
|
def event_title(eventid):
|
|
"""Human title for a curated event id, or a generic fallback."""
|
|
entry = EVENT_CATALOG.get(eventid)
|
|
return entry[0] if entry else 'Event {}'.format(eventid)
|
|
|
|
|
|
def event_severity(eventid):
|
|
"""Severity ('critical'|'warning'|'info') for a curated event id."""
|
|
entry = EVENT_CATALOG.get(eventid)
|
|
return entry[1] if entry else 'info'
|