Active Directory environments often accumulate unused accounts over time - former employees, test accounts, or service accounts that are no longer needed. These dormant accounts pose security risks and create management overhead.
The most efficient way to audit last login times is using PowerShell's Active Directory module. Here's a comprehensive script that:
# Import the Active Directory module
Import-Module ActiveDirectory
# Get all users and their last logon timestamp
$users = Get-ADUser -Filter * -Properties LastLogonDate,Enabled,PasswordLastSet,EmailAddress
# Filter for inactive users (more than 90 days)
$inactiveThreshold = (Get-Date).AddDays(-90)
$inactiveUsers = $users | Where-Object {
$_.Enabled -eq $true -and
($_.LastLogonDate -lt $inactiveThreshold -or $_.LastLogonDate -eq $null)
}
# Export results to CSV
$inactiveUsers | Select-Object Name,SamAccountName,EmailAddress,LastLogonDate,PasswordLastSet |
Export-Csv -Path "InactiveUsers_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
# Display summary
Write-Host "Found $($inactiveUsers.Count) inactive user accounts (no login in 90 days)"
For more granular control, you can modify the filter criteria:
# Never logged in accounts
$neverLoggedIn = $users | Where-Object { $_.LastLogonDate -eq $null }
# Accounts with expired passwords
$passwordExpiryThreshold = (Get-Date).AddDays(-365)
$expiredPasswordUsers = $users | Where-Object {
$_.PasswordLastSet -lt $passwordExpiryThreshold
}
Once you've identified stale accounts, you can automatically disable them:
# Disable inactive accounts
$inactiveUsers | ForEach-Object {
Set-ADUser -Identity $_.SamAccountName -Enabled $false
Write-Host "Disabled account: $($_.SamAccountName)"
}
The LastLogonDate property is convenient but replicated. For domain controllers in different sites, you might need to check the LastLogon attribute:
# Check LastLogon across all domain controllers
$domainControllers = Get-ADDomainController -Filter *
$users = Get-ADUser -Filter * -Properties LastLogon
$results = foreach ($user in $users) {
$lastLogon = [DateTime]::FromFileTime($user.LastLogon)
[PSCustomObject]@{
Name = $user.Name
SamAccountName = $user.SamAccountName
LastLogon = $lastLogon
}
}
$results | Where-Object { $_.LastLogon -lt $inactiveThreshold }
- Run audits quarterly
- Create documentation for exception accounts
- Implement a formal offboarding process
- Consider implementing account expiration policies
Managing user accounts in Active Directory becomes challenging as organizations grow. A common scenario is accumulating stale accounts - either from departed employees or service accounts no longer in use. These dormant accounts pose security risks and clutter your directory.
Here's a comprehensive PowerShell script that queries last login timestamps and identifies inactive accounts:
# Import Active Directory module
Import-Module ActiveDirectory
# Set your organizational threshold (e.g., 90 days)
$inactiveThreshold = (Get-Date).AddDays(-90)
# Get all enabled user accounts
$users = Get-ADUser -Filter {Enabled -eq $true} -Properties LastLogonDate, EmailAddress, Department
# Filter and sort inactive users
$inactiveUsers = $users | Where-Object {
$_.LastLogonDate -lt $inactiveThreshold -or
$_.LastLogonDate -eq $null
} | Sort-Object LastLogonDate
# Output results
$inactiveUsers | Select-Object Name, SamAccountName, EmailAddress, Department, @{
Name="LastLogonDate";
Expression={if($_.LastLogonDate) {$_.LastLogonDate.ToString("yyyy-MM-dd")} else {"Never"}}
} | Export-CSV "InactiveUsers_Report_$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformation
The LastLogonDate attribute is a replicated version of the actual lastLogon timestamp. While less precise, it's more practical for forest-wide queries as it doesn't require checking each domain controller.
For large environments, consider this extension to automatically disable inactive accounts:
foreach ($user in $inactiveUsers) {
Set-ADUser -Identity $user.SamAccountName -Enabled $false
Write-Host "Disabled account: $($user.SamAccountName)"
}
For more precise results in multi-domain environments, you might need to:
- Query each domain controller's
lastLogonattribute - Compare timestamps to find the most recent
- Account for time synchronization issues
For production environments, consider adding:
# Email notification before disabling
$notificationDays = 14
$warningThreshold = $inactiveThreshold.AddDays($notificationDays)
$soonToBeDisabled = $users | Where-Object {
$_.LastLogonDate -lt $warningThreshold -and
$_.LastLogonDate -gt $inactiveThreshold
}
For GUI-based solutions:
- Open Active Directory Administrative Center
- Navigate to Global Search
- Create a query with filter: "Last logon time is less than [date]"
- Save as reusable query
When implementing account cleanup:
- Maintain a grace period before deletion
- Backup disabled accounts before removal
- Document your retention policy
- Consider exempting certain service accounts