Журнал событий Windows напоминает поток, в котором каждую секунду проплывают сотни записей. Вход пользователя, запуск службы, ошибка драйвера, попытка доступа к файлу - всё это оседает в логах ровными строчками. По отдельности каждая запись почти ничего не говорит. Сила прячется в другом: в том, как события связаны между собой, в какое время они появляются, как часто повторяются и в каком порядке выстраиваются. Именно эти связи и называют закономерностями. Опытный администратор читает поток событий не как список фактов, а как историю, в которой у каждого происшествия есть причина и следствие.

Беда в том, что стандартный просмотрщик eventvwr.msc показывает события по одному и плохо приспособлен для поиска повторяющихся структур. Он удобен, когда нужно глянуть последнюю ошибку на одной машине. Но как только встаёт задача свести данные с десяти серверов или найти повторяющийся узор в миллионе записей, оснастка становится почти беспомощной. Стандартная оснастка не поддерживает регулярные выражения и поиск подстрок, а XPath в ней реализован не полностью. Поэтому серьёзная работа с потоком событий почти всегда уходит в командную строку и PowerShell.

Почему отдельная запись молчит, а последовательность записей говорит

Возьмём простой пример. Событие с кодом 4625 в журнале безопасности означает неудачный вход в учётную запись. Одно такое событие - это, скорее всего, опечатка в пароле, человек просто промахнулся мимо клавиши. Никакой тревоги. Но пятьдесят событий 4625 подряд за две минуты по одной и той же учётке с разными вариантами пароля - это уже не опечатка, а перебор. Узор сложился, и он рассказывает совсем другую историю.

Та же логика работает с техническими сбоями. Единичная ошибка службы с кодом 7034 может быть случайностью. А вот ошибка 7034, которая стабильно появляется через сорок секунд после каждой загрузки системы, указывает на конкретную службу, падающую при старте. Время появления превращает разрозненные ошибки в чёткую причинно-следственную цепочку.

Чтобы такие узоры стали видны, нужно научиться задавать потоку событий правильные вопросы. Вопрос звучит так: покажи мне не все события, а только те, что отвечают набору условий по коду, времени, уровню важности и содержимому. И вот тут на сцену выходят инструменты фильтрации.

Язык структурированных запросов XPath как основа точной выборки

Под капотом каждое событие Windows хранится в формате XML, и доступ к ним организован через специальный язык запросов. Структурированные XML-запросы основаны на синтаксисе XPath 1.0 и предназначены для выборки событий из журналов, эта возможность появилась ещё в Windows Vista и Server 2008. Понимание этого языка - ключ к поиску закономерностей, потому что именно он позволяет вытащить из потока только нужные узоры.

У XPath в журналах событий есть свои границы. Основное ограничение в том, что селектором события может быть выбран только XML-элемент, представляющий событие, а любой путь, который не выбирает событие, считается недопустимым. Все корректные пути начинаются со звёздочки или со слова Event и работают на узлах событий. Поддерживаются только дочерняя ось и ось атрибутов с сокращением через собачку. Это звучит сухо, но на практике укладывается в простые шаблоны.

Базовый фильтр обращается к системной части события. Звёздочка означает любое событие, блок System указывает, что условие проверяется в системной секции, а условие внутри задаёт конкретный код. Если нужны сразу успешные и неудачные входы, их объединяют через or, и тогда в одну выборку попадают оба типа событий, что позволяет сравнить, сколько входов завершилось успехом, а сколько провалом.

*[System[(EventID=4625)]]
*[System[(EventID=4624 or EventID=4625)]]

Сложнее становится, когда узор спрятан внутри описания события, а не в его системной части. Имя пользователя, адрес источника, тип входа - всё это лежит в секции EventData, и фильтр обращается к данным по имени атрибута. Запрос ниже вытащит только те события, где целевая учётная запись равна admin.

*[EventData[Data[@Name='TargetUserName']='admin']]

Комбинируя условия по System и по EventData, администратор отсекает шум и оставляет лишь те записи, которые складываются в искомую картину. Это и есть азбука поиска: сначала ограничить выборку кодом, потом сузить её по содержимому.

Командлет Get-WinEvent как рабочий инструмент аналитика журналов

PowerShell дал администраторам командлет, который читает и классические журналы, и современные каналы. Get-WinEvent читает классические логи вроде System, Application и Security, а также современные каналы Applications and Services Logs, включая журналы планировщика задач, групповых политик, службы печати и сотни других. Его главный козырь в производительности. Ключевое преимущество в том, что фильтрация идёт на стороне поставщика событий, то есть в память не грузится весь журнал, а Windows сразу отдаёт только нужные записи по каналу, уровню, идентификатору и времени.

