Consulta los registros de eventos de forma eficiente con PowerShell Get-EventLog

Cada administrador de sistemas de Windows probablemente está familiarizado con el Registro de eventos de Windows. El uso de este cmdlet en PowerShell permite a los sysadmins analizar muchos eventos a la vez en muchos equipos a la vez. Libera a los sysadmins de hacer clic en el Visor de eventos tratando de averiguar el filtro adecuado para usar y determinar dónde se almacena precisamente ese evento crítico. Sin embargo, Get-EventLog tiene sus inconvenientes que verás.

Enumeración de registros de eventos con Get-EventLog

El cmdlet Get-EventLog está disponible en todas las versiones modernas de Windows PowerShell. En su uso más directo, este cmdlet necesita un registro de eventos para consultar, que luego mostrará todos los eventos en ese registro de eventos.

Pero, ¿qué pasa si no conoces el nombre del registro de eventos en primer lugar? En ese caso, necesitamos averiguar todos los registros de eventos que están disponibles en nuestra computadora local. Hacemos eso usando el comando Get-EventLog -List.

Get-EventLog -List

Puedes ver que tengo algunos registros de eventos en mi sistema local ahora, pero podrías preguntarte ¿dónde están los demás? Hay docenas de otros registros de eventos que aparecen bajo Aplicaciones y registros de servicios en el Visor de eventos. ¿Por qué no están aquí?

Si necesitas esos eventos, lamentablemente, Get-EventLog no va a funcionar. En su lugar, deberás consultar Get-WinEvent. El cmdlet Get-EventLog podría considerarse un cmdlet heredado en este punto, pero es uno que todavía uso con frecuencia simplemente porque es muy fácil de usar.

Consulta de eventos con Get-EventLog

Ahora que conocemos todos los registros de eventos disponibles, podemos leer los eventos dentro de ese registro de eventos. Tal vez quiera ver todos los eventos en el registro de eventos de la Aplicación. Para obtener esos eventos, necesito especificar el parámetro LogName con Get-EventLog y el cmdlet se encargará de devolver todos los eventos en ese registro de eventos.

Get-EventLog -LogName Application

De forma predeterminada, solo verás seis propiedades en la salida:

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

En realidad, Get-EventLog devuelve 16 de ellas. La razón por la que solo ves seis es debido a las reglas de formato de PowerShell que definen la salida. A continuación, se muestra un ejemplo de la salida real encontrada al pasar Get-EventLog a Select-Object y seleccionar todas las propiedades.

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

Filtrando con Get-EventLog

Lo más probable es que al buscar eventos, no necesitemos todos los eventos. En cambio, solo necesitamos algunos. En ese caso, necesitamos filtrar eventos específicos. Get-EventLog tiene algunas formas diferentes de hacer esto. El cmdlet Get-EventLog puede filtrar según la marca de tiempo, el tipo de entrada, el ID de evento, el mensaje, la fuente y el nombre de usuario. Esto se encarga de la mayoría de las formas de encontrar eventos.

Para demostrar el filtrado, tal vez estoy consultando eventos de vez en cuando y quiero encontrar los diez eventos más recientes. En ese caso, puedo usar el parámetro Newest y especificar cuántos eventos me gustaría ver. Get-EventLog -LogName Application -Newest 10 devolverá solo los diez eventos más recientes.

Tal vez quiera encontrar todos los eventos después de un momento específico. Para eso, tenemos el parámetro After. El parámetro After toma una fecha/hora, así que si quiero encontrar solo los eventos dentro del registro de la aplicación que ocurrieron después del 26/01/19 a las 10:17 AM, podría hacer esto Get-EventLog -LogName Application -After '26/01/19 10:17'. También podríamos realizar el mismo proceso pero seleccionar eventos que ocurrieron antes de una fecha específica con, quizás lo hayas adivinado, el parámetro Before.

El Get-EventLog tiene muchas formas diferentes de filtrar, sin incluir la marca de tiempo. También podemos filtrar eventos según otros atributos como el ID del evento (ID de instancia) y el mensaje, que tienden a ser atributos comunes para buscar. Tal vez sé que estoy buscando un evento con un ID de 916; pasaríamos 916 al parámetro InstanceId.

