Active Directory: DirectoryEntry member list <> GroupPrincipal.GetMembers() - c#
I have a group, lets call it GotRocks. I am attempting to get all of its members, but I am getting wildly different results count-wise between DirectoryEntry and AccountManagement. Below are the counts by member retrieval method:
Method 1: DirectoryEntry.PropertyName.member = 350
Method 2: AccountManagement.GroupPrincipal.GetMembers(false) = 6500
Method 2: AccountManagement.GroupPrincipal.GetMembers(true) = 6500
As a sanity check, I went into ADUC and pulled the list of members from the group, which is limited to 2,000 by default. The important thing here is that ADUC seems to validate the AccountManagement result. I have checked the Children property as well, but it is blank. Also, none of the members listed in DirectoryEntry are of the SchemaName group - they are all users.
I do not think this is a code problem, but perhaps a lack of understanding of how DirectoryEntry and the GetMembers methods retrieve group members. Can anyone explain why the DirectoryEntry member list would yield a different result from the GetMembers recursive function? Is there a certain method or property I need to be aware of? Note: I have built a function that will query DirectoryEntry by "member;range={0}-{1}" where the loop gets members in chunks of 1,500. I am at a complete and utter loss here.
The fact that DirectoryEntry is returning so few results is problematic because I want to use DirectoryEntry for the simple fact that going this route is, at a minimum, two orders of magnitude faster than AccountManagement (i.e., stopwatch times of 1,100 milliseconds versus 250,000 milliseconds).
UPDATE 1: Methods:
Method 1: DirectoryEntry
private List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
// Variable declaration(s).
List<string> listGroupMemberDn = new List<string>();
string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
string strMemberPropertyRange = null;
DirectoryEntry directoryEntryGroup = null;
DirectorySearcher directorySearcher = null;
SearchResultCollection searchResultCollection = null;
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx
const int intIncrement = 1500;
// Load the DirectoryEntry.
try
{
directoryEntryGroup = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
directoryEntryGroup.RefreshCache();
}
catch (Exception)
{ }
try
{
if (directoryEntryGroup.Properties["member"].Count > 0)
{
int intStart = 0;
while (true)
{
int intEnd = intStart + intIncrement - 1;
// Define the PropertiesToLoad attribute, which contains a range flag that LDAP uses to get a list of members in a pre-specified chunk/block of members that is defined by each loop iteration.
strMemberPropertyRange = string.Format("member;range={0}-{1}", intStart, intEnd);
directorySearcher = new DirectorySearcher(directoryEntryGroup)
{
Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))", // User, Contact, Group, Computer objects
SearchScope = SearchScope.Base,
PageSize = intActiveDirectoryPageSize,
PropertiesToLoad = { strMemberPropertyRange }
};
try
{
searchResultCollection = directorySearcher.FindAll();
foreach (SearchResult searchResult in searchResultCollection)
{
var membersProperties = searchResult.Properties;
// Find the property that starts with the PropertyName of "member;" and get all of its member values.
var membersPropertyNames = membersProperties.PropertyNames.OfType<string>().Where(n => n.StartsWith("member;"));
// For each record in the memberPropertyNames, get the PropertyName and add to the lest.
foreach (var propertyName in membersPropertyNames)
{
var members = membersProperties[propertyName];
foreach (string memberDn in members)
{
listGroupMemberDn.Add(memberDn);
}
}
}
}
catch (DirectoryServicesCOMException)
{
// When the start of the range exceeds the number of available results, an exception is thrown and we exit the loop.
break;
}
intStart += intIncrement;
}
}
return listGroupMemberDn;
}
finally
{
listGroupMemberDn = null;
strPath = null;
strMemberPropertyRange = null;
directoryEntryGroup.Close();
if(directoryEntryGroup != null) directoryEntryGroup.Dispose();
if (directorySearcher != null) directorySearcher.Dispose();
if(searchResultCollection != null) searchResultCollection.Dispose();
}
}
Method 2: AccountManagement (toggle bolRecursive as either true or false).
private List<Guid> GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive)
{
// Variable declaration(s).
List<Guid> listGroupMemberGuid = null;
GroupPrincipal groupPrincipal = null;
PrincipalSearchResult<Principal> listPrincipalSearchResult = null;
List<Principal> listPrincipalNoNull = null;
PrincipalContext principalContext = null;
ContextType contextType;
IdentityType identityType;
try
{
listGroupMemberGuid = new List<Guid>();
contextType = ContextType.Domain;
principalContext = new PrincipalContext(contextType, strDomainController);
// Setup the IdentityType. Use IdentityType.Guid because GUID is unique and never changes for a given object. Make sure that is what strPropertyValue is receiving.
// This is required, otherwise you will get a MultipleMatchesException error that says "Multiple principals contain a matching Identity."
// This happens when you have two objects that AD thinks match whatever you're passing to UserPrincipal.FindByIdentity(principalContextDomain, strPropertyValue)
identityType = IdentityType.Guid;
groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, identityType, strPropertyValue);
if (groupPrincipal != null)
{
// Get all members that the group contains and add it to the list.
// Note: The true flag in GetMembers() specifies a recursive search, which enables the application to search a group recursively and return only principal objects that are leaf nodes.
listPrincipalSearchResult = groupPrincipal.GetMembers(bolRecursive);
// Remove the nulls from the list, otherwise the foreach loop breaks prematurly on the first null found and misses all other object members.
listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();
foreach (Principal principal in listPrincipalNoNull)
{
listGroupMemberGuid.Add((Guid)principal.Guid);
}
}
return listGroupMemberGuid;
}
catch (MultipleMatchesException)
{
// Multiple principals contain a matching identity.
// In other words, the same property value was found on more than one record in either of the six attributes that are listed within the IdentityType enum.
throw new MultipleMatchesException(strPropertyValue);
}
finally
{
// Cleanup objects.
listGroupMemberGuid = null;
if(listPrincipalSearchResult != null) listPrincipalSearchResult.Dispose();
if(principalContext != null) principalContext.Dispose();
if(groupPrincipal != null) groupPrincipal.Dispose();
}
}
UPDATE 2:
public static void Main()
{
Program objProgram = new Program();
// Other stuff here.
objProgram.GetAllUserSingleDc();
// Other stuff here.
}
private void GetAllUserSingleDc()
{
string strDomainController = "domain.com";
string strActiveDirectoryHost = "LDAP://" + strDomainController;
int intActiveDirectoryPageSize = 1000;
string[] strAryRequiredProperties = null;
DirectoryEntry directoryEntry = null;
DirectorySearcher directorySearcher = null;
SearchResultCollection searchResultCollection = null;
DataTypeConverter objConverter = null;
Type fieldsType = null;
fieldsType = typeof(AdUserInfoClass);
objConverter = new DataTypeConverter();
directoryEntry = new DirectoryEntry(strActiveDirectoryHost, null, null, AuthenticationTypes.Secure);
directorySearcher = new DirectorySearcher(directoryEntry)
{
//Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))", // User, Contact, Group, Computer objects
Filter = "(sAMAccountName=GotRocks)", // Group
SearchScope = SearchScope.Subtree,
PageSize = intActiveDirectoryPageSize
PropertiesToLoad = { "isDeleted","isCriticalSystemObject","objectGUID","objectSid","objectCategory","sAMAccountName","sAMAccountType","cn","employeeId",
"canonicalName","distinguishedName","userPrincipalName","displayName","givenName","sn","mail","telephoneNumber","title","department",
"description","physicalDeliveryOfficeName","manager","userAccountControl","accountExpires","lastLogon","logonCount","lockoutTime",
"primaryGroupID","pwdLastSet","uSNCreated","uSNChanged","whenCreated","whenChanged","badPasswordTime","badPwdCount","homeDirectory",
"dNSHostName" }
};
searchResultCollection = directorySearcher.FindAll();
try
{
foreach (SearchResult searchResult in searchResultCollection)
{
clsAdUserInfo.GidObjectGuid = objConverter.ConvertByteAryToGuid(searchResult, "objectGUID");
clsAdUserInfo.StrDirectoryEntryPath = strActiveDirectoryHost + "/<GUID=" + clsAdUserInfo.GidObjectGuid + ">";
clsAdUserInfo.StrSchemaClassName = new DirectoryEntry(clsAdUserInfo.StrDirectoryEntryPath, null, null, AuthenticationTypes.Secure).SchemaClassName;
if (clsAdUserInfo.StrSchemaClassName == "group")
{
// Calling the functions here.
List<string> listGroupMemberDnMethod1 = GetGroupMemberListStackOverflow(clsAdUserInfo.GidObjectGuid.ToString(), strActiveDirectoryHost, intActiveDirectoryPageSize);
List<Guid> listGroupMemberGuidMethod2 = GetGroupMemberList(clsAdUserInfo.GidObjectGuid.ToString(), strDomainController, false)
}
// More stuff here.
}
}
finally
{
// Cleanup objects.
// Class constructors.
objProgram = null;
clsAdUserInfo = null;
// Variables.
intActiveDirectoryPageSize = -1;
strActiveDirectoryHost = null;
strDomainController = null;
strAryRequiredProperties = null;
directoryEntry.Close();
if(directoryEntry !=null) directoryEntry.Dispose();
if(directorySearcher != null) directorySearcher.Dispose();
if(searchResultCollection != null) searchResultCollection.Dispose();
objConverter = null;
fieldsType = null;
}
}
UPDATE 3:
Below is the list of namespaces that I am using.
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Security.Principal;
using System.Text;
using System.Linq;
using System.Collections;
UPDATE 4: Program.cs
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Security.Principal;
using System.Text;
using System.Linq;
namespace activeDirectoryLdapExamples
{
public class Program
{
public static void Main()
{
Program objProgram = new Program();
objProgram.GetAllUserSingleDc();
}
#region GetAllUserSingleDc
private void GetAllUserSingleDc()
{
Program objProgram = new Program();
string strDomainController = "EnterYourDomainhere";
string strActiveDirectoryHost = "LDAP://" + strDomainController;
int intActiveDirectoryPageSize = 1000;
DirectoryEntry directoryEntry = null;
DirectorySearcher directorySearcher = null;
SearchResultCollection searchResultCollection = null;
DataTypeConverter objConverter = null;
objConverter = new DataTypeConverter();
directoryEntry = new DirectoryEntry(strActiveDirectoryHost, null, null, AuthenticationTypes.Secure);
directorySearcher = new DirectorySearcher(directoryEntry)
{
Filter = "(sAMAccountName=GotRocks)", // Group
SearchScope = SearchScope.Subtree,
PageSize = intActiveDirectoryPageSize,
PropertiesToLoad = { "isDeleted","isCriticalSystemObject","objectGUID","objectSid","objectCategory","sAMAccountName","sAMAccountType","cn","employeeId",
"canonicalName","distinguishedName","userPrincipalName","displayName","givenName","sn","mail","telephoneNumber","title","department",
"description","physicalDeliveryOfficeName","manager","userAccountControl","accountExpires","lastLogon","logonCount","lockoutTime",
"primaryGroupID","pwdLastSet","uSNCreated","uSNChanged","whenCreated","whenChanged","badPasswordTime","badPwdCount","homeDirectory",
"dNSHostName" }
};
searchResultCollection = directorySearcher.FindAll();
try
{
foreach (SearchResult searchResult in searchResultCollection)
{
Guid? gidObjectGuid = objConverter.ConvertByteAryToGuid(searchResult, "objectGUID");
string StrSamAccountName = objConverter.ConvertToString(searchResult, "sAMAccountName");
// Get new DirectoryEntry and retrieve the SchemaClassName from it by binding the current objectGUID to it.
string StrDirectoryEntryPath = strActiveDirectoryHost + "/<GUID=" + gidObjectGuid + ">";
string StrSchemaClassName = new DirectoryEntry(StrDirectoryEntryPath, null, null, AuthenticationTypes.Secure).SchemaClassName;
#region GetGroupMembers
if (StrSchemaClassName == "group")
{
// FAST!
var watch = System.Diagnostics.Stopwatch.StartNew();
List<string> listGroupMemberDn = GetGroupMemberList(gidObjectGuid.ToString(), strActiveDirectoryHost, intActiveDirectoryPageSize);
watch.Stop();
var listGroupMemberDnElapsedMs = watch.ElapsedMilliseconds;
// SLOW!
watch = System.Diagnostics.Stopwatch.StartNew();
List<Guid> listGroupMemberGuidRecursiveTrue = GetGroupMemberList(gidObjectGuid.ToString(), strDomainController, true);
watch.Stop();
var listGroupMemberGuidRecursiveTrueElapsedMs = watch.ElapsedMilliseconds;
watch = System.Diagnostics.Stopwatch.StartNew();
List<Guid> listGroupMemberGuidRecursiveFalse = GetGroupMemberList(gidObjectGuid.ToString(), strDomainController, false);
watch.Stop();
var listGroupMemberGuidRecursiveFalseElapsedMs = watch.ElapsedMilliseconds;
////// Display all members of the list.
//listGroupMemberDn.ForEach(item => Console.WriteLine("Member GUID: {0}", item));
//listGroupMemberGuidRecursiveTrue.ForEach(item => Console.WriteLine("Member GUID: {0}", item));
//listGroupMemberGuidRecursiveFalse.ForEach(item => Console.WriteLine("Member GUID: {0}", item));
Console.WriteLine("objectGUID: {0}", gidObjectGuid);
Console.WriteLine("sAMAccountName: {0}", strSamAccountName);
// Result: 350
Console.WriteLine("\nlistGroupMemberDn Count Members: {0}", listGroupMemberDn.Count);
Console.WriteLine("Total RunTime listGroupMemberDnElapsedMs (in milliseconds): {0}", listGroupMemberDnElapsedMs);
// Result: 6500
Console.WriteLine("\nlistGroupMemberGuidRecursiveTrue Count Members: {0}", listGroupMemberGuidRecursiveTrue.Count);
Console.WriteLine("Total RunTime listGroupMemberGuidRecursiveTrueElapsedMs (in milliseconds): {0}", listGroupMemberGuidRecursiveTrueElapsedMs);
// Result: 6500
Console.WriteLine("\nlistGroupMemberGuidRecursiveFalse Count Members: {0}", listGroupMemberGuidRecursiveFalse.Count);
Console.WriteLine("Total RunTime listGroupMemberGuidRecursiveFalseElapsedMs (in milliseconds): {0}", listGroupMemberGuidRecursiveFalseElapsedMs);
Console.WriteLine("\n");
}
#endregion
#region CurrentSearchResult
else
{
Console.WriteLine("ObjectGuid = {0}", gidObjectGuid);
Console.WriteLine("SamAccountName = {0}", strSamAccountName);
}
#endregion
}
Console.WriteLine("\nPress any key to continue.");
Console.ReadKey();
}
finally
{
objProgram = null;
intActiveDirectoryPageSize = -1;
strActiveDirectoryHost = null;
strDomainController = null;
directoryEntry.Close();
if (directoryEntry != null) directoryEntry.Dispose();
if (directorySearcher != null) directorySearcher.Dispose();
if (searchResultCollection != null) searchResultCollection.Dispose();
objConverter = null;
}
}
#endregion
#region GetGroupMemberListGuid
private List<Guid> GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive)
{
List<Guid> listGroupMemberGuid = null;
List<Principal> listPrincipalNoNull = null;
GroupPrincipal groupPrincipal = null;
PrincipalSearchResult<Principal> listPrincipalSearchResult = null;
PrincipalContext principalContext = null;
ContextType contextType;
IdentityType identityType;
try
{
listGroupMemberGuid = new List<Guid>();
contextType = ContextType.Domain;
principalContext = new PrincipalContext(contextType, strDomainController);
identityType = IdentityType.Guid;
groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, identityType, strPropertyValue);
if (groupPrincipal != null)
{
listPrincipalSearchResult = groupPrincipal.GetMembers(bolRecursive);
listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();
foreach (Principal principal in listPrincipalNoNull)
{
listGroupMemberGuid.Add((Guid)principal.Guid);
}
}
return listGroupMemberGuid;
}
catch (MultipleMatchesException)
{
throw new MultipleMatchesException(strPropertyValue);
}
finally
{
// Cleanup objects.
listGroupMemberGuid = null;
listPrincipalNoNull = null;
principalContext = null;
if (groupPrincipal != null) groupPrincipal.Dispose();
if (listPrincipalSearchResult != null) listPrincipalSearchResult.Dispose();
if (principalContext != null) principalContext.Dispose();
}
}
#endregion
#region GetGroupMemberListDn
private List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
List<string> listGroupMemberDn = new List<string>();
string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
const int intIncrement = 1500; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx
var members = new List<string>();
// The count result returns 350.
var group = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
//var group = new DirectoryEntry($"LDAP://{"EnterYourDomainHere"}/<GUID={strPropertyValue}>", null, null, AuthenticationTypes.Secure);
while (true)
{
var memberDns = group.Properties["member"];
foreach (var member in memberDns)
{
members.Add(member.ToString());
}
if (memberDns.Count < intIncrement) break;
group.RefreshCache(new[] { $"member;range={members.Count}-*" });
}
return members;
}
#endregion
#region DataTypeConvert
private class DataTypeConverter
{
public DataTypeConverter() { }
public String ConvertToString(SearchResult searchResult, string strPropertyName)
{
String bufferObjectString = null;
try
{
bufferObjectString = (String)this.GetPropertyValue(searchResult, strPropertyName);
if (string.IsNullOrEmpty(bufferObjectString))
{
return null;
}
else
{
return bufferObjectString;
}
}
finally
{
bufferObjectString = null;
}
}
public Guid? ConvertByteAryToGuid(SearchResult searchResult, string strPropertyName)
{
Guid? bufferObjectGuid = null;
try
{
bufferObjectGuid = new Guid((Byte[])(Array)this.GetPropertyValue(searchResult, strPropertyName));
if (bufferObjectGuid == null || bufferObjectGuid == Guid.Empty)
{
throw new NullReferenceException("The field " + strPropertyName + ", of type GUID, can neither be NULL nor empty.");
}
else
{
return bufferObjectGuid;
}
}
finally
{
bufferObjectGuid = null;
}
}
}
#endregion
}
}
The last code block (Update 2) is the answer!
The code you have for reading the member attribute is more complicated than it needs to be. There may be a reason in there why it's returning skewed results, but I didn't look too hard because you don't need to be using DirectorySearcher at all. I just rewrote it.
Here is what it should look like, in it's simplest form:
private static List<string> GetGroupMemberList(string groupGuid, string domainDns) {
var members = new List<string>();
var group = new DirectoryEntry($"LDAP://{domainDns}/<GUID={groupGuid}>", null, null, AuthenticationTypes.Secure);
while (true) {
var memberDns = group.Properties["member"];
foreach (var member in memberDns) {
members.Add(member.ToString());
}
if (memberDns.Count == 0) break;
try {
group.RefreshCache(new[] {$"member;range={members.Count}-*", "member"});
} catch (System.Runtime.InteropServices.COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
break;
}
throw;
}
}
return members;
}
Call it like this:
var members = GetGroupMemberList("00000000-0000-0000-0000-000000000000", "domain.com");
This is not recursive. To make it recursive, you will have to create a new DirectoryEntry from each member and test if it is a group, then get the members of that group.
I have the code open, so here's the recursive version. It is slow because it has to bind to each member to see if it's a group.
This is not bullet-proof. There are still cases where you might get weird results (like if you have users on trusted external domains in a group).
private static List<string> GetGroupMemberList(string groupGuid, string domainDns, bool recurse = false) {
var members = new List<string>();
var group = new DirectoryEntry($"LDAP://{domainDns}/<GUID={groupGuid}>", null, null, AuthenticationTypes.Secure);
while (true) {
var memberDns = group.Properties["member"];
foreach (var member in memberDns) {
if (recurse) {
var memberDe = new DirectoryEntry($"LDAP://{member}");
if (memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(
new Guid((byte[]) memberDe.Properties["objectGuid"].Value).ToString(), domainDns,
true));
} else {
members.Add(member.ToString());
}
} else {
members.Add(member.ToString());
}
}
if (memberDns.Count == 0) break;
try {
group.RefreshCache(new[] {$"member;range={members.Count}-*", "member"});
} catch (System.Runtime.InteropServices.COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
break;
}
throw;
}
}
return members;
}
Update:
I did have to edit your GetMembers example, since it kept throwing exceptions on me. I commented out the .Where line and changed the foreach loop that adds the members to the list:
//listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();
if (groupPrincipal != null) {
foreach (Principal principal in listPrincipalSearchResult) {
listGroupMemberGuid.Add(((DirectoryEntry)principal.GetUnderlyingObject()).Guid);
}
}
This, of course, is compiling a list of Guids rather than DNs.
Update 2: Here is a version that also pulls users who have the group as the primary group (but not listed in the member attribute of the group). GetMembers seems to do this. It would be odd for a user-created group to be the primary group, but it is technically possible. Parts of this are copied from the answer here: How to retrieve Users in a Group, including primary group users
private List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
// Variable declaration(s).
List<string> listGroupMemberDn = new List<string>();
string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
const int intIncrement = 1500; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx
var members = new List<string>();
// The count result returns 350.
var group = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
//var group = new DirectoryEntry($"LDAP://{"EnterYourDomainHere"}/<GUID={strPropertyValue}>", null, null, AuthenticationTypes.Secure);
while (true)
{
var memberDns = group.Properties["member"];
foreach (var member in memberDns)
{
members.Add(member.ToString());
}
if (memberDns.Count < intIncrement) break;
group.RefreshCache(new[] { $"member;range={members.Count}-*" });
}
//Find users that have this group as a primary group
var secId = new SecurityIdentifier(group.Properties["objectSid"][0] as byte[], 0);
/* Find The RID (sure exists a best method)
*/
var reg = new Regex(#"^S.*-(\d+)$");
var match = reg.Match(secId.Value);
var rid = match.Groups[1].Value;
/* Directory Search for users that has a particular primary group
*/
var dsLookForUsers =
new DirectorySearcher {
Filter = string.Format("(primaryGroupID={0})", rid),
SearchScope = SearchScope.Subtree,
PageSize = 1000,
SearchRoot = new DirectoryEntry(strActiveDirectoryHost)
};
dsLookForUsers.PropertiesToLoad.Add("distinguishedName");
var srcUsers = dsLookForUsers.FindAll();
foreach (SearchResult user in srcUsers)
{
members.Add(user.Properties["distinguishedName"][0].ToString());
}
return members;
}
Related
Removing Groups from Active Directory results return only Users
I am using Active Directory to return a list of Users in a autocomplete input field. The issue I am having when the user types the name of a group it returns data. Ideally what I would like is to only show Users, not groups. Is this possible? Would appreciate your help. Thanks in advance. public List<Client> Search(string name) { List<Client> results = new List<Client>(); string domainname = "domain"; string rootQuery = "LDAP://domain.com/DC=domain,DC=com"; name = name + "*"; using (DirectoryEntry root = new DirectoryEntry(rootQuery)) { domainname = root.Properties["Name"].Value.ToString(); using (DirectorySearcher searcher = new DirectorySearcher(root)) { //searcher.Filter = "(ObjectClass=" + name + ")"; //searchFilter; searcher.Filter = "(SAMAccountName=" + name + ")"; //searchFilter; searcher.PropertiesToLoad.Add("displayName"); searcher.PropertiesToLoad.Add("sAMAccountName"); searcher.PropertiesToLoad.Add("mail"); SearchResultCollection src = searcher.FindAll(); foreach (SearchResult sr in src) { Client r = new Client(); r.domain = domainname; // Iterate through each property name in each SearchResult. foreach (string propertyKey in sr.Properties.PropertyNames) { ResultPropertyValueCollection valueCollection = sr.Properties[propertyKey]; if (propertyKey == "mail") { foreach (Object propertyValue in valueCollection) { r.email = propertyValue.ToString(); } } if (propertyKey == "displayname") { foreach (Object propertyValue in valueCollection) { r.name = propertyValue.ToString(); } } if (propertyKey == "samaccountname") { foreach (Object propertyValue in valueCollection) { r.loginName = propertyValue.ToString(); } } } if (r.name != null && r.email != null) { results.Add(r); } } } } return results; }
PowerShell AD query vs C# AD Query - Speed
I'm learning C# and am a novice. Please be patient with me. I've developed an app in C# to search for users, groups, and group members in AD by Piping PowerShell commands into it. Now I'm trying to use DirectoryServices in C# to get the same results, however the time it take to get the same results back is much longer than it is in PowerShell. Here is what I am doing now with DirectoryServices for a quick test: using System.DirectoryServices; using System.DirectoryServices.ActiveDirectory; private void button1_Click(object sender, EventArgs e) { string textbox = textBox1.Text.ToString(); listBox1.Items.Clear(); listView1.Items.Clear(); listView1.Columns.Clear(); try { // Bind to the object for which to retrieve property data. DirectoryEntry de = new DirectoryEntry(""); DirectorySearcher ds = new DirectorySearcher(de); ds.Filter = "(&(objectClass=Group)(cn="+ textbox + "))"; ds.SearchScope = SearchScope.Subtree; SearchResultCollection rsAll = ds.FindAll(); listView1.Columns.Add("samsaccountname"); string samsaccountname = ""; foreach (SearchResult searchresult in rsAll) { if (searchresult.GetDirectoryEntry().Properties["samaccountname"].Value != null) { samsaccountname = searchresult.GetDirectoryEntry().Properties["samaccountname"].Value.ToString(); } else { samsaccountname = ""; } ListViewItem lvi = new ListViewItem(samsaccountname); //lvi.SubItems.Add(givenName); //lvi.SubItems.Add(sn); //lvi.SubItems.Add(mail); listView1.Items.Add(lvi); } listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); } catch { // Add error handling. } } Here is what I did in PowerShell + C# private string SearchDLScript(string searchDL) { listViewSearchDL.Items.Clear(); listViewSearchDL.Columns.Clear(); listViewSearchDL.Columns.Add(""); listViewSearchDL.Items.Add("Loading list, please wait."); listViewSearchDL.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); if (textSearchDL.Text.Length < 8) { listViewSearchDL.Items.Add("Hint: The more you type, the quicker the seach."); listViewSearchDL.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); } string rbName = ""; if (radioButtonDisplayName.Checked == true) { rbName = "DisplayName"; } else if (radioButtonAlias.Checked == true) { rbName = "SamAccountName"; } string searchDLScriptCommand = #"Import-Module ActiveDirectory Get-ADGroup -Filter '"+rbName+ #" -Like """ + searchDL + #"*"" ' -Properties * | Select-Object DisplayName,SamAccountName | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1"; string scriptOutput = RunPowerShellCommands.RunPowerShellCode(searchDLScriptCommand); string[] strArr = scriptOutput.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.None); strArr = strArr.Where(x => !string.IsNullOrEmpty(x)).ToArray(); listViewSearchDL.Columns.Clear(); listViewSearchDL.Items.Clear(); listViewSearchDL.Columns.Add("Display Name"); listViewSearchDL.Columns.Add("Alias"); foreach (string user in strArr) { string userDetails = user.Replace("\"", ""); string[] columns = userDetails.Split(','); ListViewItem lvi = new ListViewItem(columns[0]); for (int i = 1; i < columns.Count(); i++) { lvi.SubItems.Add(columns[i]); } listViewSearchDL.Items.Add(lvi); } if (scriptOutput == "\r\n") { listViewSearchDL.Items.Clear(); listViewSearchDL.Columns.Clear(); listViewSearchDL.Columns.Add(""); listViewSearchDL.Items.Add("There are no records"); } listViewSearchDL.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); listViewSearchDL.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); return "scriptOutput"; }
In the C# sample you implicitly perform two extra lookups per object returned by the DirectorySearcher, by calling GetDirectoryEntry(): foreach (SearchResult searchresult in rsAll) { if (searchresult.GetDirectoryEntry().Properties["samaccountname"].Value != null) { samsaccountname = searchresult.GetDirectoryEntry().Properties["samaccountname"].Value.ToString(); } else { samsaccountname = ""; } // and then updating the listview } The documentation for GetDirectoryEntry() even warns you about this: Note Calling GetDirectoryEntry on each SearchResult returned through DirectorySearcher can be slow. What you'd want to do is add the list of property names that you'll need to the searcher (this is what the Get-AD* -Properties parameter does in the background), and have them returned after the very first search: DirectorySearcher ds = new DirectorySearcher(de); // do this before calling FindAll() ds.PropertiesToLoad.Add("samaccountname") and then when you process the search results, grab the property value directly from each search result instead of calling GetDirectoryEntry() again: foreach (SearchResult searchresult in rsAll) { if (searchresult.Properties["samaccountname"].Value != null) { samsaccountname = searchresult.Properties["samaccountname"].Value.ToString(); } else { samsaccountname = ""; } // and then updating the listview }
Can't retrieve EmployeeId from Active Directory
My GetActiveDirectory() method is used to get data from Active Directory using the SamAccountName, and it's working but the problem is the user.EmployeeId return no sign of data. Why I can't receive the EmployeeId and how can I fix it? This is my codes: public void GetActiveDirectory(DataTable DataStorage, string SamAccountName) { var domainContext = new PrincipalContext( ContextType.Domain, null, _ldapPath, _ldapUsername, _ldapPassword); var group = GroupPrincipal.FindByIdentity(domainContext, "Domain Users"); if (group != null) { DataStorage.Columns.Add("SamAccountName"); DataStorage.Columns.Add("Surname"); DataStorage.Columns.Add("Guid"); DataStorage.Columns.Add("Enabled"); DataStorage.Columns.Add("GivenName"); DataStorage.Columns.Add("EmailAddress"); DataStorage.Columns.Add("SID"); DataStorage.Columns.Add("DateCreated"); DataStorage.Columns.Add("DateModified"); DataStorage.Columns.Add("EmployeeNumber"); DataStorage.AcceptChanges(); foreach (var p in group.GetMembers(false)) { if(p.SamAccountName != null) { try { var user = UserPrincipal.FindByIdentity( domainContext, IdentityType.SamAccountName, SamAccountName); if (user != null) { var userDE = (DirectoryEntry)p.GetUnderlyingObject(); DateTime dateCreated = userDE.Properties["WhenCreated"].Value != null ? (DateTime)userDE.Properties["WhenCreated"].Value : DateTime.MinValue; DateTime dateModified = userDE.Properties["WhenChanged"].Value != null ? (DateTime)userDE.Properties["WhenChanged"].Value : DateTime.MinValue; DataRow dr = DataStorage.NewRow(); dr["SamAccountName"] = user.SamAccountName; dr["Surname"] = user.Surname; dr["Guid"] = user.Guid.ToString(); dr["Enabled"] = user.Enabled; dr["GivenName"] = user.GivenName; dr["EmailAddress"] = user.EmailAddress; dr["SID"] = user.Sid.Value; dr["EmployeeNumber"] = user.EmployeeId; //Always give an empty space or null. dr["DateCreated"] = dateCreated; dr["DateModified"] = dateModified; DataStorage.Rows.Add(dr); return; } } catch { } break; } } } }
THIS IS A TEMPORARY ANSWER TO UserPrincipal.EmployeeId I don't know why UserPrincipal.EmployeeId is not working so I decide to use the old way method. What I've tried to solve my own problem in .EmployeeId is to go back using System.DirectoryServices Here is my method to get EmployeeId using System.DirectoryServices var oDirecotyrEntry = new DirectoryEntry( _ldapPath, _ldapUsername, _ldapPassword, AuthenticationTypes.Secure); SearchResultCollection odrSearchResultCollection; var odrUser = new DirectoryEntry(); var odrDirectorySearcher = new DirectorySearcher {Filter = "sAMAccountName="+SamAccountName+"", SearchRoot = oDirecotyrEntry}; using(odrDirectorySearcher) { odrSearchResultCollection = odrDirectorySearcher.FindAll(); if(odrSearchResultCollection.Count > 0) { foreach(SearchResult result in odrSearchResultCollection) { var num = result.Properties["employeeNumber"]; foreach(var no in num) { dr["EmployeeNumber"] = no.ToString(); } } } } and to complete my project I use System.DirectoryServices.AccountManagement var oPricipalContext = new PrincipalContext( ContextType.Domain, _ldapPath2, _ldapUsername, _ldapPassword); UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPricipalContext, SamAccountName); if (oUserPrincipal != null) { var oDateTime = (DirectoryEntry)oUserPrincipal.GetUnderlyingObject(); DateTime dateCreated = oDateTime.Properties["WhenCreated"].Value != null ? (DateTime)oDateTime.Properties["WhenCreated"].Value : DateTime.MinValue; DateTime dateChanged = oDateTime.Properties["WhenChanged"].Value != null ? (DateTime)oDateTime.Properties["WhenChanged"].Value : DateTime.MinValue; dr["SamAccountName"] = oUserPrincipal.SamAccountName; dr["Surname"] = oUserPrincipal.Surname; dr["Guid"] = oUserPrincipal.Guid.ToString(); dr["Enabled"] = oUserPrincipal.Enabled; dr["GivenName"] = oUserPrincipal.GivenName; dr["EmailAddress"] = oUserPrincipal.EmailAddress; dr["SID"] = oUserPrincipal.Sid.Value; dr["DateCreated"] = dateCreated; dr["DateModified"] = dateChanged; DataStorage.Rows.Add(dr); } System.DirectoryServices.AccountManagement is require to my project so I need to use it. SORRY FOR MY GRAMMAR. Here is my full code. No snippet format??? using System.DirectoryServices; using System.DirectoryServices.AccountManagement; public void GetUsers(DataTable DataStorage, string SamAccountName) { DataStorage.Columns.Add("SamAccountName"); DataStorage.Columns.Add("Surname"); DataStorage.Columns.Add("Guid"); DataStorage.Columns.Add("Enabled"); DataStorage.Columns.Add("GivenName"); DataStorage.Columns.Add("EmailAddress"); DataStorage.Columns.Add("SID"); DataStorage.Columns.Add("DateCreated"); DataStorage.Columns.Add("DateModified"); DataStorage.Columns.Add("EmployeeNumber"); DataStorage.AcceptChanges(); DataRow dr = DataStorage.NewRow(); //System.DirectoryServices var oDirecotyrEntry = new DirectoryEntry( _ldapPath, _ldapUsername, _ldapPassword, AuthenticationTypes.Secure); SearchResultCollection odrSearchResultCollection; var odrUser = new DirectoryEntry(); var odrDirectorySearcher = new DirectorySearcher {Filter = "sAMAccountName="+SamAccountName+"", SearchRoot = oDirecotyrEntry}; using(odrDirectorySearcher) { odrSearchResultCollection = odrDirectorySearcher.FindAll(); if(odrSearchResultCollection.Count > 0) { foreach(SearchResult result in odrSearchResultCollection) { var num = result.Properties["employeeNumber"]; foreach(var no in num) { dr["EmployeeNumber"] = no.ToString(); } } } } //System.DirectoryServices.AccountManagement var oPricipalContext = new PrincipalContext( ContextType.Domain, _ldapPath2, _ldapUsername, _ldapPassword); UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPricipalContext, SamAccountName); if (oUserPrincipal != null) { var oDateTime = (DirectoryEntry)oUserPrincipal.GetUnderlyingObject(); DateTime dateCreated = oDateTime.Properties["WhenCreated"].Value != null ? (DateTime)oDateTime.Properties["WhenCreated"].Value : DateTime.MinValue; DateTime dateChanged = oDateTime.Properties["WhenChanged"].Value != null ? (DateTime)oDateTime.Properties["WhenChanged"].Value : DateTime.MinValue; dr["SamAccountName"] = oUserPrincipal.SamAccountName; dr["Surname"] = oUserPrincipal.Surname; dr["Guid"] = oUserPrincipal.Guid.ToString(); dr["Enabled"] = oUserPrincipal.Enabled; dr["GivenName"] = oUserPrincipal.GivenName; dr["EmailAddress"] = oUserPrincipal.EmailAddress; dr["SID"] = oUserPrincipal.Sid.Value; dr["DateCreated"] = dateCreated; dr["DateModified"] = dateChanged; DataStorage.Rows.Add(dr); } }
GetGroups method in system.DirectoryServices.AccountManagement does seem to refresh
Essentially what I'm trying to do is add a computer/user to a group. After I add the object to the group, I want to query the groups of the object to see what they have. It seems that the GetGroups method doesn't update fast enough. My test always seems to fail. If I put some breakpoints in VS, it will run if I wait enough. I'm new to playing with the AccounManagement namespace (I've used the pre .net 3.5 code alot). I guess I could loop through the code a few times but I'm seeing if other people have suggestions for this. I've done the following unit test [Test] public void Check() { string distinguishedName = "ComputerDistinguishedName"; string groupDN = "GroupDistinguished name"; // Remove the identity from the group so it does crashes if it's already part of it. GroupCtrl.RemoveIdentityFromGroup(groupDN, distinguishedName); using (var ctx = new PrincipalContext(ContextType.Domain)) { var group = GroupPrincipal.FindByIdentity(ctx, groupDN); Console.WriteLine(group.Members.Count); if (!group.Members.Contains(ctx, IdentityType.DistinguishedName, distinguishedName)) { group.Members.Add(ctx, IdentityType.DistinguishedName, distinguishedName); group.Save(); } foreach (var item in group.Members) { Console.WriteLine(item.DistinguishedName); } Console.WriteLine(group.Members.Count); } var isMemberOf = false; using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain)) { var found = Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName); if (found != null) { Console.WriteLine(found.DistinguishedName); foreach (var item in found.GetGroups()) { Console.WriteLine(item.DistinguishedName); if (item.DistinguishedName == groupDN) { isMemberOf = true; } } } Assert.AreEqual(true, isMemberOf); } // Reset our group membership to run the test again. GroupCtrl.RemoveIdentityFromGroup(groupDN, distinguishedName); } Edit 1: So I tried two different approaches 1 - I tried getting the getUnderlyingObject and then looping through the memberOf properties (same result) 2 - I avoided the AccountManagement code and used a DirectorySearcher and looped through the memberOf property and it comes up every time. Sighh
So I changed my code to the following. The old way of checking memberOf with DirectorySearch works everytime. I was hoping to use only use the AccountManagement class for this project. I wonder if future version of the class will be better. [Test] public void Check() { //var test = new Constructor(); var test = new AdContextObject(); string distinguishedName = "ComputerDistinguishedName"; string groupDN = "GroupDistinguished name"; // Remove the identity from the group so it does crashes if it's already part of it. GroupCtrl.RemoveIdentityFromGroup(groupDN, distinguishedName); using (var ctx = test.GetContext()) { var group = GroupPrincipal.FindByIdentity(ctx, groupDN); Console.WriteLine(group.Members.Count); if (!group.Members.Contains(ctx, IdentityType.DistinguishedName, distinguishedName)) { Console.WriteLine("addGroup"); group.Members.Add(ctx, IdentityType.DistinguishedName, distinguishedName); group.Save(); } foreach (var item in group.Members) { Console.WriteLine(item.DistinguishedName); } Console.WriteLine(group.Members.Count); } DirectoryEntry de = new DirectoryEntry(); de.Path = "LdapSource"; DirectorySearcher ser = new DirectorySearcher(de); ser.Filter = "(&(ObjectCategory=computer)(name=ComputerName))"; ser.PropertiesToLoad.Add("name"); ser.PropertiesToLoad.Add("memberOf"); var returnValue = ser.FindAll(); var isMemberOf = false; foreach (SearchResult res in returnValue) { var memberOf = GetMultiValue(res, "MemberOf"); foreach (var item in memberOf) { Console.WriteLine(item); if (item.Equals(groupDN, StringComparison.OrdinalIgnoreCase)) { isMemberOf = true; } } } Assert.AreEqual(true, isMemberOf); Console.WriteLine("old way worked fine"); isMemberOf = false; using (PrincipalContext ctx = test.GetContext()) { var found = Principal.FindByIdentity(ctx, IdentityType.DistinguishedName, distinguishedName); if (found != null) { foreach (var item in found.GetGroups()) { Console.WriteLine(item.DistinguishedName); if (item.DistinguishedName.Equals(groupDN, StringComparison.OrdinalIgnoreCase)) { isMemberOf = true; } } } Assert.AreEqual(true, isMemberOf); } // Reset our group membership to run the test again. GroupCtrl.RemoveIdentityFromGroup(groupDN, distinguishedName); } public static string[] GetMultiValue(SearchResult result, string fieldName) { string[] returnValue = null; if (result != null) { if (result.Properties.Contains(fieldName)) { ResultPropertyValueCollection propertyValue = result.Properties[fieldName]; if (propertyValue != null) { if (propertyValue.Count > 1) { string[] valueArray = new string[propertyValue.Count]; for (int i = 0; i < propertyValue.Count; i++) { string valStr = propertyValue[i].ToString(); valueArray[i] = valStr; } returnValue = valueArray; } else if (propertyValue.Count == 1) { string[] tempString = new string[] { propertyValue[0].ToString() }; returnValue = tempString; } else { string[] tempString = new string[] { }; returnValue = tempString; } } } } return returnValue; } public class AdContextObject { public PrincipalContext GetContext() { return new PrincipalContext(ContextType.Domain, "domainStuff", "MoreDomainStuff"); } }
c# Bitlocker information from Active Directory
I'm trying to get bit locker recovery information for all users from active directory via below functions. I've tried many different solution but none of them solved my problem. I can get most of the computer properties like Operation System,Service Pack expect bit locker info. Is there any special permission or method to search with msFVE-RecoveryInformation ? Method 1: DirectoryEntry child = new DirectoryEntry("LDAP://XXXXX"); DirectoryEntries enteries = child.Children; DirectorySearcher ds = new DirectorySearcher(child); ds.Filter = "(&(objectCategory=Computer)(cn=computerName))"; ds.SearchScope = SearchScope.Subtree; SearchResultCollection item = ds.FindAll(); //string dn = item.GetDirectoryEntry().Properties["distinguishedname"].Value.ToString(); string temp1 = System.DirectoryServices.AccountManagement.UserPrincipal.Current.DistinguishedName; child = new DirectoryEntry(("LDAP://" + temp1), "XXXXX", "XXXXX"); enteries = child.Children; ds = new DirectorySearcher(child); ds.Filter = "(objectClass=msFVE-RecoveryInformation)"; ds.SearchScope = SearchScope.Subtree; SearchResultCollection result = ds.FindAll(); if (result.Count > 0) { foreach (SearchResult sr in result) { string recoveryKeyPackage = sr.GetDirectoryEntry().Properties["msFVE-KeyPackage"].Value.ToString(); string recoveryGUID = sr.GetDirectoryEntry().Properties["msFVE-RecoveryGuid"].Value.ToString(); string recoveryPassword = sr.GetDirectoryEntry().Properties["msFVE-RecoveryPassword"].Value.ToString(); string recoveryVolumeGUID = sr.GetDirectoryEntry().Properties["msFVE-VolumeGuid"].Value.ToString(); } } else { Console.Write ("No recovery information found!"); } Method 2: public Dictionary<string, string> GetBitlockerKeys(string computerName) { DirectoryEntry dEntry = new DirectoryEntry("LDAP://XXXX"); var keys = new Dictionary<string, string>(); using (var computerObject = dEntry) { var children = computerObject.Children; var schemaFilter = children.SchemaFilter; schemaFilter.Add("msFVE-RecoveryInformation"); foreach (DirectoryEntry child in children) using (child) { var recoveryGuid = new Guid((byte[])child.Properties["msFVE-RecoveryGuid"].Value).ToString(); var recoveryPassword = child.Properties["msFVE-RecoveryPassword"].Value.ToString(); if (!keys.ContainsKey(recoveryGuid)) { keys.Add(recoveryGuid, recoveryPassword); } } } return keys; }