33

Is it possible to use System.DirectoryServices.AccountManagement.PrincipalSearcher to search based on multiple parameters using "or" (not "and").

i.e.

// This uses an and
//(&(objectCategory=person)(!UserAccountControl:1.2.840.113556.1.4.803:=2)(&(SAMAccountName=tom*)(DisplayName=tom*)))
var searchPrinciple = new UserPrincipal(context);
searchPrinciple.DisplayName =  "tom*";
searchPrinciple.SamAccountName = "tom*";

var searcher = new PrincipalSearcher();
searcher.QueryFilter = searchPrinciple;

var results = searcher.FindAll();

and I would like a search similar to this (in LDAP) using PrincipalSearcher (not DirectorySearcher)

// (&(objectCategory=person)(!UserAccountControl:1.2.840.113556.1.4.803:=2)(|(SAMAccountName=tom*)(DisplayName=tom*)))
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
doobist
  • 905
  • 2
  • 11
  • 20

5 Answers5

26

It's obviously not possible, here is a workaround:

List<UserPrincipal> searchPrinciples = new List<UserPrincipal>();
searchPrinciples.Add(new UserPrincipal(context) { DisplayName="tom*"});
searchPrinciples.Add(new UserPrincipal(context) { SamAccountName = "tom*" });
searchPrinciples.Add(new UserPrincipal(context) { MiddleName = "tom*" });
searchPrinciples.Add(new UserPrincipal(context) { GivenName = "tom*" });

List<Principal> results = new List<Principal>();
var searcher = new PrincipalSearcher();
foreach (var item in searchPrinciples)
{
    searcher = new PrincipalSearcher(item);
    results.AddRange(searcher.FindAll());
}
Jim
  • 3,482
  • 22
  • 18
Bedouin
  • 490
  • 6
  • 8
  • 4
    You will have to handle duplicates using this. If the display name, given name and account name all contain the name "tom" you will have duplicates. – Chad Schouggins May 23 '14 at 15:46
  • I was wanting this as well, though at the point of multiple queries is it significantly less performant? I wonder why I shouldn't just fall back to the DirectorySearcher if it can't be done with PrincipalSearcher – PandaWood Jun 09 '16 at 04:10
8

Not necessarily as clean as some of the other answers but here is how I've implemented this in a project I'm working on. I wanted both searches to be run async to try and reduce any slow down due to running two AD queries.

public async static Task<List<ADUserEntity>> FindUsers(String searchString)
{
    searchString = String.Format("*{0}*", searchString);
    List<ADUserEntity> users = new List<ADUserEntity>();

    using (UserPrincipal searchMaskDisplayname = new UserPrincipal(domainContext) { DisplayName = searchString })
    using (UserPrincipal searchMaskUsername = new UserPrincipal(domainContext) { SamAccountName = searchString })
    using (PrincipalSearcher searcherDisplayname = new PrincipalSearcher(searchMaskDisplayname))
    using (PrincipalSearcher searcherUsername = new PrincipalSearcher(searchMaskUsername))
    using (Task<PrincipalSearchResult<Principal>> taskDisplayname = Task.Run<PrincipalSearchResult<Principal>>(() => searcherDisplayname.FindAll()))
    using (Task<PrincipalSearchResult<Principal>> taskUsername = Task.Run<PrincipalSearchResult<Principal>>(() => searcherUsername.FindAll()))
    {
        foreach (UserPrincipal userPrincipal in (await taskDisplayname).Union(await taskUsername))
            using (userPrincipal)
            {
                users.Add(new ADUserEntity(userPrincipal));
            }
    }

    return users.Distinct().ToList();
}

My ADUserEntity class has an equality check based on the SID. I tried to add the Distinct() on to the Union() of the two searcher results but that didn't work.

I welcome any constructive criticism on my answer as I'd like to know if there is any way I can improve it.

AeroX
  • 3,387
  • 2
  • 25
  • 39
  • 2
    To get only distinct users returned, you can use LINQ to group by an identifier (e.g. SID): Replace `return users.Distinct().ToList();` with `return users.GroupBy(user => user.Sid).Select(group => group.First()).ToList();` – Matt Hanson Feb 21 '16 at 21:35
  • What about using a simple if-statement before adding the ADUserEntity object to the collection? if (!users.Contains(userPrincipal) users.Add(new ADUserEntity(userPrincipal)); – SiL3NC3 Jan 31 '19 at 13:55
4

I know this is kind of late, but this is the construct I use when searching AD:

public static Task<IEnumerable<SomeUserModelClass>> GetUsers(//Whatever filters you want)
{
    return Task.Run(() =>
    {
        PrincipalContext context = new PrincipalContext(ContextType.Domain);
        UserPrincipal principal = new UserPrincipal(context);
        principal.Enabled = true;
        PrincipalSearcher searcher = new PrincipalSearcher(principal);

        var users = searcher.FindAll().Cast<UserPrincipal>()
            .Where(x => x.SomeProperty... // Perform queries)
            .Select(x => new SomeUserModelClass
            {
                userName = x.SamAccountName,
                email = x.UserPrincipalName,
                guid = x.Guid.Value
            }).OrderBy(x => x.userName).AsEnumerable();

        return users;
    });
}
Jaime Still
  • 1,949
  • 20
  • 31
  • 3
    You are basically reading the entire directory, and performing the filtering in the client. This won't scale for any medium sized directory and bigger. – Tsahi Asher Nov 13 '17 at 14:15
  • I've since added, before executing the search, filtering to the UserPrincipal object and narrowing down the scope of the search to a specific starting OU. – Jaime Still Nov 13 '17 at 14:20
-3

The FindAll method searches the domain specified in the principal context for objects that have identical properties to those set on the query filter. The FindAll method returns all objects that match the supplied object whereas the FindOne method returns only a single matching principal object. http://msdn.microsoft.com/en-us/library/bb384378(v=vs.90).aspx

I don't know what you need, but you could do a search by 1 proprety and 1 by other and then use LINQ on the lists to merge,filter and etc...

Alan Araya
  • 701
  • 1
  • 12
  • 27
  • Yes, this is the workaround I am currently using, I was hoping there would be a way to do this more cleanly, in one search. Thanks though. – doobist May 15 '12 at 20:16
-5
PrincipalContext pContext = new PrincipalContext(ContextType.Machine, Environment.MachineName);
GroupPrincipal gp = GroupPrincipal.FindByIdentity(pContext, "Administrators");
bool isMember = UserPrincipal.Current.IsMemberOf(gp);
Mohtashim
  • 216
  • 2
  • 8