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

我遵循这个问题的解决scheme如何从活动目录中获得用户列表? 并能够从AD得到一个用户列表。 我遇到的问题是需要35秒才能加载所有的logging。

必须有一种更有效的方式来一次查询所有数据,而不必等待35秒才能返回700+条logging。 我写了一个方法来返回用户列表。 我已经放了一些额外的代码来尝试和过滤掉任何不是人类帐户的用户。

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

ActiveUser的构造函数只需要entry.property [“whataver”]并将其分配给该类的属性。 头顶上的似乎是在线上

 DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry; 

我可以将用户列表caching到一个文件,但是对于一个列表加载超过30秒仍然太多。 必须有一个更快的方法来做到这一点。

作为一种学习体验,我在这方面采用了许多不同的方法。

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

我有限的C#知识导致我尝试了各种方法,比如IEnumerator直接通过DirectoryEntry, PrincipleSearcher和Context,但是这两种方法都很慢,并且根据信息做了很大变化

最后,这是我结束了。 它是最快的,并且在增加解析选项时不会有明显的性能下降。

注意:这实际上是一个完整的复制/粘贴类的powershell包装,因为我目前不在使用Visual Studio的VM附近。

 $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 

我在一个拥有160个用户的特定域上运行这个,这里是结果;

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

 PS > Measure-Command { [soexample.search]::Get(args as above..) } 

输出:

 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 

给出的每个额外的字符串参数似乎都将总处理时间增加了大约100ms。

运行只有 samaccountname 需要0.1s列出160个用户,解析到控制台。

在这里使用微软的例子,并修改它只列出一个属性,花了3秒钟,每增加一个属性花了大约一秒钟。

一对夫妇笔记:

  • (sAMAccountType=805306368)(&(objectClass=user)(objectCategory=person)) (见https://stackoverflow.com/a/10053397/3544399 )和其他许多例子

  • searcher.CacheResults = true; 似乎没有什么区别(无论如何是我的领域)是真的还是明确的假。

  • searcher.PageSize = 100; 做出了可衡量的差异。 我相信2012R2 DC上的默认MaxPageSize是1000( https://technet.microsoft.com/en-us/library/cc770976 ( v= ws.11).aspx)

  • 这些属性不区分大小写 (即,无论给予搜索者的结果是否返回result.Properties.PropertyNames ,为什么foreach循环只是迭代这些propertiesToLoad

  • 乍一看这三个foreach循环看起来并不是必须的,但是每次成功移除一个循环都会导致在转换和运行方法扩展时花费更多的开销。

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

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

为了更新,我发现了一个部分的工作。 它比上面的方法快,但缺少一些额外的数据。 从读什么,问题的方法是相当于做类似的事情

 SELECT id FROM sometable foreach row in table SELECT * FROM sometable where id = ? 

所以很明显,为什么它很慢。 下面的方法在一秒钟内执行,并给我所有我需要的属性。 为了获得这些数据,需要对目录条目进行单独的调用,但这很容易实现,因为如果提供一些搜索参数,可能只抓取一个用户。

这是一个更高效的更新方法。

 DirectoryEntry de = new DirectoryEntry("ldap://mydomain"); using (DirectorySearcher search = new DirectorySearcher()) { search.Filter = "(&(objectClass=user)(objectCategory=person))"; search.PropertiesToLoad.Add("userAccountControl"); search.PropertiesToLoad.Add("sn"); search.PropertiesToLoad.Add("department"); search.PropertiesToLoad.Add("l"); search.PropertiesToLoad.Add("title"); search.PropertiesToLoad.Add("givenname"); search.PropertiesToLoad.Add("co"); search.PropertiesToLoad.Add("displayName"); search.PropertiesToLoad.Add("distinguishedName"); foreach (SearchResult searchrecord in search.FindAll()) { //do stuff } }