diff --git a/Get-UserLogonActivity.ps1 b/Get-UserLogonActivity.ps1 index 3f8620c..a735a89 100644 --- a/Get-UserLogonActivity.ps1 +++ b/Get-UserLogonActivity.ps1 @@ -682,21 +682,57 @@ Function Write-UserLogonActivityOutput { Write-PlainTextTable -Rows $Rows -Columns @('Date', 'Time', 'User', 'Activity', 'Source', 'Details') } -Function Get-PossibleUseWindowAction { +Function Get-LocalUnlockedWindowAction { Param( [AllowNull()] [string]$Activity ) Switch ($Activity) { - 'session reconnect' { Return 'reconnect' } - 'session disconnect' { Return 'disconnect' } + 'session reconnect' { Return 'RDP reconnect' } + 'session disconnect' { Return 'session disconnect' } 'screensaver dismiss' { Return 'screensaver dismiss' } 'screensaver start' { Return 'screensaver start' } Default { Return $Activity } } } +Function Format-LocalUnlockedDuration { + Param( + [Parameter(Mandatory=$true)] + [datetime]$StartTime, + + [Parameter(Mandatory=$true)] + [datetime]$EndTime, + + [switch]$Maximum + ) + + $Duration = $EndTime - $StartTime + If ($Duration.TotalSeconds -lt 0) { + $Duration = [TimeSpan]::Zero + } + + If ($Duration.Days -gt 0) { + $Text = '{0}d {1}h {2}m' -f $Duration.Days, $Duration.Hours, $Duration.Minutes + } + ElseIf ($Duration.Hours -gt 0) { + $Text = '{0}h {1}m' -f $Duration.Hours, $Duration.Minutes + } + ElseIf ($Duration.Minutes -gt 0) { + $Text = '{0}m {1}s' -f $Duration.Minutes, $Duration.Seconds + } + Else { + $Text = '{0}s' -f $Duration.Seconds + } + + If ($Maximum) { + Return "up to ${Text}" + } + + Return $Text +} + Function Test-RedundantOpeningUnlock { Param( [Parameter(Mandatory=$true)] @@ -712,7 +748,10 @@ Function Test-RedundantOpeningUnlock { ForEach ($CandidateEvent in @($ActivityEvents)) { If ( - $CandidateEvent.Activity -in @('logon', 'session reconnect') -and + ( + $CandidateEvent.Activity -eq 'logon' -or + (Test-RemoteSessionReconnect -ActivityEvent $CandidateEvent) + ) -and [Math]::Abs(($CandidateEvent.TimeCreated - $ActivityEvent.TimeCreated).TotalSeconds) -le 5 ) { Return $true @@ -722,23 +761,56 @@ Function Test-RedundantOpeningUnlock { Return $false } -Function Get-PossibleUseWindow { +Function Get-ActivityEventDetailValue { + Param( + [Parameter(Mandatory=$true)] + [object]$ActivityEvent, + + [Parameter(Mandatory=$true)] + [string]$Name + ) + + $Details = [string]$ActivityEvent.Details + If ([string]::IsNullOrWhiteSpace($Details)) { Return $null } + + ForEach ($Part in ($Details -split ';')) { + $TrimmedPart = $Part.Trim() + + If ($TrimmedPart -match "^$([regex]::Escape($Name))=(.*)$") { + Return $Matches[1].Trim() + } + } + + Return $null +} + +Function Test-RemoteSessionReconnect { + Param( + [Parameter(Mandatory=$true)] + [object]$ActivityEvent + ) + + If ($ActivityEvent.Activity -ne 'session reconnect') { Return $false } + + $Address = Get-ActivityEventDetailValue -ActivityEvent $ActivityEvent -Name 'Address' + If ([string]::IsNullOrWhiteSpace($Address)) { Return $false } + If ($Address -eq '-' -or $Address -ieq 'LOCAL') { Return $false } + + Return $true +} + +Function Get-LocalUnlockedWindow { Param( [AllowNull()] [object[]]$ActivityEvents ) $OpenWindow = $null - $UseWindows = New-Object System.Collections.Generic.List[object] - $OpeningActivities = @('logon', 'unlock', 'remote logon', 'session reconnect', 'screensaver dismiss') - $ImpliedClosureDescriptions = @{ - 'unlock' = 'lock before unlock' - 'session reconnect' = 'disconnect before reconnect' - 'screensaver dismiss' = 'screensaver before dismiss' - } - $ImpliedClosureNotes = @{ + $UnlockedWindows = New-Object System.Collections.Generic.List[object] + $OpeningActivities = @('logon', 'unlock', 'screensaver dismiss') + $ClosingActivities = @('logoff', 'lock', 'screensaver start', 'session disconnect', 'session reconnect', 'remote logon', 'sleep', 'shutdown', 'restart') + $RepeatedOpeningClosureDescriptions = @{ 'unlock' = 'lock time unknown' - 'session reconnect' = 'disconnect time unknown' 'screensaver dismiss' = 'screensaver start unknown' } @@ -753,16 +825,26 @@ Function Get-PossibleUseWindow { Continue } - If ($Activity -in @('logoff', 'lock', 'session disconnect', 'sleep', 'shutdown', 'restart')) { + If ($Activity -eq 'session reconnect' -and -not (Test-RemoteSessionReconnect -ActivityEvent $ActivityEvent)) { + Continue + } + + If ($Activity -in $ClosingActivities) { If ($null -ne $OpenWindow) { - $UseWindows.Add([PSCustomObject]@{ + $Note = '' + If ($Activity -eq 'session reconnect' -or $Activity -eq 'remote logon') { + $Note = 'local console unavailable during remote session' + } + + $UnlockedWindows.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') + Duration = Format-LocalUnlockedDuration -StartTime $OpenWindow.StartTime -EndTime $ActivityEvent.TimeCreated User = $OpenWindow.User Opened = $OpenWindow.Opened - Closed = Get-PossibleUseWindowAction -Activity $Activity - Note = '' + Closed = Get-LocalUnlockedWindowAction -Activity $Activity + Note = $Note }) | Out-Null $OpenWindow = $null } @@ -775,15 +857,16 @@ Function Get-PossibleUseWindow { } If ($Activity -in $OpeningActivities) { - If ($null -ne $OpenWindow -and $ImpliedClosureDescriptions.ContainsKey($Activity)) { - $UseWindows.Add([PSCustomObject]@{ + If ($null -ne $OpenWindow -and $RepeatedOpeningClosureDescriptions.ContainsKey($Activity)) { + $UnlockedWindows.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'))" + Duration = Format-LocalUnlockedDuration -StartTime $OpenWindow.StartTime -EndTime $ActivityEvent.TimeCreated -Maximum User = $OpenWindow.User Opened = $OpenWindow.Opened - Closed = $ImpliedClosureDescriptions[$Activity] - Note = $ImpliedClosureNotes[$Activity] + Closed = $RepeatedOpeningClosureDescriptions[$Activity] + Note = 'latest possible duration' }) | Out-Null $OpenWindow = $null } @@ -792,17 +875,18 @@ Function Get-PossibleUseWindow { $OpenWindow = [PSCustomObject]@{ StartTime = $ActivityEvent.TimeCreated User = $User - Opened = Get-PossibleUseWindowAction -Activity $Activity + Opened = Get-LocalUnlockedWindowAction -Activity $Activity } } } } If ($null -ne $OpenWindow) { - $UseWindows.Add([PSCustomObject]@{ + $UnlockedWindows.Add([PSCustomObject]@{ StartTime = $OpenWindow.StartTime Start = $OpenWindow.StartTime.ToString('yyyy-MM-dd HH:mm:ss') End = 'open through returned events' + Duration = 'open' User = $OpenWindow.User Opened = $OpenWindow.Opened Closed = 'not returned' @@ -810,25 +894,25 @@ Function Get-PossibleUseWindow { }) | Out-Null } - Return $UseWindows + Return $UnlockedWindows } -Function Write-PossibleUseWindowOutput { +Function Write-LocalUnlockedWindowOutput { Param( [AllowNull()] [object[]]$ActivityEvents ) - $UseWindows = @(Get-PossibleUseWindow -ActivityEvents $ActivityEvents | Sort-Object -Property StartTime) + $UnlockedWindows = @(Get-LocalUnlockedWindow -ActivityEvents $ActivityEvents | Sort-Object -Property StartTime) - If ($UseWindows.Count -eq 0) { - Write-Output 'Possible use windows inferred from returned events: none' + If ($UnlockedWindows.Count -eq 0) { + Write-Output 'Local unlocked windows inferred from returned events: none' Return } - $Rows = @($UseWindows | Select-Object Start,End,User,Opened,Closed,Note) - Write-Output 'Possible use windows inferred from returned events:' - Write-PlainTextTable -Rows $Rows -Columns @('Start', 'End', 'User', 'Opened', 'Closed', 'Note') + $Rows = @($UnlockedWindows | Select-Object Start,End,Duration,User,Opened,Closed,Note) + Write-Output 'Local unlocked windows inferred from returned events:' + Write-PlainTextTable -Rows $Rows -Columns @('Start', 'End', 'Duration', 'User', 'Opened', 'Closed', 'Note') } # Winlogon 7001/7002 tracks user session logon/logoff more reliably than Security 4624/4634. @@ -945,9 +1029,9 @@ ForEach ($PowerFilter in $PowerFilters) { $OutputEvents = @(Get-UniqueUserLogonActivity -ActivityEvents $ActivityEvents | Sort-Object -Property TimeCreated) Write-UserLogonActivityOutput -ActivityEvents $OutputEvents -StartDateTime $StartDateTime -EndDateTime $EndDateTime Try { - Write-PossibleUseWindowOutput -ActivityEvents $OutputEvents + Write-LocalUnlockedWindowOutput -ActivityEvents $OutputEvents } Catch { - $PossibleUseWindowError = Get-UserLogonActivityErrorMessage -ErrorRecord $_ -FallbackMessage 'Unable to infer possible use windows.' - Write-Output "Possible use windows inferred from returned events: unavailable. ${PossibleUseWindowError}" + $LocalUnlockedWindowError = Get-UserLogonActivityErrorMessage -ErrorRecord $_ -FallbackMessage 'Unable to infer local unlocked windows.' + Write-Output "Local unlocked windows inferred from returned events: unavailable. ${LocalUnlockedWindowError}" }