<# .SYNOPSIS Report on inactive Active Directory user accounts based on true LastLogon .DESCRIPTION Queries all domain controllers to find the most recent LastLogon time for users. The LastLogon attribute is not replicated, so each DC must be queried individually to get accurate results. Identifies accounts inactive for specified days. .PARAMETER InactiveDays Number of days without logon to consider account inactive (default: 90) .PARAMETER OutputFolder Destination folder for reports. Default: .\InactiveADUsers- .PARAMETER SearchBase Optional OU to limit search scope (default: entire domain) .PARAMETER IncludeDisabled Include disabled accounts in the report (default: $false) .NOTES ⚠️ AI-GENERATED SCRIPT - UNTESTED This script was generated by Claude AI and has not been tested in production. Review and test thoroughly in a non-production environment before use. - Requires Active Directory PowerShell module - Run with appropriate AD permissions - Can be slow in large environments (queries each DC) - LastLogon attribute is not replicated between DCs .EXAMPLE .\Get-ADUserLastLogon.ps1 .EXAMPLE .\Get-ADUserLastLogon.ps1 -InactiveDays 180 -SearchBase "OU=Users,DC=domain,DC=com" #> [CmdletBinding()] param( [int]$InactiveDays = 90, [string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("InactiveADUsers-" + (Get-Date -Format "yyyyMMdd-HHmm"))), [string]$SearchBase = "", [bool]$IncludeDisabled = $false ) function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow Write-Host "[$(NowTag)] Starting AD user lastlogon analysis..." -ForegroundColor Green # Import AD module try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Host "[$(NowTag)] ERROR: Active Directory module not available" -ForegroundColor Red exit 1 } # Create output folder New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null $cutoffDate = (Get-Date).AddDays(-$InactiveDays) Write-Host "[$(NowTag)] Cutoff date: $($cutoffDate.ToString('yyyy-MM-dd'))" # Get all domain controllers Write-Host "[$(NowTag)] Discovering domain controllers..." $domainControllers = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName Write-Host "[$(NowTag)] Found $($domainControllers.Count) domain controller(s)" # Get all users Write-Host "[$(NowTag)] Retrieving AD users..." $getUserParams = @{ Filter = "ObjectClass -eq 'user' -and Enabled -eq 'True'" Properties = @('LastLogon', 'LastLogonDate', 'WhenCreated', 'Department', 'Title', 'Description', 'PasswordLastSet', 'PasswordNeverExpires', 'Enabled') } if ($SearchBase) { $getUserParams['SearchBase'] = $SearchBase } if ($IncludeDisabled) { $getUserParams['Filter'] = "ObjectClass -eq 'user'" } $users = Get-ADUser @getUserParams $userCount = ($users | Measure-Object).Count Write-Host "[$(NowTag)] Found $userCount user(s) to analyze" # Query each DC for LastLogon and find the most recent $userLogonData = @() $current = 0 foreach ($user in $users) { $current++ $pct = [int](($current / $userCount) * 100) Write-Progress -Activity "Querying LastLogon from Domain Controllers" -Status "Processing $($user.SamAccountName) ($current/$userCount)" -PercentComplete $pct $mostRecentLogon = $null foreach ($dc in $domainControllers) { try { $userFromDC = Get-ADUser -Identity $user.SamAccountName -Server $dc -Properties LastLogon -ErrorAction SilentlyContinue if ($userFromDC.LastLogon) { $logonDate = [DateTime]::FromFileTime($userFromDC.LastLogon) if ($null -eq $mostRecentLogon -or $logonDate -gt $mostRecentLogon) { $mostRecentLogon = $logonDate } } } catch { Write-Verbose "Could not query $dc for $($user.SamAccountName)" } } # Determine if inactive $isInactive = $false $reason = "" $daysSinceLogon = "N/A" if ($null -eq $mostRecentLogon) { $isInactive = $true $reason = "Never logged on" } elseif ($mostRecentLogon -lt $cutoffDate) { $isInactive = $true $daysSinceLogon = [int]((Get-Date) - $mostRecentLogon).TotalDays $reason = "Last logon: $($mostRecentLogon.ToString('yyyy-MM-dd'))" } else { $daysSinceLogon = [int]((Get-Date) - $mostRecentLogon).TotalDays } if ($isInactive) { $userLogonData += [PSCustomObject]@{ SamAccountName = $user.SamAccountName DisplayName = $user.Name UserPrincipalName = $user.UserPrincipalName Enabled = $user.Enabled LastLogon = $mostRecentLogon DaysSinceLogon = $daysSinceLogon LastLogonDate = $user.LastLogonDate # Replicated attribute for comparison WhenCreated = $user.WhenCreated PasswordLastSet = $user.PasswordLastSet PasswordNeverExpires = $user.PasswordNeverExpires Department = $user.Department Title = $user.Title Description = $user.Description DistinguishedName = $user.DistinguishedName Reason = $reason } } } Write-Progress -Activity "Querying LastLogon from Domain Controllers" -Completed # Export results Write-Host "[$(NowTag)] Found $($userLogonData.Count) inactive user(s)" if ($userLogonData.Count -gt 0) { $csvFile = Join-Path $OutputFolder "Inactive-ADUsers.csv" $userLogonData | Sort-Object DaysSinceLogon -Descending | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $csvFile Write-Host "[$(NowTag)] Report exported: $csvFile" -ForegroundColor Green # Summary statistics Write-Host "`nINACTIVE USER SUMMARY:" -ForegroundColor Cyan Write-Host " Total Inactive Users: $($userLogonData.Count)" Write-Host " Never Logged On: $(($userLogonData | Where-Object { $_.DaysSinceLogon -eq 'N/A' }).Count)" Write-Host " Enabled Accounts: $(($userLogonData | Where-Object Enabled).Count)" Write-Host " Disabled Accounts: $(($userLogonData | Where-Object { -not $_.Enabled }).Count)" Write-Host " Password Never Expires: $(($userLogonData | Where-Object PasswordNeverExpires).Count)" # By department $byDept = $userLogonData | Where-Object Department | Group-Object Department | Sort-Object Count -Descending | Select-Object -First 5 if ($byDept) { Write-Host "`nTop 5 Departments with Inactive Users:" -ForegroundColor Cyan $byDept | ForEach-Object { Write-Host " $($_.Name): $($_.Count)" } } } else { Write-Host "[$(NowTag)] No inactive users found!" -ForegroundColor Green } Write-Host "`n[$(NowTag)] Analysis complete! Output folder: $OutputFolder"