Skip to content

Instantly share code, notes, and snippets.

@hongymagic
Created April 22, 2025 07:01
Show Gist options
  • Save hongymagic/da77a83383b5e317f3a46519bce6ba87 to your computer and use it in GitHub Desktop.
Save hongymagic/da77a83383b5e317f3a46519bce6ba87 to your computer and use it in GitHub Desktop.
Check windows users' last logon and logoff times
param (
[Parameter(Mandatory=$true, HelpMessage="Enter the username in email format (e.g., [email protected])")]
[ValidatePattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")]
[string]$Username,
[Parameter(HelpMessage="Number of days to look back for events")]
[int]$DaysBack = 1,
[Parameter(HelpMessage="Use alternative methods that don't require admin privileges")]
[switch]$UseAlternativeMethods = $true
)
# Extract username part (before the @)
$usernameOnly = $Username.Split('@')[0]
# Display the username being searched
Write-Host "Searching for logon/logoff events for user: $Username" -ForegroundColor Cyan
Write-Host "Also searching for username part: $usernameOnly" -ForegroundColor Cyan
Write-Host "Looking back $DaysBack days" -ForegroundColor Cyan
# Check if running as administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Warning "This script is not running with administrator privileges."
Write-Warning "Security logs may not be accessible without admin rights."
Write-Warning "Try running PowerShell as Administrator and run this script again."
}
# Function to check if an event matches our username
function Test-EventForUsername {
param($event, $fullUsername, $shortUsername)
# Convert event to XML for easier property access
$eventXml = [xml]$event.ToXml()
$eventData = $eventXml.Event.EventData.Data
# Check for username in various formats
foreach ($data in $eventData) {
$value = $data.'#text'
if ($value -eq $fullUsername -or
$value -eq $shortUsername -or
$value -like "*\$shortUsername" -or
$value -like "*@*" -and $value -match [regex]::Escape($shortUsername)) {
return $true
}
}
# Also check raw properties
foreach ($prop in $event.Properties) {
$value = $prop.Value
if ($value -is [string] -and (
$value -eq $fullUsername -or
$value -eq $shortUsername -or
$value -like "*\$shortUsername" -or
$value -like "*@*" -and $value -match [regex]::Escape($shortUsername))) {
return $true
}
}
return $false
}
# Function to get logon type description
function Get-LogonTypeDescription {
param([int]$logonType)
switch ($logonType) {
2 { "Interactive (Local logon)" }
3 { "Network (Remote logon)" }
4 { "Batch (Scheduled task)" }
5 { "Service (Service startup)" }
7 { "Unlock (Screen unlock)" }
8 { "NetworkCleartext (Network logon with cleartext credentials)" }
9 { "NewCredentials (RunAs using alternate credentials)" }
10 { "RemoteInteractive (Terminal Services, Remote Desktop)" }
11 { "CachedInteractive (Cached domain credentials)" }
default { "Other ($logonType)" }
}
}
# Function to get user sessions using Security event logs (requires admin)
function Get-UserSessionsFromEventLog {
param(
$username,
$usernameOnly,
$daysBack,
[switch]$IncludeSystemLogons = $false
)
$startTime = (Get-Date).AddDays(-$daysBack)
$logonEvents = @()
$logoffEvents = @()
try {
Write-Host "Retrieving Security events since $($startTime.ToString('yyyy-MM-dd HH:mm:ss'))..." -ForegroundColor Yellow
# First get all logon events to analyze
$allLogonEvents = Get-WinEvent -FilterHashtable @{
LogName='Security';
ID=4624;
StartTime=$startTime
} -MaxEvents 200 -ErrorAction Stop
Write-Host "Found $($allLogonEvents.Count) total logon events to analyze" -ForegroundColor Yellow
# Filter events for our username and extract logon type
$logonEvents = @()
foreach ($event in $allLogonEvents) {
if (Test-EventForUsername -event $event -fullUsername $username -shortUsername $usernameOnly) {
# Extract logon type from event
$eventXml = [xml]$event.ToXml()
$logonType = $null
foreach ($data in $eventXml.Event.EventData.Data) {
if ($data.Name -eq "LogonType") {
$logonType = [int]$data.'#text'
break
}
}
# Skip system logons unless specifically requested
if (-not $IncludeSystemLogons -and ($logonType -eq 4 -or $logonType -eq 5)) {
continue
}
# Add logon type to event as a note property
$event | Add-Member -NotePropertyName LogonType -NotePropertyValue $logonType -Force
$event | Add-Member -NotePropertyName LogonTypeDescription -NotePropertyValue (Get-LogonTypeDescription -logonType $logonType) -Force
$logonEvents += $event
}
}
# Get logoff events
$allLogoffEvents = Get-WinEvent -FilterHashtable @{
LogName='Security';
ID=4634;
StartTime=$startTime
} -MaxEvents 200 -ErrorAction Stop
Write-Host "Found $($allLogoffEvents.Count) total logoff events to analyze" -ForegroundColor Yellow
$logoffEvents = $allLogoffEvents | Where-Object {
Test-EventForUsername -event $_ -fullUsername $username -shortUsername $usernameOnly
}
return @{
LogonEvents = $logonEvents
LogoffEvents = $logoffEvents
Success = $true
}
}
catch {
Write-Warning "Error accessing Security event logs: $_"
# Try with a smaller set of events as a fallback
try {
Write-Host "No exact matches found. Analyzing sample events to help troubleshoot..." -ForegroundColor Yellow
# Get a few sample events to analyze
$sampleEvents = Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4624} -MaxEvents 5 -ErrorAction SilentlyContinue
if ($sampleEvents.Count -gt 0) {
Write-Host "Sample event analysis:" -ForegroundColor Magenta
$sampleEvent = $sampleEvents[0]
# Convert to XML for better analysis
$eventXml = [xml]$sampleEvent.ToXml()
$eventData = $eventXml.Event.EventData.Data
Write-Host "Event data fields from sample logon event:" -ForegroundColor Magenta
foreach ($data in $eventData) {
$name = $data.Name
$value = $data.'#text'
Write-Host " $name : $value"
# Check if this might be a username field
if ($value -like "*@*" -or $value -like "*\*") {
Write-Host " ^ This field might contain a username" -ForegroundColor Green
}
}
# Also show raw properties for comparison
Write-Host "`nRaw properties from sample logon event:" -ForegroundColor Magenta
for ($i = 0; $i -lt $sampleEvent.Properties.Count; $i++) {
$value = $sampleEvent.Properties[$i].Value
Write-Host " Property[$i]: $value"
}
}
}
catch {
Write-Error "Cannot access Security logs at all. Run as Administrator."
}
return @{
LogonEvents = @()
LogoffEvents = @()
Success = $false
Error = $_
}
}
}
# Function to get user sessions using WMI (alternative method)
function Get-UserSessionsFromWMI {
param($username, $usernameOnly, $daysBack)
Write-Host "Using WMI to get user session information..." -ForegroundColor Yellow
$sessions = @()
$startTime = (Get-Date).AddDays(-$daysBack)
try {
# Get all user profiles
$userProfiles = Get-WmiObject -Class Win32_UserProfile | Where-Object {
$_.LastUseTime -and [Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) -gt $startTime
}
foreach ($profile in $userProfiles) {
# Try to get the username from the SID
try {
$sid = New-Object System.Security.Principal.SecurityIdentifier($profile.SID)
$user = $sid.Translate([System.Security.Principal.NTAccount])
$profileUsername = $user.Value
# Check if this profile matches our target user
if ($profileUsername -like "*$usernameOnly*" -or
$profileUsername -eq $username -or
$profileUsername -like "*\$usernameOnly") {
$lastUseTime = [Management.ManagementDateTimeConverter]::ToDateTime($profile.LastUseTime)
$sessions += [PSCustomObject]@{
Username = $profileUsername
LogonTime = $lastUseTime
LogoffTime = $null # We don't have logoff time from this method
Source = "UserProfile"
}
}
}
catch {
# Skip profiles where we can't resolve the username
continue
}
}
# Also try to get currently logged on users
$loggedOnUsers = Get-WmiObject -Class Win32_ComputerSystem | Select-Object -ExpandProperty UserName
foreach ($loggedOnUser in $loggedOnUsers) {
if ($loggedOnUser -like "*$usernameOnly*" -or
$loggedOnUser -eq $username -or
$loggedOnUser -like "*\$usernameOnly") {
$sessions += [PSCustomObject]@{
Username = $loggedOnUser
LogonTime = Get-Date # We don't know the exact logon time
LogoffTime = $null # Currently logged in
Source = "CurrentlyLoggedOn"
}
}
}
# Try to get logon sessions
$logonSessions = Get-WmiObject -Class Win32_LogonSession | Where-Object { $_.LogonType -eq 2 -or $_.LogonType -eq 10 }
foreach ($session in $logonSessions) {
$logonUser = Get-WmiObject -Class Win32_LoggedOnUser | Where-Object { $_.Dependent.Path -like "*$($session.LogonId)*" }
if ($logonUser) {
$userAccountPath = $logonUser.Antecedent.Path
$userAccountName = ($userAccountPath -split '"')[1]
if ($userAccountName -like "*$usernameOnly*" -or
$userAccountName -eq $username -or
$userAccountName -like "*\$usernameOnly") {
$startTime = [Management.ManagementDateTimeConverter]::ToDateTime($session.StartTime)
$sessions += [PSCustomObject]@{
Username = $userAccountName
LogonTime = $startTime
LogoffTime = $null # Currently logged in
Source = "LogonSession"
}
}
}
}
return @{
Sessions = $sessions
Success = $true
}
}
catch {
Write-Warning "Error accessing WMI information: $_"
return @{
Sessions = @()
Success = $false
Error = $_
}
}
}
# Try to get user sessions
$logonEvents = @()
$logoffEvents = @()
$wmiSessions = @()
$foundSessions = $false
# First try Security event logs if we're admin
if ($isAdmin -or -not $UseAlternativeMethods) {
Write-Host "Attempting to use Security event logs (requires admin)..." -ForegroundColor Yellow
$eventLogResult = Get-UserSessionsFromEventLog -username $Username -usernameOnly $usernameOnly -daysBack $DaysBack
if ($eventLogResult.Success -and ($eventLogResult.LogonEvents.Count -gt 0 -or $eventLogResult.LogoffEvents.Count -gt 0)) {
$logonEvents = $eventLogResult.LogonEvents
$logoffEvents = $eventLogResult.LogoffEvents
$foundSessions = $true
}
}
# If we didn't find anything or aren't admin, try alternative methods
if ((-not $foundSessions -or $UseAlternativeMethods) -and -not $isAdmin) {
Write-Host "Attempting to use alternative methods (WMI)..." -ForegroundColor Yellow
$wmiResult = Get-UserSessionsFromWMI -username $Username -usernameOnly $usernameOnly -daysBack $DaysBack
if ($wmiResult.Success -and $wmiResult.Sessions.Count -gt 0) {
$wmiSessions = $wmiResult.Sessions
$foundSessions = $true
}
}
# Check if we found any sessions
if (-not $foundSessions) {
Write-Warning "No sessions found for user '$Username' or '$usernameOnly'"
Write-Host "`nTroubleshooting tips:" -ForegroundColor Cyan
Write-Host "1. Try running as Administrator" -ForegroundColor Cyan
Write-Host "2. Try increasing the days to look back with -DaysBack parameter" -ForegroundColor Cyan
Write-Host "3. The username might be in a different format in the logs (domain\\username)" -ForegroundColor Cyan
Write-Host "4. Check if Security auditing is enabled in Group Policy" -ForegroundColor Cyan
exit
}
# Display results from Event Logs if available
if ($logonEvents.Count -gt 0 -or $logoffEvents.Count -gt 0) {
Write-Host "`nSuccess! Found $($logonEvents.Count) logon events and $($logoffEvents.Count) logoff events for user '$Username'" -ForegroundColor Green
# Show the found events
if ($logonEvents.Count -gt 0) {
Write-Host "`nLogon events found:" -ForegroundColor Green
foreach ($event in $logonEvents) {
Write-Host " $($event.TimeCreated) - ID: $($event.Id) - RecordId: $($event.RecordId) - Type: $($event.LogonTypeDescription)"
}
}
# Group logon events by session ID if available
$sessionGroups = @{}
foreach ($logon in $logonEvents) {
$eventXml = [xml]$logon.ToXml()
$sessionId = $null
foreach ($data in $eventXml.Event.EventData.Data) {
if ($data.Name -eq "TargetLogonId") {
$sessionId = $data.'#text'
break
}
}
if ($sessionId) {
if (-not $sessionGroups.ContainsKey($sessionId)) {
$sessionGroups[$sessionId] = @{
Logon = $logon
Logoffs = @()
}
}
}
}
# Match logoff events to sessions
foreach ($logoff in $logoffEvents) {
$eventXml = [xml]$logoff.ToXml()
$sessionId = $null
foreach ($data in $eventXml.Event.EventData.Data) {
if ($data.Name -eq "TargetLogonId") {
$sessionId = $data.'#text'
break
}
}
if ($sessionId -and $sessionGroups.ContainsKey($sessionId)) {
$sessionGroups[$sessionId].Logoffs += $logoff
}
}
# Process logon events to match with logoff events
Write-Host "`nUser sessions:" -ForegroundColor Green
# First try to use session IDs for matching
$sessionsFound = $false
foreach ($sessionId in $sessionGroups.Keys) {
$session = $sessionGroups[$sessionId]
$logon = $session.Logon
$logoffs = $session.Logoffs | Sort-Object TimeCreated
if ($logoffs.Count -gt 0) {
$logoff = $logoffs[0] # Take the first logoff event
$duration = $logoff.TimeCreated - $logon.TimeCreated
# Only show sessions with a reasonable duration (more than 1 second)
if ($duration.TotalSeconds -gt 1) {
$formattedDuration = "{0:D2}:{1:D2}:{2:D2}" -f $duration.Hours, $duration.Minutes, $duration.Seconds
Write-Host " Session ID: $sessionId" -ForegroundColor Yellow
Write-Host " Logon: $($logon.TimeCreated) - Type: $($logon.LogonTypeDescription)"
Write-Host " Logoff: $($logoff.TimeCreated)"
Write-Host " Duration: $formattedDuration"
Write-Host ""
$sessionsFound = $true
}
}
else {
# No logoff found, user might still be logged in
Write-Host " Session ID: $sessionId" -ForegroundColor Yellow
Write-Host " Logon: $($logon.TimeCreated) - Type: $($logon.LogonTypeDescription)"
Write-Host " Status: Still logged in"
Write-Host ""
$sessionsFound = $true
}
}
# If no sessions were found using session IDs, fall back to time-based matching
if (-not $sessionsFound) {
Write-Host " No sessions found using session IDs. Using time-based matching:" -ForegroundColor Yellow
foreach ($logon in $logonEvents) {
# Skip system or service logons
if ($logon.LogonType -eq 4 -or $logon.LogonType -eq 5) {
continue
}
$logoff = $logoffEvents | Where-Object { $_.TimeCreated -gt $logon.TimeCreated } | Sort-Object TimeCreated | Select-Object -First 1
if ($logoff) {
$duration = $logoff.TimeCreated - $logon.TimeCreated
# Only show sessions with a reasonable duration (more than 1 second)
if ($duration.TotalSeconds -gt 1) {
$formattedDuration = "{0:D2}:{1:D2}:{2:D2}" -f $duration.Hours, $duration.Minutes, $duration.Seconds
Write-Host " Logon: $($logon.TimeCreated) - Type: $($logon.LogonTypeDescription)"
Write-Host " Logoff: $($logoff.TimeCreated)"
Write-Host " Duration: $formattedDuration"
Write-Host ""
}
}
else {
Write-Host " Logon: $($logon.TimeCreated) - Type: $($logon.LogonTypeDescription)"
Write-Host " Status: Still logged in or no matching logoff event found"
Write-Host ""
}
}
}
# If we found logon events but no valid sessions, show a message
if ($logonEvents.Count -gt 0 -and -not $sessionsFound) {
Write-Host "`nNote: Found logon/logoff events but no valid user sessions with reasonable duration." -ForegroundColor Yellow
Write-Host "This might be because:" -ForegroundColor Yellow
Write-Host "1. The events are related to system processes rather than actual user logons" -ForegroundColor Yellow
Write-Host "2. The logon and logoff events occurred very close together (less than 1 second apart)" -ForegroundColor Yellow
Write-Host "3. The logoff events don't properly match with the logon events" -ForegroundColor Yellow
# Show raw events for debugging
Write-Host "`nRaw logon events for reference:" -ForegroundColor Magenta
foreach ($event in $logonEvents) {
Write-Host " $($event.TimeCreated) - ID: $($event.Id) - Type: $($event.LogonTypeDescription) - RecordId: $($event.RecordId)"
}
}
}
# Display results from WMI if available
if ($wmiSessions.Count -gt 0) {
Write-Host "`nSuccess! Found $($wmiSessions.Count) user sessions from WMI for user '$Username'" -ForegroundColor Green
# Show the found sessions
Write-Host "`nUser sessions found (from WMI):" -ForegroundColor Green
foreach ($session in $wmiSessions) {
$status = if ($session.LogoffTime) { "Logged off" } else { "Still logged in" }
Write-Host " Username: $($session.Username)"
Write-Host " Logon Time: $($session.LogonTime)"
Write-Host " Status: $status"
Write-Host " Source: $($session.Source)"
Write-Host ""
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment