Эффективный запрос журналов событий с помощью PowerShell Get-EventLog

Каждый администратор системы Windows, вероятно, знаком с журналом событий Windows. Использование этой командлеты в PowerShell позволяет системным администраторам анализировать множество событий одновременно на многих компьютерах. Это освобождает системных администраторов от необходимости кликать мышью в Просмотре событий, пытаясь определить правильный фильтр для использования и определить точное место хранения этого критического события. Однако у Get-EventLog есть свои недостатки, о которых вы увидите далее.

Вывод журналов событий с помощью Get-EventLog

Командлет Get-EventLog доступен во всех современных версиях Windows PowerShell. В его самом простом использовании этот командлет требует указания журнала событий для запроса, после чего он отобразит все события в этом журнале.

Но что, если вы сначала не знаете имени журнала событий? В этом случае нам нужно определить все журналы событий, доступные на нашем локальном компьютере. Мы делаем это с помощью команды Get-EventLog -List.

Get-EventLog -List

Теперь вы можете видеть, что у меня есть несколько журналов событий на моей локальной системе, но вас, возможно, интересует, где остальные? Десятки других журналов событий появляются в разделе Журналы приложений и служб в Просмотре событий. Почему они здесь не отображаются?

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

Запрос событий с помощью Get-EventLog

Теперь, когда мы знаем все доступные журналы событий, мы можем прочитать события внутри этого журнала событий. Может быть, я хочу увидеть все события в журнале событий “Приложения”. Чтобы получить эти события, мне нужно указать параметр LogName с помощью Get-EventLog, и командлет вернет все события в этом журнале событий.

Get-EventLog -LogName Application

По умолчанию вы увидите только шесть свойств в выводе:

  • Index
  • Time
  • EntryType
  • Source
  • InstanceId
  • Message

На самом деле Get-EventLog возвращает 16 из них. Причина, по которой вы видите только шесть, заключается в правилах форматирования PowerShell, которые определяют вывод. Ниже приведен пример фактического вывода, найденного путем перенаправления Get-EventLog к Select-Object и выбора всех свойств.

Get-EventLog -LogName Application | Select-Object -Property * EventID

Фильтрация с помощью Get-EventLog

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

Чтобы продемонстрировать фильтрацию, я могу запросить события через определенные промежутки времени и найти десять самых новых событий. Для этого я могу использовать параметр Newest и указать, сколько событий я хочу увидеть. Команда Get-EventLog -LogName Application -Newest 10 вернет только десять последних событий.

Возможно, мне нужно найти все события, произошедшие после определенного момента времени. Для этого есть параметр After. Параметр After принимает дату и время. Если я хочу найти только события в журнале Application, которые произошли после 26/01/19 10:17, я могу использовать команду Get-EventLog -LogName Application -After '26/01/19 10:17'. Таким же образом мы можем выбрать события, которые произошли до определенной даты, с помощью параметра Before.

У команды Get-EventLog есть много разных способов фильтрации, не включая фильтрацию по временной метке. Мы также можем фильтровать события по другим атрибутам, таким как идентификатор события (Instance ID) и сообщение, которые обычно являются общими атрибутами для поиска. Возможно, я знаю, что ищу событие с ID 916; мы передаем 916 параметру InstanceId.

PS> Get-EventLog -LogName Application -InstanceId 916

Мы также можем комбинировать фильтры. Возможно, мне возвращается много событий с ID 916, но я хочу отфильтровать только те, которые содержат строку svchost в сообщении. В этом случае мы можем добавить параметр Message к команде Get-EventLog и указать подстановочный знак, например, svchost.

PS> Get-EventLog -LogName Application -InstanceId 916 -Message '*svchost*'

Бонусный скрипт!

Вам нужен отличный пример использования Get-EventLog в скрипте реального мира? Если да, то вы везунчик! Ниже приведен расширенный вариант использования Get-EventLog, который вы можете скачать и использовать уже сегодня!

.СИНОПСИС
    Этот сценарий ищет в компьютере под управлением Windows все записи журналов событий или текстовых журналов
    между указанным временем начала и временем окончания.
.ОПИСАНИЕ
    Этот сценарий перечисляет все журналы событий в определенном временном интервале и все текстовые журналы
    с последним временем записи в заданных временных рамках или содержащие строку с датой/временем
    в указанном временном интервале. Он записывает эту информацию в набор файлов и копирует
    любые интересные файлы журналов для дальнейшего анализа.
.ПРИМЕР
    PS> .\Get-EventsFromTimeframe.ps1 -StartTimestamp '01-29-2014 13:25:00' -EndTimeStamp '01-29-2014 13:28:00' -ComputerName COMPUTERNAME
 
    В этом примере находятся все записи журналов событий между StartTimestamp и EndTimestamp и любой файл
    с расширением, указанным в параметре $FileExtension, который либо был изменен в указанный временной интервал,
    либо содержит строку с датой/временем в указанных временных рамках.
