Search code examples
.net.net-3.5account-managementactive-directory-group

How to Optimize Decomposing AD Groups Using C# .Net 3.5


I need to retrieve group members for several hundred AD groups. While the code below gives the right answer, it is very slow. Is there a more efficient approach to decompose these groups to its members?

My current approach is using System.DirectoryServices.AccountManagement. I have a List<T> of group names to search. Iterate through each one and call GroupPrincipal.GetMembers() for each (Currently this code takes 2+ minutes to decompose about 100 groups; my target would be under 15 seconds)

[EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
        private static void GetGroupMembership(List<ActiveDirectoryPrincipalProperties> userGroupProperties)
        {
            List<ActiveDirectoryPrincipalProperties> groupProperties = new List<ActiveDirectoryPrincipalProperties>();

            foreach (ActiveDirectoryPrincipalProperties gProperties in userGroupProperties)
            {
                if (gProperties.groupYesNo)
                {
                    PrincipalContext ctx = new PrincipalContext(ContextType.Domain, gProperties.groupDomain);
                    try
                    {
                        GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, IdentityType.Name, gProperties.groupName);

                        foreach (Principal member in group.GetMembers(true))
                        {
                            ActiveDirectoryPrincipalProperties memberProperties = new ActiveDirectoryPrincipalProperties();
                            memberProperties.fullGroupName = gProperties.fullGroupName;
                            memberProperties.groupDomain = gProperties.groupDomain;
                            memberProperties.groupName = gProperties.groupName;
                            memberProperties.groupType = gProperties.groupType;
                            memberProperties.groupYesNo = false;
                            memberProperties.memberDomain = member.Context.Name.ToString();
                            memberProperties.memberName = member.SamAccountName.ToString();
                            memberProperties.memberType = member.StructuralObjectClass.ToString();
                            memberProperties.sqlUserOnlyYesNo = false;

                            groupProperties.Add(memberProperties);
                        }
                        group.Dispose();
                    }
                    finally
                    {
                        ctx.Dispose();
                    }
                }

            }

            userGroupProperties.AddRange(groupProperties);
        }

        public class ActiveDirectoryPrincipalProperties
        {
            public string fullGroupName { get; set; }
            public string groupDomain { get; set; }
            public string groupName { get; set; }
            public string groupType { get; set; }
            public string memberDomain { get; set; }
            public string memberName { get; set; }
            public string memberType { get; set; }
            public bool groupYesNo { get; set; }
            public bool sqlUserOnlyYesNo { get; set; }
        }

Solution

  • I shaved off about a half a minute from my procedure using a LAMDA filter on my list before iterating it's elements and removed one conditional statement in the method. Here is my updated code.

    [SecurityCritical]
            [SecurityPermissionAttribute(SecurityAction.Demand)] 
            private static void GetGroupMembership(List<ActiveDirectoryPrincipalProperties> userGroupProperties)
            {
                List<ActiveDirectoryPrincipalProperties> groupProperties = new List<ActiveDirectoryPrincipalProperties>();
    
                foreach (ActiveDirectoryPrincipalProperties gProperties in userGroupProperties.FindAll(token => token.groupYesNo.Equals(true)))
                {
    
                    PrincipalContext ctx = new PrincipalContext(ContextType.Domain, gProperties.groupDomain);
                    try
                    {
    
                        GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, IdentityType.Name, gProperties.groupName);
    
                        foreach (Principal member in group.GetMembers(true))
                        {
                            ActiveDirectoryPrincipalProperties memberProperties = new ActiveDirectoryPrincipalProperties();
                            memberProperties.fullGroupName = gProperties.fullGroupName;
                            memberProperties.groupDomain = gProperties.groupDomain;
                            memberProperties.groupName = gProperties.groupName;
                            memberProperties.groupType = gProperties.groupType;
                            memberProperties.groupYesNo = false;
                            memberProperties.memberDomain = member.Context.Name.ToString();
                            memberProperties.memberName = member.SamAccountName.ToString();
                            memberProperties.memberType = member.StructuralObjectClass.ToString();
                            memberProperties.sqlUserOnlyYesNo = false;
    
                            groupProperties.Add(memberProperties);
                        }
                        group.Dispose();
                    }
                    finally
                    {
                        ctx.Dispose();
                    }
    
    
                }
    
                userGroupProperties.AddRange(groupProperties);
            }