我需要从活动目录中给定组的成员中检索某些属性,我认为我走错了方向。
我当前的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; }
}
}
}
我已经在我写的一篇文章中写到了这个问题 寻亲,因为群成员资格有时候是一个很奇怪的复杂的东西。但这里有一个我放在那里的方法,可能对你的情况足够好。
我把它修改为返回一个 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但我帮不上忙
由于我目前的答案与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两次。