.ПАРАМЕТР StartTimestamp
    Ранняя дата/время, с которой вы хотели бы начать поиск событий.
.ПАРАМЕТР EndTimestamp
    Поздняя дата/время, с которой вы хотели бы начать поиск событий.
.ПАРАМЕТР Computername
    Имя удаленного (или локального) компьютера, на котором вы хотели бы выполнить поиск.
.ПАРАМЕТР OutputFolderPath
    Путь к папке, в которой будут содержаться текстовые файлы, содержащие события.
.ПАРАМЕТР LogAuditFilPath
    Путь к текстовому файлу, который будет документировать файл журнала, номер строки и тип совпадения
    для журналов, которые были найдены.
.ПАРАМЕТР EventLogsOnly
    Используйте этот параметр-переключатель, если вы хотите выполнить поиск только в журналах событий.
.ПАРАМЕТР LogFilesOnly
    Используйте этот параметр, если вы хотите выполнить поиск только файлов в файловой системе.
.ПАРАМЕТР ExcludeDirectory
    Если вы выполняете поиск в файловой системе, укажите любые пути к папкам, которые вы хотели бы пропустить.
.ПАРАМЕТР FileExtension
    Укажите одно или несколько наборов расширений файлов, разделенных запятыми, которые вы хотели бы найти
    в файловой системе. По умолчанию используются расширения 'log,txt,wer'.
#>
[CmdletBinding(DefaultParameterSetName = 'Neither')]
param (
    [Parameter(Mandatory)]
    [datetime]$StartTimestamp,
    [Parameter(Mandatory)]
    [datetime]$EndTimestamp,
    [Parameter(ValueFromPipeline,
        ValueFromPipelineByPropertyName)]
    [string]$ComputerName = 'localhost',
    [Parameter()]
 [string]$OutputFolderPath = ".\$Computername",
 [Parameter(ParameterSetName = 'LogFiles')]
 [string]$LogAuditFilePath = "$OutputFolderPath\LogActivity.csv",
 [Parameter(ParameterSetName = 'EventLogs')]
 [switch]$EventLogsOnly,
    [Parameter(ParameterSetName = 'LogFiles')]
    [switch]$LogFilesOnly,
    [Parameter(ParameterSetName = 'LogFiles')]
 [string[]]$ExcludeDirectory,
 [Parameter(ParameterSetName = 'LogFiles')]
 [string[]]$FileExtension = @('log', 'txt', 'wer')
)
 
begin {
 if (!$EventLogsOnly.IsPresent) {
 ## Создание локального каталога, в котором будут храниться все совпавшие файлы журналов
 $LogsFolderPath = "$OutputFolderPath\logs"
 if (!(Test-Path $LogsFolderPath)) {
 mkdir $LogsFolderPath | Out-Null
 }
 }
 
 function Add-ToLog($FilePath,$LineText,$LineNumber,$MatchType) {
 $Audit = @{
 'FilePath' = $FilePath;
 'LineText' = $LineText
 'LineNumber' = $LineNumber
 'MatchType' = $MatchType
 }
 [pscustomobject]$Audit | Export-Csv -Path $LogAuditFilePath -Append -NoTypeInformation
 }
}
 
