I need to retrieve certain attributes from members of a given group in Active Directory and I think I'm on the wrong track.
My current LDAP query looks like this:
(&(memberOf:1.2.840.113556.1.4.1941:={DN for group})(objectClass=user)(objectCategory={objectCategory}))
This is a rather heavy query, which in average takes 10 - 20 seconds regardless of the result contains 0, 1 or a 1000 users. The result can be replicated in C# and powershell (Get-ADGroup -LDAPFilter {Your filter})
A colleague of mine proposed implementing something similar to this powershell query
$group = "{samAccountName}"
$attributes = "employeeId","sn","givenName","telephoneNumber","mobile","hPRnr","cn","samAccountName","gender","company","reshId"
Get-ADGroupMember -Identity $group | Get-ADUser -Properties $attributes | select $attributes
Is it possible to use the api available in C# to implement the powershell query somehow or is there a better solution?
To clearify. I have a C# approach today which has an emphasis on LDAP. The average perfomance is between 10 - 15 seconds whether there are 0 or 1000 members in the AD group.
A complete example of how the code works with the following libraries added to the project:
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; }
}
}
}