使用C#快速获取Active Directory中的组成员列表

Jer*_*emy 6 .net c# active-directory

在Web应用程序中,我们希望显示属于特定组成员的用户的sam帐户列表.在许多情况下,组可以有500个或更多成员,我们需要页面响应.

一组约500名成员需要7-8秒才能获得该组所有成员的sam帐户列表.有更快的方法吗?我知道Active Directory管理控制台会在一秒钟内完成它.

我尝试过几种方法:

1)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
List<string> lst = grp.Members.Select(g => g.SamAccountName).ToList();
Run Code Online (Sandbox Code Playgroud)

2)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
PrincipalSearchResult<Principal> lstMembers = grp.GetMembers(true);
List<string> lst = new List<string>();
foreach (Principal member in lstMembers )
{
    if (member.StructuralObjectClass.Equals("user"))
    {
        lst.Add(member .SamAccountName);
    }
}
Run Code Online (Sandbox Code Playgroud)

3)

PrincipalContext pcRoot = new PrincipalContext(ContextType.Domain)
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcRoot, "MyGroup");
System.DirectoryServices.DirectoryEntry de = (System.DirectoryServices.DirectoryEntry)grp.GetUnderlyingObject();
List<string> lst = new List<string>();
foreach (string sDN in de.Properties["member"])
{
    System.DirectoryServices.DirectoryEntry deMember = new System.DirectoryServices.DirectoryEntry("LDAP://" + sDN);
    lst.Add(deMember.Properties["samAccountName"].Value.ToString());
}
Run Code Online (Sandbox Code Playgroud)

小智 5

使用各种Active Directory检索方法时,我的同事在查询时间方面遇到类似问题.他最终将信息缓存在数据库中并每晚刷新,然后只是访问数据库.

考虑到用户帐户不会经常改变,这对他来说是一个可接受的妥协.根据您的使用情况,这可能是也可能是不可接受的.


JPB*_*anc 5

这是使用ADSI的递归搜索(嵌套组中的搜索用户)。

static void Main(string[] args)
{
  /* Connection to Active Directory
   */
  string sFromWhere = "LDAP://SRVENTR2:389/dc=societe,dc=fr";
  DirectoryEntry deBase = new DirectoryEntry(sFromWhere, "societe\\administrateur", "test.2011");

  /* To find all the users member of groups "Grp1"  :
   * Set the base to the groups container DN; for example root DN (dc=societe,dc=fr) 
   * Set the scope to subtree
   * Use the following filter :
   * (member:1.2.840.113556.1.4.1941:=CN=Grp1,OU=MonOu,DC=X)
   */
  DirectorySearcher dsLookFor = new DirectorySearcher(deBase);
  dsLookFor.Filter = "(&(memberof:1.2.840.113556.1.4.1941:=CN=Grp1,OU=MonOu,DC=societe,DC=fr)(objectCategory=user))";
  dsLookFor.SearchScope = SearchScope.Subtree;
  dsLookFor.PropertiesToLoad.Add("cn");
  dsLookFor.PropertiesToLoad.Add("samAccountName");  

  SearchResultCollection srcUsers = dsLookFor.FindAll();

  /* Just show each user
   */
  foreach (SearchResult srcUser in srcUsers)
  {
    Console.WriteLine("{0}", srcUser.Path);
    Console.WriteLine("{0}", srcUser.Properties["samAccountName"][0]);
  }

  Console.ReadLine();
Run Code Online (Sandbox Code Playgroud)

}


对于@Gabriel Luci评论:Microsoft文档

成员

memberOf属性是一个多值属性,其中包含用户为直接成员的组,但由primaryGroupId表示的主要组除外。组成员身份取决于从中获取此属性的域控制器(DC):

  • 在包含用户的域的DC上,对于该域中组的成员身份,该用户的memberOf是完整的。但是,memberOf不包含用户在其他域中的本地和全局组中的成员身份。

  • 在GC服务器上,对于用户的memberOf对于所有通用组成员身份都是完整的。如果DC的两个条件都成立,则这两个数据集都包含在memberOf中。

请注意,此属性在其成员属性中列出了包含用户的组-它不包含嵌套前辈的递归列表。例如,如果用户O是组C的成员,并且组B和组B嵌套在组A中,则用户O的memberOf属性将列出组C和组B,而不列出组A。

此属性未存储-它是计算的反向链接属性。

  • 反对的主要原因之一是因为AccountManagement的性能很差。真正获得广告搜索性能的唯一方法是移至DirectoryServices库。笨拙的API但速度很快 (2认同)

Gab*_*uci 5

如果你想要的速度,不要使用System.DirectoryServices.AccountManagement在所有(命名空间GroupPrincipalUserPrincipal等等)。它使编码更容易,但它是slloooowwww。

仅使用DirectorySearcherDirectoryEntry。(AccountManagement无论如何,命名空间只是一个包装器)

不久前我和别人讨论过这个问题。你可以在这里阅读完整的聊天记录,但在一个群组有 4873 名成员的情况下,AccountManagementGetMember()方法需要 200 秒,而使用DirectoryEntry只需要 16 秒。