PS> Get-EventLog -LogName Application -InstanceId 916

También podemos combinar filtros. Tal vez obtenga muchos eventos devueltos con un ID de 916, pero quiero esos eventos con la cadena svchost en el mensaje. En ese caso, podemos agregar el parámetro Message a Get-EventLog y especificar un comodín como svchost.

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

¡Script de bonificación!

¿Necesitas un gran ejemplo de cómo usar Get-EventLog en un script del mundo real? ¡Si es así, estás de suerte! A continuación se muestra un caso de uso avanzado de Get-EventLog que puedes descargar y usar hoy mismo!

<#
.SINOPSIS
    Este script busca en una computadora con Windows todas las entradas de registros de eventos o registros de texto
    entre un tiempo de inicio y fin especificado.
.DESCRIPCIÓN
    Este script enumera todos los registros de eventos dentro de un período de tiempo especificado y todos los registros de texto
    que tienen un tiempo de última escritura dentro del período o contienen una línea que tiene una fecha/hora
    referencia dentro del período. Registra esta información en un conjunto de archivos y copia
    cualquier archivo de registro interesante para un análisis adicional.
.EJEMPLO
    PS> .\Get-EventsFromTimeframe.ps1 -StartTimestamp '01-29-2014 13:25:00' -EndTimeStamp '01-29-2014 13:28:00' -ComputerName NOMBREDELCOMPUTADOR

    Este ejemplo encuentra todas las entradas de registros de eventos entre StartTimestamp y EndTimestamp y cualquier archivo
    con una extensión dentro del parámetro $FileExtension que ya sea fue escrito por última vez dentro del período
    o contiene una línea con una referencia de fecha/hora dentro del período.
.PARÁMETRO StartTimestamp
    La fecha/hora más temprana desde la cual desea comenzar a buscar eventos.
.PARÁMETRO EndTimestamp
    La fecha/hora más reciente desde la cual desea comenzar a buscar eventos.
.PARÁMETRO Computername
    El nombre de la computadora remota (o local) en la que desea buscar.
.PARÁMETRO OutputFolderPath
    La ruta de la carpeta que contendrá los archivos de texto que contendrán los eventos.
.PARÁMETRO LogAuditFilPath
    La ruta al archivo de texto que documentará el archivo de registro, el número de línea y el tipo de coincidencia
    con los registros que coincidieron.
.PARÁMETRO EventLogsOnly
    Utilice este interruptor si solo desea buscar en los registros de eventos.
.PARÁMETRO LogFilesOnly
    Utilice este parámetro si solo desea buscar registros en el sistema de archivos.
.PARÁMETRO ExcludeDirectory
    Si está buscando en el sistema de archivos, especifique las rutas de las carpetas que desea omitir.
