Difference between PrincipalSearcher and DirectorySearcher
active-directory, c#, directoryservices
Solution
I've spent a lot of time analyzing the differences between these two. Here's what I've learned.
`DirectorySearcher` comes from the `System.DirectoryServices` namespace.
`PrincipalSearcher` comes from the `System.DirectoryServices.AccountManagement` namespace, which is built on top of `System.DirectoryServices`. `PrincipalSearcher` internally uses `DirectorySearcher`.
The `AccountManagement` namespace (i.e. `PrincipalSearcher`) was designed to simplify management of User, Group, and Computer objects (i.e. Principals). In theory, it's usage should be easier to understand, and produce fewer lines of code. Though in my practice so far, it seems to heavily depend on what you're doing.
`DirectorySearcher` is more low-level and can deal with more than just User, Group and Computer objects.
For general usage, when you're working with basic attributes and only a few objects, `PrincipalSearcher` will result in fewer lines of code and faster run time.
The advantage seems to disappear the more advanced the tasks you're doing become. For instance if you're expecting more than few hundred results, you'll have to get the underlying `DirectorySearcher` and set the `PageSize`
DirectorySearcher ds = search.GetUnderlyingSearcher() as DirectorySearcher;
if( ds != null )
ds.PageSize = 1000;
`DirectorySearcher` can be significantly faster than `PrincipalSearcher` if you make use of `PropertiesToLoad`.
`DirectorySearcher` and like classes can work with all objects in AD, whereas `PrincipalSearcher` is much more limited. For example, you can not modify an Organizational Unit using `PrincipalSearcher` and like classes.
Here is a chart I made to analyze using `PrincipalSearcher`, `DirectorySearcher` without using `PropertiesToLoad`, and `DirectorySearcher` with using `PropertiesToLoad`. All tests...
- Use a `PageSize` of `1000`
- Query a total of 4,278 user objects
- Specify the following criteria
- `objectClass=user`
- `objectCategory=person`
- Not a scheduling resource (i.e. `!msExchResourceMetaData=ResourceType:Room`)
- Enabled (i.e. `!userAccountControl:1.2.840.113556.1.4.803:=2`)
Code For Each Test
Using `PrincipalSearcher`
[DirectoryRdnPrefix("CN")]
[DirectoryObjectClass("Person")]
public class UserPrincipalEx: UserPrincipal
{
private AdvancedFiltersEx _advancedFilters;
public UserPrincipalEx( PrincipalContext context ): base(context)
{
this.ExtensionSet("objectCategory","User");
}
public new AdvancedFiltersEx AdvancedSearchFilter
{
get {
if( null == _advancedFilters )
_advancedFilters = new AdvancedFiltersEx(this);
return _advancedFilters;
}
}
}
public class AdvancedFiltersEx: AdvancedFilters
{
public AdvancedFiltersEx( Principal principal ):
base(principal) { }
public void Person()
{
this.AdvancedFilterSet("objectCategory", "person", typeof(string), MatchType.Equals);
this.AdvancedFilterSet("msExchResourceMetaData", "ResourceType:Room", typeof(string), MatchType.NotEquals);
}
}
//...
for( int i = 0; i < 10; i++ )
{
uint count = 0;
Stopwatch timer = Stopwatch.StartNew();
PrincipalContext context = new PrincipalContext(ContextType.Domain);
UserPrincipalEx filter = new UserPrincipalEx(context);
filter.Enabled = true;
filter.AdvancedSearchFilter.Person();
PrincipalSearcher search = new PrincipalSearcher(filter);
DirectorySearcher ds = search.GetUnderlyingSearcher() as DirectorySearcher;
if( ds != null )
ds.PageSize = 1000;
foreach( UserPrincipalEx result in search.FindAll() )
{
string canonicalName = result.CanonicalName;
count++;
}
timer.Stop();
Console.WriteLine("{0}, {1} ms", count, timer.ElapsedMilliseconds);
}
Using `DirectorySearcher`
for( int i = 0; i < 10; i++ )
{
uint count = 0;
string queryString = "(&(objectClass=user)(objectCategory=person)(!msExchResourceMetaData=ResourceType:Room)(!userAccountControl:1.2.840.113556.1.4.803:=2))";
Stopwatch timer = Stopwatch.StartNew();
DirectoryEntry entry = new DirectoryEntry();
DirectorySearcher search = new DirectorySearcher(entry,queryString);
search.PageSize = 1000;
foreach( SearchResult result in search.FindAll() )
{
DirectoryEntry user = result.GetDirectoryEntry();
if( user != null )
{
user.RefreshCache(new string[]{"canonicalName"});
string canonicalName = user.Properties["canonicalName"].Value.ToString();
count++;
}
}
timer.Stop();
Console.WriteLine("{0}, {1} ms", count, timer.ElapsedMilliseconds);
}
Using `DirectorySearcher` with `PropertiesToLoad`
Same as "Using `DirectorySearcher` but add this line
search.PropertiesToLoad.AddRange(new string[] { "canonicalName" });
After
search.PageSize = 1000;
Problem
I see Active Directory examples that use `PrincipalSearcher` and other examples that do the same thing but use `DirectorySearcher`. What is the difference between these two examples? Example using `PrincipalSearcher` ``` PrincipalContext context = new PrincipalContext(ContextType.Domain); PrincipalSearcher search = new PrincipalSearcher(new UserPrincipal(context)); foreach( UserPrincipal user in search.FindAll() ) { if( null != user ) Console.WriteLine(user.DistinguishedName); } ``` Example using `DirectorySearcher` ``` DirectorySearcher search = new DirectorySearcher("(&(objectClass=user)(objectCategory=person))"); search.PageSize = 1000; foreach( SearchResult result in search.FindAll() ) { DirectoryEntry user = result.GetDirectoryEntry(); if( null != user ) Console.WriteLine(user.Properties["distinguishedName"].Value.ToString()); } ```