但是,有一些注意事项:

  1. 不要看memberOf属性(正如 JPBlanc 的回答所暗示的那样)。它不会找到域本地组的成员。该memberOf属性仅显示通用组,而全局组仅显示在同一域中。域本地组不会出现在那里。
  2. 查看member一个组的属性一次只能为您提供1500个成员。您必须分批检索 1500 个成员。
  3. 帐户可以将primaryGroupId设置设置为任何组,并被视为该组的一部分(但不会显示在该member组的属性中)。这通常是Domain Users组的情况。
  4. 如果域本地组具有来自外部域的用户,它们将显示为外部安全主体对象,这些对象保存外部域上实际帐户的 SID。需要做一些额外的工作才能找到外部域上的帐户。

AccountManagement命名空间的GetMember()方法照顾到所有的这些事情,只是没有有效的,因为它可以。

在帮助其他用户时,我确实组合了一个方法来涵盖上述前三个问题,但不包括 #4。这是此答案中的最后一个代码块:https : //stackoverflow.com/a/49241443/1202807

更新:

(我已经在我的网站上记录了所有这些:查找组的所有成员

您提到最耗时的部分是遍历成员。那是因为您绑定到每个成员,这是可以理解的。你可以通过调用减轻这种.RefreshCache()在上DirectoryEntry对象只加载您需要的属性。否则,当您第一次使用时Properties,它会获取每个具有值的属性,这会无缘无故地增加时间。

下面是我使用的一个例子。我对一个拥有 803 名成员(在嵌套组中)的组进行了测试,发现.RefreshCache()线路持续减少了大约 10 秒,如果不是更多的话(大约 60 秒没有,大约 45-50 秒有)。

这种方法不会考虑我上面提到的第 3 点和第 4 点。例如,它会默默地忽略外部安全主体。但是,如果您只有一个没有信任的域,则无需关心。

private static List<string> GetGroupMemberList(DirectoryEntry group, bool recurse = false) {
    var members = new List<string>();

    group.RefreshCache(new[] { "member" });

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            var memberDe = new DirectoryEntry($"LDAP://{member}");
            memberDe.RefreshCache(new[] { "objectClass", "sAMAccountName" });
            if (recurse && memberDe.Properties["objectClass"].Contains("group")) {
                members.AddRange(GetGroupMemberList(memberDe, true));
            } else {
                var username = memberDe.Properties["sAMAccountName"]?.Value?.ToString();
                if (!string.IsNullOrEmpty(username)) { //It will be null if this is a Foreign Security Principal
                    members.Add(username);
                }
            }
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*"});
        } catch (COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }

    return members;
}
Run Code Online (Sandbox Code Playgroud)


小智 0

您尝试过 LDAP 查询吗?页面底部有一个 C# 示例,用于枚举组以获取成员。 MSDN博尔

using System;
using System.DirectoryServices;

namespace ADAM_Examples
{
class EnumMembers
{
    /// <summary>
    /// Enumerate AD LDS groups and group members.
    /// </summary>
    [STAThread]
    static void Main()
    {
        DirectoryEntry objADAM;                   // Binding object.
        DirectoryEntry objGroupEntry;             // Group Results.
        DirectorySearcher objSearchADAM;          // Search object.
        SearchResultCollection objSearchResults;  // Results collection.
        string strPath;                           // Binding path.

        // Construct the binding string.
        strPath = "LDAP://localhost:389/OU=TestOU,O=Fabrikam,C=US";
        Console.WriteLine("Bind to: {0}", strPath);
        Console.WriteLine("Enum:    Groups and members.");

        // Get the AD LDS object.
        try
        {
            objADAM = new DirectoryEntry(strPath);
            objADAM.RefreshCache();
        }
        catch (Exception e)
        {
            Console.WriteLine("Error:   Bind failed.");
            Console.WriteLine("         {0}", e.Message);
            return;
        }

        // Get search object, specify filter and scope,
        // perform search.
        try
        {
            objSearchADAM = new DirectorySearcher(objADAM);
            objSearchADAM.Filter = "(&(objectClass=group))";
            objSearchADAM.SearchScope = SearchScope.Subtree;
            objSearchResults = objSearchADAM.FindAll();
        }
        catch (Exception e)
        {
            Console.WriteLine("Error:   Search failed.");
            Console.WriteLine("         {0}", e.Message);
            return;
        }

        // Enumerate groups and members.
        try
        {
            if (objSearchResults.Count != 0)
            {
                foreach(SearchResult objResult in objSearchResults)
                {
                    objGroupEntry = objResult.GetDirectoryEntry();
                    Console.WriteLine("Group    {0}",
                        objGroupEntry.Name);
                    foreach(object objMember
                        in objGroupEntry.Properties["member"])
                    {
                        Console.WriteLine(" Member: {0}",
                            objMember.ToString());
                    }
                }
            }
            else
            {
                Console.WriteLine("Results: No groups found.");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error:   Enumerate failed.");
            Console.WriteLine("         {0}", e.Message);
            return;
        }

        Console.WriteLine("Success: Enumeration complete.");
        return;
    }
}
Run Code Online (Sandbox Code Playgroud)

}