Click here to Skip to main content
Click here to Skip to main content

Querying Active Directory using .NET classes and LDAP queries

By , 30 May 2003
 

Sample Image - ActiveDirQueryDemo.jpg

Introduction

This article describes how to use .NET framework to manage resources under Windows Active Directory Services. Microsoft provides ADSI (Active Directory Services Interface) which can interact with many providers including IIS (Internet Information Services), LDAP (Lightweight Directory Access Protocol), WinNT and NDS (Novell Netware Directory Service).
Since my aim is to demonstrate the usage of .NET DirectoryService classes I am restricting the demo project to querying some commonly used resources (computers, users and printers) in the domain where the user's machine is, at the same time demonstrating the power of Active Directory services and the ease with which Active Directory objects can be retrieved.

I have also used LDAP in this demo since I thought that it will be useful to know this protocol as it is a platform independent protocol.

There are different ways to query the Active Directory Services in a C# program

  • using the ADSI through COM Interop. For this, in the Visual Studio C# project go to "Add References..." and select the COM tab and select Active DS Type Library from the list. Add using ActiveDs; statement to the top of your file or use fully qualified class names to access ADSI functions.
  • Using the Active Directory Services OleDB Provider (ADsDSOObject). This approach is mostly useful to add Active Directory as a linked server in SQL Server. This subject is out of scope for the present article, I will discuss how to query Active Directory using the ADS OleDB provider in a different article.
  • Using the classes provided under .NET System.DirectoryServices namespace. To access these classes add System.DirectoryServices.dll to the references. This article demonstrates this approach.

.NET System.DirectoryServices namespace

The System.DirectoryServices namespaces provides two important classes DirectoryEntry and DirectorySearcher to work with the Active Directory. The DirectoryEntry class represents a resource in the Active Directory and the DirectorySearcher class is used to query the Active Directory. The other classes under this namespace are for .NET security and collection classes to support the above said main classes.
You have to add System.DirectoryServices to your references by selecting System.DirectoryServices.dll from .NET tab in the Add Reference dialog box.

LDAP format filter strings

The DirectorySearcher class uses a search root which is a server where the search begins and a LDAP filter string (which is analogous to where clause in SQL) to query the Active Directory resources. LDAP format filter strings are similar to LISP. A condition is enclosed by parenthesis and an operator precedes 2 conditions. Eg. (& (A)(B)) The statement is equivalent to saying A and B. Remember the parenthesis. Another example (| (& (A)(B) ) (C) ) should be interpreted as (A and B) or (C).

