Difference between PrincipalSearcher and DirectorySearcher
I've spent a lot of time analyzing the differences between these two. Here's what I've learned.
DirectorySearcher
comes from theSystem.DirectoryServices
namespace.PrincipalSearcher
comes from theSystem.DirectoryServices.AccountManagement
namespace, which is built on top ofSystem.DirectoryServices
.PrincipalSearcher
internally usesDirectorySearcher
.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 thePageSize
DirectorySearcher ds = search.GetUnderlyingSearcher() as DirectorySearcher; if( ds != null ) ds.PageSize = 1000;
DirectorySearcher
can be significantly faster thanPrincipalSearcher
if you make use ofPropertiesToLoad
.DirectorySearcher
and like classes can work with all objects in AD, whereasPrincipalSearcher
is much more limited. For example, you can not modify an Organizational Unit usingPrincipalSearcher
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
of1000
- 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;
PrincipalSearcher
is used to query the Directory for Groups or Users. DirectorySearcher
is used to query all kinds of objects.
I used DirectorySearcher
to get groups before then I discovered PrincipalSearcher
so when I replaced the former with the latter, the speed of my program improved (maybe it was just PrincipalSearcher
that created a better query for me. For what I care, PrincipalSearcher
was just easier to use and more suitable for the task of getting pricipals.
DirectorySearcher
on the other hand is more general as it can get other kinds of objects. This is why it can't be strongly typed as mentioned in the comments. PrincipalSearcher
is all about principals so it will have strongly typed objects that pertain to principals, and this is why also you don't need to tell it to get you an object of kind user or group, it will be implied by the Principal classes you use.