PowerShell Get-EventLog로 이벤트 로그 효율적으로 조회하는 방법

모든 Windows 시스템 관리자는 아마도 Windows 이벤트 로그에 익숙할 것입니다. PowerShell에서 이 cmdlet을 사용하면 여러 컴퓨터에서 한꺼번에 많은 이벤트를 파싱할 수 있습니다. 이로 인해 이벤트 뷰어에서 적절한 필터를 찾거나 중요한 이벤트가 정확히 어디에 저장되어 있는지 찾으려고 마우스로 클릭하는 번거로움을 덜 수 있습니다. 그러나 Get-EventLog에는 문제점이 있습니다. 이를 확인하게 될 것입니다.

Get-EventLog를 사용하여 이벤트 로그 목록 나열

Get-EventLog cmdlet은 모든 최신 버전의 Windows PowerShell에서 사용할 수 있습니다. 가장 간단한 사용법은 이 cmdlet이 쿼리할 이벤트 로그를 필요로 하며 해당 이벤트 로그의 모든 이벤트를 표시합니다.

그러나 처음에 이벤트 로그 이름을 모르는 경우 어떻게 해야 할까요? 이 경우에는 로컬 컴퓨터에서 사용 가능한 모든 이벤트 로그를 찾아야 합니다. 이를 위해 Get-EventLog -List 명령을 사용합니다.

Get-EventLog -List

저는 로컬 시스템에 몇 개의 이벤트 로그가 있는 것을 볼 수 있지만, 다른 이벤트 로그는 어디에 있을까요? 이벤트 뷰어의 “응용 프로그램 및 서비스 로그”에는 수십 개의 다른 이벤트 로그가 표시됩니다. 왜 여기에 나타나지 않을까요?

그러한 이벤트가 필요한 경우, 불행히도 Get-EventLog는 작동하지 않습니다. 대신 Get-WinEvent를 확인해야 합니다. 현재 Get-EventLog cmdlet은 레거시 cmdlet으로 간주될 수 있지만, 사용하기가 너무 쉬워서 여전히 자주 사용합니다.

Get-EventLog를 사용하여 이벤트 쿼리하기

지금 우리는 사용 가능한 모든 이벤트 로그를 알게 되었으므로, 해당 이벤트 로그 내에서 이벤트를 읽을 수 있습니다. 어쩌면 나는 응용 프로그램 이벤트 로그의 모든 이벤트를 보고 싶을 수도 있습니다. 이벤트를 가져오기 위해 Get-EventLog와 함께 LogName 매개변수를 지정해주어야 하며, 이렇게하면 해당 이벤트 로그의 모든 이벤트가 반환됩니다.

Get-EventLog -LogName Application

기본적으로 출력에서는 다음과 같이 여섯 가지 속성만 볼 수 있습니다:

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

실제로 Get-EventLog는 그 중 16가지를 반환합니다. 여섯 가지만 보이는 이유는 PowerShell의 형식 지정 규칙 때문입니다. 아래는 Get-EventLogSelect-Object에 파이핑하여 모든 속성을 선택한 실제 출력 예시입니다.

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

Get-EventLog로 필터링

이벤트를 찾을 때 대부분의 경우, 모든 이벤트가 필요하지 않을 것입니다. 대신, 필요한 몇 가지 이벤트만 필터링해야 합니다. 이를 위해 Get-EventLog에는 몇 가지 다른 방법이 있습니다. Get-EventLog cmdlet은 타임스탬프, 엔트리 유형, 이벤트 ID, 메시지, 소스, 사용자 이름을 기반으로 필터링할 수 있습니다. 이렇게 하면 대부분의 이벤트를 찾는 방법이 해결됩니다.

필터링을 보여주기 위해, 아마도 일정한 간격으로 이벤트를 쿼리하고 가장 최근의 열 개 이벤트를 찾고 싶을 것입니다. 이 경우에는 Newest 매개변수를 사용하여 표시할 이벤트 수를 지정할 수 있습니다. Get-EventLog -LogName Application -Newest 10은 최근 열 개의 이벤트만 반환합니다.

