Monitorando eventos do sistema com PowerShell e Event Log

1. Introdução ao Windows Event Log e PowerShell

O Windows Event Log é o sistema centralizado de registro de eventos do sistema operacional, organizado em logs principais como System (eventos do sistema e drivers), Security (logons, auditorias) e Application (eventos de aplicativos). Cada evento contém metadados críticos: ID único, nível (Erro, Aviso, Informação, Crítico), timestamp, origem e mensagem descritiva.

O PowerShell se destaca para consultar e monitorar esses logs por três razões fundamentais:
- Cmdlets nativos como Get-WinEvent e Get-EventLog oferecem acesso direto e rápido
- Filtros avançados permitem consultas complexas sem carregar todo o log na memória
- Automação possibilita criar scripts de monitoramento contínuo com notificações em tempo real

Enquanto Get-EventLog é mais simples e compatível com versões antigas, Get-WinEvent é o cmdlet moderno recomendado, com suporte a XPath e melhor desempenho em logs grandes.

2. Consultando logs com Get-WinEvent

A sintaxe básica do Get-WinEvent é direta:

# Listar todos os logs disponíveis
Get-WinEvent -ListLog *

# Consultar os 10 eventos mais recentes do log System
Get-WinEvent -LogName System -MaxEvents 10

# Filtrar por nível (2=Erro, 3=Aviso, 4=Informação)
Get-WinEvent -LogName System -MaxEvents 5 | Where-Object {$_.LevelDisplayName -eq "Error"}

Para consultas mais precisas, use filtros XPath:

# Eventos de erro com ID específico (41 = desligamento inesperado)
$xmlFilter = @"
<QueryList>
  <Query Id="0">
    <Select Path="System">
      *[System[(EventID=41) and (Level=2)]]
    </Select>
  </Query>
</QueryList>
"@
Get-WinEvent -FilterXml $xmlFilter -MaxEvents 10

Exemplos práticos adicionais:

# Últimos 50 eventos de logon (ID 4624) no Security
Get-WinEvent -LogName Security -MaxEvents 50 | Where-Object {$_.Id -eq 4624}

# Falhas de disco (ID 7, 11, 15) no System
$diskIDs = 7,11,15
Get-WinEvent -LogName System | Where-Object {$diskIDs -contains $_.Id -and $_.LevelDisplayName -eq "Error"}

3. Filtrando eventos por data, nível e origem

O PowerShell permite filtrar eventos por janelas de tempo com objetos [datetime]:

# Eventos das últimas 24 horas
$startDate = (Get-Date).AddHours(-24)
Get-WinEvent -LogName System | Where-Object {$_.TimeCreated -ge $startDate}

# Eventos entre datas específicas
$start = Get-Date "2025-01-01"
$end = Get-Date "2025-01-15"
Get-WinEvent -LogName Security -MaxEvents 100 | Where-Object {
    $_.TimeCreated -ge $start -and $_.TimeCreated -le $end
}

Filtragem por nível e origem:

# Eventos críticos (Level 1) de qualquer log
Get-WinEvent -FilterHashtable @{LogName='System','Application'; Level=1}

# Eventos de uma origem específica (ex: Service Control Manager)
Get-WinEvent -LogName System | Where-Object {$_.ProviderName -eq "Service Control Manager"}

# Combinando múltiplos critérios
Get-WinEvent -FilterHashtable @{
    LogName='Security'
    ID=4624,4625
    StartTime=(Get-Date).AddDays(-7)
}

4. Exportando e salvando resultados de eventos

Para análises posteriores ou compartilhamento, exporte os resultados:

# Exportar para CSV
Get-WinEvent -LogName System -MaxEvents 100 | 
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Export-Csv -Path "C:\Logs\eventos_sistema.csv" -NoTypeInformation

