added Get-UserLogonActivity.ps1

This commit is contained in:
2026-07-07 14:45:18 -04:00
parent 91c3bc858d
commit 26191c9e58
+215
View File
@@ -0,0 +1,215 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Gets user logon, logoff, lock, and unlock activity from the Windows Security log.
.EXAMPLE
$Start = '2026-07-03T00:00:00Z'
$End = '2026-07-06T00:00:00Z'
.\Get-UserLogonActivity.ps1
#>
If (-not (Get-Variable -Name Start -ErrorAction SilentlyContinue)) {
Write-Error '$Start must be defined by the caller. Use ISO 8601 UTC format: yyyy-MM-ddTHH:mm:ssZ. Example: 2026-07-03T00:00:00Z'
Exit 1
}
If (-not (Get-Variable -Name End -ErrorAction SilentlyContinue)) {
Write-Error '$End must be defined by the caller. Use ISO 8601 UTC format: yyyy-MM-ddTHH:mm:ssZ. Example: 2026-07-06T00:00:00Z'
Exit 1
}
Function ConvertTo-ActivityDateTime {
Param(
[Parameter(Mandatory=$true)]
[AllowNull()]
[object]$Value,
[Parameter(Mandatory=$true)]
[string]$ParameterName
)
If ($Value -is [DateTimeOffset]) {
Return $Value
}
If ($Value -is [DateTime]) {
Return [DateTimeOffset]$Value
}
$TrimmedValue = ([string]$Value).Trim()
$Culture = [Globalization.CultureInfo]::InvariantCulture
$ParsedDateTime = [DateTimeOffset]::MinValue
$UtcFormats = [string[]]@(
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.FFFFFFF'Z'"
)
$OffsetFormats = [string[]]@(
"yyyy-MM-dd'T'HH:mm:sszzz",
"yyyy-MM-dd'T'HH:mm:ss.FFFFFFFzzz"
)
$LocalFormats = [string[]]@(
"yyyy-MM-dd'T'HH:mm:ss",
"yyyy-MM-dd'T'HH:mm:ss.FFFFFFF",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd",
"MM/dd/yyyy HH:mm:ss",
"MM/dd/yyyy"
)
$UtcStyles = [Globalization.DateTimeStyles]::AllowWhiteSpaces -bor [Globalization.DateTimeStyles]::AssumeUniversal -bor [Globalization.DateTimeStyles]::AdjustToUniversal
$LocalStyles = [Globalization.DateTimeStyles]::AllowWhiteSpaces -bor [Globalization.DateTimeStyles]::AssumeLocal
If ([DateTimeOffset]::TryParseExact($TrimmedValue, $UtcFormats, $Culture, $UtcStyles, [ref]$ParsedDateTime)) {
Return $ParsedDateTime
}
If ([DateTimeOffset]::TryParseExact($TrimmedValue, $OffsetFormats, $Culture, [Globalization.DateTimeStyles]::AllowWhiteSpaces, [ref]$ParsedDateTime)) {
Return $ParsedDateTime
}
If ([DateTimeOffset]::TryParseExact($TrimmedValue, $LocalFormats, $Culture, $LocalStyles, [ref]$ParsedDateTime)) {
Return $ParsedDateTime
}
If ([DateTimeOffset]::TryParse($TrimmedValue, $Culture, $LocalStyles, [ref]$ParsedDateTime)) {
Return $ParsedDateTime
}
Throw "Invalid -${ParameterName} datetime '${Value}'. Use ISO 8601 UTC format: yyyy-MM-ddTHH:mm:ssZ. Example: 2026-07-03T00:00:00Z"
}
Function Get-EventDataValue {
Param(
[Parameter(Mandatory=$true)]
[xml]$EventXml,
[Parameter(Mandatory=$true)]
[string]$Name
)
$Data = $EventXml.Event.EventData.Data | Where-Object { $_.Name -eq $Name } | Select-Object -First 1
If ($null -eq $Data) { Return $null }
Return [string]$Data.'#text'
}
Function Test-UserLogonActivityUser {
Param(
[AllowNull()]
[string]$UserName
)
If ([string]::IsNullOrWhiteSpace($UserName)) { Return $false }
If ($UserName -eq '-') { Return $false }
If ($UserName.EndsWith('$')) { Return $false }
If ($UserName -in @('ANONYMOUS LOGON', 'LOCAL SERVICE', 'NETWORK SERVICE', 'SYSTEM')) { Return $false }
If ($UserName -match '^(DWM|UMFD)-\d+$') { Return $false }
Return $true
}
Function Format-UserName {
Param(
[AllowNull()]
[string]$DomainName,
[Parameter(Mandatory=$true)]
[string]$UserName
)
If ([string]::IsNullOrWhiteSpace($DomainName) -or $DomainName -eq '-') {
Return $UserName
}
Return "${DomainName}\${UserName}"
}
Function ConvertTo-UserLogonActivity {
Param(
[Parameter(Mandatory=$true)]
[object]$Event
)
$EventXml = [xml]$Event.ToXml()
$UserName = Get-EventDataValue -EventXml $EventXml -Name 'TargetUserName'
$DomainName = Get-EventDataValue -EventXml $EventXml -Name 'TargetDomainName'
$LogonType = Get-EventDataValue -EventXml $EventXml -Name 'LogonType'
$Activity = $null
Switch ($Event.Id) {
4624 {
If ($LogonType -notin @('2', '10', '11')) { Return $null }
$Activity = 'logon'
}
4634 {
If ($LogonType -notin @('2', '10', '11')) { Return $null }
$Activity = 'logoff'
}
4647 {
$Activity = 'logoff'
}
4800 {
$Activity = 'lock'
}
4801 {
$Activity = 'unlock'
}
Default {
Return $null
}
}
If (-not (Test-UserLogonActivityUser -UserName $UserName)) { Return $null }
$EventTime = $Event.TimeCreated
[PSCustomObject]@{
Date = $EventTime.ToString('yyyy-MM-dd')
Time = $EventTime.ToString('HH:mm:ss')
User = Format-UserName -DomainName $DomainName -UserName $UserName
Activity = $Activity
}
}
Try {
$StartDateTime = ConvertTo-ActivityDateTime -Value $Start -ParameterName 'Start'
$EndDateTime = ConvertTo-ActivityDateTime -Value $End -ParameterName 'End'
}
Catch {
Write-Error $_.Exception.Message
Exit 1
}
If ($EndDateTime -le $StartDateTime) {
Write-Error "-End must be later than -Start."
Exit 1
}
$Filter = @{
LogName = 'Security'
Id = @(4624, 4634, 4647, 4800, 4801)
StartTime = $StartDateTime.LocalDateTime
EndTime = $EndDateTime.LocalDateTime
}
Try {
$Events = Get-WinEvent -FilterHashtable $Filter -ErrorAction Stop
}
Catch {
If ($_.FullyQualifiedErrorId -like '*NoMatchingEventsFound*' -or $_.Exception.Message -like '*No events were found*') {
$Events = @()
}
Else {
Write-Error "Unable to read the Windows Security event log. Run PowerShell as administrator if access is denied. $($_.Exception.Message)"
Exit 1
}
}
ForEach ($Event in ($Events | Sort-Object -Property TimeCreated)) {
ConvertTo-UserLogonActivity -Event $Event
}