如何使用C#从Windows活动目录有效地获取用户列表

Dan*_*ngs 5 .net c# windows active-directory

我按照这个问题的解决方案如何从活动目录中获取用户列表?并且能够从AD获得用户列表.我遇到的问题是加载所有记录需要35秒.

必须有一种更有效的方法来一次查询所有数据,而不是等待35秒才能返回700多条记录.我写了一个方法来返回用户列表.我已经添加了一些额外的代码来尝试过滤掉任何不属于人类的用户.

public List<ActiveUser> GetActiveDirectoryUsers()
{
    List<ActiveUser> response = new List<ActiveUser>();
    using (var context = new PrincipalContext(ContextType.Domain, "mydomain"))
    {
        using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
        {
            foreach (var result in searcher.FindAll())
            {
                DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
                if (de.NativeGuid != null && !Convert.ToBoolean((int)de.Properties["userAccountControl"].Value & 0x0002) &&
                    de.Properties["department"].Value != null && de.Properties["sn"].Value != null) response.Add(new ActiveUser(de));
            }
        }
    }
    return response.OrderBy(x => x.DisplayName).ToList();
}
Run Code Online (Sandbox Code Playgroud)

ActiveUser的构造函数只需要输入entry.property ["whataver"]并将其分配给该类的属性.开销似乎就行了

DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
Run Code Online (Sandbox Code Playgroud)

我可以将用户列表缓存到一个文件中,但是对于一个列表来说仍然需要超过30秒的加载时间.必须有一种更快的方法来做到这一点.

hme*_*ia1 3

作为一种学习经验,我使用了多种不同的方法来解决这个问题。

我自己发现,所有方法都可以adspath很快地列出一组值,但是一旦在迭代中引入 Console.WriteLine 就会导致性能发生巨大变化。

我有限的 C# 知识导致我尝试了各种方法,例如IEnumerator直接通过 DirectoryEntry 和PrincipleSearcher上下文,但这两种方法都很慢,并且根据对信息执行的操作而变化很大

最后,这就是我的结局。它无疑是最快的,并且在增加解析选项时不会对性能造成任何明显的影响。

注意:这实际上是该类的完整复制/粘贴powershell包装器,因为我目前没有使用 Visual Studio 的虚拟机。

$Source = @"
// " "  <-- this just makes the code highlighter work
// Syntax:  [soexample.search]::Get("LDAP Path", "property1", "property2", "etc...")
// Example: [soexample.search]::Get("LDAP://CN=Users,DC=mydomain,DC=com","givenname","sn","samaccountname","distinguishedname")

namespace soexample
{
    using System;
    using System.DirectoryServices;

    public static class search
    {
        public static string Get(string ldapPath, params string[] propertiesToLoad)
        {
            DirectoryEntry entry = new DirectoryEntry(ldapPath);
            DirectorySearcher searcher = new DirectorySearcher(entry);
            searcher.SearchScope = SearchScope.OneLevel;
            foreach (string p in propertiesToLoad) { searcher.PropertiesToLoad.Add(p); }
            searcher.PageSize = 100;
            searcher.SearchRoot = entry;
            searcher.CacheResults = true;
            searcher.Filter = "(sAMAccountType=805306368)";
            SearchResultCollection results = searcher.FindAll();

            foreach (SearchResult result in results)
            {
                foreach (string propertyName in propertiesToLoad)
                {
                    foreach (object propertyValue in result.Properties[propertyName])
                    {
                        Console.WriteLine(string.Format("{0} : {1}", propertyName, propertyValue));
                    }
                }
                Console.WriteLine("");

            }
            return "";
        }
    }
}
"@
$Asem = ('System.DirectoryServices','System')
Add-Type -TypeDefinition $Source -Language CSharp -ReferencedAssemblies $Asem
Run Code Online (Sandbox Code Playgroud)

我在一个有 160 个用户的特定域上运行了这个程序,结果如下:

使用代码注释中的示例命令:

PS > Measure-Command { [soexample.search]::Get(args as above..) }
Run Code Online (Sandbox Code Playgroud)

输出:

givenname : John
sn : Surname
samaccountname : john.surname
distinguishedname : CN=John Surname,CN=Users,DC=mydomain,DC=com 

etc ... 159 more ...

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 431
Ticks             : 4317575
TotalDays         : 4.99719328703704E-06
TotalHours        : 0.000119932638888889
TotalMinutes      : 0.00719595833333333
TotalSeconds      : 0.4317575
TotalMilliseconds : 431.7575 
Run Code Online (Sandbox Code Playgroud)

每给出一个额外的字符串参数,似乎都会增加大约 100 毫秒的总处理时间。

运行它只需要 samaccountname0.1 秒即可列出 160 个用户,并解析到控制台中。

使用此处的 Microsoft 示例,并将其修改为仅列出一个属性,花费了 3 秒多的时间,每个附加属性花费了大约一秒。

一些注意事项:

  • (sAMAccountType=805306368)事实证明比(&(objectClass=user)(objectCategory=person))(参见/sf/answers/703737821/)和许多其他示例更有效

  • searcher.CacheResults = true;无论它是正确的还是明显错误的,似乎都没有任何区别(无论如何在我的领域)。

  • searcher.PageSize = 100;产生显着的差异。MaxPageSize我相信2012R2 DC 上的默认值是 1000 ( https://technet.microsoft.com/en-us/library/cc770976(v=ws.11).aspx )

  • 这些属性不区分大小写(即提供给搜索者的任何内容都会在 中返回result.Properties.PropertyNames,因此foreach循环只是迭代这些属性propertiesToLoad)

  • foreach乍一看,这三个循环似乎没有必要,但每次成功删除循环最终都会在强制转换和运行方法扩展方面花费更多的开销。

可能还有更好的方法,我已经看到了一些带有线程和结果缓存的详细示例,我只是不知道该怎么办,但是调整确实DirectorySearcher似乎是最灵活的,并且这里的代码只需要System和System.DirectoryServices命名空间。

不确定你到底用你的"//do stuff"方法做什么,这是否有帮助,但我确实发现这是一个有趣的练习,因为我不知道有这么多方法可以做这样的事情。