# Exportar para HTML com formatação
Get-WinEvent -LogName System -MaxEvents 50 |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    ConvertTo-Html -Title "Eventos do Sistema" |
    Out-File "C:\Logs\eventos.html"

# Exportar para XML (preserva todos os campos)
Get-WinEvent -LogName System -MaxEvents 10 | Export-Clixml -Path "C:\Logs\eventos.xml"

Para grandes volumes, use paginação e evite carregar tudo na memória:

# Processamento em lotes de 1000 eventos
$batchSize = 1000
$totalProcessed = 0
Get-WinEvent -LogName System -Oldest | ForEach-Object {
    $_ | Export-Csv -Path "C:\Logs\batch_$totalProcessed.csv" -Append
    $totalProcessed++
    if ($totalProcessed % $batchSize -eq 0) {
        Write-Host "Processados $totalProcessed eventos..."
    }
}

5. Monitoramento contínuo com loops e eventos em tempo real

Para monitoramento em tempo real, use Register-ObjectEvent com o System.Diagnostics.Eventing.Reader.EventLogWatcher:

# Monitorar novos eventos de erro no System
$logWatcher = New-Object System.Diagnostics.Eventing.Reader.EventLogWatcher(
    New-Object System.Diagnostics.Eventing.Reader.EventLogQuery("System", [System.Diagnostics.Eventing.Reader.PathType]::LogName, "*[System[(Level=2)]]")
)

Register-ObjectEvent -InputObject $logWatcher -EventName "EventRecordWritten" -Action {
    $event = $EventArgs.EventRecord
    Write-Host "[$(Get-Date)] NOVO EVENTO: ID=$($event.Id) - $($event.LevelDisplayName)"
    Write-Host "Mensagem: $($event.FormatDescription())"
    Write-Host "---"
}

$logWatcher.Enabled = $true
Write-Host "Monitoramento iniciado. Pressione Ctrl+C para parar."
Wait-Event

Alternativa com loop simples:

# Loop de verificação a cada 30 segundos
$lastCheck = (Get-Date).AddMinutes(-5)
while ($true) {
    $newEvents = Get-WinEvent -LogName System | 
        Where-Object {$_.TimeCreated -gt $lastCheck -and $_.LevelDisplayName -eq "Error"}

    foreach ($event in $newEvents) {
        Write-Host "[ALERTA] Evento ID $($event.Id) em $($event.TimeCreated)"
    }

    $lastCheck = Get-Date
    Start-Sleep -Seconds 30
}

6. Automatizando notificações e ações com base em eventos

Combinando monitoramento com ações automáticas:

# Script de notificação por e-mail para eventos críticos
$emailParams = @{
    From = "monitor@empresa.com"
    To = "admin@empresa.com"
    Subject = "[ALERTA CRÍTICO] Evento detectado"
    SmtpServer = "smtp.empresa.com"
}

# Monitorar e reagir a eventos específicos
$action = {
    $event = $EventArgs.EventRecord
    if ($event.Id -eq 41 -or $event.Id -eq 6008) {
        # Desligamento inesperado - reiniciar serviço crítico
        Restart-Service -Name "Spooler" -Force

        # Enviar alerta
        $body = "Evento crítico detectado:`nID: $($event.Id)`nData: $($event.TimeCreated)`nLog: $($event.LogName)"
        Send-MailMessage @emailParams -Body $body
    }
}

# Aplicar o watcher com a ação
$query = New-Object System.Diagnostics.Eventing.Reader.EventLogQuery("System", [System.Diagnostics.Eventing.Reader.PathType]::LogName, "*[System[(Level=1 or Level=2)]]")
$watcher = New-Object System.Diagnostics.Eventing.Reader.EventLogWatcher($query)
Register-ObjectEvent -InputObject $watcher -EventName "EventRecordWritten" -Action $action
$watcher.Enabled = $true

7. Boas práticas e considerações de segurança

