diff --git a/Get-UserLastLogonComputer.ps1 b/Get-UserLastLogonComputer.ps1 index eb0b346..92a3404 100644 --- a/Get-UserLastLogonComputer.ps1 +++ b/Get-UserLastLogonComputer.ps1 @@ -4,8 +4,9 @@ Exports a list of users and the last PC they logged into. .DESCRIPTION - Queries Domain Controller security event logs for interactive logon events (4624) + Queries Domain Controller security event logs for logon events (4624) to determine which computer each user last authenticated from. + Resolves IP addresses to computer names for network logons. .PARAMETER OutputPath Path for the output CSV file. Defaults to current directory. @@ -13,8 +14,11 @@ .PARAMETER DaysBack Number of days of event logs to search. Default is 7. -.PARAMETER LogonTypes - Array of logon types to include. Default is 2 (Interactive) and 10 (RemoteInteractive/RDP). +.PARAMETER IncludeNetworkLogons + Include Type 3 (Network) logons. Default is true. + +.PARAMETER ResolveIPs + Attempt to resolve IP addresses to hostnames. Default is true. .EXAMPLE .\Get-UserLastLogonComputer.ps1 @@ -25,6 +29,7 @@ .NOTES Must be run on a Domain Controller with appropriate permissions to read Security logs. Logon Type 2 = Interactive (console) + Logon Type 3 = Network (file shares, etc.) Logon Type 10 = RemoteInteractive (RDP) Logon Type 11 = CachedInteractive #> @@ -38,9 +43,18 @@ param( [int]$DaysBack = 7, [Parameter()] - [int[]]$LogonTypes = @(2, 10, 11) + [switch]$ExcludeNetworkLogons, + + [Parameter()] + [switch]$SkipIPResolve ) +# Build logon types list +$LogonTypes = @(2, 10, 11) # Interactive, RDP, Cached +if (-not $ExcludeNetworkLogons) { + $LogonTypes += 3 # Network +} + $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $CsvFile = Join-Path $OutputPath "UserLastLogonComputer_$Timestamp.csv" @@ -50,10 +64,46 @@ Write-Host "" $StartDate = (Get-Date).AddDays(-$DaysBack) +# Cache for IP to hostname resolution +$IPCache = @{} + +function Resolve-IPToHostname { + param([string]$IP) + + if ([string]::IsNullOrWhiteSpace($IP) -or $IP -eq '-' -or $IP -eq '::1' -or $IP -eq '127.0.0.1') { + return $null + } + + # Check cache first + if ($IPCache.ContainsKey($IP)) { + return $IPCache[$IP] + } + + try { + # Try DNS reverse lookup + $hostname = [System.Net.Dns]::GetHostEntry($IP).HostName + # Extract just the computer name (before the domain) + $computerName = ($hostname -split '\.')[0].ToUpper() + $IPCache[$IP] = $computerName + return $computerName + } catch { + # Try querying AD for the IP + try { + $computer = Get-ADComputer -Filter "IPv4Address -eq '$IP'" -Properties Name -ErrorAction SilentlyContinue + if ($computer) { + $IPCache[$IP] = $computer.Name + return $computer.Name + } + } catch {} + + $IPCache[$IP] = $null + return $null + } +} + try { Write-Host "Retrieving logon events from Security log..." -ForegroundColor Yellow - # Use hashtable filter - more reliable than XPath for date filtering $FilterHash = @{ LogName = 'Security' ID = 4624 @@ -66,8 +116,14 @@ try { $UserLogons = @{} $ProcessedCount = 0 + $i = 0 foreach ($Event in $Events) { + $i++ + if ($i % 5000 -eq 0) { + Write-Host " Processing event $i of $($Events.Count)..." -ForegroundColor Gray + } + $xml = [xml]$Event.ToXml() $LogonType = [int]($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'LogonType' }).'#text' @@ -78,16 +134,30 @@ try { $Username = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text' $Domain = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetDomainName' }).'#text' $Workstation = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'WorkstationName' }).'#text' + $IPAddress = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' }).'#text' $LogonTime = $Event.TimeCreated # Filter out computer accounts and system accounts if ($Username -match '\$$') { continue } - if ($Username -in @('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE', 'DWM-1', 'DWM-2', 'DWM-3', 'UMFD-0', 'UMFD-1', 'UMFD-2', 'ANONYMOUS LOGON', '-')) { continue } + if ($Username -in @('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE', 'DWM-1', 'DWM-2', 'DWM-3', 'DWM-4', 'UMFD-0', 'UMFD-1', 'UMFD-2', 'UMFD-3', 'ANONYMOUS LOGON', '-')) { continue } if ($Domain -in @('Window Manager', 'Font Driver Host', 'NT AUTHORITY')) { continue } if ([string]::IsNullOrWhiteSpace($Username)) { continue } - if ([string]::IsNullOrWhiteSpace($Workstation)) { - $Workstation = "Unknown" + # Determine computer name - try WorkstationName first, then resolve IP + $Computer = $null + if (-not [string]::IsNullOrWhiteSpace($Workstation) -and $Workstation -ne '-') { + $Computer = $Workstation.ToUpper() + } elseif (-not $SkipIPResolve -and -not [string]::IsNullOrWhiteSpace($IPAddress)) { + $Computer = Resolve-IPToHostname -IP $IPAddress + } + + if ([string]::IsNullOrWhiteSpace($Computer)) { + # Store IP if we couldn't resolve + if (-not [string]::IsNullOrWhiteSpace($IPAddress) -and $IPAddress -ne '-') { + $Computer = "[$IPAddress]" + } else { + $Computer = "Unknown" + } } $UserKey = "$Domain\$Username" @@ -98,10 +168,12 @@ try { $UserLogons[$UserKey] = [PSCustomObject]@{ Domain = $Domain Username = $Username - Computer = $Workstation.ToUpper() + Computer = $Computer + IPAddress = if ($IPAddress -and $IPAddress -ne '-') { $IPAddress } else { '' } LogonTime = $LogonTime LogonType = switch ($LogonType) { 2 { "Interactive" } + 3 { "Network" } 10 { "RDP" } 11 { "Cached" } default { "Type $LogonType" } @@ -116,15 +188,11 @@ try { if ($Results.Count -eq 0) { Write-Host "`nNo user logon events found matching criteria." -ForegroundColor Yellow - Write-Host "This could mean:" - Write-Host " - No interactive/RDP logons in the last $DaysBack days" - Write-Host " - Audit policy may not be logging logon events" - Write-Host " - Try increasing -DaysBack value" return } # Export to CSV - $Results | Select-Object Domain, Username, Computer, LogonTime, LogonType | + $Results | Select-Object Domain, Username, Computer, IPAddress, LogonTime, LogonType | Export-Csv -Path $CsvFile -NoTypeInformation -Encoding UTF8 Write-Host "`n===== Results =====" -ForegroundColor Green @@ -133,18 +201,11 @@ try { Write-Host "" # Display summary table - $Results | Format-Table -AutoSize + $Results | Format-Table Domain, Username, Computer, LogonTime, LogonType -AutoSize } catch [System.Exception] { if ($_.Exception.Message -match "No events were found") { Write-Host "`nNo logon events (Event ID 4624) found in the last $DaysBack days." -ForegroundColor Yellow - Write-Host "`nPossible causes:" -ForegroundColor Cyan - Write-Host " 1. Audit policy not enabled - Run: auditpol /get /category:`"Logon/Logoff`"" - Write-Host " 2. Security log was cleared recently" - Write-Host " 3. Try a larger -DaysBack value" - Write-Host "" - Write-Host "To enable logon auditing:" -ForegroundColor Cyan - Write-Host " auditpol /set /subcategory:`"Logon`" /success:enable" } elseif ($_.Exception.Message -match "Access is denied") { Write-Host "`nError: Access denied. Run PowerShell as Administrator." -ForegroundColor Red } else {