Add Binoculars event filters and all-user mode
This commit is contained in:
+321
-20
@@ -23,6 +23,25 @@ $script:EventCatalog = @{
|
||||
4771 = @{ Name = 'Kerberos Pre-Authentication Failure'; Category = 'Kerberos'; DefaultOutcome = 'Failure' }
|
||||
4776 = @{ Name = 'NTLM Credential Validation'; Category = 'NTLM'; DefaultOutcome = 'Success' }
|
||||
}
|
||||
$script:EventFilterGroups = [ordered]@{
|
||||
'Successful logon (4624)' = @(4624)
|
||||
'Failed logon (4625)' = @(4625)
|
||||
'Lockout (4740)' = @(4740)
|
||||
'Unlock (4767)' = @(4767)
|
||||
'Logoff (4634, 4647)' = @(4634, 4647)
|
||||
'Kerberos tickets (4768, 4769, 4770)' = @(4768, 4769, 4770)
|
||||
'Kerberos pre-auth failure (4771)' = @(4771)
|
||||
'NTLM validation (4776)' = @(4776)
|
||||
}
|
||||
$script:EventProfiles = [ordered]@{
|
||||
'All Authentication' = @($script:RelevantEventIds)
|
||||
'Lockouts and Failures' = @(4625, 4740, 4767, 4771, 4776)
|
||||
'Successful Sign-Ins' = @(4624, 4768, 4769, 4770)
|
||||
'Session Trail' = @(4624, 4625, 4634, 4647, 4740, 4767)
|
||||
'Kerberos Focus' = @(4768, 4769, 4770, 4771)
|
||||
'NTLM Focus' = @(4776)
|
||||
'Custom' = @()
|
||||
}
|
||||
$script:LogonTypeCatalog = @{
|
||||
2 = 'Interactive'
|
||||
3 = 'Network'
|
||||
@@ -114,6 +133,87 @@ function ConvertTo-BinocularsSafeFileName {
|
||||
return $safeValue
|
||||
}
|
||||
|
||||
function Get-BinocularsEventIdSetKey {
|
||||
param(
|
||||
[AllowEmptyCollection()]
|
||||
[int[]]$EventIds
|
||||
)
|
||||
|
||||
return ((@($EventIds | Sort-Object -Unique)) -join ',')
|
||||
}
|
||||
|
||||
function Set-BinocularsEventFilterCheckBoxes {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[System.Collections.IDictionary]$CheckBoxMap,
|
||||
[AllowEmptyCollection()]
|
||||
[int[]]$EventIds
|
||||
)
|
||||
|
||||
$selectedIds = @($EventIds | Sort-Object -Unique)
|
||||
foreach ($entry in $CheckBoxMap.GetEnumerator()) {
|
||||
$checkBox = $entry.Value
|
||||
$groupIds = @($checkBox.Tag)
|
||||
$checkBox.Checked = (@($groupIds | Where-Object { $_ -in $selectedIds }).Count -eq $groupIds.Count)
|
||||
}
|
||||
}
|
||||
|
||||
function Get-BinocularsSelectedEventIds {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[System.Collections.IDictionary]$CheckBoxMap
|
||||
)
|
||||
|
||||
$selectedIds = New-Object System.Collections.Generic.List[int]
|
||||
foreach ($checkBox in $CheckBoxMap.Values) {
|
||||
if (-not $checkBox.Checked) {
|
||||
continue
|
||||
}
|
||||
|
||||
foreach ($eventId in @($checkBox.Tag)) {
|
||||
if (-not $selectedIds.Contains([int]$eventId)) {
|
||||
$selectedIds.Add([int]$eventId) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @($selectedIds | Sort-Object)
|
||||
}
|
||||
|
||||
function Get-BinocularsMatchingProfileName {
|
||||
param(
|
||||
[AllowEmptyCollection()]
|
||||
[int[]]$EventIds
|
||||
)
|
||||
|
||||
$targetKey = Get-BinocularsEventIdSetKey -EventIds $EventIds
|
||||
foreach ($profileName in $script:EventProfiles.Keys) {
|
||||
if ($profileName -eq 'Custom') {
|
||||
continue
|
||||
}
|
||||
|
||||
if ((Get-BinocularsEventIdSetKey -EventIds $script:EventProfiles[$profileName]) -eq $targetKey) {
|
||||
return $profileName
|
||||
}
|
||||
}
|
||||
|
||||
return 'Custom'
|
||||
}
|
||||
|
||||
function Get-BinocularsEventFilterSummary {
|
||||
param(
|
||||
[AllowEmptyCollection()]
|
||||
[int[]]$EventIds
|
||||
)
|
||||
|
||||
$profileName = Get-BinocularsMatchingProfileName -EventIds $EventIds
|
||||
if ($profileName -ne 'Custom') {
|
||||
return $profileName
|
||||
}
|
||||
|
||||
return 'Custom [{0}]' -f ((@($EventIds | Sort-Object -Unique)) -join ',')
|
||||
}
|
||||
|
||||
function Get-BinocularsArchiveRoot {
|
||||
$candidates = @()
|
||||
|
||||
@@ -381,11 +481,21 @@ function Resolve-BinocularsIdentity {
|
||||
)
|
||||
|
||||
$trimmedInput = $UserInput.Trim()
|
||||
$domain = Get-ADDomain -ErrorAction Stop
|
||||
if ([string]::IsNullOrWhiteSpace($trimmedInput)) {
|
||||
throw 'Enter a user account to search for.'
|
||||
return [pscustomobject]@{
|
||||
Input = ''
|
||||
SamAccountName = 'All Users'
|
||||
UserPrincipalName = ''
|
||||
Sid = ''
|
||||
DisplayName = 'All Users'
|
||||
DomainDnsName = $domain.DNSRoot
|
||||
DomainNetBIOSName = $domain.NetBIOSName
|
||||
SearchTerms = (New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase))
|
||||
SearchAllUsers = $true
|
||||
}
|
||||
}
|
||||
|
||||
$domain = Get-ADDomain -ErrorAction Stop
|
||||
$lookupValues = New-Object System.Collections.Generic.List[string]
|
||||
$lookupValues.Add($trimmedInput)
|
||||
|
||||
@@ -459,6 +569,7 @@ function Resolve-BinocularsIdentity {
|
||||
DomainDnsName = $domain.DNSRoot
|
||||
DomainNetBIOSName = $domain.NetBIOSName
|
||||
SearchTerms = $searchTerms
|
||||
SearchAllUsers = $false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,17 +660,32 @@ function Test-BinocularsPrincipalMatch {
|
||||
[Parameter(Mandatory)]
|
||||
[System.Collections.IDictionary]$DataMap,
|
||||
[Parameter(Mandatory)]
|
||||
[psobject]$Identity
|
||||
[psobject]$Identity,
|
||||
[string]$RawMessage
|
||||
)
|
||||
|
||||
if ($Identity.SearchAllUsers) {
|
||||
return $true
|
||||
}
|
||||
|
||||
$candidateValues = New-Object System.Collections.Generic.HashSet[string] ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
$priorityKeys = @(
|
||||
'TargetUserName',
|
||||
'TargetAccountName',
|
||||
'TargetOutboundUserName',
|
||||
'TargetSid',
|
||||
'TargetUserSid',
|
||||
'LogonAccount',
|
||||
'MappedAccountName',
|
||||
'SamAccountName',
|
||||
'MemberName',
|
||||
'MemberSid',
|
||||
'SubjectUserName',
|
||||
'SubjectAccountName',
|
||||
'AccountName',
|
||||
'UserName',
|
||||
'TargetSid',
|
||||
'SubjectUserSid'
|
||||
'SubjectUserSid',
|
||||
'UserSid'
|
||||
)
|
||||
|
||||
foreach ($priorityKey in $priorityKeys) {
|
||||
@@ -598,6 +724,55 @@ function Test-BinocularsPrincipalMatch {
|
||||
}
|
||||
}
|
||||
|
||||
$looseNeedles = @(
|
||||
$Identity.SamAccountName,
|
||||
$Identity.UserPrincipalName,
|
||||
$Identity.Sid,
|
||||
"$($Identity.DomainNetBIOSName)\$($Identity.SamAccountName)",
|
||||
"$($Identity.SamAccountName)@$($Identity.DomainDnsName)"
|
||||
) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
|
||||
|
||||
$combinedTextParts = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($value in $candidateValues) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($value)) {
|
||||
$combinedTextParts.Add($value) | Out-Null
|
||||
}
|
||||
}
|
||||
foreach ($value in $DataMap.Values) {
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$value)) {
|
||||
$combinedTextParts.Add(([string]$value).Trim().ToLowerInvariant()) | Out-Null
|
||||
}
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($RawMessage)) {
|
||||
$combinedTextParts.Add($RawMessage.Trim().ToLowerInvariant()) | Out-Null
|
||||
}
|
||||
|
||||
$combinedText = ($combinedTextParts | Select-Object -Unique) -join ' '
|
||||
foreach ($needle in $looseNeedles) {
|
||||
$normalizedNeedle = $needle.Trim().ToLowerInvariant()
|
||||
if ([string]::IsNullOrWhiteSpace($normalizedNeedle)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($combinedText -like "*$normalizedNeedle*") {
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($normalizedNeedle.Contains('\')) {
|
||||
$shortNeedle = $normalizedNeedle.Split('\')[-1]
|
||||
if ($combinedText -like "*$shortNeedle*") {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($normalizedNeedle.Contains('@')) {
|
||||
$samLikeNeedle = $normalizedNeedle.Split('@')[0]
|
||||
if ($combinedText -like "*$samLikeNeedle*") {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
@@ -777,7 +952,7 @@ function ConvertTo-BinocularsRecord {
|
||||
)
|
||||
|
||||
$eventData = Get-BinocularsEventDataMap -Xml $RawEvent.Xml
|
||||
if (-not (Test-BinocularsPrincipalMatch -DataMap $eventData -Identity $Identity)) {
|
||||
if (-not (Test-BinocularsPrincipalMatch -DataMap $eventData -Identity $Identity -RawMessage $RawEvent.Message)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
@@ -812,6 +987,7 @@ function ConvertTo-BinocularsRecord {
|
||||
RecordId = [long]$RawEvent.RecordId
|
||||
Fingerprint = '{0}|{1}|{2}' -f $domainController, $RawEvent.RecordId, $eventId
|
||||
RawFields = $rawFields
|
||||
RawMessage = $RawEvent.Message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,6 +1045,7 @@ function Invoke-BinocularsParallelEventQuery {
|
||||
RecordId = [long]$_.RecordId
|
||||
TimeCreated = $_.TimeCreated
|
||||
Xml = $_.ToXml()
|
||||
Message = $_.Message
|
||||
Error = $null
|
||||
}
|
||||
}
|
||||
@@ -880,6 +1057,7 @@ function Invoke-BinocularsParallelEventQuery {
|
||||
RecordId = $null
|
||||
TimeCreated = $null
|
||||
Xml = $null
|
||||
Message = $null
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
@@ -1002,17 +1180,22 @@ function Invoke-BinocularsSearch {
|
||||
[datetime]$EndTime,
|
||||
[AllowEmptyCollection()]
|
||||
[string[]]$DomainControllers,
|
||||
[AllowEmptyCollection()]
|
||||
[int[]]$EventIds = $script:RelevantEventIds,
|
||||
[object]$StatusTextBox
|
||||
)
|
||||
|
||||
if (-not $DomainControllers) {
|
||||
throw 'No reachable writable DCs are currently available. Use Refresh DCs to recheck unreachable servers.'
|
||||
}
|
||||
if (-not $EventIds) {
|
||||
throw 'Select at least one event type to search.'
|
||||
}
|
||||
|
||||
Write-BinocularsStatus -Message "Searching $($DomainControllers.Count) writable DCs in $($Identity.DomainDnsName)." -StatusTextBox $StatusTextBox
|
||||
Write-BinocularsStatus -Message "Query window: $($StartTime.ToString('yyyy-MM-dd HH:mm:ss')) to $($EndTime.ToString('yyyy-MM-dd HH:mm:ss'))." -StatusTextBox $StatusTextBox
|
||||
|
||||
$rawQuery = Invoke-BinocularsParallelEventQuery -DomainControllers $domainControllers -StartTime $StartTime -EndTime $EndTime -StatusTextBox $StatusTextBox
|
||||
$rawQuery = Invoke-BinocularsParallelEventQuery -DomainControllers $domainControllers -StartTime $StartTime -EndTime $EndTime -EventIds $EventIds -StatusTextBox $StatusTextBox
|
||||
$seenFingerprints = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
$records = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
@@ -1047,6 +1230,7 @@ function Invoke-BinocularsSearch {
|
||||
Errors = @($rawQuery.Errors)
|
||||
SuccessfulDomainControllers = @($rawQuery.SuccessfulDomainControllers)
|
||||
DomainControllers = @($domainControllers)
|
||||
EventIds = @($EventIds | Sort-Object -Unique)
|
||||
Identity = $Identity
|
||||
StartTime = $StartTime
|
||||
EndTime = $EndTime
|
||||
@@ -1072,7 +1256,7 @@ function Write-BinocularsArchive {
|
||||
$fileStamp = Get-Date -Format 'yyyyMMdd'
|
||||
$safeUser = ConvertTo-BinocularsSafeFileName -Value ('{0}-{1}' -f $Identity.DomainDnsName, $Identity.SamAccountName)
|
||||
$archivePath = Join-Path $archiveRoot ('Binoculars-{0}-{1}.csv' -f $safeUser, $fileStamp)
|
||||
$exportRows = $Records | Select-Object TimeCreated, DomainController, EventId, EventName, Category, Outcome, User, UserDomain, SourceHost, SourceIP, LogonType, Status, StatusText, Summary, RecordId, Fingerprint, RawFields
|
||||
$exportRows = $Records | Select-Object TimeCreated, DomainController, EventId, EventName, Category, Outcome, User, UserDomain, SourceHost, SourceIP, LogonType, Status, StatusText, Summary, RecordId, Fingerprint, RawFields, RawMessage
|
||||
|
||||
if (Test-Path -Path $archivePath) {
|
||||
$exportRows | Export-Csv -Path $archivePath -NoTypeInformation -Append
|
||||
@@ -1111,6 +1295,10 @@ function Format-BinocularsRecordDetails {
|
||||
'-------'
|
||||
$Record.Summary
|
||||
''
|
||||
'Raw Message'
|
||||
'-----------'
|
||||
$Record.RawMessage
|
||||
''
|
||||
'Raw Fields'
|
||||
'----------'
|
||||
(($Record.RawFields -split '; ') -join [Environment]::NewLine)
|
||||
@@ -1330,7 +1518,7 @@ function Export-BinocularsResults {
|
||||
|
||||
if ($saveDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
||||
$Records |
|
||||
Select-Object TimeCreated, DomainController, EventId, EventName, Category, Outcome, User, UserDomain, SourceHost, SourceIP, LogonType, Status, StatusText, Summary, RecordId, Fingerprint, RawFields |
|
||||
Select-Object TimeCreated, DomainController, EventId, EventName, Category, Outcome, User, UserDomain, SourceHost, SourceIP, LogonType, Status, StatusText, Summary, RecordId, Fingerprint, RawFields, RawMessage |
|
||||
Export-Csv -Path $saveDialog.FileName -NoTypeInformation
|
||||
|
||||
return $saveDialog.FileName
|
||||
@@ -1377,7 +1565,7 @@ function Start-BinocularsHeadless {
|
||||
|
||||
if ($ExportPath) {
|
||||
$result.Records |
|
||||
Select-Object TimeCreated, DomainController, EventId, EventName, Category, Outcome, User, UserDomain, SourceHost, SourceIP, LogonType, Status, StatusText, Summary, RecordId, Fingerprint, RawFields |
|
||||
Select-Object TimeCreated, DomainController, EventId, EventName, Category, Outcome, User, UserDomain, SourceHost, SourceIP, LogonType, Status, StatusText, Summary, RecordId, Fingerprint, RawFields, RawMessage |
|
||||
Export-Csv -Path $ExportPath -NoTypeInformation
|
||||
Write-Host "Exported to $ExportPath"
|
||||
}
|
||||
@@ -1400,8 +1588,11 @@ function Show-BinocularsGui {
|
||||
$script:watchMode = $false
|
||||
$script:queryInProgress = $false
|
||||
$script:activeIdentity = $null
|
||||
$script:activeEventIds = @($script:RelevantEventIds)
|
||||
$script:activeEventFilterSummary = Get-BinocularsEventFilterSummary -EventIds $script:activeEventIds
|
||||
$script:cancelSearchRequested = $false
|
||||
$script:domainControllerInventory = @()
|
||||
$script:updatingEventFilters = $false
|
||||
|
||||
$form = New-Object System.Windows.Forms.Form
|
||||
$form.Text = 'Binoculars'
|
||||
@@ -1412,7 +1603,7 @@ function Show-BinocularsGui {
|
||||
|
||||
$topPanel = New-Object System.Windows.Forms.Panel
|
||||
$topPanel.Dock = 'Top'
|
||||
$topPanel.Height = 120
|
||||
$topPanel.Height = 170
|
||||
|
||||
$titleLabel = New-Object System.Windows.Forms.Label
|
||||
$titleLabel.Text = 'Binoculars - Writable DC Authentication Tracker'
|
||||
@@ -1506,15 +1697,51 @@ function Show-BinocularsGui {
|
||||
$archiveButton.Size = New-Object System.Drawing.Size(100, 28)
|
||||
$topPanel.Controls.Add($archiveButton)
|
||||
|
||||
$eventProfileLabel = New-Object System.Windows.Forms.Label
|
||||
$eventProfileLabel.Text = 'Event Profile'
|
||||
$eventProfileLabel.Location = New-Object System.Drawing.Point(15, 80)
|
||||
$eventProfileLabel.AutoSize = $true
|
||||
$topPanel.Controls.Add($eventProfileLabel)
|
||||
|
||||
$eventProfileComboBox = New-Object System.Windows.Forms.ComboBox
|
||||
$eventProfileComboBox.Location = New-Object System.Drawing.Point(90, 76)
|
||||
$eventProfileComboBox.Size = New-Object System.Drawing.Size(185, 23)
|
||||
$eventProfileComboBox.DropDownStyle = 'DropDownList'
|
||||
foreach ($profileName in $script:EventProfiles.Keys) {
|
||||
[void]$eventProfileComboBox.Items.Add($profileName)
|
||||
}
|
||||
$topPanel.Controls.Add($eventProfileComboBox)
|
||||
|
||||
$eventFilterFlowPanel = New-Object System.Windows.Forms.FlowLayoutPanel
|
||||
$eventFilterFlowPanel.Location = New-Object System.Drawing.Point(290, 72)
|
||||
$eventFilterFlowPanel.Size = New-Object System.Drawing.Size(1170, 36)
|
||||
$eventFilterFlowPanel.WrapContents = $true
|
||||
$eventFilterFlowPanel.AutoScroll = $true
|
||||
$eventFilterFlowPanel.FlowDirection = 'LeftToRight'
|
||||
$eventFilterFlowPanel.Padding = New-Object System.Windows.Forms.Padding(0)
|
||||
$eventFilterFlowPanel.Margin = New-Object System.Windows.Forms.Padding(0)
|
||||
$topPanel.Controls.Add($eventFilterFlowPanel)
|
||||
|
||||
$eventFilterCheckBoxes = [ordered]@{}
|
||||
foreach ($groupName in $script:EventFilterGroups.Keys) {
|
||||
$checkBox = New-Object System.Windows.Forms.CheckBox
|
||||
$checkBox.Text = $groupName
|
||||
$checkBox.AutoSize = $true
|
||||
$checkBox.Margin = New-Object System.Windows.Forms.Padding(0, 6, 14, 0)
|
||||
$checkBox.Tag = @($script:EventFilterGroups[$groupName])
|
||||
$eventFilterFlowPanel.Controls.Add($checkBox)
|
||||
$eventFilterCheckBoxes[$groupName] = $checkBox
|
||||
}
|
||||
|
||||
$summaryLabel = New-Object System.Windows.Forms.Label
|
||||
$summaryLabel.Text = 'No results loaded.'
|
||||
$summaryLabel.Location = New-Object System.Drawing.Point(15, 76)
|
||||
$summaryLabel.Location = New-Object System.Drawing.Point(15, 118)
|
||||
$summaryLabel.Size = New-Object System.Drawing.Size(1460, 18)
|
||||
$topPanel.Controls.Add($summaryLabel)
|
||||
|
||||
$infoLabel = New-Object System.Windows.Forms.Label
|
||||
$infoLabel.Text = 'DCs are strongest for auth, failed auth and lockout events. Logoff visibility is limited to sessions the DC itself records.'
|
||||
$infoLabel.Location = New-Object System.Drawing.Point(15, 96)
|
||||
$infoLabel.Location = New-Object System.Drawing.Point(15, 140)
|
||||
$infoLabel.Size = New-Object System.Drawing.Size(1460, 18)
|
||||
$infoLabel.ForeColor = [System.Drawing.Color]::DimGray
|
||||
$topPanel.Controls.Add($infoLabel)
|
||||
@@ -1627,7 +1854,11 @@ function Show-BinocularsGui {
|
||||
$userTextBox.Enabled = -not $script:watchMode -and -not $Busy
|
||||
$lookbackUpDown.Enabled = -not $script:watchMode -and -not $Busy
|
||||
$intervalUpDown.Enabled = -not $script:watchMode -and -not $Busy
|
||||
$eventProfileComboBox.Enabled = -not $script:watchMode -and -not $Busy
|
||||
$archiveButton.Enabled = -not $Busy
|
||||
foreach ($filterCheckBox in $eventFilterCheckBoxes.Values) {
|
||||
$filterCheckBox.Enabled = -not $script:watchMode -and -not $Busy
|
||||
}
|
||||
|
||||
if ($Busy) {
|
||||
$form.Cursor = [System.Windows.Forms.Cursors]::WaitCursor
|
||||
@@ -1682,7 +1913,18 @@ function Show-BinocularsGui {
|
||||
$unreachableCount = @($inventory | Where-Object { $_.IsReachable -eq $false }).Count
|
||||
$unknownCount = @($inventory | Where-Object { $null -eq $_.IsReachable }).Count
|
||||
$pdcEntry = @($inventory | Where-Object { $_.IsPdcEmulator } | Select-Object -First 1)
|
||||
$resolvedUser = if ($script:activeIdentity) { "$($script:activeIdentity.DisplayName) [$($script:activeIdentity.SamAccountName)]" } else { 'No user selected' }
|
||||
$resolvedUser = if ($script:activeIdentity) {
|
||||
if ($script:activeIdentity.SearchAllUsers) {
|
||||
'All Users'
|
||||
}
|
||||
else {
|
||||
"$($script:activeIdentity.DisplayName) [$($script:activeIdentity.SamAccountName)]"
|
||||
}
|
||||
}
|
||||
else {
|
||||
'No user selected'
|
||||
}
|
||||
$filterText = if ($script:activeEventFilterSummary) { $script:activeEventFilterSummary } else { 'Default' }
|
||||
$windowText = if ($Result) {
|
||||
'{0} -> {1}' -f $Result.StartTime.ToString('yyyy-MM-dd HH:mm:ss'), $Result.EndTime.ToString('yyyy-MM-dd HH:mm:ss')
|
||||
}
|
||||
@@ -1692,8 +1934,9 @@ function Show-BinocularsGui {
|
||||
$pdcText = if ($pdcEntry) { $pdcEntry[0].HostName } else { 'N/A' }
|
||||
$searchedDcCount = if ($Result) { $Result.DomainControllers.Count } else { $reachableCount }
|
||||
|
||||
$summaryLabel.Text = 'User: {0} | Search DCs: {1} | Green: {2} | Red: {3} | Unknown: {4} | PDC: {5} | Events: {6} | Success: {7} | Failure: {8} | Lockout: {9} | Unlock: {10} | Logoff: {11} | Errors: {12} | Window: {13}' -f `
|
||||
$summaryLabel.Text = 'User: {0} | Filter: {1} | Search DCs: {2} | Green: {3} | Red: {4} | Unknown: {5} | PDC: {6} | Events: {7} | Success: {8} | Failure: {9} | Lockout: {10} | Unlock: {11} | Logoff: {12} | Errors: {13} | Window: {14}' -f `
|
||||
$resolvedUser, `
|
||||
$filterText, `
|
||||
$searchedDcCount, `
|
||||
$reachableCount, `
|
||||
$unreachableCount, `
|
||||
@@ -1726,6 +1969,8 @@ function Show-BinocularsGui {
|
||||
$script:lastArchivePath = $null
|
||||
$script:lastPollEnd = $null
|
||||
$script:activeIdentity = $null
|
||||
$script:activeEventIds = @(Get-BinocularsSelectedEventIds -CheckBoxMap $eventFilterCheckBoxes)
|
||||
$script:activeEventFilterSummary = Get-BinocularsEventFilterSummary -EventIds $script:activeEventIds
|
||||
$script:cancelSearchRequested = $false
|
||||
Set-BinocularsGridData -Grid $grid -Records @()
|
||||
$detailsTextBox.Clear()
|
||||
@@ -1757,8 +2002,14 @@ function Show-BinocularsGui {
|
||||
else {
|
||||
Resolve-BinocularsIdentity -UserInput $userTextBox.Text
|
||||
}
|
||||
$selectedEventIds = @(Get-BinocularsSelectedEventIds -CheckBoxMap $eventFilterCheckBoxes)
|
||||
if (-not $selectedEventIds) {
|
||||
throw 'Select at least one event type before searching.'
|
||||
}
|
||||
|
||||
$script:activeIdentity = $identity
|
||||
$script:activeEventIds = @($selectedEventIds)
|
||||
$script:activeEventFilterSummary = Get-BinocularsEventFilterSummary -EventIds $script:activeEventIds
|
||||
$endTime = Get-Date
|
||||
if ($Incremental -and $script:lastPollEnd) {
|
||||
$startTime = $script:lastPollEnd.AddSeconds(-1 * $script:PollOverlapSeconds)
|
||||
@@ -1768,6 +2019,7 @@ function Show-BinocularsGui {
|
||||
}
|
||||
|
||||
Write-BinocularsStatus -Message "Tracking $($identity.DisplayName) [$($identity.SamAccountName)]" -StatusTextBox $statusTextBox
|
||||
Write-BinocularsStatus -Message "Event filter: $($script:activeEventFilterSummary)" -StatusTextBox $statusTextBox
|
||||
if (-not (& $refreshDcInventory $false)) {
|
||||
& $updateSummary $null
|
||||
return $false
|
||||
@@ -1787,7 +2039,7 @@ function Show-BinocularsGui {
|
||||
throw 'All writable DCs are currently marked unreachable. Use Refresh DCs to recheck them.'
|
||||
}
|
||||
|
||||
$result = Invoke-BinocularsSearch -Identity $identity -StartTime $startTime -EndTime $endTime -DomainControllers $searchableControllers -StatusTextBox $statusTextBox
|
||||
$result = Invoke-BinocularsSearch -Identity $identity -StartTime $startTime -EndTime $endTime -DomainControllers $searchableControllers -EventIds $selectedEventIds -StatusTextBox $statusTextBox
|
||||
foreach ($successfulDc in $result.SuccessfulDomainControllers) {
|
||||
Set-BinocularsDomainControllerState -Inventory $script:domainControllerInventory -HostName $successfulDc -Reachable $true
|
||||
}
|
||||
@@ -1855,6 +2107,52 @@ function Show-BinocularsGui {
|
||||
}
|
||||
}
|
||||
|
||||
$syncEventFilterProfile = {
|
||||
$selectedIds = @(Get-BinocularsSelectedEventIds -CheckBoxMap $eventFilterCheckBoxes)
|
||||
$matchedProfile = Get-BinocularsMatchingProfileName -EventIds $selectedIds
|
||||
|
||||
$script:updatingEventFilters = $true
|
||||
$eventProfileComboBox.SelectedItem = $matchedProfile
|
||||
$script:updatingEventFilters = $false
|
||||
|
||||
$script:activeEventIds = @($selectedIds)
|
||||
$script:activeEventFilterSummary = Get-BinocularsEventFilterSummary -EventIds $selectedIds
|
||||
if (-not $script:queryInProgress) {
|
||||
& $updateSummary $null
|
||||
}
|
||||
}
|
||||
|
||||
$eventProfileComboBox.Add_SelectedIndexChanged({
|
||||
if ($script:updatingEventFilters) {
|
||||
return
|
||||
}
|
||||
|
||||
$selectedProfile = [string]$eventProfileComboBox.SelectedItem
|
||||
if ([string]::IsNullOrWhiteSpace($selectedProfile) -or $selectedProfile -eq 'Custom') {
|
||||
return
|
||||
}
|
||||
|
||||
$script:updatingEventFilters = $true
|
||||
Set-BinocularsEventFilterCheckBoxes -CheckBoxMap $eventFilterCheckBoxes -EventIds $script:EventProfiles[$selectedProfile]
|
||||
$script:updatingEventFilters = $false
|
||||
|
||||
$script:activeEventIds = @($script:EventProfiles[$selectedProfile] | Sort-Object -Unique)
|
||||
$script:activeEventFilterSummary = $selectedProfile
|
||||
if (-not $script:queryInProgress) {
|
||||
& $updateSummary $null
|
||||
}
|
||||
})
|
||||
|
||||
foreach ($filterCheckBox in $eventFilterCheckBoxes.Values) {
|
||||
$filterCheckBox.Add_CheckedChanged({
|
||||
if ($script:updatingEventFilters) {
|
||||
return
|
||||
}
|
||||
|
||||
& $syncEventFilterProfile
|
||||
})
|
||||
}
|
||||
|
||||
$grid.Add_SelectionChanged({
|
||||
if ($grid.SelectedRows.Count -eq 0) {
|
||||
return
|
||||
@@ -1980,6 +2278,13 @@ function Show-BinocularsGui {
|
||||
$form.Controls.Add($topPanel)
|
||||
$form.Controls.Add($statusStrip)
|
||||
|
||||
$script:updatingEventFilters = $true
|
||||
Set-BinocularsEventFilterCheckBoxes -CheckBoxMap $eventFilterCheckBoxes -EventIds $script:RelevantEventIds
|
||||
$eventProfileComboBox.SelectedItem = 'All Authentication'
|
||||
$script:updatingEventFilters = $false
|
||||
$script:activeEventIds = @($script:RelevantEventIds)
|
||||
$script:activeEventFilterSummary = 'All Authentication'
|
||||
|
||||
Set-BinocularsGridData -Grid $grid -Records @()
|
||||
try {
|
||||
$script:domainControllerInventory = @(Get-BinocularsDomainControllerInventory)
|
||||
@@ -2005,10 +2310,6 @@ catch {
|
||||
}
|
||||
|
||||
if ($NoGui) {
|
||||
if ([string]::IsNullOrWhiteSpace($UserName)) {
|
||||
throw 'Use -UserName when running Binoculars.ps1 with -NoGui.'
|
||||
}
|
||||
|
||||
Start-BinocularsHeadless -UserName $UserName -LookbackMinutes $LookbackMinutes -ExportPath $ExportPath
|
||||
}
|
||||
else {
|
||||
|
||||
Reference in New Issue
Block a user