Click here to Skip to main content
Email Password   helpLost your password?

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

.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

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralBasic design fault?
RenniePet
8:07 12 Jan '10  
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.
QuestionVersion for Visual Studio 08
tbark
16:55 27 Jan '09  
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 08
CoderGirl42
13:15 26 Mar '09  
Here's a creative solution... Write your own. Smile

Enjoy!
AnswerRe: Version for Visual Studio 08
RenniePet
7:37 12 Jan '10  
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.
GeneralHow to get other information of the user
dabuskol
21:49 2 Dec '08  
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)
Asif Mukaty
21:38 17 Jun '08  
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 servers
wojtekp
20:53 22 Feb '08  
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 version
bort1983
11:02 27 Jul '07  
I have the latest! (v3.0) Please help. Thanks
GeneralActive computers
Mubshir Raza Ali
3:18 6 Jul '07  
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 groups
Refky Wahib
0:02 3 Apr '07  
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 size
siedell
11:14 12 Dec '06  
Does anyone know if there is a limit to the character size of a Search Filter, if any?
Confused
GeneralExcelente! (Great!)
goarango
10:04 27 Nov '06  
El mas completo ejemplo del tema que haya visto. (The best and complete sample about this theme).

G.O. Arango
GeneralThank you Sriram!
ssudhaa
7:25 7 Nov '06  
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?
William Yeung
2:09 10 Oct '06  
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?
Duoc BT
14:54 26 Oct '06  
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]
Bigbirdtung
1:33 6 Jun '06  
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 LAN
Bigbirdtung
1:15 6 Jun '06  
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 ? how can i fix this?
Thanks!

BigBird
AnswerRe: Can't conect to DC from LAN
zohaibabrar
23:35 22 Sep '06  
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 Limit
cavedog6
6:06 5 Jun '06  
How do I query for more than 1000 records? I can't figure out how to get more than 1000 from the server.
GeneralRe: Size Limit
andreas.eppinger
0:39 27 Jun '06  
it's a serversetting.

You can't get more records as the serversettings for sizelimit.
Your Administrator must increase this value.
GeneralRe: Size Limit
Marlon Rodriguez
17:46 20 Jul '06  
Actually you can. Just using the pagesize and then using a while loop with getnextrow until you get the NO_MORE_DATA error. I'm not clear on this. There are more examples in the net I was trying to find one I read a while back about this.
GeneralRe: Size Limit
jportelas
8:41 29 Jan '07  
How can an Administrator increase this value?



jportelas

AnswerRe: Size Limit
andreas.eppinger
10:55 29 Jan '07  
To increase the maximum page size (MaxPageSize), use the Ntdsutil.exe tool.

Take alook here:
http://support.microsoft.com/default.aspx?scid=kb;en-us;315071&sd=tech
GeneralRe: Size Limit
jportelas
3:28 30 Jan '07  
Thanks a lot Andreas!

jportelas

GeneralLDAP Query for Groups in Active Directory
chitransh
1:43 2 Jun '06  
Hi,


HOw to write query got getting all the groups defined in Active directory?

I want to fetch all the groups from active directory.

Thanx in advance

chitransh@dotnet


Last Updated 31 May 2003 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010