Active Directory Filter Builder


Recently, I got involved with a project migrating a client from On-Premise to Azure. Their Local AD wasn't the best fit for directly running the Azure AD Connect tool to push the local AD objects to Azure. So, one of the pre-migration steps was to clean up their local AD to make it compatible by updating a few fields like UPN, Mail NickName etc.

Problem Statement

The conflict was that they didn't want to update this for all the users in one go. They wanted to divide the users based on different departments or the same account or other attributes. So the filter needed to be dynamic.
If someone has worked with the LDAP filter query, it gets quite messy with all the brackets and conditions. If some of the conditions are missed, then it takes quite some time for troubleshooting the issue.

I came up with the solution to write a few filter wrappers around the LDAP query to make it easier to create an LDAP filter query that can be injected to the Directory Searcher object for fetching out the results. 
How Do LDAP Queries Work?

The LDAP Query follows Polish Notation and it normally works on the concept of the hierarchy between different criteria like AND and OR.

For example,  if the requirement is to fetch A, B, and C users, then the query would look like this. Each AND/OR can be treated as an individual criterion.

  1. (&(objectCategory=person)(objectClass=user)(|(cn=A)(cn=B)(cn=C))  
The first part would be AND condition for the objectcategory and objectclass and the second is the OR condition. So, it would follow a hierarchical pattern. 
Here is a good article explaining the syntax in detail.  

Filter Overview
  1. ADFilterCondition Class
    The Class objects hold single LDAP conditions. For instance, it will help to build the condition like (objectCategory=person)
  2.  ADFilter Class
    The class holds the collection of the ADfilterCondition class. 

  3. ADFilterBuilder Class
    The class is the wrapper for building the filter that can be applied to the Directory Searcher object. 
All of the above classes can be found in the same code under the folder ADFilters. 
How to use the Wrappers? 
  1. Initialize the ADFilter class with the criteria i.e AND/OR
    1. ADFilter _filter = new ADFilter(ADFilterBuilder.ADFilterExpression.AND);
  2.  Add the condition to the filter using the Add() method. The add method takes in ADFiltercondition as an input parameter. 
    1. _filter.Add(new ADFilterCondition("objectCategory", ADFilterCondition.ADFilterOperators.EQUALTO, "person"));  
  3. Build the filter query using the ADFilterBuilder
    1. ADFilterBuilder.GetFilter(_filter,out _error);  
Example 1 - Fetch All the user filter 

The below function will return the filter for all the users.
  1. /// <summary>  
  2.         /// Returns (&(objectCategory=person)(objectClass=user))  
  3.         /// </summary>  
  4.         /// <returns></returns>  
  5.         private static string AllUserFilter()  
  6.         {  
  7.             string _filterQuery = string.Empty;  
  8.             string _error = string.Empty;   
  10.             ADFilter _filter = new ADFilter(ADFilterBuilder.ADFilterExpression.AND);  
  11.             _filter.Add(new ADFilterCondition("objectCategory", ADFilterCondition.ADFilterOperators.EQUALTO, "person"));  
  12.             _filter.Add(new ADFilterCondition("objectClass", ADFilterCondition.ADFilterOperators.EQUALTO, "user"));  
  14.             return ADFilterBuilder.GetFilter(_filter, out _error);  
  16.         }  
Example 2 - Fetch the specified users. 

The below function will return the users with the specified filters. 
  1. /// <summary>  
  2.        /// Returns (&(objectCategory=person)(objectClass=user)(|(cn=USER1)(cn=USER2)))  
  3.        /// </summary>  
  4.        /// <param name="users"></param>  
  5.        /// <returns></returns>  
  6.        private static string GetUserCollectionFilter(params string[] users)  
  7.        {  
  8.            string _filterQuery = string.Empty;  
  9.            string _error = string.Empty;  
  11.            ADFilter _childFilter = new ADFilter(ADFilterBuilder.ADFilterExpression.OR);  
  12.            users.ToList<string>().ForEach(delegate (string user)  
  13.            {  
  14.                _childFilter.Add(new ADFilterCondition("cn", ADFilterCondition.ADFilterOperators.EQUALTO, user));  
  15.            }  
  16.            );  
  19.            ADFilter _filter = new ADFilter(ADFilterBuilder.ADFilterExpression.AND,_childFilter);  
  20.            _filter.Add(new ADFilterCondition("objectCategory", ADFilterCondition.ADFilterOperators.EQUALTO, "person"));  
  21.            _filter.Add(new ADFilterCondition("objectClass", ADFilterCondition.ADFilterOperators.EQUALTO, "user"));  
  23.            return ADFilterBuilder.GetFilter(_filter,out _error);  
  25.        }   

Use Directory Searcher for fetching the AD objects. 
  1. private static SearchResultCollection GetUsers(string filter)  
  2.      {  
  3.          SearchResultCollection _searchResultCollections = null;  
  5.          using (DirectoryEntry rootEntry = new DirectoryEntry())  
  6.          {  
  7.              rootEntry.Path = $"LDAP://,dc=com";  
  8.              //rootEntry.Username = ;  
  9.              //rootEntry.Password = ;  
  11.              using (DirectorySearcher _searcher = new DirectorySearcher(rootEntry))  
  12.              {  
  13.                  _searcher.Filter = filter;  
  14.                  //_searcher.PageSize = 1000;  
  16.                  _searchResultCollections = _searcher.FindAll();  
  17.              }  
  18.          }  
  20.          return _searchResultCollections;  
  21.      }  

Main Method

  1.  static void Main(string[] args)  
  2.         {  
  3.             // Print All Users   
  4.             PrintResults(GetUsers(AllUserFilter()));  
  6.             // Print Sepcific Users   
  7.             PrintResults(GetUsers(GetUserCollectionFilter( "ABC" , "XYZ" )));  
  9.             Console.ReadLine();  
  10.         }  
  12. private static void PrintResults(SearchResultCollection results)  
  13.         {  
  14.             int counter = 1;  
  15.             results.Cast<SearchResult>().ToList<SearchResult>().ForEach(delegate (SearchResult result)  
  16.             {  
  17.                 Console.WriteLine($" {counter++} : {result.GetDirectoryEntry().Properties["cn"]?.Value}");  
  18.             });  
  19.         }  
Quick Start

The sample code is the console application. The main method in program.cs will be the best way to get started.