Attribute Scope Queries using LinqToLdap

Attribute Scope Queries can be very useful – given a directory object, say a group, they allow you to search objects that are associated with it by distinguishedName and return their properties.

Using attribute scope queries, you can (for example):

  • return the mail properties of the members of a group;
  • return the sAMAccountName and telephoneNumber property of a user’s manager.
  • return the lastLogonTimestamp of the accounts listed in a user’s seeAlso attribute.

Here are some examples. They use the usual project definition:

This first example, shows how to extraact the  cn, sAMAccountName and email addresses (mail) of the members of a group. Replace Program.cs with this:

using System;
using System.Linq;
using LinqToLdap;

namespace AttributeScopeQuery1
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new LdapConfiguration();
            config.ConfigureFactory("dc1");    // Insert your DC name or domain DNS name here

            var context = new DirectoryContext();

            // ----- Start of section to replace -----

            var distinguishedName = "CN=Group1,OU=Test Groups,DC=big,DC=wooden,DC=badger";
            var response = context.Query(distinguishedName, System.DirectoryServices.Protocols.SearchScope.Base)
                .WithControls(new[] { new System.DirectoryServices.Protocols.AsqRequestControl("member") })
                .Where(u => Filter.Equal(u, "objectclass", "user"))
                .Select("cn", "sAMAccountName", "mail");

            foreach (var item in response)
            {
                var dirAtts = item as LinqToLdap.Collections.DirectoryAttributes;
                Console.WriteLine("DN: {0}", dirAtts.DistinguishedName);
                foreach (var dirAtt in dirAtts)
                {
                    Console.WriteLine("Key: {0:30} Value:{1}", dirAtt.Key, dirAtt.Value);
                }
                Console.WriteLine();
            }

            // ----- End of section to replace -----

            Console.WriteLine("\r\nPress a key to continue...");
            Console.ReadKey(true);
        }
    }
}

Each query returns a set of DirectoryAttributes collections. Each DirectoryAttributes collection is a set of KeyValuePairs<string, object>, the string being the attribute name and the object being the attribute value.

The second example repeats the above query but processes the results differently: an excuse to show how to gather the results from the set of DirectoryAttributes collections into a single set. Let’s say you need the three attributes for three separate purposes. You can still process it as a single query and use ToList() to run it once. You can then use queries on the results of the ToList() as in the ‘var emailAddresses =’ query below to pull out those values for processing. (I haven’t shown queries for cn and sAMAccountName but they’d be similar.)  You need an IgnoreCase StringComparison option if you tend to type the descriptions as they are in AD, such as sAMAccountName, so I include it here even though it’s not necessasry in this case.

    // ----- Start of section to replace -----

    var distinguishedName = "CN=Group1,OU=Test Groups,DC=big,DC=wooden,DC=badger";
    var response = context.Query(distinguishedName, System.DirectoryServices.Protocols.SearchScope.Base)
        .WithControls(new[] { new System.DirectoryServices.Protocols.AsqRequestControl("member") })
        .Where(u => Filter.Equal(u, "objectclass", "user"))
        .Select("cn", "sAMAccountName", "mail");

    var attributes = response.ToList();

    var emailAddresses = attributes.SelectMany(collection => collection
        .Where(kvp => kvp.Key.Equals("mail", StringComparison.CurrentCultureIgnoreCase))
        .Select(kvp => kvp.Value));

    foreach (var email in emailAddresses)
    {
        Console.WriteLine(email);
    }

    // ----- End of section to replace -----

The third example is another variation on the original. This time, only a single attribute, mail, is retrieved from the group members. They’re pulled into a single set of results in an additional query.

    // ----- Start of section to replace -----

    var distinguishedName = "CN=Group1,OU=Test Groups,DC=big,DC=wooden,DC=badger";
    var response = context.Query(distinguishedName, System.DirectoryServices.Protocols.SearchScope.Base)
        .WithControls(new[] { new System.DirectoryServices.Protocols.AsqRequestControl("member") })
        .Where(u => Filter.Equal(u, "objectclass", "user"))
        .Select("mail");

    var attributes = response.ToList();

    var emailAddresses = attributes.SelectMany(collection => collection.Select(kvp => kvp.Value));

    foreach (var email in emailAddresses)
    {
        Console.WriteLine(email);
    }

    // ----- End of section to replace -----

You could put the two queries together into a single statement:

    var emailAddresses = context.Query(distinguishedName, System.DirectoryServices.Protocols.SearchScope.Base)
        .WithControls(new[] { new System.DirectoryServices.Protocols.AsqRequestControl("member") })
        .Where(u => Filter.Equal(u, "objectclass", "user"))
        .Select("mail")
        .ToList()
        .SelectMany(collection => collection.Select(kvp => kvp.Value));

but it feels a bit messy to me, with the ToList() in the middle.

The final example shows how to query a user to get the telephoneNumber of the user’s manager.

    // ----- Start of section to replace -----

    var user = "CN=Test User 1,OU=Test Users,DC=big,DC=wooden,DC=badger";
    var telephoneNumber = context.Query(user, System.DirectoryServices.Protocols.SearchScope.Base)
        .WithControls(new[] { new System.DirectoryServices.Protocols.AsqRequestControl("manager") })
        .Where(u => Filter.Equal(u, "objectclass", "user"))
        .Select("telephoneNumber")
        .SingleOrDefault()
        .SingleOrDefault().Value.ToString();

    Console.WriteLine(telephoneNumber);

    // ----- End of section to replace -----

Further reading:

Introduction to System.DirectoryServices.Protocols (S.DS.P): Creating an Attribute Scoped Query (ASQ)

DirectorySearcher.AttributeScopeQuery Property

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: