A bit of background
Before showing some examples of the LDAP search filters required to find users it is worth pointing out that there are several different types of “user” in Active Directory. There are three main types of users:
- User objects
- Contact objects
- inetOrgPerson objects
User objects are security principals within AD. In other words, they have a unique security identifier (SID) and as such can be assigned permissions to access resources. Other examples of security principals in AD include Computer objects and security groups.
Contact objects are not security principals – they are typically used to represent users with accounts that exist elsewhere. If you are familiar with the concept of Exchange 5.5 Custom Recipient then think of Contacts as the AD equivalent.
inetOrgPerson objects are security principals. They are very similar to user objects and can be used interchangeably. They were introduced via a schema update for Windows 2000 AD, but are part of the Windows Server 2003 AD base schema. The reason for the inclusion of inetOrgPerson objects into AD was to ensure closer compliance to internet RFCs (see http://www.faqs.org/rfcs/rfc2798.html) and to allow better inter-operation with other LDAP directories.
The table below shows these three types of object, together with their object classes and object categories. For reasons that will become clearer later in the article I have also included computer objects.
User Type | objectClass | objectCategory | sAMAccountType |
User | top; person; organizationalPerson; user | Person | 805306368 |
Contact | top; person; organizationalPerson; contact | Person | <none> |
inetOrgPerson | top; person; organizationalPerson; user; inetOrgPerson | Person | 805306368 |
| | | |
Computer | top; person; organizationalPerson; user; computer; | Computer | 805306369 |
Searching for users.
All of the examples shown make use of ADFIND.EXE, a really good free command line LDAP tool from joeware.net. If you prefer to use something from Microsoft, get to grips with LDP.EXE, which is an LDAP client UI that comes as part of the Windows 2003 and XP Support Tools.
When starting to get to grips with LDAP searches, some people make some basic mistakes and end up with unexpected results. Here’s an example:
adfind -default -f "(objectClass=user)"
You might be surprised to find the results of this search returning user, inetOrgPerson and computer objects. The reason for this apparent anomaly is that the search using objectClass as the filter will return all objects that have “user” present within their objectClass attribute value. This includes computer objects because the computer object class is a subordinate object of the user class (see table above).
Another, less obvious, problem with this search is that it is inefficient. objectClass is not indexed in the directory (see screenshot below). This means that the DC processing the search request has to work harder, especially if the search is of type Subtree and is performed using the root as the base DN (e.g DC=MyCo,DC=Com).

At this point you might decide to switch tactics and use objectCategory as part of the filter, e.g.
adfind -default -f "(objectCategory=Person)"
The first good thing about this search is that it is more efficient because it uses an indexed attribute (objectCategory). The second benefit is that computer objects are no longer returned in the result. This is because computer objects belong to a different object category (Computer) as shown in the table above. The downside is that the search will not only return user and inetOrgPerson objects, but also contacts. The reason for this is that contact objects also belong to the Person object category.
So what do you need to do if you just want to find user and inetOrgPerson objects and not have contacts and/or computers returned in the result? Here’s one way to achieve this:
adfind -default -f "(&(objectClass=user)(objectCategory=Person))"
Another alternative to obtain the same result is to use the sAMAccountType attribute shown in the table above. Like objectCategory the sAMAccountType attribute is indexed and as such is efficient to use within a search filter, as shown here:
adfind -default -f “(sAMAccountType=805306368)"
If you want to only search for only objects of inetOrgPerson class you can use the following filter.
adfind -default -f “(&(objectClass=inetOrgPerson)(objectCategory=Person))"
To find user objects without returning inetOrgPerson objects you need to specifically exclude inetOrgPerson from the filter, as follows:
adfind -default -f “(&(sAMAccountType=805306368)(!(objectClass=inetOrgPerson)))"
Note that in general searches that use the logical NOT operator (such as the one above) should be avoided unless there is no alternative. This is because it can cause the query processor to return objects to which you do not have access or specific attributes that do not have a value. In this case I couldn’t think of a better alternative.
Other things to be aware of
In the examples above I have used used ADFIND.EXE with the “-default” option. This is a special base designator, which auto-populates the search base with DN of the default naming context of the server connected to. For more efficient searches start at the lowest point in the AD hierarchy that will give you the result you are looking for. For example, if all your users are located in a an OU tree named AllInternalUsers then start the search from that point, e.g. OU=AllInternalUsers,DC=MyCo,DC=com.
Sometimes you might see a search constructed with “(objectCategory=user)”. Now you might think that this will not work given that “(objectCategory=Person)” is typically used. Strangely enough it does work! The explanation below was provided by Dean Wells in a post on the ActiveDir.org mailing list (thanks Dean).
The attribute "objectCategory" is of syntax distinguishedName and naturally requires a syntactically compatible value. AD, however, is smart enough to take a non-DN value for objectCategory and derive a DN from it, assuming the constructed DN results in a viable schema entry, AD then reads the value of the resulting object's defaultObjectCategory property and substitutes the original query-value with the defaultObjectCategory value thereby permitting the query to proceed without error.
e.g. if the filter provided contains "(&(objectCategory=user)(sn=wells))" -
1. construct DN
- cn=user,cn=schema,cn=configuration,dc=mset,dc=local
2. read DN's defaultObjectCategory value
- CN=Person,CN=Schema,CN=Configuration,DC=mset,DC=local
3. modify original query and execute, resulting query -
- "(&(objectcategory=CN=Person,CN=Schema,CN=Configuration,DC=mset,DC=local)(sn=wells))".
Further reading.
For more information on ADFIND.EXE, including examples, see http://www.joeware.net/win/free/tools/adfind.htm
To find tips on how to create more efficient LDAP queries, see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnactdir/html/efficientadapps.asp