NOTE: This post – drafted, composed, written, and published by me – originally appeared on https://blogs.technet.microsoft.com/johnbai and is potentially (c) Microsoft.
A while back, I posted about using ADSI COM to get the data that you wanted. I want to show you how you can do this 10 times faster, using System.DirectoryServices.Protocols and LINQ.
Class for the actual AD query being performed:
/// <summary> /// Initializes a new instance of the <see cref="LdapSearcher"/> class. /// </summary> internal class LdapSearcher { /// <summary> /// Initializes a new instance of the <see cref="SearchTheForestTask"/> method. /// </summary> /// <param name="filter">The filter to use for the LDAP query.</param> /// <param name="sizeLimit">The limit of return objects for the query.</param> /// <param name="configContainer"></param> /// <returns></returns> public static async Task<SearchResponse> SearchTheForestTask(string filter, int? sizeLimit, bool configContainer) { // More information on the return statement can be found here: https://msdn.microsoft.com/en-us/library/1h3swy84.aspx // More information on the async/await keywords can be found here: https://msdn.microsoft.com/en-us/library/hh191443.aspx // More information on the Task<TResult>.Run() method can be found here: https://msdn.microsoft.com/en-us/library/hh160382(v=vs.110).aspx return await Task.Run(async /* Run on asynchronous thread so we don't block. */ () => { // More information on the return statement can be found here: https://msdn.microsoft.com/en-us/library/1h3swy84.aspx // More information on the async/await keywords can be found here: https://msdn.microsoft.com/en-us/library/hh191443.aspx // More information on the Task<TResult>.Run() method can be found here: https://msdn.microsoft.com/en-us/library/hh160382(v=vs.110).aspx return await Task.Run(() => SearchTheForest(filter, sizeLimit, configContainer)); }); } /// <summary> /// Initializes a new instance of the <see cref="SearchTheForest"/> method. /// </summary> /// <param name="filter">The filter to use for the LDAP query.</param> /// <param name="sizeLimit">The limit of return objects for the query.</param> /// <param name="configContainer"></param> /// <returns></returns> private static SearchResponse SearchTheForest(string filter, int? sizeLimit, bool configContainer) { string targetServer = Domain.GetComputerDomain().FindDomainController().Name; // We can't await a Task to get RootDSE via async or it may execute AFTER we need it. DirectoryEntry rootDse = new DirectoryEntry("LDAP://RootDSE") { // More information on the DirectoryEntry.AuthenticationType property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.authenticationtype(v=vs.110).aspx // More information on the AuthenticationTypes enumeration can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.authenticationtypes(v=vs.110).aspx AuthenticationType = AuthenticationTypes.Encryption }; string searchRoot = AdMethods.GetSearchRootTask(rootDse, configContainer).Result; // We instantiate the object to clear data. Ad.NewSearchResponse = null; // If the size limit isn't defined, we set it to 1k. int pageSize = sizeLimit ?? 1000; // We specify the page size so that we don't break all the things. // More information on the PageResultRequestControl class can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.pageresultrequestcontrol(v=vs.100).aspx PageResultRequestControl newPageResultRequestControl = new PageResultRequestControl(pageSize); // We include deleted objects in the search. // More information on the ShowDeletedControl class can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.showdeletedcontrol(v=vs.110).aspx ShowDeletedControl newShowDeletedControl = new ShowDeletedControl(); // Establish connection to the Directory. LdapConnection newLdapConnection = new LdapConnection(targetServer); // Create the Search Request. // More information on the SearchRequest class be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchrequest(v=vs.110).aspx SearchRequest newSearchRequest = new SearchRequest { // More information on the SearchRequest.Filter property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchrequest.filter(v=vs.110).aspx Filter = filter, // We specify the search root for the query. Don't let the documentation lie to you, // this property does not specify the object to query FOR. // More information on the SearchRequest.DistinguishedName property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchrequest.distinguishedname(v=vs.110).aspx DistinguishedName = searchRoot, // More information on the SearchRequest.Scope property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchrequest.scope(v=vs.110).aspx // More information on the SearchScope enumeration can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.searchscope(v=vs.110).aspx Scope = System.DirectoryServices.Protocols.SearchScope.Subtree, // More information on the SearchRequest.TimeLimit property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchrequest.timelimit(v=vs.110).aspx TimeLimit = TimeSpanConstants.OneMinuteTimeSpan, // More information on the SearchRequest.RequestId property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.directoryrequest.requestid(v=vs.110).aspx RequestId = GuidConstants.RequestGuid.ToString(), // More information on the SearchRequest.Aliases property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchrequest.aliases(v=vs.110).aspx // More information on the DereferenceAlias enumeration can be found there: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.dereferencealias(v=vs.100).aspx Aliases = System.DirectoryServices.Protocols.DereferenceAlias.Never, // More information on the SearchRequest.TypesOnly property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchrequest.typesonly(v=vs.110).aspx TypesOnly = false, // More information on the SearchRequest.Controls property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.directoryrequest.controls(v=vs.110).aspx Controls = { newPageResultRequestControl, newShowDeletedControl } }; if (sizeLimit.HasValue) { // More information on the SearchRequest.SizeLimit property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchrequest.sizelimit(v=vs.110).aspx newSearchRequest.SizeLimit = sizeLimit.Value; } // Since we're entering a critical section, let's lock so we block further calls. // More information on the lock keyword can be found here: https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx lock (ObjectConstants.NewLock) { // Enter the critical section. // More information on the try keyword can be found here: https://msdn.microsoft.com/en-us/library/6dekhbbc.aspx try { // More information on the SearchResponse class can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchresponse(v=vs.110).aspx // More information on the LdapConnection.SendRequest(DirectoryRequest) method can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.ldapconnection.sendrequest(v=vs.110).aspx Ad.NewSearchResponse = (SearchResponse) newLdapConnection.SendRequest(newSearchRequest, TimeSpanConstants.OneMinuteTimeSpan); } catch (LdapException exception) { // We throw the base exception so it can be unwound by the calling thread. // More information on the Exception.GetBaseException() method can be found here: https://msdn.microsoft.com/en-us/library/system.exception.getbaseexception(v=vs.110).aspx throw exception.GetBaseException(); } } // Since we need to await completion, we pass a value back. // More information on the return statement can be found here: https://msdn.microsoft.com/en-us/library/1h3swy84.aspx return Ad.NewSearchResponse; } }
The real magic – taking the return and using LINQ:
// More information on the TaskFactory class can be found here: https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskfactory(v=vs.110).aspx TaskFactory<SearchResponse> newTaskFactory = new TaskFactory<SearchResponse>(); // More information on the using statement can be found here: https://msdn.microsoft.com/en-us/library/yh598w02.aspx // More information on the Task<TResult> class can be found here: https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx // More information on the TaskFactory.StartNew(Action) method can be found here: https://msdn.microsoft.com/en-us/library/dd321439(v=vs.110).aspx using (Task<SearchResponse> newTask = newTaskFactory.StartNew(() => LdapSearcher.SearchTheForestTask(filter, null, false).Result)) { // More information on the TaskAwaiter<TResult> class can be found here: https://msdn.microsoft.com/en-us/library/hh138386(v=vs.110).aspx // More information on the Task.GetAwaiter() method can be found here: https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.getawaiter(v=vs.110).aspx TaskAwaiter<SearchResponse> newTaskAwaiter = newTask.GetAwaiter(); // More information on the TaskAwaiter.IsCompleted property can be found here: https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.iscompleted(v=vs.110).aspx while (!newTaskAwaiter.IsCompleted) { // More information on the string.Format() method can be found here: https://msdn.microsoft.com/en-us/library/system.string.format(v=vs.110).aspx this.WriteDebug(string.Format("Awaiting task with id'{0}' to finish.", newTask.Id)); // Let's wait for task completion. System.Threading.Thread.Sleep(500); } // More information on the TaskAwaiter.IsCompleted property can be found here: https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.iscompleted(v=vs.110).aspx if (newTaskAwaiter.IsCompleted) { // More information on the string.Format() method can be found here: https://msdn.microsoft.com/en-us/library/system.string.format(v=vs.110).aspx this.WriteDebug(string.Format("Task with Id '{0}' completed.", newTask.Id)); // More information on the Task.IsFaulted property can be found here: https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.isfaulted(v=vs.110).aspx if (!newTask.IsFaulted) { // More information on the TaskAwaiter.GetResult() method can be found here: https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter.getresult(v=vs.110).aspx newTaskAwaiter.GetResult(); // More information on the SearchResultEntryCollection class can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchresultentrycollection(v=vs.110).aspx SearchResultEntryCollection newSearchResultEntryCollection = Ad.NewSearchResponse.Entries; // More information on the SearchResultEntry class can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchresultentry(v=vs.110).aspx SearchResultEntry newSearchResultEntry = newSearchResultEntryCollection[0]; // More information on the SearchResultAttributeCollection class can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchresultattributecollection(v=vs.110).aspx SearchResultAttributeCollection newSearchResultAttributeCollection = newSearchResultEntry.Attributes; // Validate we don't hit the System.NullReferenceException // More information on the SearchResultAttributeCollection.Values property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchresultattributecollection.values(v=vs.110).aspx if (newSearchResultAttributeCollection.Values != null) { // To save overhead from iterating through each attribute on the object, we need to convert it into a queryable. // This takes some magic but the overall result is the same data, just slightly faster. // More information on the SearchResultAttributeCollection.Values property can be found here: https://msdn.microsoft.com/en-us/library/system.directoryservices.protocols.searchresultattributecollection.values(v=vs.100).aspx ICollection newCollection = newSearchResultAttributeCollection.Values; // More information on the Enumerable.Cast<TResult>() method can be found here: https://msdn.microsoft.com/en-us/library/bb341406(v=vs.110).aspx IEnumerable newDirectoryAttributesCollection = newCollection.Cast<DirectoryAttribute>(); // More information on the Queryable.AsQueryable() method can be found here: https://msdn.microsoft.com/en-us/library/bb353734(v=vs.110).aspx IQueryable<DirectoryAttribute> newQueryable = (IQueryable<DirectoryAttribute>) newDirectoryAttributesCollection.AsQueryable(); // More information on the Where() method can be found here: https://msdn.microsoft.com/en-us/library/bb546161.aspx // More information on the Select() method can be found here: https://msdn.microsoft.com/en-us/library/bb546168.aspx IQueryable<object[]> newQueryableObjectUno = newQueryable.Where(x => x.Name.Equals("msExchMailboxSecurityDescriptor")).Select(x => x.GetValues(typeof (byte[]))); // More information on the FirstOrDefault() method can be found here: https://msdn.microsoft.com/en-us/library/bb546140.aspx object[] newObjectsUno = newQueryableObjectUno.FirstOrDefault(); // If the data is null, we went wrong somewhere but it's best to prevent System.NullReferenceException. if (newObjectsUno != null) { byte[] newByteArrayBytes = (byte[]) newObjectsUno[0]; // We decode the string. CommonSecurityDescriptor newCommonSecurityDescriptor = new CommonSecurityDescriptor(true, true, newByteArrayBytes, 0); string attrValue = newCommonSecurityDescriptor.GetSddlForm(AccessControlSections.All); this.WriteObject(attrValue); } } } } }
While this isn’t quite straight-forward, what’s happening is that we’re obtaining all of the properties for the object in AD, in byte format, and then performing a select against the specific property that we want to see.
Happy coding! 🙂