The OLAP conditional statements are formed by using Active Directory attributes like name, objectCategory, objectClass, printerName, ListedName etc. For eg. to query for a list of printers the condition would be (objectCategory=printQueue) where objectCategory is an attribute and printQueue is the value expected to be assigned to a printer resource in Active Directory. Similary to query for all printers which start with a character 'G' the LDAP query would be (& (objectCategory=printQueue) (name=G*) ) In the above filter observe that the values do not have quotes (' or ") around them.

Using the code

The demo project demonstrates how to query the Active Directory Services and fetch different objects. The LDAP queries and the usage of .NET classes used are confined to QueryObjectsByNETClasses() and GetFilterString() methods.

The following code in QueryObjectsByNETClasses() method creates a DirectorySearcher object and sets properties on the searcher depending on the user preferences given in the main form. The description of different properties is in the comments in the code. Only "name" is added to the PropertiesToLoad property of DirectorySearcher class to save time as we are only interested in retrieving name and objectClass for the list of objects returned.

    DirectorySearcher ds = new DirectorySearcher();
    ds.SearchRoot = new DirectoryEntry("");    
      // start searching from local domain
    ds.Filter = GetFilterString();        
      // get the LDAP filter string based on selections on the form
    ds.PropertyNamesOnly = true;        
      // this will get names of only those 
      // properties to which a value is set
    ds.PropertiesToLoad.Add("name");

    // (PageSize) Maximum number of objects 
    // the server will return per page
    // in a paged search. Default is 0, i.e. no paged search
    if (ObjsPerPage.Text.Length > 0) 
        ds.PageSize = Int32.Parse(ObjsPerPage.Text);

    // (ServerPageTimeLimit) the amount of time the server
    // should observe to search a page of results
    // default is -1, i.e. search indefinitely
    if (PageTimeLimit.Text.Length > 0) 
        ds.ServerPageTimeLimit = new TimeSpan((long)(Decimal.Parse(
            PageTimeLimit.Text) * TimeSpan.TicksPerSecond));

    // (SizeLimit) maximum number of objects the server 
    // returns in a search
    // default is 0 - interpreted as server 
    // set default limit of 1000 entries
    if (ObjsToFetch.Text.Length > 0) 
        ds.SizeLimit = Int32.Parse(ObjsToFetch.Text);

    // (ServerTimeLimit) amount of time that the server 
    // should observe in a search
    // default is -1 interpreted as server default limit of 120 seconds 
    if (TotalTimeLimit.Text.Length > 0) 
        ds.ServerTimeLimit = new TimeSpan((long)(Decimal.Parse(
           TotalTimeLimit.Text) * TimeSpan.TicksPerSecond));

    // (SearchScope) option to search one level or complete subtree
    // default is Subtree, so set this option only if oneLevel is selected
    if (searchOptionCB.SelectedIndex == 1)
        ds.SearchScope = SearchScope.OneLevel;

    // (CacheResults) property by default is true
    ds.CacheResults = CacheResultsCB.Checked;

    ds.ReferralChasing = ReferralChasingOption.None;

    if (SortResultsCB.Checked)
        ds.Sort = new SortOption("name", SortDirection.Ascending);

The FormFilter() and GetFilterString() functions are used to form the LDAP query strings (see the explaination for the format of these strings in the LDAP format filter strings section above). Mainly observe the placement of & and | operators before the lists. For a complete set of attributes on which the queries can be formed for different objects refer to the Active Directory Schema under MSDN here.

    // form a filter string for the search in LDAP format
    private string FormFilter(string objectCategory, string filter)
    {
        String result;
        result = String.Format("(&(objectCategory={0})(name={1}))", 
           objectCategory, filter);
        return result;
    }

    // this function forms the filter string based on the selected
    // objects on the form
    private string GetFilterString()
    {
        // form the filter string for directory search
        string filter = "";
        if (UsersCB.Checked)
            filter += FormFilter("user", UsersFilter.Text);
        if (ComputersCB.Checked)
            filter += FormFilter("computer", ComputersFilter.Text);
        if (PrintersCB.Checked)
            filter += FormFilter("printQueue", PrintersFilter.Text);

        // add all the above filter strings
        return "(|" + filter + ")";
    }

Important Points and Notes

  • Use objectCategory attribute instead of objectClass where ever possible. Active Directory documentation mentions two things related to this issue :
    • objectClass attribute can have multiple values. This can be a problem especially if you are retriving objectClass. You can end up with multiple values !
    • objectCategory is an indexed attribute in Active Directory. So using objectCategory speeds up queries.

The second point is more important because all the examples given in MSDN use objectClass, whereas using objectCategory will speed up queries !

  • If the list queried is too large then there is a possibility of a timeout. So don't be surprised if your query does not return the complete list. A point to be noted is that you cannot set a value to ServerTimeLimit larger than the default value of 120 seconds ! So if you are looking for all objects and your directory is too large it is better to query number of times by changing your LDAP filter string incrementally (for eg. a*, b* ..) and combining the results.
  • Try to use PropertiesToLoad and PropertyNamesOnly properties of DirectorySearcher if you know what properties you are trying to retrieve. If PropertyNamesOnly is set to true, the query will fetch the names of only those properties for which a value is set. Giving names of properties to be loaded to PropertiesToLoad will reduce the fetch time. By default PropertiesToLoad is set to an empty StringCollection to fetch all the properties, and PropertyNamesOnly is set to false to retrieve all properties' names even if a value is not set. For eg. in my demo I have given "name" property to be loaded and set PropertyNamesOnly to true. Please note that even if not specified objectClass and objectCategory propertied are automatically loaded whenever an object is fetched.
  • By default all the results of a Active Directory fetch are cached. Set the CacheResults property of DirectorySearcher to false to refresh the object cache on the local computer.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Sriram Chitturi
Architect
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questioncommentmemberpatamaporn23 Jan '13 - 16:12 
Excellent,
Thank you so much.
GeneralMy vote of 5memberSavindraSingh11 Jan '13 - 1:49 
Simple and the best. Thanks for this code.
QuestionMSLDAP.membermacupryk6 Oct '11 - 4:18 
Do you guys know where I can get some code to connect to msldap?
GeneralThanks Srirammemberanurag842514 Oct '10 - 9:22 
It helped me a lot.. Nice Article..
GeneralMy vote of 5memberWhy .NET12 Oct '10 - 19:12 
GOOD
QuestionBasic design fault?memberRenniePet12 Jan '10 - 7:07 
First, I was very happy to find this project. It got me up and running with LDAP very quickly. Many thanks to the author.
 
But after some modifications so I optionally fetch only certain properties via the DirectorySearcher.PropertiesToLoad property, I realized that LDAP was still returning all of the properties for each directory entry, not just the ones I wanted.
 
After much tearing out of hair and Googling, I found this very helpful article:
 
http://geekswithblogs.net/mhamilton/archive/2006/07/15/85303.aspx[^]
 
Basically, I think the program presented in this Code Project article has a design fault, or maybe two of them.
 
1. The idea of doing an LDAP search should be that the results returned by the DirectorySearcher.FindAll() function should contain all the data you need without any further LDAP access, at least for a simple application like this. But this program uses the SearchResult.GetDirectoryEntry() function on each returned object. This invokes LDAP all over again, once for each directory entry, and obtains all of the properties for each directory entry, even if you have explicitly used DirectorySearcher.PropertiesToLoad to avoid that.
 
2. This program contains this statement and comment:
 
ds.PropertyNamesOnly = true;  // this will get names of only those properties to which a value is set
 
This is very confusing - it took me a long time to figure out what the DirectorySearcher.PropertyNamesOnly property really does, mostly because Microsoft's explanation is really, really poor and confusing.
 
So what I'm trying to say is that for a simple application where you use LDAP to get some data from Active Directory (or a Domino Server - it's almost the same), you should not use DirectorySearcher.PropertyNamesOnly and you should not have to use SearchResult.GetDirectoryEntry(). See the other article for details of how to process the data returned by DirectorySearcher.FindAll().
 