Разница в скорости огромна. Когда фильтр уходит внутрь Windows, а не отрабатывает уже в PowerShell после выгрузки всех записей, обработка идёт на порядок быстрее. Get-WinEvent перебирает миллионы записей, не загружая их в память. Для поиска закономерностей это критично: узор часто проявляется только на больших объёмах, а просмотреть миллион записей вручную невозможно.

Самый быстрый путь - хеш-таблица в параметре FilterHashtable. Простейший вызов вытащит ошибки системного журнала за последний час, где уровень, равный двойке, означает "ошибка", а StartTime отсекает всё старше часа.

Get-WinEvent -FilterHashtable @{
    LogName   = 'System'
    Level     = 2
    StartTime = (Get-Date).AddHours(-1)
}

Высокопроизводительный путь - это именно FilterHashtable, а чтение журнала безопасности и некоторых операционных каналов требует прав администратора. Параметр StartTime со значением Get-Date минус сутки заставляет фильтр уйти в Windows, и скорость возрастает на порядок против выгрузки всего журнала с последующей фильтрацией.

Поиск узора по нескольким кодам сразу делается перечислением идентификаторов через запятую. Запрос ниже одним движением соберёт события жизненного цикла учётных записей.

Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    ID      = 4720, 4722, 4725, 4726
}

Коды здесь не случайны. Событие 4720 фиксирует создание учётной записи, 4722 - её включение, 4725 - отключение, 4726 - удаление. Если в выборке вдруг видно создание учётки, её мгновенное включение и удаление через десять минут, это законченный подозрительный узор, который при ручном просмотре никто бы не заметил.

Когда хеш-таблицы упираются в потолок и нужен XPath напрямую

У хеш-таблиц есть жёсткое ограничение по числу условий. При задаче сформировать запрос, в котором больше двадцати пяти условий, через FilterHashtable получить результат не выходит, что обнаруживается опытным путём. Когда нужно перекрыть широкий диапазон кодов или построить сложную логику, на помощь приходит параметр FilterXPath, в который передаётся та самая XPath-строка. Пример ниже ищет события конкретного пользователя в журнале Sysmon, а звёздочки в имени журнала избавляют от необходимости помнить полное название канала.

Get-WinEvent -LogName *Sysmon* -FilterXPath "*[EventData[Data[@Name='User']='LAB\vadim']]"

Для частых задач такой путь предпочтительнее, потому что условие отрабатывает на стороне Windows и не тормозит на больших журналах. XPath ныряет прямо в данные события, минуя медленную фильтрацию уже выгруженных записей.

Ещё мощнее формат FilterXml, где запрос описывается целым блоком QueryList. Этот формат поддерживает элемент Suppress, который исключает заранее известный шум из выборки. Представьте, что нужно найти все ошибки службы, кроме одной известной и безобидной. Тогда основной блок Select собирает все события нужного кода, а блок Suppress выкидывает ту единственную запись, которая мешает увидеть реальный узор.

<QueryList>
  <Query Id="0" Path="System">
    <Select Path="System">*[System[(Level=2)]]</Select>
    <Suppress Path="System">*[System[(EventID=10016)]]</Suppress>
  </Query>
</QueryList>

Так выборка очищается до состояния, в котором закономерность становится очевидной. Известный безобидный сбой больше не маячит в результатах и не отвлекает внимание от настоящей проблемы.

Связывание событий по времени и сведение журналов в единую картину

Самый плодотворный приём поиска закономерностей - это анализ по времени. Узор часто прячется именно в интервалах между событиями. Чтобы поймать активность в нерабочие часы, к выборке добавляют фильтр по часу появления записи: сначала командлет вытягивает все успешные входы за период, а затем Where-Object отсеивает по условию, что событие произошло ночью.

Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4624} |
    Where-Object { $_.TimeCreated.Hour -lt 6 -or $_.TimeCreated.Hour -gt 22 }

Тип входа добавляет ещё один срез. Код входа, равный десяти, означает удалённый вход по протоколу RDP. Связка кода события и кода типа входа вытаскивает именно сетевые подключения к рабочему столу. Если в логах видно, что глубокой ночью прошёл удалённый вход с незнакомого адреса, а сразу за ним посыпались события создания процессов с кодом 4688, складывается цельная и тревожная история, где у каждого звена есть конкретное время и конкретный смысл.

