用C# .NET Core 3.1返回活动目录中组成员的属性。

问题描述 投票:0回答:1

我需要从活动目录中给定组的成员中检索某些属性,我认为我走错了方向。

我当前的LDAP查询是这样的。

(&(memberOf:1.2.840.113556.1.4.1941:={DN for group})(objectClass=user)(objectCategory={objectCategory}))

这是一个相当繁重的查询,平均需要10 - 20秒,无论结果包含0,1或1000个用户。结果可以在C#和powershell中复制(Get-ADGroup -LDAPFilter {Your filter})。

我的一个同事建议实现类似于这个powershell查询的功能。

$group = "{samAccountName}"

$attributes = "employeeId","sn","givenName","telephoneNumber","mobile","hPRnr","cn","samAccountName","gender","company","reshId"

Get-ADGroupMember -Identity $group | Get-ADUser -Properties $attributes | select $attributes

是否可以使用C#中的api来实现powerhell查询,或者有更好的解决方案?

澄清一下.我今天有一个C#的方法,它的重点是LDAP。无论AD组中的成员是0个还是1000个,平均敷衍时间在10-15秒之间。

一个完整的例子,在项目中添加了以下库,代码如何工作。

Microsoft.AspNet.WebApi.Client
Microsoft.Extensions.Logging.Log4Net.AspNetCore
Newtonsoft.Json
System.DirectoryServices
System.DirectoryServices.AccountManagement
System.Runtime.Serialization.Json
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using Microsoft.Extensions.Logging;

namespace ActiveDirectoryLibrary.Standard.Services
{
    public class LdapService
    {
        private ILogger _logger;
        private string PersonCategory = "ObjectCategoryForUser";

        public LdapService(ILogger logger)
        {
            _logger = logger;
        }

        public List<User> GetUserRecordsInNestedGroupDetailed(string nestedGroup, string ou)
        {
            var groupStopwatch = new Stopwatch();
            groupStopwatch.Start();
            var group = GetGroup(nestedGroup, ou);
            groupStopwatch.Stop();
            _logger.LogDebug(
                $"Method {nameof(GetUserRecordsInNestedGroupDetailed)}: Getting the group {nestedGroup} took {groupStopwatch.ElapsedMilliseconds} ms");

            if (group == null || !string.IsNullOrEmpty(group.DistinguishedName)) return new List<User>();

            //PersonCategory is the object category for a user object in Active Directory

            var ldapFilter =
                $"(&(memberOf:1.2.840.113556.1.4.1941:={group.DistinguishedName})(objectClass=user)(objectCategory={PersonCategory}))";
            var groupMembers = new List<User>();

            using (var adsEntry = new DirectoryEntry())
            {
                using (var ds = new DirectorySearcher(adsEntry))
                {
                    var stopwatch = new Stopwatch();
                    stopwatch.Start();
                    ds.Filter = ldapFilter;
                    ds.SearchScope = SearchScope.Subtree;
                    LoadAdUserProperties(ds);
                    var members = ds.FindAll();
                    stopwatch.Stop();
                    _logger.LogDebug(
                        $"Method {nameof(GetUserRecordsInNestedGroupDetailed)}: Time consumed {stopwatch.ElapsedMilliseconds} ms for {group.DistinguishedName}");
                    foreach (SearchResult sr in members)
                    {
                        groupMembers.Add(MapSearchResultToUser(sr));
                    }
                }
            }

            return groupMembers;
        }

        public Group GetGroup(string samAccountName, string ou)
        {
            using (var entry = new DirectoryEntry($"LDAP://{ou}"))
            {
                var ds = new DirectorySearcher(entry)
                {
                    Filter = "(&(objectcategory=group)(SamAccountName=" + samAccountName + "))"
                };
                var group = ds.FindOne();
                return group == null ? null : MapSearchResultToGroup(group);
            }
        }