Permissões necessárias:
- Para logs padrão (System, Application): geralmente acesso de leitura para todos os usuários
- Para Security log: privilégios de administrador ou SeSecurityPrivilege
- Execute scripts como SYSTEM ou conta de serviço dedicada

Cuidados com performance:
- Sempre use -MaxEvents ou filtros XPath para evitar carregar logs completos
- Em loops infinitos, inclua Start-Sleep para evitar 100% de CPU
- Monitore o consumo de memória com Get-Process -Id $pid

Estratégias de rotação:

# Verificar tamanho do log e forçar rotação se necessário
$log = Get-WinEvent -ListLog System
if ($log.FileSize -gt 100MB) {
    # Backup antes de limpar
    Get-WinEvent -LogName System -Oldest | Export-Csv "C:\Backup\system_$(Get-Date -Format yyyyMMdd).csv"
    # Limpar log (requer admin)
    Clear-EventLog -LogName System
}

8. Exemplo completo: script de monitoramento em produção

<#
.SYNOPSIS
    Monitor de eventos do sistema com notificação e ação automática
.DESCRIPTION
    Monitora logs do Windows e executa ações baseadas em regras configuráveis
.PARAMETER LogName
    Nome do log a monitorar (System, Security, Application)
.PARAMETER CriticalIDs
    Array de IDs de eventos considerados críticos
.PARAMETER AlertEmail
    Endereço de e-mail para notificações
#>

param(
    [string]$LogName = "System",
    [int[]]$CriticalIDs = @(41, 6008, 1001),
    [string]$AlertEmail = "admin@empresa.com"
)

function Send-EventAlert {
    param($Event)
    $body = @"
ALERTA DE EVENTO CRÍTICO
Log: $($Event.LogName)
ID: $($Event.Id)
Data: $($Event.TimeCreated)
Nível: $($Event.LevelDisplayName)
Mensagem: $($Event.FormatDescription())
"@

    Send-MailMessage -From "monitor@empresa.com" -To $AlertEmail `
        -Subject "[CRÍTICO] Evento ID $($Event.Id) em $($Event.TimeCreated)" `
        -Body $body -SmtpServer "smtp.empresa.com"
}

function Invoke-Remediation {
    param($Event)
    switch ($Event.Id) {
        41 { Restart-Service -Name "Spooler" -Force -ErrorAction SilentlyContinue }
        6008 { Write-EventLog -LogName Application -Source "Monitor" -EntryType Warning `
                -EventId 999 -Message "Sistema reiniciou inesperadamente" }
        default { Write-Host "Nenhuma ação definida para ID $($Event.Id)" }
    }
}

# Configuração do watcher
$queryXml = @"
<QueryList>
  <Query Id="0">
    <Select Path="$LogName">
      *[System[($(($CriticalIDs | ForEach-Object {"EventID=$_"}) -join " or "))]]
    </Select>
  </Query>
</QueryList>
"@

$watcher = New-Object System.Diagnostics.Eventing.Reader.EventLogWatcher(
    [System.Diagnostics.Eventing.Reader.EventLogQuery]::new($LogName, [System.Diagnostics.Eventing.Reader.PathType]::LogName, $queryXml)
)

$action = {
    $event = $EventArgs.EventRecord
    Write-Host "[$(Get-Date)] Evento crítico detectado: ID $($event.Id)"
    Send-EventAlert -Event $event
    Invoke-Remediation -Event $event
}

Register-ObjectEvent -InputObject $watcher -EventName "EventRecordWritten" -Action $action
$watcher.Enabled = $true

Write-Host "Monitor iniciado em $LogName para IDs: $($CriticalIDs -join ', ')"
Write-Host "Pressione Ctrl+C para parar."
Wait-Event

Para testar o script, gere eventos de teste:

# Simular evento crítico (requer admin)
Write-EventLog -LogName System -Source "EventLog" -EntryType Error -EventId 41 -Message "Teste de monitoramento"

Referências