Active Directory Directory foundations
LDAP searches that answer a question
Search bases, scopes, filters, paging, range retrieval, query policy and ways to find an expensive search.
Start with base, scope and attributes
When two searches disagree, compare more than the filter. Check the server and port, bind identity, search base, scope, requested attributes and controls. One changed input can explain a very different result.
$dc = 'dc1.contoso.com'
$base = (Get-ADRootDSE -Server $dc).defaultNamingContext
Get-ADObject -Server $dc -SearchBase $base -SearchScope Subtree `
-LDAPFilter '(anr=Alice)' `
-Properties sAMAccountName,description |
Select-Object Name,sAMAccountName,DistinguishedName,description
Base searches one object. OneLevel searches its immediate children. Subtree searches the base and its descendants. Start from a narrower OU when that is the population you actually need.
ANR means ambiguous name resolution. It searches a configured set of name attributes; it is not an arbitrary substring search across every attribute. For an exact account, use (sAMAccountName=alice).
The ANR set comes from schema flags, so inspect the actual set rather than assuming a fixed list of name properties:
$schema = (Get-ADRootDSE -Server $dc).schemaNamingContext
Get-ADObject -Server $dc -SearchBase $schema -SearchScope OneLevel `
-LDAPFilter '(&(objectClass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=4))' `
-Properties lDAPDisplayName,searchFlags |
Select-Object lDAPDisplayName,searchFlags
ANR searches across those naming attributes using its own name-matching behavior. Compare the matches and cost with the equality or prefix search the application needs. The schema’s ANR flag definition also requires ordinary indexing. An ANR match doesn’t mean you’ve found an account through a unique property.
Useful LDAP filters
| Question | Filter |
|---|---|
| User accounts, excluding computer objects | (&(objectCategory=person)(objectClass=user)) |
| Disabled user accounts | (&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2)) |
| Objects with an SPN | (servicePrincipalName=*) |
| Groups | (objectCategory=group) |
| A description starting with Built | (description=Built*) |
| Either of two names | (|(sAMAccountName=alice)(sAMAccountName=bob)) |
1.2.840.113556.1.4.803 is the bitwise AND matching rule. It tests flags without assuming the attribute contains only that one value. LDAP filter syntax and the AD PowerShell -Filter language are different languages; pass LDAP syntax to -LDAPFilter.
Escape untrusted filter values. In an LDAP assertion value, *, (, ), backslash and NUL have special encodings (\2a, \28, \29, \5c, \00). DN escaping is a separate problem. Microsoft’s LDAP query syntax reference covers the operators and escaping.
Export objects before formatting
Get-ADObject -Server $dc -SearchBase $base -SearchScope Subtree `
-LDAPFilter '(anr=Alice)' -Properties sAMAccountName,description |
Select-Object Name,sAMAccountName,DistinguishedName,description |
Export-Csv -Path .\directory-search.csv -NoTypeInformation -Encoding UTF8
Keep Format-Table and Format-List at the end of an interactive display pipeline. Export the objects themselves so the CSV contains data rather than formatting records.
Paging and range retrieval solve different limits
Paging retrieves many matching objects in batches. Range retrieval retrieves many values of one attribute, such as the member attribute of a large group. A page size of 1,000 does not mean a group can contain only 1,000 members.
In a raw LDAP client, request a range such as member;range=0-999. Inspect the returned attribute name: the server may return a smaller range. Continue from the actual upper bound until it returns a name ending in -*. Do not assume the first returned slice is the complete group. Attribute range retrieval describes the inclusive, zero-based range syntax.
For normal administration, let the AD module handle the directory protocol details where it can:
Get-ADGroupMember -Identity 'Application Operators' -Server $dc
Recursive expansion changes the question to effective nested membership and can be expensive. It also does not reproduce every aspect of an issued access token; see groups and tokens.
The ADWS operation used by membership cmdlets has its own limits, distinct from LDAP page size and attribute range retrieval. A large-group failure is not repaired by changing Get-ADObject -ResultPageSize. Microsoft documents the 5,000-result membership-cmdlet limitation in its group-based administration example. Inspect the actual API and error before deciding whether to use a different enumeration method or change an ADWS limit.
Check whether the result is complete
A search can return useful-looking objects before hitting a size limit, timeout, rejected control or failed referral. In a raw LDAP client, check the final result code and paging cookie before calling the answer complete. In PowerShell, keep the failures and the names of the targets that failed.
Get-ADObject -Server $dc -SearchBase $base -SearchScope Subtree `
-LDAPFilter '(objectCategory=group)' -ResultPageSize 500 `
-Properties description -ErrorAction Stop |
Select-Object ObjectGUID,DistinguishedName,description
-ResultPageSize sets the batch size; -ResultSetSize caps the total result. A cap is useful for a quick preview. Remember to remove it before using the query to count an inventory. Get-ADObject’s parameter definitions cover both.
A referral is another directory request
Searching one domain on port 389 can return continuation references for subordinate naming contexts that the server does not hold. Whether the client follows them, and with which credentials, affects both results and timing. A GC search is often simpler for a forest-wide object lookup, provided the needed attributes are in its partial replicas. A forest GC search is not a cross-forest directory search.
Search from the domain root for a domain query, the application partition for its data, or a suitable GC for a forest lookup. When comparing client libraries, check how each handles referrals as well as its filter. Microsoft’s referral explanation covers the client/server behavior.
A filter on an attribute missing from a GC’s partial replica can exclude the object itself, rather than merely omit a returned column. A successful domain LDAP query does not prove that the same filter works against the GC interface. Explain an incomplete GC result covers the two-stage lookup, membership and application-partition boundaries.
Return less data when measuring a search
In LDP, attribute 1.1 requests no attributes. Use it when you want to compare matching work without retrieving all the values. The search statistics control can report entries visited and returned. If the search visits many entries to return a few, check the base, filter and existing indexes before adding another index.
The (description=*123*) example can be expensive. A leading wildcard may prevent the ordinary index access you expected. Measure it on a representative test directory before blaming the server.
Inspect query policy before changing limits
$config = (Get-ADRootDSE -Server $dc).configurationNamingContext
$policyBase = "CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,$config"
Get-ADObject -Server $dc -SearchBase $policyBase -SearchScope OneLevel `
-LDAPFilter '(objectClass=queryPolicy)' -Properties lDAPAdminLimits |
Select-Object Name,lDAPAdminLimits
This lists the policies. To find the one a DC uses, check its assignment, then the site’s assignment, then the default. Ntdsutil’s show values displays the default even when a custom policy is assigned. Resolve the selected query policy before relying on those numbers. Raising MaxPageSize for an application that can’t page can add server work without fixing the client.
Find the caller of an expensive query
Directory Service event 1644 can identify expensive or inefficient searches, including the client and filter. Its diagnostic logging and thresholds must be configured deliberately and restored after collection. Compare events with the CPU or latency window, then fix the application’s scope, attributes, filter or frequency. Diagnosing an expensive query gives the collection and interpretation sequence, with Microsoft’s event 1644 guide as the implementation reference.
Change tracking is tied to the server and mechanism
Change notification, DirSync and uSNChanged polling are not interchangeable. USNs are local to a DC; a polling client that changes servers without accounting for server identity and restore can skip changes. An application also needs to handle deletions, permissions and a full resynchronization. LDAP change tracking explains checkpointing, the DirSync object-security flag, and why a narrow query can miss objects that move out of scope.