Monday, July 24, 2006

A Recursive Pattern For DN Syntax Attributes Part 2

I am revisiting this particular topic once again to finish it up.  Last time, we established a general pattern for searching any DN syntax attribute in Active Directory or ADAM and chasing down all the nested results in either direction (i.e. forward link to back link or vice versa).  The solution worked well with one caveat:  intermediate values.  We often do not want to capture the intermediate values, but only the end results.  As an example, if we were to expand the group membership for a group object (the ‘member’ attribute) to discover the users, we would not want to include the other nested groups as values in our results, but we would only want to include the users in those nested groups.  In other words, we would want to exclude the intermediate values.  This is different than another example, say of discovering an org chart by expanding the ‘directReports’ attribute where we would clearly want to know all the intermediate reports.

For the specific example of expanding a group object to get membership, I posted an example in the book that used recursion and specifically excluded the result if the ‘objectClass’ was ‘group’.  I posed the question, “was this necessary?”.  Or more specifically, “can we create a general solution that will deal with both cases for intermediate values?”

The answer, of course, is yes.  We can pretty easily create a solution that will allow us to keep the intermediate values or discard them if we want.  In the general case, we do not need to know the object types.  In our example in the book, I think it was more clear what was happening by putting knowledge of the object type.  However, it also could have been solved with a simple boolean and no knowledge of the objects themselves.  Here is the revised solution.  I am omitting the IADsPathname interface this time, but you can easily pull it from the last post.

public class RecursiveLinkPair

{

    DirectoryEntry entry;

    ArrayList members;

    Hashtable processed;

    string attrib;

    bool includeAll;

 

    public RecursiveLinkPair(DirectoryEntry entry, string attrib, bool includeIntermediate)

    {

        if (entry == null)

            throw new ArgumentNullException("entry");

 

        if (String.IsNullOrEmpty(attrib))

            throw new ArgumentException("attrib");

 

        this.includeAll = includeIntermediate;

        this.attrib = attrib;

        this.entry = entry;

        this.processed = new Hashtable();

        this.processed.Add(

            this.entry.Properties[

                "distinguishedName"][0].ToString(),

            null

            );

 

        this.members = Expand(this.entry);

    }

 

    public ArrayList Members

    {

        get { return this.members; }

    }

 

    private ArrayList Expand(DirectoryEntry group)

    {

        ArrayList al = new ArrayList(5000);

 

        DirectorySearcher ds = new DirectorySearcher(

            entry,

            "(objectClass=*)",

            new string[] {

                this.attrib,

                "distinguishedName",

                "objectClass" },

            SearchScope.Base

            );

 

        ds.AttributeScopeQuery = this.attrib;

        ds.PageSize = 1000;

 

        using (SearchResultCollection src = ds.FindAll())

        {

            string dn = null;

            foreach (SearchResult sr in src)

            {

                dn = (string)

                    sr.Properties["distinguishedName"][0];

 

                if (!this.processed.ContainsKey(dn))

                {

                    this.processed.Add(dn, null);

 

                    if (sr.Properties.Contains(this.attrib))

                    {

                        if (this.includeAll)

                            al.Add(dn);

 

                        SetNewPath(this.entry, dn);

                        al.AddRange(Expand(this.entry));

                    }

                    else

                        al.Add(dn);

                }

            }

        }

        return al;

    }

 

    //we will use IADsPathName utility function instead

    //of parsing string values.  This particular function

    //allows us to replace only the DN portion of a path

    //and leave the server and port information intact

    private void SetNewPath(DirectoryEntry entry, string dn)

    {

        IAdsPathname pathCracker = (IAdsPathname)new Pathname();

 

        pathCracker.Set(entry.Path, 1);

        pathCracker.Set(dn, 4);

 

        entry.Path = pathCracker.Retrieve(5);

    }

}

This simple class uses the AttributeScopeQuery option to enumerate the attribute and then uses the utility interface IADsPathname to reset the entry as we recurse the results.  So, we now finally have a generic solution that will work on all DN syntax attributes in both directions and with or without intermediate values.

Comments are closed.