Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting all direct Reports from Active Directory

I'm trying to get all the direct reports of a User through Active Directory, recursively. So given a user, i will end up with a list of all users who have this person as manager or who have a person as manager who has a person as manager ... who eventually has the input user as manager.

My current attempt is rather slow:

private static Collection<string> GetDirectReportsInternal(string userDN, out long elapsedTime)
{
    Collection<string> result = new Collection<string>();
    Collection<string> reports = new Collection<string>();

    Stopwatch sw = new Stopwatch();
    sw.Start();

    long allSubElapsed = 0;
    string principalname = string.Empty;

    using (DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}",userDN)))
    {
        using (DirectorySearcher ds = new DirectorySearcher(directoryEntry))
        {
            ds.SearchScope = SearchScope.Subtree;
            ds.PropertiesToLoad.Clear();
            ds.PropertiesToLoad.Add("directReports");
            ds.PropertiesToLoad.Add("userPrincipalName");
            ds.PageSize = 10;
            ds.ServerPageTimeLimit = TimeSpan.FromSeconds(2);
            SearchResult sr = ds.FindOne();
            if (sr != null)
            {
                principalname = (string)sr.Properties["userPrincipalName"][0];
                foreach (string s in sr.Properties["directReports"])
                {
                    reports.Add(s);
                }
            }
        }
    }

    if (!string.IsNullOrEmpty(principalname))
    {
        result.Add(principalname);
    }

    foreach (string s in reports)
    {
        long subElapsed = 0;
        Collection<string> subResult = GetDirectReportsInternal(s, out subElapsed);
        allSubElapsed += subElapsed;

        foreach (string s2 in subResult)
        {
        result.Add(s2);
        }
    }



    sw.Stop();
    elapsedTime = sw.ElapsedMilliseconds + allSubElapsed;
    return result;
}

Essentially, this function takes a distinguished Name as input (CN=Michael Stum, OU=test, DC=sub, DC=domain, DC=com), and with that, the call to ds.FindOne() is slow.

I found that it is a lot faster to search for the userPrincipalName. My Problem: sr.Properties["directReports"] is just a list of strings, and that is the distinguishedName, which seems slow to search for.

I wonder, is there a fast way to convert between distinguishedName and userPrincipalName? Or is there a faster way to search for a user if I only have the distinguishedName to work with?

Edit: Thanks to the answer! Searching the Manager-Field improved the function from 90 Seconds to 4 Seconds. Here is the new and improved code, which is faster and more readable (note that there is most likely a bug in the elapsedTime functionality, but the actual core of the function works):

private static Collection<string> GetDirectReportsInternal(string ldapBase, string userDN, out long elapsedTime)
{
    Collection<string> result = new Collection<string>();

    Stopwatch sw = new Stopwatch();
    sw.Start();
    string principalname = string.Empty;

    using (DirectoryEntry directoryEntry = new DirectoryEntry(ldapBase))
    {
        using (DirectorySearcher ds = new DirectorySearcher(directoryEntry))
        {
            ds.SearchScope = SearchScope.Subtree;
            ds.PropertiesToLoad.Clear();
            ds.PropertiesToLoad.Add("userPrincipalName");
            ds.PropertiesToLoad.Add("distinguishedName");
            ds.PageSize = 10;
            ds.ServerPageTimeLimit = TimeSpan.FromSeconds(2);
            ds.Filter = string.Format("(&(objectCategory=user)(manager={0}))",userDN);

            using (SearchResultCollection src = ds.FindAll())
            {
                Collection<string> tmp = null;
                long subElapsed = 0;
                foreach (SearchResult sr in src)
                {
                    result.Add((string)sr.Properties["userPrincipalName"][0]);
                    tmp = GetDirectReportsInternal(ldapBase, (string)sr.Properties["distinguishedName"][0], out subElapsed);
                    foreach (string s in tmp)
                    {
                    result.Add(s);
                    }
                }
            }
          }
        }
    sw.Stop();
    elapsedTime = sw.ElapsedMilliseconds;
    return result;
}
like image 225
Michael Stum Avatar asked Oct 10 '08 08:10

Michael Stum


People also ask

What is direct reports in Active Directory?

2.55 Attribute directReportsThis attribute contains the list of users that directly report to the user. The users that are listed as reports are those that have the property manager property set to this user. Each item in the list is a linked reference to the object that represents the user.

How do I edit a direct report in Ad?

You cannot modify the directReports attribute of a manager directly. Instead, you must modify the manager attribute of the person that reports to the manager. When you modify the manager attribute of a user, the system automatically updates the directReports attribute of the manager.


1 Answers

First off, setting Scope to "subtree" is unnecessary when you already have the DN you are looking for.

Also, how about finding all objects whose "manager" property is the person you look for, then iterating them. This should generally be faster than the other way around.

(&(objectCategory=user)(manager=<user-dn-here>))

EDIT: The following is important but has only been mentioned in the comments to this answer so far:

When the filter string is built as indicated above, there is the risk of breaking it with characters that are valid for a DN, but have special meaning in a filter. These must be escaped:

*   as  \2a
(   as  \28
)   as  \29
\   as  \5c
NUL as  \00
/   as  \2f

// Arbitrary binary data can be represented using the same scheme.

EDIT: Setting the SearchRoot to the DN of an object, and the SearchScope to Base also is a fast way to pull a single object out of AD.

like image 183
Tomalak Avatar answered Sep 23 '22 00:09

Tomalak