        public static Group MapSearchResultToGroup(SearchResult @group)
        {
            var returnGroup = new Group
            {
                Changed = GetProperty<DateTime>(@group, "whenchanged"),
                SamAccountName = GetProperty<string>(group, "SamAccountName"),
                Description = GetProperty<string>(group, "Description"),
                Created = GetProperty<DateTime>(group, "whencreated"),
                DistinguishedName = GetProperty<string>(group, "distinguishedname"),
                Name = GetProperty<string>(group, "name")
            };
            return returnGroup;
        }

        private static void LoadAdUserProperties(DirectorySearcher ds)
        {
            ds.PropertiesToLoad.Add("reshid");
            ds.PropertiesToLoad.Add("employeeid");
            ds.PropertiesToLoad.Add("sn");
            ds.PropertiesToLoad.Add("givenname");
            ds.PropertiesToLoad.Add("gender");
            ds.PropertiesToLoad.Add("telephonenumber");
            ds.PropertiesToLoad.Add("mobile");
            ds.PropertiesToLoad.Add("cn");
            ds.PropertiesToLoad.Add("distinguishedName");
            ds.PropertiesToLoad.Add("samaccountname");
            ds.PropertiesToLoad.Add("companyname");
        }

        public static User MapSearchResultToUser(SearchResult userProperty)
        {

            var reshId = GetProperty<string>(userProperty, "reshid");
            var employeeElement = GetProperty<string>(userProperty, "employeeid");
            var surname = GetProperty<string>(userProperty, "sn");
            var givenname = GetProperty<string>(userProperty, "givenname");
            var gender = GetProperty<string>(userProperty, "gender");
            var phone = GetProperty<string>(userProperty, "telephonenumber");
            var mobile = GetProperty<string>(userProperty, "mobile");
            var hpr = GetProperty<string>(userProperty, "hprnr");
            var cn = GetProperty<string>(userProperty, "cn");
            var samAccountName = GetProperty<string>(userProperty, "samaccountname");
            var company = GetProperty<string>(userProperty, "company");
            var account = new User
            {
                EmployeeId = employeeElement,
                Sn = surname,
                GivenName = givenname,
                Gender = gender,
                Telephone = phone,
                Mobile = mobile,
                Cn = cn,
                SamAccountName = samAccountName,
                Company = company,
                ReshId = reshId
            };
            return account;
        }

        private static T GetProperty<T>(SearchResult userProperty, string key)
        {
            if (userProperty.Properties[key].Count == 1)
            {
                return (T) userProperty.Properties[key][0];
            }

            return default(T);
        }

        public class Group
        {
            public DateTime Changed { get; set; }
            public string SamAccountName { get; set; }
            public string Description { get; set; }
            public DateTime Created { get; set; }
            public string DistinguishedName { get; set; }
            public string Name { get; set; }
        }

        public class User
        {
            public string EmployeeId { get; set; }
            public string Sn { get; set; }
            public string GivenName { get; set; }
            public string Telephone { get; set; }
            public string OfficePhone { get; set; }
            public string Mobile { get; set; }
            public string Mail { get; set; }
            public string Cn { get; set; }
            public string SamAccountName { get; set; }
            public string Gender { get; set; }
            public string Company { get; set; }
            public string ReshId { get; set; }
        }
    }
}
c# .net powershell active-directory ldap
1个回答
3
投票

我已经在我写的一篇文章中写到了这个问题 寻亲,因为群成员资格有时候是一个很奇怪的复杂的东西。但这里有一个我放在那里的方法,可能对你的情况足够好。

我把它修改为返回一个 User 对象,就像你在代码中一样。如果你把 true 对于 recursive 参数,它将遍历嵌套组。你应该可以根据你的需要修改它。

