PowerShell AD query vs C# AD Query - Speed - c#

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
}

Related

ALL employees under a director or above using Active Directory and c#

I'm attempting to create a cascading drop down in which the first populates a list of Director level employees (directors have multiple managers and the managers have multiple employees) which would then populate the second dropdown through ajax. I'm trying to put together the logic that given a displayname will grab all direct reports and the direct reports of the direct reports. With the logic I've put together it will print everyone im looking for but only returns the reports to the original Director. I'm struggling to put everything together. The code isnt at all pretty and if there's an easier way to do this I'm open for suggestions.
There are three methods in which I'm attempting to populate the directReports list in the DirectReports method with all employees
public List<string> DirectReports(string name)
{
List<string> directReports = new List<string>();
var domain = new PrincipalContext(ContextType.Domain, "somedomain");
UserPrincipal user = new UserPrincipal(domain);
user.DisplayName = name;
PrincipalSearcher ps = new PrincipalSearcher();
ps.QueryFilter = user;
DirectorySearcher ds = ps.GetUnderlyingSearcher() as DirectorySearcher;
ds.SearchScope = SearchScope.Subtree;
ds.Filter = "(&(objectClass=user)(objectCategory=person)(displayName=" + name + "))";
ds.PropertiesToLoad.Add("DirectReports");
SearchResultCollection results = ds.FindAll();
foreach(SearchResult result in results)
{
if (hasDirectReports(result))
{
//directReports.AddRange(GetReportsFinal(GetDirectReportsList(result)));
directReports.AddRange(GetDirectReportsList(result));
directReports.Add(result.Properties["displayname"][0].ToString() + " (" + result.Properties["samaccountname"][0].ToString()+")");
}
else
{
directReports.Add(result.Properties["displayname"][0].ToString() + " (" + result.Properties["samaccountname"][0].ToString() + ")");
}
}
foreach(var item in directReports)
{
Debug.WriteLine(item);
}
return directReports;
}
public bool hasDirectReports(SearchResult result)
{
if(result.Properties["directreports"].Count >= 1)
{
return true;
}
else
{
return false;
}
}
public List<string> GetDirectReportsList(SearchResult result)
{
List<string> formattedDirectReportsList = new List<string>();
foreach (var thing in result.Properties["directreports"])
{
string employeeString = thing.ToString();
//employeeString = employeeString.Split(setp, StringSplitOptions.None)[0];
employeeString = employeeString.Replace("CN=", "");
employeeString = employeeString.TrimEnd(',');
employeeString = employeeString.Replace("\\, ", ", ");
//Debug.WriteLine(employeeString);
if (employeeString.Split(',')[2] == "OU=Users")
{
string fn = employeeString.Split(',')[1];
string ln = employeeString.Split(',')[0];
var domain = new PrincipalContext(ContextType.Domain, "somedomain");
UserPrincipal directreportname = new UserPrincipal(domain);
directreportname.Name = ln + "," + fn;
PrincipalSearcher prinsearcher = new PrincipalSearcher();
prinsearcher.QueryFilter = directreportname;
DirectorySearcher dirsearcher = prinsearcher.GetUnderlyingSearcher() as DirectorySearcher;
Principal reportResults = prinsearcher.FindOne();
formattedDirectReportsList.Add(reportResults.DisplayName + " (" + reportResults.SamAccountName + ")");
DirectReports(reportResults.DisplayName);
}
}
return formattedDirectReportsList;
}

Active Directory: DirectoryEntry member list <> GroupPrincipal.GetMembers()

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

ASP.NET LDAP SearchResults Properties returning Byte Array

