从Active Directory获取所有直接报告人

12
我正尝试通过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”]只是一个字符串列表,那就是distinguishedName,似乎很难搜索。

我想知道是否有一种快速的方法可以在distinguishedName和userPrincipalName之间进行转换?或者如果我只有distinguishedName来使用,是否有更快的搜索用户的方法?

编辑:感谢答案! 通过搜索Manager字段,将函数从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;
}

您可以通过将DirectoryEntry和DirectorySearcher从递归中移出来来获得更多速度。它们之间没有变化,是吗? - Tomalak
不再是这样了。我没有说的是:我正在Sharepoint环境中使用它,该调用被包装在SPSecurity.RunWithElevatedPrivileges调用中,这意味着ref参数不可能,并且我不确定将其作为普通参数传递是否有效(奇怪的Sharepoint安全性)。 - Michael Stum
我认为它应该可以工作。据我所知,对象总是作为引用传递。 请参阅:https://dev59.com/SXVC5IYBdhLWcg3wykQt - Tomalak
1
解决SharePoint安全性问题的一个想法。为什么不将此功能托管在SP之外的Web服务中,然后将代码放入SharePoint以访问此服务。 - JohnLBevan
1个回答

10

首先,当你已经拥有你要查找的DN时,将作用域设置为“subtree”是不必要的。

此外,如何查找所有其“manager”属性为所寻找人员的对象,然后对它们进行迭代。这通常比反过来快。

(&(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.

编辑:将SearchRoot设置为对象的DN,并将SearchScope设置为Base也是从AD中快速获取单个对象的方法。


谢谢。我会看看没有子树的表现如何。对于你的第二个建议,听起来很有趣。我需要花点时间理解一下,因为函数仍然需要是递归的,但我会立即测试它。 - Michael Stum
1
如果我可以给你投10票,我会这样做。将函数更改为搜索经理后,它的运行时间从90秒缩短到了仅4秒。 - Michael Stum
请注意,使用这种方法时,您需要迁移风险,即在DN中有效但在过滤器字符串中保留的字符可能会破坏您的过滤器字符串。就我所知,至少需要转义“#”字符。 - Tomalak
谢谢您提供的信息。我现在在一本书中找到了这个内容:“字符<,;+">不能出现在DN中,除非它们被转义为\字符。另外,如果#是DN中的第一个字符,则必须进行转义。” - Michael Stum
实际上,我认为我看错了源代码。返回的DN将始终正确转义为DN,但您指的是过滤器:http://msdn.microsoft.com/en-us/library/aa746475.aspx =>“特殊字符”。那么我会在我的代码中添加一个“为过滤器转义DN”的函数。 - Michael Stum
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接