어떤 특정 시점 이후의 모든 이벤트를 찾고 싶을 수도 있습니다. 이를 위해 After 매개변수를 사용할 수 있습니다. After 매개변수는 날짜/시간을 입력받으므로, 예를 들어 1/26/19 10:17 AM 이후에 발생한 Application 로그의 이벤트만 찾으려면 다음과 같이 할 수 있습니다. Get-EventLog -LogName Application -After '1/26/19 10:17'. 마찬가지로, 특정 날짜 이전에 발생한 이벤트를 선택하려면 Before 매개변수를 사용할 수 있습니다.

Get-EventLog에는 타임스탬프를 기반으로 한 필터링 외에도 다양한 필터링 방법이 있습니다. 우리는 이벤트 ID(인스턴스 ID)와 메시지와 같은 다른 속성을 기반으로 이벤트를 필터링할 수도 있습니다. 아마도 ID가 916인 이벤트를 찾고 싶다면 InstanceId 매개변수에 916을 전달합니다.

PS> Get-EventLog -LogName Application -InstanceId 916

필터를 결합할 수도 있습니다. 아마도 ID가 916인 이벤트가 많이 반환되지만 메시지에 svchost 문자열이 포함된 이벤트를 원한다면, Get-EventLogMessage 매개변수를 추가하고 svchost와 같은 와일드카드를 지정할 수 있습니다.

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

보너스 스크립트!

당신은 실제 스크립트에서 Get-EventLog를 사용하는 훌륭한 예제가 필요한가요? 그렇다면 다행이에요! 아래는 오늘 다운로드하고 사용할 수 있는 Get-EventLog의 고급 사용 사례입니다!

<#
.SYNOPSIS
    이 스크립트는 지정된 시작 및 종료 시간 사이의 모든 이벤트 로그 또는 텍스트 로그 항목을 검색합니다.
.DESCRIPTION
    이 스크립트는 지정된 시간대 내에 마지막으로 작성된 이벤트 로그 및 시간대 내에 날짜/시간 참조를 포함하는 텍스트 로그를 열거합니다. 이 정보를 파일 집합으로 기록하고 흥미로운 로그 파일을 추가 분석을 위해 복사합니다.
.EXAMPLE
    PS> .\Get-EventsFromTimeframe.ps1 -StartTimestamp '01-29-2014 13:25:00' -EndTimeStamp '01-29-2014 13:28:00' -ComputerName COMPUTERNAME
 
    이 예제는 StartTimestamp와 EndTimestamp 사이의 모든 이벤트 로그 항목과 시간대 내에 마지막으로 작성된 파일 또는 시간대 내에 날짜/시간 참조를 포함하는 확장자가 $FileExtension 매개변수 내에 있는 파일을 찾습니다.
.PARAMETER StartTimestamp
 이벤트를 검색하기 시작할 가장 이른 날짜/시간입니다.
.PARAMETER EndTimestamp
 이벤트를 검색하기 시작할 가장 최근 날짜/시간입니다.
.PARAMETER Computername
 검색할 원격(또는 로컬) 컴퓨터의 이름입니다.
.PARAMETER OutputFolderPath
 이벤트를 포함하는 텍스트 파일이 들어갈 폴더의 경로입니다.
.PARAMETER LogAuditFilPath
 로그 파일, 라인 번호 및 일치 유형을 기록할 텍스트 파일의 경로입니다.
.PARAMETER EventLogsOnly
 이벤트 로그에서만 검색하려면 이 스위치 매개변수를 사용하세요.
.PARAMETER LogFilesOnly
 파일 시스템에서 로그만 검색하려면 이 매개변수를 사용하세요.
.PARAMETER ExcludeDirectory
 파일 시스템에서 검색하지 않을 폴더 경로를 지정하세요.
.PARAMETER 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) {
 ## 최소한 하나의 이벤트를 포함하는 모든 이벤트 로그 이름 찾기
 $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 
 }
 ## 다양한 날짜/시간 형식을 검색하기 위해 정규식 문자열 생성
 ## 이는 각 텍스트 파일에서 날짜/시간 문자열을 검색하는 데 사용됩니다.
 ##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자를 초과하는 파일 경로가 있는 경우 작동하지 않습니다.
 ##TODO: 긴 경로 지원이 구현되면 오류 작업 제거
 $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/