PowerShell LDAP Query: Search Active Directory Directly

RSAT tools are not always installed, and the ActiveDirectory module is not available on every machine where you need Active Directory data. A PowerShell LDAP query using the built-in System.DirectoryServices namespace works on any domain-joined Windows machine without any module installation. This post shows you how to search users, groups, and computers using DirectorySearcher and LDAP filter syntax.
Quick Answer / TL;DR
Create a [DirectorySearcher] object, set its Filter to an LDAP filter string like (&(objectClass=user)(sAMAccountName=jsmith)), call .FindAll() or .FindOne(), and access properties via .Properties['attribute'][0].
DirectoryEntry and DirectorySearcher Basics
DirectoryEntry represents a node in the directory (typically the domain root). DirectorySearcher performs the actual LDAP query against that entry. Both live in System.DirectoryServices, which is available in all Windows PowerShell versions. When you omit the path from DirectoryEntry, it binds to the current domain automatically.
# Bind to current domain root automatically
$domain = [ADSI]''
# Or specify a domain explicitly
$domain = New-Object System.DirectoryServices.DirectoryEntry('LDAP://DC=contoso,DC=com')
# Create a searcher against that domain
$searcher = New-Object System.DirectoryServices.DirectorySearcher($domain)
$searcher.PageSize = 1000 # retrieve up to 1000 results per page
Basic LDAP Filter Syntax
LDAP filters use prefix notation with parentheses. The most common operators are = (equals), & (AND), | (OR), and ! (NOT). Attribute names are LDAP attribute names, not PowerShell property names: sAMAccountName not SamAccountName, givenName not GivenName. Wildcards use *.
# All enabled user accounts
'(&(objectClass=user)(objectCategory=person)(!userAccountControl:1.2.840.113556.1.4.803:=2))'
# Users whose name starts with J
'(&(objectClass=user)(givenName=J*))'
# All computers in the domain
'(objectClass=computer)'
Find User by SAMAccountName
Searching by sAMAccountName is the fastest way to find a specific user. It is indexed by default in Active Directory, so the query returns quickly even in large directories. Access result properties through the .Properties hashtable — each property is a collection, so append [0] to get the scalar value.
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.Filter = '(&(objectClass=user)(sAMAccountName=jsmith))'
$searcher.PropertiesToLoad.AddRange(@('displayName','mail','department','title'))
$result = $searcher.FindOne()
if ($result) {
[PSCustomObject]@{
DisplayName = $result.Properties['displayName'][0]
Email = $result.Properties['mail'][0]
Department = $result.Properties['department'][0]
Title = $result.Properties['title'][0]
}
}
Find All Members of a Group
Group membership is stored in the member multi-valued attribute. Query the group object and iterate the member collection. Each value is a Distinguished Name string. To get user details for each member, perform a second lookup per DN or retrieve all at once with an |(memberOf=...) filter on the user side.
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.Filter = '(&(objectClass=group)(cn=IT-Admins))'
$searcher.PropertiesToLoad.Add('member') | Out-Null
$group = $searcher.FindOne()
$members = $group.Properties['member']
foreach ($dn in $members) {
$userEntry = [ADSI]"LDAP://$dn"
Write-Host $userEntry.Properties['displayName'][0]
}
Search with Multiple Conditions
Combine conditions with the & (AND) operator to narrow results. This example finds all enabled users in a specific department, which is a typical query for generating department rosters.
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.Filter = '(&(objectClass=user)(objectCategory=person)(department=Finance)(!userAccountControl:1.2.840.113556.1.4.803:=2))'
$searcher.PageSize = 500
$results = $searcher.FindAll()
$users = foreach ($r in $results) {
[PSCustomObject]@{
Name = $r.Properties['displayName'][0]
Email = $r.Properties['mail'][0]
}
}
$users | Sort-Object Name
Return Specific Attributes
By default, DirectorySearcher returns all attributes, which is expensive on large directories. Use PropertiesToLoad to restrict which attributes are returned. This significantly reduces network traffic and query time in large Active Directory environments.
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.Filter = '(objectClass=computer)'
$searcher.PropertiesToLoad.AddRange(@('cn','operatingSystem','lastLogonTimestamp'))
$searcher.PageSize = 1000
$computers = $searcher.FindAll() | ForEach-Object {
[PSCustomObject]@{
Name = $_.Properties['cn'][0]
OS = $_.Properties['operatingSystem'][0]
LastLogon = if ($_.Properties['lastLogonTimestamp'][0]) {
[DateTime]::FromFileTime($_.Properties['lastLogonTimestamp'][0])
} else { 'Never' }
}
}
$computers | Sort-Object Name | Format-Table -AutoSize
Common Errors and Fixes
- LDAP filter syntax differs from PowerShell filter language. PowerShell’s
-Filterparameter onGet-ADUseruses a different syntax than LDAP filters. In LDAP:(&(objectClass=user)(sAMAccountName=jsmith)). In AD module:-Filter "SamAccountName -eq 'jsmith'". Do not mix the two. - Result properties need .Properties[‘name’][0] access — not direct property. Unlike
Get-ADUseroutput,DirectorySearcherresults return aResultPropertyCollection. Each property is a collection even for single-value attributes. Always index with[0]or use.Valueon the collection to get the scalar.
Related Cmdlets / See Also
Wrapping Up
DirectorySearcher gives you full LDAP query power on any domain-joined machine without installing RSAT or the AD module. Master the filter syntax, always specify PropertiesToLoad for efficiency, and remember to index into .Properties with [0]. For routine AD administration on machines that have RSAT, the ActiveDirectory module remains more convenient.


