LDAP, hands-on
See the directory the way LDAP sees it — search bases, scope, and filter syntax — by binding to lab.local with ldp.exe and running real searches against the data you built.
Goal
Session 1 named LDAP as one of the four pillars — the protocol you use to read and write the directory store. This session makes it concrete. By the end you'll understand the three moving parts of every LDAP search — base DN, scope, and filter — you'll be able to write filters including the logical operators, and you'll run real searches against lab.local with ldp.exe, seeing the users, OUs, and groups you built in Sessions 3–4 exactly as LDAP sees them. Still Tier 1: you're learning to read the directory fluently, not administer LDAP at scale.
Concepts
LDAP is how you talk to the store
Recall the pillar separation from Session 1: the directory store is the object database; LDAP is the wire protocol that reads and writes it. LDAP defines a small set of core operations — bind (authenticate the connection), search (read), add, modify, delete — but the one you'll live in day to day, and the whole focus here, is search. Get search right and the directory stops being a mystery GUI and becomes something you can query precisely.
Anatomy of a search: base, scope, filter
Every LDAP search is three decisions (plus an optional fourth). Miss any of the first three and you either get nothing or get the whole directory.
- Base DN — where in the tree the search starts. A Distinguished Name, exactly like Session 1. Start at the domain root
DC=lab,DC=localto search everything, or narrow to a branch likeOU=Sales,DC=lab,DC=localto search just that OU. - Scope — how deep from the base to look. Three choices (illustrated below):
- base — only the base object itself (one entry, if it matches).
- one-level — the immediate children of the base, but not deeper.
- subtree — the base and everything beneath it, all the way down. This is the everyday default.
- Filter — what to match. The predicate each candidate entry is tested against (the next section is all about this).
- Attributes (optional) — which fields to return. Ask for just
cn, mailinstead of every attribute, to keep results readable.
Think of it as: base + scope define the candidate set, and the filter selects from it.
Filter syntax — prefix notation, not SQL
If you know SQL, unlearn the shape of a WHERE clause here. LDAP filters are prefix (Polish) notation: the operator comes first, then its operands, and every term is wrapped in parentheses.
- Basic match —
(attribute=value), e.g.(sAMAccountName=jsmith). - Wildcard —
*matches any substring, e.g.(cn=John*)or(mail=*@lab.local). - AND —
(&(...)(...)): the&leads, followed by two or more parenthesized conditions. Real user accounts:(&(objectCategory=person)(objectClass=user)). - OR —
(|(...)(...)): e.g.(|(sAMAccountName=jsmith)(sAMAccountName=mjones)). - NOT —
(!(...)): negates a single condition, e.g.(!(objectClass=computer)). - By type — narrow by object type with
objectClassorobjectCategory(more on the difference in the gotcha below).
Operators nest: an AND can contain an OR that contains a NOT, each fully parenthesized. The parentheses aren't decoration — they are the grammar.
| Operator | Meaning | Example |
|---|---|---|
= | Attribute equals value (exact) | (sAMAccountName=jsmith) |
* | Wildcard — any substring | (cn=J*) matches John, Jane, … |
& | AND — all conditions must match | (&(objectCategory=person)(objectClass=user)) |
| | OR — any condition matches | (|(department=Sales)(department=IT)) |
! | NOT — negates one condition | (!(objectClass=computer)) |
Watch out
LDAP filters are prefix notation, not SQL. It is (&(a=1)(b=2)) — the & first, each clause in its own parentheses — never (a=1 AND b=2). Writing it infix, or dropping a parenthesis, is the single most common beginner mistake and produces either a syntax error or silently wrong results. When a filter "doesn't work," count your parentheses first: every operator and every condition is wrapped, and the operator leads.
Watch out
(objectClass=user) also matches computers. In AD's schema a computer object is a subclass of user, so (objectClass=user) returns computer accounts and real people. The reliable "human user accounts only" filter is (&(objectCategory=person)(objectClass=user)): objectCategory=person excludes the machines while objectClass=user keeps genuine user objects. You'll watch this happen in the lab — the same search, two very different result counts.
Scope, visualized
With the base DN set to OU=Sales: base scope returns only the Sales OU object; one-level returns jsmith, mjones, and the Laptops OU (its direct children); subtree returns all of those plus SALES-PC1 underneath Laptops.
Ports: LDAP, LDAPS, and one to defer
A quick recap from Session 1: plain LDAP runs on port 389, and LDAPS (LDAP over TLS) on 636 — same protocol, encrypted transport. In the lab you'll bind to 389 on the DC itself. You may also see port 3268 referenced (the Global Catalog); ignore it for now — the GC gets its proper treatment in Session 9.
Lab
Goal: bind to lab.local with ldp.exe, browse the tree you've been building, and run a sequence of filtered searches — watching the result set change as you vary base, scope, and filter. ldp.exe ships with the AD DS tools and is itself a GUI, so this is GUI-first by nature. Fake values only — lab.local, the sample users/groups from Sessions 3–4, no real credentials.
Launch it from Run (ldp.exe) or Server Manager → Tools → ldp.exe. The rhythm is always Connection → Connect, then Connection → Bind, then browse or search.
Note
In the Browse → Search dialog, the three concepts map to three fields: Base DN (where to start), Scope (Base / One Level / Subtree radio buttons), and Filter (the parenthesized expression). Change one at a time and watch the result count in the right-hand pane — that feedback loop is the fastest way to build filter intuition.
How you know it worked: you can Connect and Bind without errors; View → Tree with base DC=lab,DC=local expands to show your Sales/IT/Servers OUs and their users and groups; and each filter returns what you'd predict — (objectClass=user) returns more than (&(objectCategory=person)(objectClass=user)) because the first sweeps in computer accounts, the wildcard and OR/NOT filters narrow or widen the set as expected, and switching the base DN to OU=Sales shrinks the candidate pool to that branch. If you can say why each result set came out the size it did — base + scope + filter — you've got it.
Note
The same filters in PowerShell — optional (we go deep on PowerShell in Session 8). Recognize this, don't master it yet: the exact LDAP filter strings you just wrote work as-is via -LDAPFilter, so this skill carries straight over.
Get-ADUser -LDAPFilter "(&(objectCategory=person)(objectClass=user))"
Get-ADObject -SearchBase "OU=Sales,DC=lab,DC=local" -LDAPFilter "(cn=J*)"