
close
close
Chance to win $250 in Petri 2023 Audience Survey
In an earlier article, I discussed how to use the Microsoft Active Directory module to discover disabled, expired and inactive user accounts. This requires a newer domain controller and a client with RSAT installed. But perhaps some of you can’t meet those requirements, or you need to develop a PowerShell solution that doesn’t require RSAT. There is another way to find disabled, expired, and inactive accounts, but it takes a bit more PowerShell scripting on your part. If you’re up to it, let’s dig in.
There is an entire library of .NET classes that are specifically made for working with Active Directory. You don’t need to be concerned about what version of Windows or Active Directory is running on your domain controllers when you’re working with these classes. As long as you can connect to the domain controllers over the traditional LDAP port of 389, I don’t think you will have any problems. We will be using an instance of System.DirectoryServices.DirectorySearcher for the task at hand. The solution that is demonstrated in this article works best if you’re running your PowerShell session with credentials that can search Active Directory.
First, I will create an instance of this object.
$search = New-Object System.DirectoryServices.DirectorySearcher
This is now just another PowerShell object.
Creating an instance of a PowerShell object. (Image Credit: Jeff Hicks)
Searching the domain with Windows PowerShell. (Image Credit: Jeff Hicks)
$search.SearchRoot = [ADSI]"LDAP://CHI-DC04/OU=Employees,DC=Globomantics,DC=Local"
The SearchRoot property is expecting a System.DirectoryServices.DirectoryEntry object, and the [ADSI] type accelerator is a shortcut for creating that type of object. The LDAP moniker is case sensitive. My path includes the name of my Windows Server 2012 domain controller, CHI-DC04. You shouldn’t have to specify a domain controller, but I found I got better results if I did specify the domain controller in my own environment.
The next and most important property to define is the filter.
$filter = "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))"
$search.Filter = $filter
You may be wondering how I came up with that filter. I will admit that creating a LDAP filter can be complicated, but I cheat a little when I can. Open Active Directory Users and Computers, and go to the Saved Queries folder.
Creating a new query. (Image Credit: Jeff Hicks)
Defining the query string. (Image Credit: Jeff Hicks)
Using the FindOne() method in Windows PowerShell. (Image Credit: Jeff Hicks)
Results after using the FindOne() method. (Image Credit: Jeff Hicks)
$r = $search.findone().Properties.GetEnumerator() | foreach -begin {$hash=@{}} -process {
$hash.add($_.key,$($_.Value))
} -end {[pscustomobject]$Hash}
$r | Select Name,Title,Department,DistinguishedName,WhenChanged,LastLogonTimeStamp
I am only interested in a few properties.
Turning information into custom objects for better readability and use within PowerShell. (Image Credit: Jeff Hicks)
Function Convert-LastLogonTimeStamp {
Param([int64]$LastOn=0)
[datetime]$utc="1/1/1601"
if ($LastOn -eq 0) {
$utc
} else {
[datetime]$utc="1/1/1601"
$i=$LastOn/864000000000
[datetime]$utcdate = $utc.AddDays($i)
#adjust for time zone
$offset = Get-WmiObject -class Win32_TimeZone
$utcdate.AddMinutes($offset.bias)
}
} #end function
Armed with this, I can revise my previous command.
$r | Select Name,Title,Department,DistinguishedName,WhenChanged,@{Name="LastLogon";Expression={Convert-LastLogonTimeStamp $_.lastLogonTimeStamp}}
A variation of the previous PowerShell command. (Image Credit: Jeff Hicks)
Using the FindAll() method in Windows PowerShell. (Image Credit: Jeff Hicks)
$all | Select Path | Out-GridView
Using Out-GridView to see the results of my disabled accounts. (Image Credit: Jeff Hicks)
$disabled = Foreach ($user in $all) {
$user.Properties.GetEnumerator() |
foreach -begin {$hash=@{}} -process {
$hash.add($_.key,$($_.Value))
} -end {[pscustomobject]$Hash}
}
Processing each user into an object that can be used. (Image Credit: Jeff Hicks)
$disabled | Select Name,Title,Department,DistinguishedName,WhenChanged,@{Name="LastLogon";Expression={Convert-LastLogonTimeStamp $_.lastLogonTimeStamp}}
Disabled or inactive users. (Image Credit: Jeff Hicks)
Filtering results by department group. (Image Credit: Jeff Hicks)
$disabled | sort Department | Format-Table -GroupBy Department -Property Name,
Title,@{Name="LastLogon";Expression={Convert-LastLogonTimeStamp $_.lastLogonTimeStamp}},
Distinguishedname
Formatting the results into a table. (Image Credit: Jeff Hicks)
[adsisearcher]$Searcher = $filter
$searcher.SearchRoot = [ADSI]"LDAP://CHI-DC04/OU=Employees,DC=Globomantics,DC=Local"
This object is the same as what I created earlier. The only difference is that I didn’t need to use New-Object.
Next, let’s find expired accounts. This is one of those queries that is calculated at run time. The AccountExpires property uses the same tick concept as LastLogon so to create a filter I need to calculate a tick value.
$today = Get-Date
[datetime]$utc = "1/1/1601"
$ticks = ($today - $utc).ticks
$searcher.filter = "(&(objectCategory=person)(objectClass=user)(!accountexpires=0)(accountexpires<=$ticks))"
Finding expired user accounts with PowerShell. (Image Credit: Jeff Hicks)
$days = 120
$cutoff = (Get-Date).AddDays(-120)
$ticks = ($cutoff - $utc).ticks
$searcher.filter = "(&(objectCategory=person)(objectClass=user)(lastlogontimestamp<=$ticks))"
$all = $searcher.FindAll()
I can work with results just as I did before.
$inactive = Foreach ($user in $all) {
$user.Properties.GetEnumerator() |
foreach -begin {$hash=@{}} -process {
$hash.add($_.key,$($_.Value))
} -end {[pscustomobject]$Hash}
}
$inactive | Select Name,Title,Department,DistinguishedName,WhenChanged,
@{Name="LastLogon";Expression={Convert-LastLogonTimeStamp $_.lastLogonTimeStamp}} |
Out-Gridview -title "Last Logon"
The most difficult part of using the directory searcher is developing an LDAP filter. Using the query builder in Active Directory Users and Computers can help. Once you have mastered creating LDAP filters, you can also use them with the Microsoft Active Directory cmdlets. Over all I think you’ll find the cmdlets easier to use. But if you have some PowerShell chops, you can certainly create your own Active Directory tools.
More in Active Directory
Microsoft Releases Update to Streamline Exchange Online License Assignments
Jan 24, 2023 | Rabia Noureen
How to Export Active Directory Users to CSV With PowerShell and ADUC
Jan 23, 2023 | Michael Reinders
ManageEngine ADSelfService Plus: Protect On-Premises and Cloud Services from Password Attacks with Multi-factor Authentication
Jan 12, 2023 | Michael Reinders
Microsoft 365 to Launch New $1.99/Month Basic Subscription with 100 GB of OneDrive Storage
Jan 11, 2023 | Rabia Noureen
Most popular on petri