From ac85a06003ac11ec3a349189ed0139cb62e67b79 Mon Sep 17 00:00:00 2001 From: ek-dyoder Date: Wed, 8 Jul 2026 11:27:51 -0400 Subject: [PATCH] add usage window --- Get-UserLogonActivity.ps1 | 174 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 169 insertions(+), 5 deletions(-) diff --git a/Get-UserLogonActivity.ps1 b/Get-UserLogonActivity.ps1 index 8f6da8b..40c5a6e 100644 --- a/Get-UserLogonActivity.ps1 +++ b/Get-UserLogonActivity.ps1 @@ -193,6 +193,28 @@ Function Resolve-UserLogonActivitySid { } } +Function Get-UserLogonActivityKey { + Param( + [AllowNull()] + [object]$Sid, + + [AllowNull()] + [string]$User + ) + + $SidString = ([string]$Sid).Trim() + If (-not [string]::IsNullOrWhiteSpace($SidString) -and $SidString -ne '-') { + Return $SidString.ToUpperInvariant() + } + + $UserString = ([string]$User).Trim() + If (-not [string]::IsNullOrWhiteSpace($UserString)) { + Return $UserString.ToUpperInvariant() + } + + Return '' +} + Function Get-UserLogonActivitySidFromEvent { Param( [Parameter(Mandatory=$true)] @@ -234,6 +256,8 @@ Function New-UserLogonActivityRecord { [string]$Details = '', + [string]$UserKey = '', + [int]$SourcePriority = 50, [switch]$AllowSystemUser @@ -241,6 +265,11 @@ Function New-UserLogonActivityRecord { If (-not $AllowSystemUser -and -not (Test-UserLogonActivityUser -UserName $User)) { Return $null } + $ResolvedUserKey = $UserKey + If ([string]::IsNullOrWhiteSpace($ResolvedUserKey)) { + $ResolvedUserKey = Get-UserLogonActivityKey -Sid $null -User $User + } + [PSCustomObject]@{ Date = $TimeCreated.ToString('yyyy-MM-dd') Time = $TimeCreated.ToString('HH:mm:ss') @@ -248,6 +277,7 @@ Function New-UserLogonActivityRecord { Activity = $Activity Source = $Source Details = $Details + UserKey = $ResolvedUserKey SourcePriority = $SourcePriority TimeCreated = $TimeCreated } @@ -265,10 +295,11 @@ Function ConvertTo-WinlogonUserActivity { Default { Return $null } } - $User = Resolve-UserLogonActivitySid -Sid (Get-UserLogonActivitySidFromEvent -Event $Event) + $UserSid = Get-UserLogonActivitySidFromEvent -Event $Event + $User = Resolve-UserLogonActivitySid -Sid $UserSid If ([string]::IsNullOrWhiteSpace($User)) { Return $null } - New-UserLogonActivityRecord -TimeCreated $Event.TimeCreated -User $User -Activity $Activity -Source $Source -SourcePriority 10 + New-UserLogonActivityRecord -TimeCreated $Event.TimeCreated -User $User -Activity $Activity -Source $Source -UserKey (Get-UserLogonActivityKey -Sid $UserSid -User $User) -SourcePriority 10 } Function ConvertTo-SecurityUserActivity { @@ -280,6 +311,7 @@ Function ConvertTo-SecurityUserActivity { $EventXml = [xml]$Event.ToXml() $UserName = Get-EventDataValueFromList -EventXml $EventXml -Names @('TargetUserName', 'SubjectUserName') $DomainName = Get-EventDataValueFromList -EventXml $EventXml -Names @('TargetDomainName', 'SubjectDomainName') + $UserSid = Get-EventDataValueFromList -EventXml $EventXml -Names @('TargetUserSid', 'SubjectUserSid') $LogonType = Get-EventDataValue -EventXml $EventXml -Name 'LogonType' $Activity = $null $Source = $null @@ -334,7 +366,8 @@ Function ConvertTo-SecurityUserActivity { } } - New-UserLogonActivityRecord -TimeCreated $Event.TimeCreated -User (Format-UserName -DomainName $DomainName -UserName $UserName) -Activity $Activity -Source $Source -Details $Details -SourcePriority $SourcePriority + $User = Format-UserName -DomainName $DomainName -UserName $UserName + New-UserLogonActivityRecord -TimeCreated $Event.TimeCreated -User $User -Activity $Activity -Source $Source -Details $Details -UserKey (Get-UserLogonActivityKey -Sid $UserSid -User $User) -SourcePriority $SourcePriority } Function ConvertTo-TerminalServicesUserActivity { @@ -367,7 +400,7 @@ Function ConvertTo-TerminalServicesUserActivity { } } - New-UserLogonActivityRecord -TimeCreated $Event.TimeCreated -User $User -Activity $Activity -Source $Source -Details ($DetailsParts -join '; ') -SourcePriority 20 + New-UserLogonActivityRecord -TimeCreated $Event.TimeCreated -User $User -Activity $Activity -Source $Source -Details ($DetailsParts -join '; ') -UserKey (Get-UserLogonActivityKey -Sid $null -User $User) -SourcePriority 20 } Function ConvertTo-PowerUserActivity { @@ -478,12 +511,22 @@ Function Get-UniqueUserLogonActivity { ForEach ($ActivityEvent in (@($ActivityEvents) | Where-Object { $null -ne $_ } | Sort-Object -Property TimeCreated,User,Activity,SourcePriority)) { $IsDuplicate = $false + $ActivityEventKey = [string]$ActivityEvent.UserKey + + If ([string]::IsNullOrWhiteSpace($ActivityEventKey)) { + $ActivityEventKey = Get-UserLogonActivityKey -Sid $null -User $ActivityEvent.User + } For ($Index = 0; $Index -lt $UniqueEvents.Count; $Index++) { $UniqueEvent = $UniqueEvents[$Index] + $UniqueEventKey = [string]$UniqueEvent.UserKey + + If ([string]::IsNullOrWhiteSpace($UniqueEventKey)) { + $UniqueEventKey = Get-UserLogonActivityKey -Sid $null -User $UniqueEvent.User + } If ( - $UniqueEvent.User -eq $ActivityEvent.User -and + $UniqueEventKey -eq $ActivityEventKey -and $UniqueEvent.Activity -eq $ActivityEvent.Activity -and [Math]::Abs(($UniqueEvent.TimeCreated - $ActivityEvent.TimeCreated).TotalSeconds) -le 5 ) { @@ -520,6 +563,125 @@ Function Write-UserLogonActivityOutput { Write-Output (($Rows | Format-Table -AutoSize | Out-String -Width 240).TrimEnd()) } +Function Get-PossibleUseWindow { + Param( + [AllowNull()] + [object[]]$ActivityEvents + ) + + $OpenWindows = @{} + $UseWindows = New-Object System.Collections.Generic.List[object] + $OpeningActivities = @('logon', 'unlock', 'remote logon', 'session reconnect', 'screensaver dismiss') + $ImpliedClosureDescriptions = @{ + 'unlock' = 'implied prior lock before' + 'session reconnect' = 'implied prior disconnect before' + 'screensaver dismiss' = 'implied prior screensaver before' + } + + ForEach ($ActivityEvent in (@($ActivityEvents) | Where-Object { $null -ne $_ } | Sort-Object -Property TimeCreated)) { + $User = [string]$ActivityEvent.User + $UserKey = [string]$ActivityEvent.UserKey + $Activity = [string]$ActivityEvent.Activity + $Source = [string]$ActivityEvent.Source + + If ([string]::IsNullOrWhiteSpace($UserKey)) { + $UserKey = Get-UserLogonActivityKey -Sid $null -User $User + } + + If ($Activity -in @('sleep', 'shutdown', 'restart')) { + ForEach ($OpenUserKey in @($OpenWindows.Keys)) { + $OpenWindow = $OpenWindows[$OpenUserKey] + $UseWindows.Add([PSCustomObject]@{ + StartTime = $OpenWindow.StartTime + Start = $OpenWindow.StartTime.ToString('yyyy-MM-dd HH:mm:ss') + End = $ActivityEvent.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') + User = $OpenWindow.User + StartedBy = $OpenWindow.StartedBy + EndedBy = "${Activity} / ${Source}" + }) | Out-Null + $OpenWindows.Remove($OpenUserKey) + } + + Continue + } + + If ([string]::IsNullOrWhiteSpace($User) -or $User -eq 'SYSTEM' -or $User -eq 'NT AUTHORITY\SYSTEM') { + Continue + } + + If ($Activity -in $OpeningActivities) { + If ($OpenWindows.ContainsKey($UserKey) -and $ImpliedClosureDescriptions.ContainsKey($Activity)) { + $OpenWindow = $OpenWindows[$UserKey] + $UseWindows.Add([PSCustomObject]@{ + StartTime = $OpenWindow.StartTime + Start = $OpenWindow.StartTime.ToString('yyyy-MM-dd HH:mm:ss') + End = "before $($ActivityEvent.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss'))" + User = $OpenWindow.User + StartedBy = $OpenWindow.StartedBy + EndedBy = "$($ImpliedClosureDescriptions[$Activity]) ${Activity} / ${Source}" + }) | Out-Null + $OpenWindows.Remove($UserKey) + } + + If (-not $OpenWindows.ContainsKey($UserKey)) { + $OpenWindows[$UserKey] = [PSCustomObject]@{ + StartTime = $ActivityEvent.TimeCreated + User = $User + StartedBy = "${Activity} / ${Source}" + } + } + } + ElseIf ($Activity -in @('logoff', 'lock', 'session disconnect')) { + If ($OpenWindows.ContainsKey($UserKey)) { + $OpenWindow = $OpenWindows[$UserKey] + $UseWindows.Add([PSCustomObject]@{ + StartTime = $OpenWindow.StartTime + Start = $OpenWindow.StartTime.ToString('yyyy-MM-dd HH:mm:ss') + End = $ActivityEvent.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') + User = $OpenWindow.User + StartedBy = $OpenWindow.StartedBy + EndedBy = "${Activity} / ${Source}" + }) | Out-Null + $OpenWindows.Remove($UserKey) + } + } + } + + ForEach ($OpenUserKey in @($OpenWindows.Keys)) { + $OpenWindow = $OpenWindows[$OpenUserKey] + $UseWindows.Add([PSCustomObject]@{ + StartTime = $OpenWindow.StartTime + Start = $OpenWindow.StartTime.ToString('yyyy-MM-dd HH:mm:ss') + End = 'open through returned events' + User = $OpenWindow.User + StartedBy = $OpenWindow.StartedBy + EndedBy = 'not closed in returned events' + }) | Out-Null + } + + Return $UseWindows +} + +Function Write-PossibleUseWindowOutput { + Param( + [AllowNull()] + [object[]]$ActivityEvents + ) + + $UseWindows = @(Get-PossibleUseWindow -ActivityEvents $ActivityEvents | Sort-Object -Property StartTime) + + If ($UseWindows.Count -eq 0) { + Write-Output '' + Write-Output 'Possible use windows inferred from returned events: none' + Return + } + + $Rows = @($UseWindows | Select-Object Start,End,User,StartedBy,EndedBy) + Write-Output '' + Write-Output 'Possible use windows inferred from returned events:' + Write-Output (($Rows | Format-Table -AutoSize | Out-String -Width 240).TrimEnd()) +} + # Winlogon 7001/7002 tracks user session logon/logoff more reliably than Security 4624/4634. $WinlogonFilter = @{ LogName = 'System' @@ -621,6 +783,7 @@ If ($UseDateRange) { $OutputEvents = @(Get-UniqueUserLogonActivity -ActivityEvents $ActivityEvents | Sort-Object -Property TimeCreated) Write-UserLogonActivityOutput -ActivityEvents $OutputEvents + Write-PossibleUseWindowOutput -ActivityEvents $OutputEvents } Else { $ActivityLimit = 15 @@ -676,4 +839,5 @@ Else { } While ($true) Write-UserLogonActivityOutput -ActivityEvents $OutputEvents + Write-PossibleUseWindowOutput -ActivityEvents $OutputEvents }