Получение всех прямых отчетов из Active Directory

Я пытаюсь получить рекурсивно все прямые отчеты пользователя через Active Directory. Поэтому, учитывая пользователя, я получаю список всех пользователей, у которых есть этот человек в качестве менеджера, или у кого есть лицо в качестве менеджера, у которого есть человек в качестве менеджера... у которого в конечном итоге есть пользователь ввода в качестве менеджера.

Моя текущая попытка довольно медленная:

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;
}

По существу, эта функция принимает выделенное имя как вход (CN = Michael Stum, OU = test, DC = sub, DC = domain, DC = com), и при этом вызов ds.FindOne() медленный.

Я обнаружил, что намного быстрее искать userPrincipalName. Моя проблема: sr.Properties [ "directReports" ] - это всего лишь список строк, и это различающееся имя, которое кажется медленным для поиска.

Интересно, есть ли быстрый способ конвертировать между выдающимся именем и userPrincipalName? Или существует ли более быстрый способ поиска пользователя, если у меня есть только отличительное имя?

Изменить: Благодаря ответу! Поиск в поле "Менеджер" улучшил функцию с 90 секунд до 4 секунд. Вот новый и улучшенный код, который быстрее и читабельнее (обратите внимание, что в функциях elapsedTime, скорее всего, есть ошибка, но фактическое ядро ​​функции работает):

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;
}

Ответ 1

Во-первых, установка Scope на "поддерево" не требуется, если у вас уже есть DN, которое вы ищете.

Кроме того, как насчет поиска всех объектов, чье "управляющее" свойство является тем, кого вы ищете, а затем итерации. Это обычно должно быть быстрее, чем наоборот.

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

РЕДАКТИРОВАТЬ: Важно следующее, но было упомянуто только в комментариях к этому ответу:

Когда строка фильтра построена, как указано выше, существует риск разбить ее на символы, которые действительны для DN, но имеют особое значение в фильтре. Эти должны быть экранированы:

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

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

EDIT: установка SearchRoot в DN объекта, а SearchScope - Base также является быстрым способом вытащить один объект из AD.