process {
 
    ## Выполнить только если пользователь хочет найти записи журналов событий
    if (!$LogFilesOnly.IsPresent) {
 ## Найти все имена журналов событий, содержащие хотя бы 1 событие
 $Logs = (Get-WinEvent -ListLog * -ComputerName $ComputerName | where { $_.RecordCount }).LogName
 $FilterTable = @{
 'StartTime' = $StartTimestamp
 'EndTime' = $EndTimestamp
 'LogName' = $Logs
 }
 
 ## Найти все события во всех журналах событий, находящихся между начальным и конечным моментами времени
 $Events = Get-WinEvent -ComputerName $ComputerName -FilterHashtable $FilterTable -ea 'SilentlyContinue'
 Write-Verbose "Found $($Events.Count) total events"
 
 ## Преобразование свойств в более понятный вид и добавление каждого события в текстовый файл журнала событий
 $LogProps = @{ }
 [System.Collections.ArrayList]$MyEvents = @()
 foreach ($Event in $Events) {
 $LogProps.Time = $Event.TimeCreated
 $LogProps.Source = $Event.ProviderName
 $LogProps.EventId = $Event.Id
 if ($Event.Message) {
 $LogProps.Message = $Event.Message.Replace("<code>n", '|').Replace("</code>r", '|')
 }
 $LogProps.EventLog = $Event.LogName
 $MyEvents.Add([pscustomobject]$LogProps) | Out-Null
 }
 $MyEvents | sort Time | Export-Csv -Path "$OutputFolderPath\eventlogs.txt" -Append -NoTypeInformation
 }
 
 ## Выполнить только если пользователь хочет найти файлы журналов
 if (!$EventLogsOnly.IsPresent) {
        ## Перечислить все удаленные административные общие ресурсы на удаленном компьютере. Я делаю это вместо перечисления всех физических накопителей, потому что
        ## диск может быть доступен, а общий ресурс - нет, что означает, что я все равно не смогу получить доступ к диску.
 $Shares = Get-WmiObject -ComputerName $ComputerName -Class Win32_Share | where { $_.Path -match '^\w{1}:\\$' }
 [System.Collections.ArrayList]$AccessibleShares = @()
 ## Убедиться, что я могу получить доступ ко всем удаленным административным общим ресурсам
 foreach ($Share in $Shares) {
 $Share = "\\$ComputerName\$($Share.Name)"
 if (!(Test-Path $Share)) {
 Write-Warning "Unable to access the '$Share' share on '$Computername'"
 } else {
 $AccessibleShares.Add($Share) | Out-Null 
 }
 }
 
 $AllFilesQueryParams = @{
 Path = $AccessibleShares
 Recurse = $true
 Force = $true
 ErrorAction = 'SilentlyContinue'
 File = $true
 }
 ## Добавить любые указанные каталоги в параметре $ExcludeDirectory для непоиска файлов журналов в них
 if ($ExcludeDirectory) {
 $AllFilesQueryParams.ExcludeDirectory = $ExcludeDirectory 
 }
 ## Создание сложной строки regex, которую я использую для поиска различных форматов даты/времени.
 ## Это используется в попытке найти строки даты/времени в каждом найденном текстовом файле
 ##TODO: Добавить возможность совпадения с Jan,Feb,Mar,и т. д.
 $DateTimeRegex = "($($StartTimestamp.Month)[\\.\-/]?$($StartTimestamp.Day)[\\.\-/]?[\\.\-/]$($StartTimestamp.Year))|($($StartTimestamp.Year)[\\.\-/]?$($StartTimestamp.Month)[\\.\-/]?[\\.\-/]?$($StartTimestamp.Day))"
 ## Перечислить все файлы, соответствующие параметрам запроса, которые имеют содержимое
 Get-ChildItem @AllFilesQueryParams | where { $_.Length -ne 0 } | foreach {
 try {
 Write-Verbose "Processing file '$($_.Name)'"
 ## Записать файл, если последнее время записи находится в заданных временных рамках. Это находит файлы журналов, которые могут не записывать
 ## временную метку даты/времени, но все равно могут быть связаны с событием, которое пытается найти пользователь.
 if (($_.LastWriteTime -ge $StartTimestamp) -and ($_.LastWriteTime -le $EndTimestamp)) {
 Write-Verbose "Last write time within timeframe for file '$($_.Name)'"
 Add-ToLog -FilePath $_.FullName -MatchType 'LastWriteTime'
 }
 ## Если найденный файл соответствует набору расширений, которые я пытаюсь найти, и это действительно файл обычного текста.
 ## Я использую Get-Content, чтобы просто дважды проверить, что это обычный текст перед его анализом.
 if ($FileExtension -contains $_.Extension.Replace('.','') -and !((Get-Content $_.FullName -Encoding Byte -TotalCount 1024) -contains 0)) {
 ## Проверить содержимое текстового файла на ссылки на даты в указанных временных рамках
 Write-Verbose "Checking log file '$($_.Name)' for date/time match in contents"
 $LineMatches = Select-String -Path $_.FullName -Pattern $DateTimeRegex
 if ($LineMatches) {
 Write-Verbose "Date/time match found in file '$($_.FullName)'"
 ## Записать все совпадающие строки в аудит-файл.
 foreach ($Match in $LineMatches) {
 Add-ToLog -FilePath $_.FullName -LineNumber $Match.LineNumber -LineText $Match.Line -MatchType 'Contents'
 }
 ## Создание такого же пути к файлу журнала на удаленном компьютере внутри выходного каталога журнала и 
 ## копирование файла журнала с событием в указанных временных рамках в этот путь.
 ## Это не сработает, если встречается путь к файлу длиной более 255 символов.
 $Trim = $_.FullName.Replace("\\$Computername\", '')
 $Destination = "$OutputFolderPath\$Trim"
 if (!(Test-Path $Destination)) {
 ##TODO: Удалить параметр действия ошибки, когда будет реализована поддержка длинных путей
 mkdir $Destination -ErrorAction SilentlyContinue | Out-Null
 }
 Copy-Item -Path $_.FullName -Destination $Destination -ErrorAction SilentlyContinue -Recurse
 }
 }
 } catch {
 Write-Warning $_.Exception.Message 
 }
 }
 }
}

Краткое описание

Командлет Get-EventLog – отличная команда для быстрого запроса общих журналов событий. Ее легко использовать, и она предоставляет базовые возможности фильтрации. Однако, если вам нужно провести более глубокое исследование журналов событий, вероятно, лучше подойдет команда Get-WinEvent, но она немного сложнее в использовании и иногда требует знания синтаксиса, такого как XPath.

Source:
https://adamtheautomator.com/get-eventlog/