LINQ to Active Directory

Published 20/01/2008 by Henry in C# | LINQ
Tags: ,

Because I needed a way to get users from Active Directory to sync them with another application (CRM). I started out writing Directory Services (LDAP) Queries. I started thinking: "Can't this be done with LINQ?", after all in C# and the .NET Framework 3.0, 3.5  we now have the Language Integrated Query possibilities in our programming toolkit. With LINQ you should be able to query all collections, as long as they impement the IQueryable interface. So why not Active Directory?
When you search: "LINQ to AD" with Google. You''ll find the following link: http://www.codeplex.com/LINQtoAD.

Bart de Smet is a belgian developer, who works for Microsoft. He is the one that made LINQ to AD. Here is a serie blogposts he wrote about this topic.
LINQ to AD is a query provider for LINQ that's capable of talking to Active Directory (and other LDAP data sources potentially) over LDAP.
Ofcourse LINQ To AD is a wrapper around System.DirectoryServices.

He wrote the BdsSoft.DirectoryServices.Linq, the LINQ to AD implementation, if you want to use LINQ To AD you need to add a reference to this assembly and you need to reference the Active DS Type Library (ActiveDs.dll). The 'activeds.dll' is a module that contains functions and object methods, or COM components, for the Active Directory Services Interfaces (ADSI) API.

One of the reasons System.DirectoryServices is so powerfull, is because you can still access native ADSI interfaces by using the NativeObject method. NativeObject will return the IADs interface of the specific type of object.
To use the NativeObject method, you'll need to add a reference to the ActiveDs.dll library. I need to get all users belonging to a particular AD Group and sync these to my application.
All I need to do is: Add a reference the BdsSoft.DirectoryServices.Linq assembly and the Active DS Type Library (ActiveDs.dll).
Next I created a method that must Retrieve all users member of a group in the Active Directory.

The method will have two parameters:

  1. domain, a string that will hold the Active Directory domain name
  2. groupName, a string that will contain the name of the group in AD the users belong to

The method will return a List of type User, User is a custom object that represents a user and is called: "RetrieveADUsersInGroup";

Here a listing of the signature: 

   1:  private List<User> RetrieveADUsersInGroup(string domain, string groupName)
   2:  {
   3:      // Put code here
   4:  }
Listing 1

In the method, an instance of the System.DirectoryServices.DirectoryEntry object needs to be instantiated.
I pass a string to the constructor ('LDAP://'), so the DirectoryEntry instance is bound to the node in Active Directory Domain Services that is located at this specific path.

   1:  DirectoryEntry rootOfDirectory = new DirectoryEntry(string.Format("LDAP://{0}", domain));
Listing 2

Now we take this DirectoryEntry object (rootOfDirectory) and pass it to the constructor of the object DirectorySource<T> (which implements the IQueryable<T> interface), together with the enum System.DirectoryServices.SearchScope (defines the scope for a Directory search).
We take SearchScope.Subtree, which means the whole SubTree, including Base object and all child objects.

Here the signature of the method that is called and lives inside the BdsSoft.DirectoryServices.Linq assembly. 

   1:  public class DirectorySource<T> : IQueryable<T>, IDirectorySource
   2:  {
   3:      // Code here
   4:  }
Listing 3

Now we use the keyword: 'var', var is a way to declare variables in C# 3.0  and up, that uses implicit typing. Keep in mind that var is not the same as 'object', or the JavaScript 'var' datatype as it’s actually strongly typed, but inferred from whatever the value is being assigned.
The proces of creating datatypes on the fly is called: 'projection'. Var is projecting the datatype from the Query.

   1:      var groups = new DirectorySource<Group>(rootOfDirectory, SearchScope.Subtree);
   2:      var listOfAllGroups = from          grp in groups
   3:                            where         grp.Name == groupName
   4:                            select new {  grp.Name, MemberCount = grp.Members.Length, grp.Members };
Listing 4

On line 1 'var groups' the complete root directory is inferred as BdsSoft.DirectoryServices.Linq.DirectorySource<T>, where 'T' is of type 'ActiveDirectory.Group', my own class that represents an ActiveDirectory group. 
From line 2 on the LINQ syntax is used to get all groups from the Collection where the name is equal to the value in methods string parameter 'groupName'. From these groups or group the Name, MemberCount and Members are taken and returned by the query and assigned to the listOfAllGroups variable.