public static IEnumerable<User> GetGroupMemberList(DirectoryEntry group, bool recursive = false) {
    var members = new List<User>();

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

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (string member in memberDns) {
            using (var memberDe = new DirectoryEntry($"LDAP://{member.Replace("/", "\\/")}")) {
                memberDe.RefreshCache(new[] { "objectClass", "samAccountName", "mail", "mobile" });

                if (recursive && memberDe.Properties["objectClass"].Contains("group")) {
                    members.AddRange(GetGroupMemberList(memberDe, true));
                } else {
                    members.Add(new User {
                        SamAccountName = (string) memberDe.Properties["samAccountName"].Value,
                        Mail = (string) memberDe.Properties["mail"].Value,
                        Mobile = (string) memberDe.Properties["mobile"].Value,
                    });
                }
            }
        }

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

您必须给它传递一个 DirectoryEntry 对象。如果你已经有了该组的DN,你可以像这样创建它。

new DirectoryEntry($"LDAP://{dn}")

如果你没有,你可以通过它的... ... sAMAccountName 像这样。

var groupSamAccountName = "MyGroup";
var ds = new DirectorySearcher($"(sAMAccountName={groupSamAccountName})") {
    PropertiesToLoad = { "cn" } //just to stop it from returning every attribute
};
var groupDirectoryEntry = ds.FindOne()?.GetDirectoryEntry();

var members = GetGroupMemberList(groupDirectoryEntry, false); //pass true if you want recursive members

在我的文章中还有其他的代码 用于查找来自外部可信域的成员(如果你有任何可信域的话) 以及查找将该组作为主组的用户,因为主组关系在该组中不可见。member 属性。

要在.NET Core中使用这段代码,您需要安装 Microsoft.Windows.Compatibility NuGet包,以便能够使用 System.DirectoryServices 命名空间。这将限制您只能在Windows上运行您的应用程序。如果你需要在非Windows操作系统上运行你的应用程序,你可以研究一下 Novell.目录.Ldap.NETStandard但我帮不上忙


0
投票

由于我目前的答案与Gabriel Lucis有很大的分歧,我想最好提出我想出的办法。

    public IEnumerable<User> GetContactDetailsForGroupMembersWithPrincipalContext(string samAccountName, string ou, bool recursive)
    {
        var ctx = new PrincipalContext(ContextType.Domain);
        var grp = GroupPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, samAccountName);
        var users = new List<User>();

        if (grp != null)
        {
            foreach (var principal in grp.GetMembers(true))
            {
                var member = (UserPrincipal) principal;
                var user = GetUser(member);
                if (user != null)
                {
                    users.Add(user);
                }
            }
        }

        return users;
    }

    private User GetUser(UserPrincipal member)
    {
        var entry = (DirectoryEntry) member.GetUnderlyingObject();
        var search = new DirectorySearcher(entry);
        search.PropertiesToLoad.Add("samAccountName");
        search.PropertiesToLoad.Add("mail");
        search.PropertiesToLoad.Add("mobile");
        var result = search.FindOne();
        var user = MapSearchResultToUser(result);
        return user;
    }

    public static User MapSearchResultToUser(SearchResult userProperty)
    {
        var mobile = GetProperty<string>(userProperty, "mobile");
        var mail = GetProperty<string>(userProperty, "mail");
        var samAccountName = GetProperty<string>(userProperty, "samaccountname");

        var account = new User
        {
            Mobile = mobile,
            Mail = mail,
            SamAccountName = samAccountName,
        };
        return account;
    }

    private static T GetProperty<T>(SearchResult userProperty, string key)
    {
        if (userProperty.Properties[key].Count == 1)
        {
            return (T)userProperty.Properties[key][0];
        }

        return default(T);
    }

50个成员的平均性能大约是500到1000毫秒. 该代码并没有很好的扩展,一个嵌套组与1079名成员有在平均13 000毫秒。

原因是我要查询AD两次。

  1. 找出AD组来获取组员
  2. 从用户中获取UserPrincipal对象中无法获得的自定义属性。
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.