.PARÁMETRO FileExtension
    Especifique uno o más conjuntos de extensiones de archivos delimitados por comas que desea buscar en el sistema de archivos.
    Esto se establece por defecto en extensiones '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) {
 ## Crea el directorio local donde almacenar los archivos de registro que cumplieron con los criterios
 $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 {
 
    ## Ejecutar solo si el usuario desea encontrar entradas de registros de eventos
    if (!$LogFilesOnly.IsPresent) {
 ## Encuentra todos los nombres de registros de eventos que contienen al menos 1 evento
 $Logs = (Get-WinEvent -ListLog * -ComputerName $ComputerName | where { $_.RecordCount }).LogName
 $FilterTable = @{
 'StartTime' = $StartTimestamp
 'EndTime' = $EndTimestamp
 'LogName' = $Logs
 }
 
 ## Encuentra todos los eventos en todos los registros de eventos que están entre las marcas de tiempo de inicio y finalización
 $Events = Get-WinEvent -ComputerName $ComputerName -FilterHashtable $FilterTable -ea 'SilentlyContinue'
 Write-Verbose "Found $($Events.Count) total events"
 
 ## Convierte las propiedades a algo más amigable y añade cada evento al archivo de texto del registro de eventos
 $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
 }
 
 ## Ejecutar solo si el usuario desea encontrar archivos de registro
 if (!$EventLogsOnly.IsPresent) {
        ## Enumera todos los recursos compartidos de administrador remoto en la computadora remota. Hago esto en lugar de enumerar todas las unidades físicas porque
        ## la unidad puede estar allí y el recurso compartido puede no estar, lo que significa que no puedo llegar a la unidad de todos modos.
 $Shares = Get-WmiObject -ComputerName $ComputerName -Class Win32_Share | where { $_.Path -match '^\w{1}:\\$' }
 [System.Collections.ArrayList]$AccessibleShares = @()
 ## Asegúrate de poder acceder a todos los recursos compartidos de administrador remoto
 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
 }
 ## Agrega cualquier directorio especificado en $ExcludeDirectory para no buscar archivos de registro
 if ($ExcludeDirectory) {
 $AllFilesQueryParams.ExcludeDirectory = $ExcludeDirectory 
 }
 ## Crea la cadena de regex loca que uso para buscar una serie de formatos de fecha/hora diferentes.
 ## Esto se usa en un intento de buscar cadenas de fecha/hora en cada archivo de texto encontrado
 ##TODO: Agregar la capacidad de hacer coincidir con Ene, Feb, Mar, etc.
 $DateTimeRegex = "($($StartTimestamp.Month)[\\.\-/]?$($StartTimestamp.Day)[\\.\-/]?[\\.\-/]$($StartTimestamp.Year))|($($StartTimestamp.Year)[\\.\-/]?$($StartTimestamp.Month)[\\.\-/]?[\\.\-/]?$($StartTimestamp.Day))"
 ## Enumera todos los archivos que coinciden con los parámetros de consulta que tienen contenido
 Get-ChildItem @AllFilesQueryParams | where { $_.Length -ne 0 } | foreach {
 try {
 Write-Verbose "Processing file '$($_.Name)'"
 ## Registra el archivo si la última hora de escritura está dentro del período. Esto encuentra archivos de registro que pueden no registrar un 
 ## sello de fecha/hora pero que aún pueden estar involucrados en el evento que el usuario está tratando de encontrar.
 if (($_.LastWriteTime -ge $StartTimestamp) -and ($_.LastWriteTime -le $EndTimestamp)) {
 Write-Verbose "Last write time within timeframe for file '$($_.Name)'"
 Add-ToLog -FilePath $_.FullName -MatchType 'LastWriteTime'
 }
 ## Si el archivo encontrado coincide con el conjunto de extensiones que estoy tratando de encontrar y realmente es un archivo de texto plano.
 ## Utilizo Get-Content para asegurarme de que sea solo texto plano antes de analizarlo.
 if ($FileExtension -contains $_.Extension.Replace('.','') -and !((Get-Content $_.FullName -Encoding Byte -TotalCount 1024) -contains 0)) {
 ## Verifica el contenido del archivo de texto para referencias a fechas dentro del período
 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)'"
 ## Registra todas las líneas coincidentes en el archivo de auditoría.
 foreach ($Match in $LineMatches) {
 Add-ToLog -FilePath $_.FullName -LineNumber $Match.LineNumber -LineText $Match.Line -MatchType 'Contents'
 }
 ## Crea la misma ruta al archivo de registro en la computadora remota dentro del directorio de registro de salida y 
 ## copia el archivo de registro con un evento dentro del período a esa ruta.
 ## Esto no funcionará si se cumple una ruta de archivo superior a 255 caracteres.
 $Trim = $_.FullName.Replace("\\$Computername\", '')
 $Destination = "$OutputFolderPath\$Trim"
 if (!(Test-Path $Destination)) {
 ##TODO: Quitar la acción de error cuando se implemente el soporte de rutas largas
 mkdir $Destination -ErrorAction SilentlyContinue | Out-Null
 }
 Copy-Item -Path $_.FullName -Destination $Destination -ErrorAction SilentlyContinue -Recurse
 }
 }
 } catch {
 Write-Warning $_.Exception.Message 
 }
 }
 }
}

Resumen

El cmdlet Get-EventLog es un excelente comando para usar si alguna vez necesitas consultar rápidamente uno de los registros de eventos comunes. Es fácil de usar y proporciona cierta capacidad de filtrado básico. Sin embargo, si necesitas hacer alguna investigación profunda en los registros de eventos, es probable que el comando Get-WinEvent funcione mejor, pero es un poco más difícil de usar y a veces requiere conocer la sintaxis como XPath.

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