I wanted to map the result from this LINQ Query directly to my User type, instead of needing to loop through every result and map each property one by one. What provides an opportunity to show that you can map your own types to the Query's result, after all you can predict the projection result. 
First the code that retrieves the users: 

   1:  var UserFromAD = new DirectorySource<Centric.PublieksDiensten.KC.ActiveDirectory.Entities.User>(rootOfMemberDirectory, SearchScope.Subtree);
   2:   
   3:  IEnumerable<User> usersFromQuery = from adUser in UserFromAD
   4:                                     select new User
   5:                                     {
   6:                                          Firstname = adUser.FirstName,
   7:                                          Lastname = adUser.LastName,
   8:                                          AccountName = adUser.AccountName,
   9:                                          Email = adUser.Email,
  10:                                          PhoneNumber = adUser.TelephoneNumber,
  11:                                          Fax = adUser.Fax,
  12:                                          AddressStreet = adUser.AddressStreet,
  13:                                          City = adUser.City,
  14:                                          State = adUser.State,
  15:                                          Country = adUser.Country
  16:                                      };
Listing 5

On line 3 the variable 'usersFromQuery' is of type 'IEnumerable of type User' (IEnumerable<User>). IEnumerable needs to be implemented if we do not want to use 'var'. On line 4 the part 'select new' is extended to 'select new User' in this way we project our own User type. In the body of the Query you see every field is mapped to a property of the User type through field name/value pairs. Under the covers var infers the field names, field values, and field data types and creates an anonymous type with the fields we project.

Summary:

  • IEnumerable of type ...
  • select new ...
  • map through field name/value pairs

Here a listing with the complete code from the Method:

   1:  private static List<User> RetrieveADUsersInGroup(string domain, string groupName)
   2:  {
   3:      DirectoryEntry rootOfDirectory = new DirectoryEntry(string.Format("LDAP://{0}", domain));
   4:   
   5:      var groups = new DirectorySource<Group>(rootOfDirectory, SearchScope.Subtree);
   6:      var listOfAllGroups = from          grp in groups
   7:                            where         grp.Name == groupName
   8:                            select new {  grp.Name, MemberCount = grp.Members.Length, grp.Members };
   9:   
  10:      List<User> userList = new List<User>();
  11:   
  12:      foreach (var currentGroup in listOfAllGroups)
  13:      {
  14:          foreach (var member in currentGroup.Members)
  15:          {
  16:              DirectoryEntry rootOfMemberDirectory = new DirectoryEntry(GetLDAPPath(domain) + "/" + member);
  17:              var UserFromAD = new DirectorySource<Centric.PublieksDiensten.KC.ActiveDirectory.Entities.User>(rootOfMemberDirectory, SearchScope.Subtree);
  18:   
  19:              IEnumerable<User> usersFromQuery = from adUser in UserFromAD
  20:                                          select new User
  21:                                          {
  22:                                              Firstname = adUser.FirstName,
  23:                                              Lastname = adUser.LastName,
  24:                                              AccountName = adUser.AccountName,
  25:                                              Email = adUser.Email,
  26:                                              PhoneNumber = adUser.TelephoneNumber,
  27:                                              Fax = adUser.Fax,
  28:                                              AddressStreet = adUser.AddressStreet,
  29:                                              City = adUser.City,
  30:                                              State = adUser.State,
  31:                                              Country = adUser.Country
  32:                                          };
  33:   
  34:              userList.AddRange(usersFromQuery);
  35:          }
  36:      }
  37:   
  38:      return userList;
  39:  }
Listing 6

LINQ is a layer of abstraction over the way we query data. It is in the case of LINQ to AD a nice beginning to get rid of the LDAP Querying. LINQ overall provides a way to query Databases, Collections, Active Directory, XML and every Datasource for that matter in the same way from now on. In the LINQ to AD case the fact that had to query all groups and then loop through these groups to get the members of the groups is a big shortcoming. I had hoped to have to write one query that did all the work and returned a list of users. Still because the datasource is DirectoryServices and Active DS I can understand that their limits are in the way of making this possible. Still I hope in the future this will be possible.
I do like to work with LINQ and will try to learn more about it's inner workings. I definitely see a shift in paradigm, no more static language only in C# and I think change like this is exiting!

Henry Cordes
My thoughts exactly...


Comments (7) -

16/01/2009 11:08:37 #