Hope this is of some help.
AnswerRe: Basic design fault?memberizogi9 May '10 - 16:52 
Thanks for pointing this out. I noticed it too and was having all kinds of problems figuring out why I seemed to be getting 80 properties back no matter what I did.
 
I think I'll wrap the SearchResult object instead, and only have it revert to the DirectoryEntry on special occasions such as if I need values from something that weren't initially expected.
QuestionVersion for Visual Studio 08membertbark27 Jan '09 - 15:55 
Could you come up with a version for Visual Studio 08 please? I get the error "Application has generated an exception that could not be handled. Process id=0xbd4 (3028), Thread id=0x7d4 (2004)." I think itmight be cos I dont have the older versions of Visual Studio. You think so?
JokeRe: Version for Visual Studio 08memberCoderGirl4226 Mar '09 - 12:15 
Here's a creative solution... Write your own. Smile | :)
 
Enjoy!
AnswerRe: Version for Visual Studio 08memberRenniePet12 Jan '10 - 6:37 
I was able to get this up and running under Visual Studio 2008 without much trouble. First I let Visual Studio convert the VS 2003 solution, which it did without reporting any errors.
 
But then the forms designer complained that the form definition statements were corrupt. Moving these four statements:
 
         this.FormBorderStyle = FormBorderStyle.FixedSingle;
         this.MaximizeBox = false;
         this.Name = "ADQForm";
         this.Text = "Querying Active Directory Service";
from where they were into the constructor fixed that problem.
 
After that it just worked.
 
Except, see my next message about what I consider to be a design fault.
QuestionHow to get other information of the usermemberdabuskol2 Dec '08 - 20:49 
I know the domain and username attached to the domain, how about other information such as telephone, department. is it available?
 
Dabsukol

GeneralUser Creation Help Required (mukatys@yahoo.com)memberAsif Mukaty17 Jun '08 - 20:38 
I am using following code in VB its working fine with administrtor user loging but i change login from another user which have no administrative rights is return exception regrading unauthorized I change different authontication type but its not working anyone help me regarding this because I want to create user without given rights to user.
 

 

 
Private Sub AddUser(ByVal login As String, ByVal password As String, ByVal fullName As String)
Dim dirEntry As DirectoryEntry
dirEntry = New DirectoryEntry("WinNT://" + Environment.MachineName + ",computer")
 

Dim entries As DirectoryEntries = dirEntry.Children
 
' Set login name and full name.
Dim newUser As DirectoryEntry = entries.Add(login, "User")
newUser.Properties("FullName").Add(fullName)
newUser.Properties("HomeDirectory").Add("c:\Documents and Settings")
newUser.Properties("Description").Add("DARS Operator")
 
' User must change password at next logon (1 - true, 0 - false)
newUser.Properties("PasswordExpired").Add(0)
 
' Password never expires.
'newUser.Properties("PasswordAge").Add(0)
 
' Set flags - User Cannot change password | Password never expires.
newUser.Properties("Userflags").Add(&H40 Or &H10000)
 
' Set the password.
Dim result As Object = newUser.Invoke("SetPassword", password)
 
newUser.CommitChanges()
 