I am using DirectorySearcher to try and get a list of users in AD to sync them with my App and have copied code from various SO sources, however I am not getting any property values. I'm using the following code:
DirectorySearcher search = new DirectorySearcher();
SearchResultCollection results = null;
string sDefaultOU = "LDAP://...";
DirectoryEntry de = new DirectoryEntry(sDefaultOU);
string userName = "DonaldDuck";
search = new DirectorySearcher
{
SearchRoot = de,
PropertiesToLoad = { "displayname", "sAMAccountName"},
Filter = "(sAMAccountName=" + userName + ")"
};
results = search.FindAll();
foreach (SearchResult result in results)
{
String name;
if (result.Properties["sAMAccountName"].Count > 0)
{
name = result.Properties["sAMAccountName"][0].ToString();
}
}
However, instead of name being equal to to "DonaldDuck", it will be "Byte[10]" or Byte[x] where x is the length.
Can anyone see what I am doing wrong.
If I add a filter it returns one user, so I am pretty sure the code is working in terms of searching
Apparently this issue has been faced by others: LDAP DirectoryEntry SearchResult returns data differently in Windows 8 than Win7
AD is using LDAPv3 encoding the values using UTF8, the solution mentioned in the link above might work for you:
if (result.Properties["sAMAccountName"][0].GetType().IsArray)
{
name = System.Text.Encoding.UTF8.GetString((byte[])result.Properties["sAMAccountName"][0]);
}
else
{
name = result.Properties["sAMAccountName"][0].ToString();
}
instead of
foreach (SearchResult result in results)
{
String name;
if (result.Properties["sAMAccountName"].Count > 0)
{
name = result.Properties["sAMAccountName"][0].ToString();
}
}
try  
foreach (SearchResult result in results)
{
String name;
if (result.Properties["sAMAccountName"].Count > 0)
{
var thisDE=result.GetDirectoryEntry();
name = thisDE.Properties["sAMAccountName"].Value.ToString();
}
}
EDIT: Example
I use this helper method to search my domain (when userDomainAndName="DOMAIN\UserName") but you should be able to tweak it for what you want.
public static DirectoryEntry GetUserDirectoryEntryFromCurrentDomain(string userDomainAndName)
{
var Split = userDomainAndName.Split(#"\\".ToCharArray());
var DomainNetBiosNAme = Split[0];
var UserName = Split[1];
var QueryString = $"(&(objectCategory=person)(objectClass=user)(sAMAccountName={UserName}))";
DirectoryEntry rootDSE = GetDirectoryObject(
"LDAP://" + DomainNetBiosNAme + "/rootDSE");
string domain = "LDAP://" + (string)rootDSE.Properties[
"defaultNamingContext"][0];
var Searcher = new DirectorySearcher(new DirectoryEntry(domain), QueryString);
var Result = Searcher.FindOne();
var tReturn = Result.GetDirectoryEntry();
return tReturn;
}
then to get my users PrimarySMTP address (for example)..
var TheUsersDirectoryEntry=GetUserDirectoryEntryFromCurrentDomain(userDomainAndName);
var TheUsersPrimarySMTP=TheUsersDirectoryEntry.Properties["mail"].Value.ToString();

LDAP Query to getUserGroups

Trying to learn LDAP queries in c# to access get all groups user is assigned to in active directory:
I am using System.DirectoryServices:
Havent tested it yet but from throwing examples together I have got:
//This should return all groups for particular user
public List<string> GetUserGroups(string UserName)
{
//create connection
DirectoryEntry entry = new DirectoryEntry(_lDAPPath);
DirectorySearcher search = new DirectorySearcher(entry);
//Get user with UserName
string query = "(&(objectCategory=User)(objectClass=person)(name=" + UserName + "*))";//(memberOf=*))";
search.Filter = query;
//properties returned by query
search.PropertiesToLoad.Add("memberOf");
search.PropertiesToLoad.Add("name");
System.DirectoryServices.SearchResultCollection mySearchResultColl = search.FindAll();
List<string> userGroups = new List<string>();
//Should only be one user in foreach loop
foreach (SearchResult result in mySearchResultColl)
{
//for user get each group assigned to
foreach (string prop in result.Properties["memberOf"])
{
if (prop.Contains(UserName))
{
//adds group name to string
userGroups.Add(result.Properties["memberOf"][0].ToString());
}
}
}
return userGroups;
}
hoping this works. does anyone see any poss problems? ta
It would be preferable to test your code and indicate any bugs you can't handle before posting. However here is tested code which I have been using for years. It searches by cn i.e. Common Name (user alias)
public static List<string> GetUserGroupDetails(string userName)
{
DirectorySearcher search = new DirectorySearcher();
List<string> groupsList = new List<string>();
search.Filter = String.Format("(cn={0})", userName);
search.PropertiesToLoad.Add("memberOf");
SearchResult result = search.FindOne();
if (result != null)
{
int groupCount = result.Properties["memberOf"].Count;
for (int counter = 0; counter < groupCount; counter++)
{
string s = (string)result.Properties["memberOf"][counter];
groupsList.Add(s);
// _log.DebugFormat("found group for user {0} : {1}", userName, s);
}
}
else
{
_log.Warn("no groups found for user " + userName);
}
return groupsList;
}
Note that the above code also returns email distribution lists for which the user is a member. When I want to exclude these I filter out the entries stating with "dl-".

Search Active Directory for all Usernames associated with email address

I have a method that searches Active Directory for Usernames based on an email address. There are cases where there may be multiple usernames for a given email address, and I'm trying to capture those. I have rewritten my method, but can't seem to get the syntax quite right. the issue is this line I believe.
foreach (Object myObject in result.Properties[property])
thanks,
Jason
private String FindNameByEmail(string emailAddress)
{
DirectoryEntry entry = GetDirectoryEntry();
emailAddress = txtEmailID.Text;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(&(objectCategory=person)(sAMAccountName=*)(mail=" + emailAddress + "))";
string[] properties = new string[] { "SAMAccountName" };
foreach (String property in properties)
search.PropertiesToLoad.Add(property);
SearchResultCollection result = search.FindAll();
if (result != null)
{
foreach (String property in properties)
foreach (Object myObject in result.Properties[property])
lblUserName.Text = myObject.ToString();
return "User Found";
}
else
{
lblStatus.Text = "user does not exist";
return "User Does Not Exist";
}
}
EDIT: Changed it to output to a list of strings
Here we go:
List<string> usernames = new List<string>();
if (result != null)
{
foreach (SearchResult sr in result)
{
usernames.Add((string)sr.Properties["SAMAccountName"][0]);
}
}
listBox1.DataSource = usernames; //Where listbox is declared in your markup
listBox1.DataBind();
Just Replace your if (result != null) logic with myne

Categories