Files
AD-ComputerLastLogon/Get-UserLastLogonComputer.ps1

240 lines
8.0 KiB
PowerShell

#Requires -Modules ActiveDirectory
<#
.SYNOPSIS
Exports a list of users and the last PC they logged into.
.DESCRIPTION
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 and looks up user display names.
.PARAMETER OutputPath
Path for the output CSV file. Defaults to current directory.
.PARAMETER DaysBack
Number of days of event logs to search. Default is 7.
.PARAMETER ExcludeNetworkLogons
Exclude Type 3 (Network) logons. Default includes them.
.PARAMETER SkipIPResolve
Skip IP to hostname resolution. Faster but less useful.
.EXAMPLE
.\Get-UserLastLogonComputer.ps1
.EXAMPLE
.\Get-UserLastLogonComputer.ps1 -OutputPath "C:\Reports" -DaysBack 30
.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
#>
[CmdletBinding()]
param(
[Parameter()]
[string]$OutputPath = (Get-Location).Path,
[Parameter()]
[int]$DaysBack = 7,
[Parameter()]
[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"
Write-Host "`nQuerying security event logs for the last $DaysBack days..." -ForegroundColor Cyan
Write-Host "Looking for logon types: $($LogonTypes -join ', ')" -ForegroundColor Cyan
Write-Host ""
$StartDate = (Get-Date).AddDays(-$DaysBack)
# Cache for IP to hostname resolution
$IPCache = @{}
# Cache for user display names
$UserCache = @{}
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
}
if ($IPCache.ContainsKey($IP)) {
return $IPCache[$IP]
}
try {
$hostname = [System.Net.Dns]::GetHostEntry($IP).HostName
$computerName = ($hostname -split '\.')[0].ToUpper()
$IPCache[$IP] = $computerName
return $computerName
} catch {
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
}
}
function Get-UserDisplayName {
param([string]$Username, [string]$Domain)
$UserKey = "$Domain\$Username"
if ($UserCache.ContainsKey($UserKey)) {
return $UserCache[$UserKey]
}
try {
$User = Get-ADUser -Identity $Username -Properties DisplayName -ErrorAction SilentlyContinue
if ($User -and $User.DisplayName) {
$UserCache[$UserKey] = $User.DisplayName
return $User.DisplayName
}
} catch {}
$UserCache[$UserKey] = ''
return ''
}
try {
Write-Host "Retrieving logon events from Security log..." -ForegroundColor Yellow
$FilterHash = @{
LogName = 'Security'
ID = 4624
StartTime = $StartDate
}
$Events = Get-WinEvent -FilterHashtable $FilterHash -ErrorAction Stop
Write-Host "Found $($Events.Count) total 4624 events. Filtering..." -ForegroundColor Yellow
$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'
if ($LogonType -notin $LogonTypes) { continue }
$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', '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 }
# Determine computer name
$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)) {
if (-not [string]::IsNullOrWhiteSpace($IPAddress) -and $IPAddress -ne '-') {
$Computer = "[$IPAddress]"
} else {
$Computer = "Unknown"
}
}
$UserKey = "$Domain\$Username"
$ProcessedCount++
# Keep only the most recent logon for each user
if (-not $UserLogons.ContainsKey($UserKey) -or $LogonTime -gt $UserLogons[$UserKey].LogonTime) {
$UserLogons[$UserKey] = [PSCustomObject]@{
Domain = $Domain
Username = $Username
DisplayName = '' # Will populate after
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" }
}
}
}
}
Write-Host "Processed $ProcessedCount matching logon events." -ForegroundColor Yellow
Write-Host "Looking up display names for $($UserLogons.Count) users..." -ForegroundColor Yellow
# Populate display names
foreach ($Key in $UserLogons.Keys) {
$User = $UserLogons[$Key]
$User.DisplayName = Get-UserDisplayName -Username $User.Username -Domain $User.Domain
}
$Results = $UserLogons.Values | Sort-Object Domain, Username
if ($Results.Count -eq 0) {
Write-Host "`nNo user logon events found matching criteria." -ForegroundColor Yellow
return
}
# Export to CSV
$Results | Select-Object Domain, Username, DisplayName, Computer, IPAddress, LogonTime, LogonType |
Export-Csv -Path $CsvFile -NoTypeInformation -Encoding UTF8
Write-Host "`n===== Results =====" -ForegroundColor Green
Write-Host "Total users found: $($Results.Count)"
Write-Host "Output saved to: $CsvFile"
Write-Host ""
# Display summary table
$Results | Format-Table Domain, Username, DisplayName, 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
} elseif ($_.Exception.Message -match "Access is denied") {
Write-Host "`nError: Access denied. Run PowerShell as Administrator." -ForegroundColor Red
} else {
Write-Host "`nError: $($_.Exception.Message)" -ForegroundColor Red
}
}