Last active
August 21, 2018 20:37
-
-
Save kiwi-cam/1f59ca1f309c480d6c9608776f5649a8 to your computer and use it in GitHub Desktop.
This script uses LogParser 2.2 from Microsoft to to build a list of logon events that match some predefined searches. Supply a path to EventPath which contains the evtx files you'd like to scan. You'll want to grab Security.evtx from C:\Windows\System32\WinEvt\Logs on all servers to scan, or use this script to automate grabbing them: https://gis…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.Synopsis | |
Crawls evtx files and gathers events based on predefined searches. | |
.DESCRIPTION | |
This script uses LogParser 2.2 from Microsoft to to build a list of logon events. | |
Supply a path to EventPath which contains the evtx files you'd like to scan. You'll want to grab Security.evtx from | |
C:\Windows\System32\WinEvt\Logs on all servers to scan. | |
.PARAMETER LogParserLocation | |
The full path to logparser.exe | |
Default: "C:\Program Files (x86)\Log Parser 2.2\LogParser.exe" | |
.PARAMETER EventPath | |
Required - The path to a folder containing .evtx files you'd like to parse. | |
.PARAMETER Start | |
Results can be filtered to only include events after a supplied Date/Time. This is a string which Powershell must be able | |
to cast to a valid date and time. | |
.PARAMETER Search | |
Type of events to find. (OldNTLM - searches for LM and NTLMv1, PTH - searches for potential Pass-The-Hash, | |
FailedLogon - finds failed logon attempts) | |
.PARAMETER Detailed | |
If specified, event details are returned rather than a count. Only valid for OldNTLM and FailedLogon searches | |
.EXAMPLE | |
./Get-LogonEvents.ps1 E:\LogFiles\ | |
.EXAMPLE | |
./Get-LogonEvents.ps1 -EventPath E:\LogFiles\ -LogParserLocation "C:\My Applications\Log Parser 2.2\LogParser.exe" | |
.EXAMPLE | |
$Output = .\Get-LogonEvents.ps1 -EventPath E:\LogFiles -Start "1/8/2018 15:00" -Detailed | |
.EXAMPLE | |
.\Get-LogonEvents.ps1 -EventPath E:\LogFiles -Start "1/8/2018 15:00" -Search PTH | |
.EXAMPLE | |
.\Get-LogonEvents.ps1 -EventPath E:\LogFiles -Start "1/8/2018 15:00" -Search FailedLogon -Detailed | |
.NOTES | |
Version: 1.2 | |
Author: Cameron McConnochie | |
Creation Date: 6 Aug 2018 | |
Purpose/Change: Changed LogParser to output XML to improve script's parsing of the results. | |
Added predefined searches and renamed script to suit new functionality | |
Updated Returned object to DataTable to allow datatypes and sorting | |
Version: 1.1 | |
Author: Cameron McConnochie | |
Creation Date: 2 Aug 2018 | |
Purpose/Change: Add Date filtering to assist in diag | |
Added Detailed option to output indivdual events rather than the count | |
Added Progress bar | |
Version: 1.0 | |
Author: Cameron McConnochie | |
Creation Date: 1 Aug 2018 | |
Purpose/Change: Initial script development | |
#> | |
[CmdletBinding()] | |
Param( | |
[Parameter(Mandatory=$True,Position=1)] | |
[ValidateScript({ | |
if(-Not ($_ | Test-Path -PathType Container) ){ | |
throw "The Path argument must be a folder." | |
} | |
return $true | |
})] | |
[System.IO.FileInfo]$EventPath, | |
[Parameter(Mandatory=$False)] | |
[ValidateScript({ | |
if(-Not ($_ | Test-Path -PathType Leaf) ){ | |
throw "LogParser.exe not found! Please install it, or provide the LogParserLocation parameter." | |
} | |
return $true | |
})] | |
[System.IO.FileInfo]$LogParserLocation = "C:\Program Files (x86)\Log Parser 2.2\LogParser.exe", | |
[Parameter(Mandatory=$False)] | |
[ValidateScript({ | |
if(-Not (Get-Date($_))){ | |
throw "Start cannot be after the current date." | |
} | |
return $true | |
})] | |
[String]$Start, | |
[Parameter(Mandatory=$False)] | |
[ValidateSet("OldNTLM","PTH","FailedLogon")] | |
[String]$Search = "OldNTLM", | |
[Parameter(Mandatory=$False)] | |
[Switch]$Detailed | |
) | |
#Error checking | |
if(-not (Test-Path $LogParserLocation)){ | |
throw "LogParser.exe not found! Please install it, or provide the LogParserLocation parameter." | |
} | |
$EVTFiles = Get-ChildItem -Path $EventPath -Filter *.evtx | |
if($EVTFiles.Length -lt 1){ | |
throw "No .evtx files found in $($EventPath)! Please check the EventPath parameter." | |
} | |
IF($Detailed -and ($Search -match "PTH")){ | |
Write-Host "Detailed parameter ignored, not supported in this search." -ForegroundColor Magenta | |
} | |
#Create a Collection for the results | |
$LPRecords = New-Object system.Data.DataTable | |
#Build the date Query | |
IF ($Start -ne $null -and $Start -ne ""){ | |
$DateQuery = " AND TimeGenerated > '$(Get-Date $Start -Format 'yyyy-MM-dd HH:mm:ss')'" | |
}else{ | |
$DateQuery = "" | |
} | |
$count = 0 | |
#iterate through the files | |
Write-Progress -Activity "Querying events from LogFiles..." -PercentComplete 0 | |
foreach ($file in $EVTFiles){ | |
Write-Progress -Activity "Querying events from LogFiles..." -Status "Parsing file: $($file.FullName) ($($count+1) of $($EVTFiles.Length))..." -PercentComplete (($count/$EVTFiles.Length)*100) -CurrentOperation "Running Query..." | |
Write-Host "Parsing file: $($file.FullName)..." -NoNewline | |
#Build the Query for LogParser | |
If ($Search -match "OldNTLM"){ | |
If($Detailed){ | |
$Query = "`"SELECT TimeGenerated, EXTRACT_TOKEN(Strings, 5, '|') as Username, EXTRACT_TOKEN(Strings, 6, '|') as Domain, ` | |
EXTRACT_TOKEN(Strings, 8, '|') as LogonType, EXTRACT_TOKEN(strings, 9, '|') AS AuthPackage, ` | |
EXTRACT_TOKEN(Strings, 11, '|') AS Workstation, EXTRACT_TOKEN(Strings, 14, '|') AS LmPackageName, ` | |
EXTRACT_TOKEN(Strings, 17, '|') AS ProcessName, EXTRACT_TOKEN(Strings, 18, '|') AS SourceIP, ComputerName ` | |
FROM '$($file.FullName)' ` | |
WHERE EventID = 4624 AND Username NOT IN ('SYSTEM'; 'ANONYMOUS LOGON'; 'LOCAL SERVICE'; 'NETWORK SERVICE') ` | |
AND Domain NOT IN ('NT AUTHORITY') AND AuthPackage LIKE '%NtLmSsp%' AND Username NOT LIKE '%$' ` | |
AND LmPackageName NOT LIKE '%NTLM V2%' $($DateQuery)`"" | |
}else{ | |
$Query = "`"SELECT COUNT(*) AS Count, EXTRACT_TOKEN(Strings, 5, '|') as Username, EXTRACT_TOKEN(Strings, 6, '|') as Domain, ` | |
EXTRACT_TOKEN(Strings, 8, '|') as LogonType, EXTRACT_TOKEN(strings, 9, '|') AS AuthPackage, ` | |
EXTRACT_TOKEN(Strings, 11, '|') AS Workstation, EXTRACT_TOKEN(Strings, 14, '|') AS LmPackageName, ` | |
EXTRACT_TOKEN(Strings, 17, '|') AS ProcessName, EXTRACT_TOKEN(Strings, 18, '|') AS SourceIP, ComputerName ` | |
FROM '$($file.FullName)' ` | |
WHERE EventID = 4624 AND Username NOT IN ('SYSTEM'; 'ANONYMOUS LOGON'; 'LOCAL SERVICE'; 'NETWORK SERVICE') ` | |
AND Domain NOT IN ('NT AUTHORITY') AND AuthPackage LIKE '%NtLmSsp%' AND Username NOT LIKE '%$' ` | |
AND LmPackageName NOT LIKE '%NTLM V2%' $($DateQuery)` | |
GROUP BY Username, Domain, LogonType, AuthPackage, Workstation, LmPackageName, ProcessName, SourceIP, ComputerName`"" | |
} | |
}elseif ($Search -match "PTH") { | |
$Query = "`"SELECT TimeGenerated AS Date, EXTRACT_TOKEN(Strings, 5, '|') as Username, EXTRACT_TOKEN(Strings, 6, '|') as Domain, ` | |
EXTRACT_TOKEN(Strings, 8, '|') as LogonType, EXTRACT_TOKEN(strings, 10, '|') AS AuthPackage, ` | |
EXTRACT_TOKEN(Strings, 11, '|') AS Workstation, EXTRACT_TOKEN(Strings, 17, '|') AS ProcessName, ` | |
EXTRACT_TOKEN(Strings, 18, '|') AS SourceIP, ComputerName ` | |
FROM '$($file.FullName)' ` | |
WHERE EventID = 4624 AND Username NOT IN ('SYSTEM'; 'ANONYMOUS LOGON'; 'LOCAL SERVICE'; 'NETWORK SERVICE') ` | |
AND Domain NOT IN ('NT AUTHORITY') AND AuthPackage LIKE '%NtLmSsp%' AND Username NOT LIKE '%$' $($DateQuery)`"" | |
}elseIf ($Search -match "FailedLogon"){ | |
If($Detailed){ | |
$Query = "`"SELECT TimeGenerated AS Date, EXTRACT_TOKEN(Strings, 5, '|') as Username, EXTRACT_TOKEN(Strings, 6, '|') as Domain, ` | |
EXTRACT_TOKEN(Strings, 10, '|') as LogonType,EXTRACT_TOKEN(strings, 11, '|') AS AuthPackage, ` | |
EXTRACT_TOKEN(Strings, 13, '|') AS Workstation, EXTRACT_TOKEN(Strings, 19, '|') AS SourceIP, ComputerName ` | |
FROM '$($file.FullName)' ` | |
WHERE EventID = 4625 AND Username NOT IN ('SYSTEM'; 'ANONYMOUS LOGON'; 'LOCAL SERVICE'; 'NETWORK SERVICE') ` | |
AND Domain NOT IN ('NT AUTHORITY') AND Username NOT LIKE '%$' $($DateQuery)`"" | |
}else{ | |
$Query = "`"SELECT COUNT(*) AS Count, EXTRACT_TOKEN(Strings, 5, '|') as Username, EXTRACT_TOKEN(Strings, 6, '|') as Domain, ` | |
EXTRACT_TOKEN(Strings, 10, '|') as LogonType, EXTRACT_TOKEN(strings, 11, '|') AS AuthPackage, ` | |
EXTRACT_TOKEN(Strings, 13, '|') AS Workstation, EXTRACT_TOKEN(Strings, 19, '|') AS SourceIP, ComputerName ` | |
FROM '$($file.FullName)' ` | |
WHERE EventID = 4625 AND Username NOT IN ('SYSTEM'; 'ANONYMOUS LOGON'; 'LOCAL SERVICE'; 'NETWORK SERVICE') ` | |
AND Domain NOT IN ('NT AUTHORITY') AND Username NOT LIKE '%$' $($DateQuery) ` | |
GROUP BY Username, Domain, LogonType, AuthPackage, Workstation, SourceIP, ComputerName`"" | |
} | |
} | |
#Run LogParser with our Query | |
try { | |
[XML]$LPRecordSet = & "$($LogParserLocation)" -q:ON -stats:OFF -i:EVT -o:XML "$($Query)" | |
} catch { | |
Write-Warning "Could not run LogParser query on $($file.FullName)" | |
continue | |
} | |
Write-Progress -Activity "Querying events from LogFiles..." -Status "Parsing file: $($file.FullName) ($($count) of $($EVTFiles.Length))..." -PercentComplete (($count/$EVTFiles.Length)*95) -CurrentOperation "Gathering Events..." | |
if ($LPRecordSet.ROOT.ROW.Count -gt 0){ | |
Write-Host " ($($LPRecordSet.ROOT.ROW.Count) found)" -NoNewline -ForegroundColor Magenta | |
} | |
#All Records are stored as ROW attributes under ROOT | |
foreach ($row in $LPRecordSet.ROOT.ROW) { | |
#First run, now we have some results, build the columns. | |
If ($LPRecords.Columns.Count -lt 1){ | |
foreach ($Property in $row.ChildNodes){ | |
If($Property.Name -match "Count"){ | |
$column = New-Object System.Data.DataColumn $Property.Name,([int]) | |
}else{ | |
$column = New-Object System.Data.DataColumn $Property.Name,([string]) | |
} | |
$LPRecords.Columns.Add($Column) | |
} | |
} | |
#Iterate through the Attributes of the current ROW | |
$Temp = $LPRecords.NewRow() | |
foreach ($Property in $row.ChildNodes){ | |
$Temp[$Property.Name] = $Property.InnerText.Trim() | |
} | |
$LPRecords.Rows.Add($Temp) | |
} | |
Write-Host " done!" -ForegroundColor Green | |
$count++ | |
} | |
Write-Host "" -ForegroundColor Green | |
#Check if anything was returned | |
If ($LPRecords.Rows.Count -lt 1) { | |
Write-Host "No events found" -ForegroundColor Cyan | |
}else{ | |
Write-Host "$($LPRecords.Rows.Count) events found" -ForegroundColor Cyan | |
Write-Progress -Activity "Querying events from LogFiles..." -Status "Sorting Results..." -PercentComplete 95 | |
$LPRecords = $LPRecords | Sort-Object -Property $LPRecords.Columns[0].ColumnName | |
} | |
Write-Progress -Activity "Querying events from LogFiles..." -Status "Complete" -PercentComplete 100 | |
#Return the resulting output | |
Return $LPRecords |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment