#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