Чтобы вытащить из события скрытые поля вроде адреса источника или типа входа, обращаются к массиву свойств. Каждое событие хранит свои данные в коллекции Properties, и нужное значение достаётся по индексу. Для события 4624 тип входа лежит в свойстве с индексом восемь, имя пользователя - в пятом, а адрес источника - в восемнадцатом. Собрав эти поля в аккуратный объект, администратор превращает сырые события в читаемую таблицу.

Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4624} -MaxEvents 100 |
    ForEach-Object {
        [PSCustomObject]@{
            Time      = $_.TimeCreated
            User      = $_.Properties[5].Value
            LogonType = $_.Properties[8].Value
            Source    = $_.Properties[18].Value
        }
    }

После такой обработки поток событий превращается в таблицу с понятными колонками, по которой узор виден невооружённым глазом. Глаз цепляется за повтор адреса, за странное время, за нетипичный тип входа куда быстрее, чем при чтении сырого XML.

Настоящая закономерность редко умещается в одном журнале. Вход фиксируется в журнале безопасности, падение службы - в системном, ошибка приложения - в журнале приложений. Чтобы увидеть полную цепочку, события из разных источников сводят вместе и сортируют по времени. После такой сборки они выстраиваются в единую хронику, где видно, как сбой в одной подсистеме тянет за собой ошибки в другой.

$sec = Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4625}
$sys = Get-WinEvent -FilterHashtable @{LogName='System'; Level=2}
$sec + $sys | Sort-Object TimeCreated

При работе с парком машин подход масштабируется на десятки серверов. Get-WinEvent умеет читать журналы удалённых компьютеров, и один скрипт способен обойти весь парк, собрать события нужного типа за период и свести их в общий отчёт. В подавляющем большинстве случаев рекомендуется использовать именно Get-WinEvent, а не устаревший Get-EventLog, потому что он производительнее, особенно в сценариях обработки большого количества событий с удалённых компьютеров. Когда одинаковый узор всплывает сразу на нескольких машинах в одно и то же время, это почти наверняка не локальная случайность, а общая причина: обновление, групповая политика, сетевой сбой.

Отдельная ловушка ждёт того, кто гонится за объёмом без разбора. Журнал безопасности на активном сервере способен распухать невероятными темпами. Он нередко растёт на десять гигабайт в день из-за избыточного аудита категорий доступа к объектам или отслеживания процессов, и стоит проверить настройки аудита и отключить лишние подкатегории. Раздутый журнал не помогает искать закономерности, он их топит в шуме. Поэтому грамотная настройка аудита - такая же часть аналитики, как и сами фильтры.

Практическая последовательность действий при разборе потока событий

Поиск закономерности почти всегда идёт по одному маршруту, и его полезно держать в голове как готовый алгоритм действий:

  1. определить гипотезу, то есть сформулировать, какой именно узор ищется, например всплеск неудачных входов или повторяющееся падение службы;
  2. выбрать журналы, в которых этот узор мог бы отразиться, и не ограничиваться одним только журналом безопасности;
  3. построить узкий фильтр по кодам, уровню и времени через FilterHashtable, чтобы отсечь основную массу шума на стороне Windows;
  4. при нехватке условий или необходимости заглянуть в EventData перейти на FilterXPath с обращением к данным по имени атрибута;
  5. свести события из разных журналов в общий массив и отсортировать по времени, чтобы проявилась причинно-следственная цепочка;
  6. оформить результат в виде таблицы и проверить, складывается ли из строк осмысленная история.

Этот маршрут одинаково работает и при разовом разборе одного инцидента, и при настройке регулярного мониторинга. Разница лишь в том, что разовый разбор человек ведёт руками в консоли, а регулярный оформляется в скрипт, который сам пробегает по журналам, ищет знакомый узор и сообщает о находке.

Что отличает осмысленный поиск закономерностей от слепого перебора

Главная мысль, которую стоит унести, проста и почти философская. Журнал событий не хранит проблемы в готовом виде, он хранит следы. Проблема собирается из следов только в голове того, кто умеет связывать записи между собой. Код события - это всего лишь буква алфавита, а закономерность - это уже слово, иногда целое предложение.

Сильный аналитик журналов отличается от новичка не знанием кодов наизусть, а привычкой задавать потоку правильные вопросы. Новичок открывает eventvwr.msc и тонет в тысячах строк. Опытный человек заранее знает, какой узор ищет, строит точный фильтр, сводит источники и читает результат как связный текст. Инструменты при этом вторичны: XPath, FilterHashtable, FilterXPath и сведение журналов служат лишь способом задать вопрос покороче и поточнее.

Поток событий честен. Он фиксирует всё, что происходит в системе, без прикрас и умолчаний. Задача администратора - услышать в ровном гуле записей тот единственный мотив, который выбивается из общего ритма. Этот выбивающийся мотив и есть закономерность, ради которой стоит вглядываться в журнал. И чем раньше она замечена, тем дешевле обходится её разгадка.