Skip to content

Commit 6c3142e

Browse files
committed
feat: Add Get-InactiveADUser
1 parent c5b3397 commit 6c3142e

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
function Get-InactiveADUser {
2+
<#
3+
.SYNOPSIS
4+
Find inactive enabled users in Active Directory based on logon activity.
5+
6+
.DESCRIPTION
7+
This script queries Active Directory for user accounts and checks both the LastLogonDate (replicated) and
8+
LastLogon (non-replicated) attributes across all domain controllers to determine true inactivity. It also
9+
displays the last password change date for each user.
10+
11+
.PARAMETER InactiveDays
12+
Number of days of inactivity to consider a user inactive. Default is 90 days.
13+
14+
.PARAMETER IncludeDisabled
15+
Switch to include disabled accounts in the results.
16+
17+
.PARAMETER CheckAllDCs
18+
Check the non-replication LastLogon user attribute on ALL domain controllers for extra validation of activity.
19+
20+
.PARAMETER ExportCSV
21+
Optional path and filename to export results to a CSV file.
22+
23+
.EXAMPLE
24+
Get-InactiveADUser -InactiveDays 60
25+
26+
Finds users inactive for 60 or more days.
27+
28+
.EXAMPLE
29+
Get-InactiveADUser -InactiveDays 90 -IncludeDisabled
30+
31+
Finds all enabled and disabled users inactive for 90 or more days.
32+
33+
.NOTES
34+
Author: Sam Erde
35+
Version: 1.1.2
36+
Requires: Active Directory PowerShell Module
37+
38+
The lastLogonTimeStamp is replicated to all domain controllers and is accurate within ~14 days.
39+
40+
.LINK
41+
https://learn.microsoft.com/en-us/services-hub/unified/health/remediation-steps-ad/regularly-check-for-and-remove-inactive-user-accounts-in-active-directory#context--best-practices
42+
43+
#>
44+
45+
[CmdletBinding()]
46+
[OutputType('Microsoft.ActiveDirectory.Management.ADUser')]
47+
Param(
48+
# The minimum number of days of inactivity to use as a cutoff for considering a user to be inactive.
49+
[Parameter(Mandatory = $false, HelpMessage = 'Minimum number of days of inactivity to use as the cutoff (1-3650).')]
50+
[ValidateRange(1, 3650)]
51+
[int]$InactiveDays = 90,
52+
53+
# Include disabled accounts in the review of inactive users. By default, only enabled users are reviewed.
54+
[switch]$IncludeDisabled,
55+
56+
# Check users' LastLogon timestamp on all domain controllers for extra confirmation.
57+
[switch]$CheckAllDCs,
58+
59+
# Export the results to a CSV file.
60+
[string] $ExportCSV
61+
)
62+
63+
# Requires the Active Directory module.
64+
try {
65+
Import-Module ActiveDirectory -ErrorAction Stop
66+
Write-Verbose 'Active Directory module imported successfully'
67+
} catch {
68+
Write-Error "Failed to import Active Directory module: $($_.Exception.Message)"
69+
return
70+
}
71+
72+
$Results = [System.Collections.Generic.List[Microsoft.ActiveDirectory.Management.ADUser]]::new()
73+
74+
$InactiveDate = (Get-Date).AddDays(-$InactiveDays)
75+
$DomainControllers = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
76+
77+
Write-Host "Finding inactive users (inactive for $InactiveDays days or more)..." -ForegroundColor Cyan
78+
Write-Host "Checking all domain controllers for most recent logon times..." -ForegroundColor Cyan
79+
80+
# Build filter for user query
81+
$Filter = { Enabled -eq $true -and LastLogonDate -lt $InactiveDate }
82+
if ($IncludeDisabled) {
83+
$Filter = "LastLogonDate -lt $InactiveDate"
84+
}
85+
86+
# Get all Active Directory users
87+
$Users = Get-ADUser -Filter $Filter -Properties LastLogonDate, PasswordLastSet, Enabled, DistinguishedName, CanonicalName, CN | Sort-Object CanonicalName
88+
89+
foreach ($User in $Users) {
90+
$MostRecentLogon = $null
91+
92+
# Check LastLogonDate (replicated attribute)
93+
if ($User.LastLogonDate) {
94+
$MostRecentLogon = $User.LastLogonDate
95+
}
96+
97+
if ($CheckAllDCs) {
98+
# Skip the check across all DCs if there is already a LastLogonDate within the past 14 days and if the most recent logon is more recent than the inactive date threshold.
99+
if ( $MostRecentLogon -lt (Get-Date).AddDays(-14) -and (-not $MostRecentLogon -lt $InactiveDate) ) {
100+
# Check LastLogon (non-replicated) on every domain controller.
101+
foreach ($DC in $DomainControllers) {
102+
try {
103+
$DCUser = Get-ADUser -Identity $User.SamAccountName -Server $DC -Properties LastLogon -ErrorAction Stop
104+
105+
if ($DCUser.LastLogon -gt 0) {
106+
$LastLogonDC = [DateTime]::FromFileTime($DCUser.LastLogon)
107+
108+
if ($null -eq $MostRecentLogon -or $LastLogonDC -gt $MostRecentLogon) {
109+
$MostRecentLogon = $LastLogonDC
110+
}
111+
}
112+
}
113+
catch {
114+
Write-Warning "Could not query $DC for user $($User.SamAccountName): $($_.Exception.Message)"
115+
}
116+
}
117+
}
118+
}
119+
120+
# Determine if user is inactive by checking the most recent logon date.
121+
$IsInactive = $false
122+
if ($null -eq $MostRecentLogon) {
123+
$IsInactive = $true
124+
} elseIf ($MostRecentLogon -lt $InactiveDate) {
125+
$IsInactive = $true
126+
}
127+
$DaysInactive = if ($MostRecentLogon) { (New-TimeSpan -Start $MostRecentLogon -End (Get-Date)).Days } else { 'Never logged on' }
128+
129+
# Add properties to the user object.
130+
$User | Add-Member -Force -MemberType NoteProperty -Name MostRecentLogon -Value $MostRecentLogon | Out-Null
131+
$User | Add-Member -Force -MemberType NoteProperty -Name IsInactive -Value $IsInactive | Out-Null
132+
$User | Add-Member -Force -MemberType NoteProperty -Name DaysInactive -Value $DaysInactive | Out-Null
133+
134+
if ($IsInactive) {
135+
$Results.Add($User)
136+
}
137+
} # end foreach user
138+
139+
# Display results
140+
if ($Results.Count -gt 0) {
141+
Write-Host "`nFound $($Results.Count) inactive user(s):" -ForegroundColor Yellow
142+
$Results | Format-Table CanonicalName, Enabled, MostRecentLogon, DaysInactive, PasswordLastSet -AutoSize | Out-Host
143+
144+
# Optional: Export to CSV
145+
if ($PSBoundParameters.ContainsKey('ExportCSV')) {
146+
$ExportPath = ".\InactiveUsers_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
147+
$Results | Export-Csv -Path $ExportPath -NoTypeInformation
148+
Write-Host "Results exported to: $ExportPath" -ForegroundColor Green
149+
}
150+
} else {
151+
Write-Host "`nNo inactive users found." -ForegroundColor Green
152+
}
153+
154+
$Results
155+
} # end Get-InactiveADUser

0 commit comments

Comments
 (0)