Include Type 3 network logons, resolve IP to hostname
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user