Great article!  I noticed that you wrote it a year ago.  Did you finally bridge the gap of CRM and AD?  What I would like to do is do an xmlFetch on CRM Accounts and map it directly to AD groups and users.  Any ideas?

thanks in advance.

mike

17/01/2009 08:00:06 #

Thanks!

I used LINQ to AD in a tool that syncs users that belong to an AD group with a role in CRM.
So all users in the AD group will be CRM users that are a member of the CRM role. It works and is in production.

Now, I do not fully understand your requirement, do you want to map the CRM systemuser, or the account entity?
You can use XmlFetch, but the RetrieveMultiple(QueryExpression) operation on the CrmService webservice would do the trick also.
Can you give me more info on what you are trying to achieve?
What exactly do you mean with 'map it directly to AD groups and users'?

17/01/2009 08:47:42 #

Thanks Henry!
The fetchXML query would look like the following to retrieve the latest changes from the users.  I'd like to use LINQ from the fetch results to modify and create new accounts in AD.  The good news is that I only need to update a few AD attributes. Smile
<fetch mapping='logical'>
  <entity name='contact'>
    <attribute name='accountid'/>
    <attribute name='contactid'/>
    <attribute name='emailaddress1'/>
    <attribute name='fax'/>
    <attribute name='firstname'/>
    <attribute name='fullname'/>
    <attribute name='lastname'/>
    <attribute name='modifiedon'/>
    <filter type='and'>
      <condition attribute='createdon' operator='on-or-after' value='2008-12-15T15:56:02-08:00'/>
    </filter>
    <filter type='or'>
      <condition attribute='modifiedon' operator='on-or-after' value='2008-12-15T15:56:02-08:00'/>
    </filter>
    <link-entity name='account' from='accountid' to='accountid' alias='Account' link-type='natural'>
      <attribute name='accountid'/>
      <attribute name='accountnumber'/>
      <attribute name='websiteurl'/>
      <attribute name='name'/>
    </link-entity>
  </entity>
</fetch>

20/01/2009 07:41:46 #

Well Mike, if you need to update AD through the use of Linq to AD, you could take a look here:
community.bartdesmet.net/.../...rting-updates.aspx
In this post Bart shows how to create a User object that is derived from DirectoryEntity.

Using the following principles, the objects are updateable:
The objects returned should be the entities themselves; we won't support the case where projections are made. Or, in other words, the query should end with a plain vanilla "select <dummy>" clause.
Entity objects should be "improved" to make them more intelligent, allowing to track changes (i.e. property setter calls).

He even helps providing codesnippets to implement the INotifyChanged interface on your properties.
In the end the following code updates your AD User:


1             var res9 = from usr in myusers
2                        select usr;
3
4             Console.WriteLine("QUERY 9\n=======");
5             foreach (var u in res9)
6                 u.AccountExpirationDate = DateTime.Now.AddDays(30);
7             Console.WriteLine();
8
9             myusers.Update();

Now, I do not know if it is possible to add users, but give it a try, and maybe that is not what you want anyway.
You always could go to using the good old LDAP Query mechanism ofcourse Wink

Good luck!

02/10/2009 18:52:02 #

LINQ to Active Directory  , okay that made me interested. I have recently set out to develop utilizing Silverlight but I am finding it is a large learning curve.  My earlier experience is with php, mysql, most linux based tools and flash. The ambition of utilising Silverlight to produce a clear page that runs rapidly in most of the major web browsers, Internet Explorer, Safari, Firefox and Google Chrome is a big headache that I find is taking umpteen hours to master.  Absorbing to study your thoughts and the remarks in your web site on Silverlight.  I find the tutorial web sites and Microsofts Silverlight site are rigid and mention the identical items, dialog in web logs frequently references factual methods to overcome issues which leads me through the learning curve more rapidly.  Thanks for the article, it has helped in a small way to take me through the migration.

Aaron | Reply

05/02/2010 03:59:06 #

Could you please tell me the method implementation of "GetLDAPPath" and this type "Centric.PublieksDiensten.KC.ActiveDirectory.Entities.User". How this type is able to fetch lot of information about the user and Why you using this type instead of using "User" type ?

06/02/2010 04:41:10 #

Hi Aaron,

GetLDAPPath gets the path to the AD, so the functionality can be used by providing the domain name, GetLDAPPath builds the right string.
The type Centric.PublieksDiensten.KC.ActiveDirectory.Entities.User, is used to make working with the user easier in the rest of the application, it implements the same properties as User with some additions.

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading