Add six-month domain and broker monitoring

This commit is contained in:
2026-09-04 16:57:34 -06:00
parent f2a40f051b
commit dcbf5e87e3
20 changed files with 998 additions and 28 deletions
+176
View File
@@ -0,0 +1,176 @@
#Requires -Version 5.1
[CmdletBinding()]
param(
[datetime]$Since = (Get-Date).AddDays(-183),
[datetime]$Until = (Get-Date),
[string]$UserName,
[string]$ComputerName,
[string]$MonitoringRoot = 'C:\ProgramData\SGU\Monitoring',
[string]$OutputCsv
)
$ErrorActionPreference = 'Stop'
$eventIds = @(4624,4625,4634,4647,4778,4779,6005,6006,6008)
$events = [Collections.Generic.List[object]]::new()
try {
# Windows Server 2025 can crash the Windows Event Log service when a
# structured query is evaluated against ForwardedEvents (wevtsvc.dll,
# exception 0xc0000420). Read the channel without a server-side query and
# apply every predicate in this process instead.
Get-WinEvent -LogName 'ForwardedEvents' -ErrorAction Stop |
Where-Object {
$_.Id -in $eventIds -and
$_.TimeCreated -ge $Since -and
$_.TimeCreated -le $Until
} |
ForEach-Object { $events.Add($_) }
}
catch [System.Exception] {
if ($_.FullyQualifiedErrorId -notlike 'NoMatchingEventsFound*') {
Write-Verbose $_.Exception.Message
}
}
$archiveRoot = Join-Path $MonitoringRoot 'Archive'
Get-ChildItem -LiteralPath $archiveRoot -Filter '*.evtx' -File -ErrorAction SilentlyContinue |
Where-Object LastWriteTime -ge $Since.AddDays(-1) |
ForEach-Object {
try {
Get-WinEvent -Path $_.FullName -Oldest -ErrorAction Stop |
Where-Object { $_.Id -in $eventIds -and $_.TimeCreated -ge $Since -and $_.TimeCreated -le $Until } |
ForEach-Object { $events.Add($_) }
}
catch {
Write-Warning "Could not read archive $($_.FullName): $($_.Exception.Message)"
}
}
function Get-EventData {
param([Parameter(Mandatory)]$EventRecord)
$xml = [xml]$EventRecord.ToXml()
$data = @{}
foreach ($item in @($xml.Event.EventData.Data)) {
if ($item.Name) {
$data[[string]$item.Name] = [string]$item.'#text'
}
}
[pscustomobject]@{
Computer = [string]$xml.Event.System.Computer
Data = $data
}
}
$openSessions = @{}
$rows = [Collections.Generic.List[object]]::new()
$ignoredUsers = @('ANONYMOUS LOGON','DWM-1','DWM-2','DWM-3','LOCAL SERVICE','NETWORK SERVICE','SYSTEM','UMFD-0','UMFD-1','UMFD-2','UMFD-3')
foreach ($eventRecord in @($events | Sort-Object TimeCreated,RecordId)) {
$parsed = Get-EventData -EventRecord $eventRecord
$machine = ($parsed.Computer -split '\.')[0].ToUpperInvariant()
$data = $parsed.Data
if ($eventRecord.Id -in 6005,6006,6008) {
foreach ($key in @($openSessions.Keys | Where-Object { $_ -like "$machine|*" })) {
$session = $openSessions[$key]
$rows.Add([pscustomobject]@{
User = $session.User
Computer = $machine
StartedAt = $session.StartedAt
EndedAt = $eventRecord.TimeCreated
Duration = $eventRecord.TimeCreated - $session.StartedAt
DurationMinutes = [math]::Round(($eventRecord.TimeCreated - $session.StartedAt).TotalMinutes, 2)
LogonType = $session.LogonType
Result = 'Interrumpida por apagado o reinicio'
FailureStatus = $null
})
$openSessions.Remove($key)
}
continue
}
if ($eventRecord.Id -eq 4625) {
$failedUser = [string]$data.TargetUserName
if ($failedUser -and $failedUser -notlike '*$' -and $failedUser.ToUpperInvariant() -notin $ignoredUsers) {
$rows.Add([pscustomobject]@{
User = if ($data.TargetDomainName) { "$($data.TargetDomainName)\$failedUser" } else { $failedUser }
Computer = $machine
StartedAt = $eventRecord.TimeCreated
EndedAt = $eventRecord.TimeCreated
Duration = [timespan]::Zero
DurationMinutes = 0
LogonType = [string]$data.LogonType
Result = 'Fallida'
FailureStatus = "$($data.Status)/$($data.SubStatus)"
})
}
continue
}
if ($eventRecord.Id -eq 4624) {
$logonType = [string]$data.LogonType
$targetUser = [string]$data.TargetUserName
if ($logonType -notin @('2','10','11') -or -not $targetUser -or $targetUser -like '*$' -or
$targetUser.ToUpperInvariant() -in $ignoredUsers) {
continue
}
$logonId = [string]$data.TargetLogonId
$key = "$machine|$logonId"
$openSessions[$key] = [pscustomobject]@{
User = if ($data.TargetDomainName) { "$($data.TargetDomainName)\$targetUser" } else { $targetUser }
StartedAt = $eventRecord.TimeCreated
LogonType = $logonType
}
continue
}
if ($eventRecord.Id -in 4634,4647) {
$logonId = if ($eventRecord.Id -eq 4634) { [string]$data.TargetLogonId } else { [string]$data.SubjectLogonId }
$key = "$machine|$logonId"
if ($openSessions.ContainsKey($key)) {
$session = $openSessions[$key]
$rows.Add([pscustomobject]@{
User = $session.User
Computer = $machine
StartedAt = $session.StartedAt
EndedAt = $eventRecord.TimeCreated
Duration = $eventRecord.TimeCreated - $session.StartedAt
DurationMinutes = [math]::Round(($eventRecord.TimeCreated - $session.StartedAt).TotalMinutes, 2)
LogonType = $session.LogonType
Result = 'Completada'
FailureStatus = $null
})
$openSessions.Remove($key)
}
}
}
foreach ($key in $openSessions.Keys) {
$session = $openSessions[$key]
$machine = ($key -split '\|', 2)[0]
$rows.Add([pscustomobject]@{
User = $session.User
Computer = $machine
StartedAt = $session.StartedAt
EndedAt = $null
Duration = $Until - $session.StartedAt
DurationMinutes = [math]::Round(($Until - $session.StartedAt).TotalMinutes, 2)
LogonType = $session.LogonType
Result = 'Sesión posiblemente activa'
FailureStatus = $null
})
}
$result = @($rows | Where-Object {
(-not $UserName -or $_.User -like "*$UserName*") -and
(-not $ComputerName -or $_.Computer -like "*$ComputerName*")
} | Sort-Object StartedAt -Descending)
if ($OutputCsv) {
$resolvedOutput = [IO.Path]::GetFullPath($OutputCsv)
New-Item -ItemType Directory -Path (Split-Path $resolvedOutput -Parent) -Force | Out-Null
$result | Export-Csv -LiteralPath $resolvedOutput -NoTypeInformation -Encoding UTF8
}
$result