' Add user to the group "Members"
Dim grp As DirectoryEntry = dirEntry.Children.Find("Dars Operator")
If (Not grp Is Nothing) Then
grp.Invoke("Add", New Object() {newUser.Path.ToString()})
End If
End Sub
GeneralSolution for other LDAP serversmemberwojtekp22 Feb '08 - 19:53 
LDAP Services provides a library that allows you to do the same thing, but connecting to any directory server (Novell, iPlanet, etc):
http://www.ldapservices.com/Products/LdapClient.Net/Default.aspx[^]
GeneralCan't open it b/c it says I have the wrong .Net runtime versionmemberbort198327 Jul '07 - 10:02 
I have the latest! (v3.0) Please help. Thanks
GeneralActive computersmemberMubshir Raza Ali6 Jul '07 - 2:18 
Hi, How do i get the active computers or users (computer on which some one has logon or all logged in users) on domain.
 
Many Thanks,

 
Mubshir Raza Ali
^^^^^^^^^^^^^^^^^^^^^^^
> www.born4code.com
> forums.born4code.com
> live.born4code.com

GeneralNested groupsmemberRefky Wahib2 Apr '07 - 23:02 
Hi
Could you please? Tell me how could I query the active directory, for a user groups including all nested groups?
 
In our structure, we add groups inside other groups then we are query the user if he belongs to specific group and normally is not direct group.
 
Could you please help?

 
Refky Wahib
senior programmer, center for learning and innovation

Questionldap filter sizemembersiedell12 Dec '06 - 10:14 
Does anyone know if there is a limit to the character size of a Search Filter, if any?
Confused | :confused:
GeneralExcelente! (Great!)membergoarango27 Nov '06 - 9:04 
El mas completo ejemplo del tema que haya visto. (The best and complete sample about this theme).
 
G.O. Arango
GeneralThank you Sriram!memberssudhaa7 Nov '06 - 6:25 
I was searching for the right filter to query ADSI. I was not getting the syntax right and spent a lot of time trying to figure it out before I bumped into your article. As always you are awesome!!!
 
Sudhaa Subramanian
QuestionHow I can get a good list of attributes and filter criteria?memberWilliam Yeung10 Oct '06 - 1:09 
I want to get a list of attributes available for PropertiesToLoad- the one in SDK is very confusing... I don't really understand what it tries to say on that particular part.

AnswerRe: How I can get a good list of attributes and filter criteria?memberDuoc BT26 Oct '06 - 13:54 
This should give you a list of property/value pairs. I used mypdc as it's my PDC but in most cases, it's not required. It can be a plain path: LDAP://DC=mydomain,DC=com,DC=au.
 

DirectorySearcher searcher = new DirectorySearcher();
searcher.SizeLimit = 1000000;
searcher.ServerTimeLimit = new TimeSpan(0, 1, 0);
searcher.SearchRoot = new DirectoryEntry
	("LDAP://mypdc/DC=mydomain,DC=com,DC=au");
// searcher.Filter = "(objectClass=group)";
searcher.Filter = "(objectClass=user)";
// searcher.Sort = new SortOption("Name", SortDirection.Ascending);
using(SearchResultCollection results = searcher.FindAll())
{
	if (results != null)
	{
		PrintSearchResults(results);
	} 
} // using

 

void PrintSearchResults(SearchResultCollection results)
{
	foreach (SearchResult res in results)
	{
		using(DirectoryEntry de = res.GetDirectoryEntry())
		{
			foreach (string propName in de.Properties.PropertyNames)
			{
				foreach (object val in de.Properties[propName])
				{
					Console.Out.WriteLine(propName + "=" + val);
				}
			}
		} // using
	}
}
 

GeneralDelete user AD [modified]memberBigbirdtung6 Jun '06 - 0:33 
great, you show me how to add user, so what about delete user? how we do it?
 

-- modified at 22:17 Tuesday 6th June, 2006
GeneralCan't conect to DC from LANmemberBigbirdtung6 Jun '06 - 0:15 
I can't conect to the DC from my PC(also Join domain).
here it is:
DirectoryEntry de = new DirectoryEntry("LDAP://DC=fbf,DC=com");
 
is that path wrongConfused | :confused: ? how can i fix this?
Thanks!
 
BigBird
AnswerRe: Can't conect to DC from LANmemberzohaibabrar22 Sep '06 - 22:35 
Try including the domain controller name in the LDAP string in the following format:
 
DirectoryEntry de = new DirectoryEntry("LDAP://DomainControllerName/DC=fbf,DC=com");
 
Noman Juzar Lakdawala.

GeneralSize Limitmembercavedog65 Jun '06 - 5:06 
How do I query for more than 1000 records? I can't figure out how to get more than 1000 from the server.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 31 May 2003
Article Copyright 2003 by Sriram Chitturi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid