177 lines
6.5 KiB
PowerShell
177 lines
6.5 KiB
PowerShell
#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
|