Get multiple user with similar names from Active Directory using C# - c#

I need to display a list of users (in autocomplete) with similar names from the AD. For example, if I search Arthur and there are 2 people with same name (first name or last name), I should display both the names in the autocomplete. I tried to get the names but it is taking around 2-4 minutes to get the names. I am using the following code:
string[] domainNames = new string[] { "domain1", "domain2" };
List<string> userNames = new List<string>();
string user = string.Empty;
foreach (string domain in domainNames)
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, "Domain Users");
if (group != null)
{
foreach (Principal p in group.GetMembers(false))
{
if (p.Name.ToLower().Contains(key.ToLower()))
{
if (!userNames.Contains(p.Name))
userNames.Add(p.Name);
}
}
}
}
}
Any way I can speed up the process? I am already using ajax call.

The cn= filter isn't really valid - there's nothing guaranteeing that format. Instead, look up Ambigous Name Resolution - it's designed for this: http://social.technet.microsoft.com/wiki/contents/articles/22653.active-directory-ambiguous-name-resolution.aspx.

DirectorySearcher ds = new DirectorySearcher();
ds.SearchRoot = new DirectoryEntry("LDAP://" + domain, domain + #"\" + userName, password);
ds.Filter = "(&(objectClass=user)(cn=*" + key + "*))";
ds.PropertyNamesOnly = true;
ds.PropertiesToLoad.Add("name");
ds.PropertiesToLoad.Add("cn");
foreach (SearchResult searchResults in ds.FindAll())
{
foreach (string propertyName in searchResults.Properties.PropertyNames)
{
foreach (Object retEntry in searchResults.Properties[propertyName])
{
var user = retEntry.ToString().Split('/').Where(x => x.Contains("CN")).Select(y => y).FirstOrDefault().Split(',').Where(z => z.Contains("CN")).Select(c => c).FirstOrDefault().Split(',').FirstOrDefault().Split('=')[1];
if(!string.IsNullOrWhiteSpace(user))
userNames.Add(user);
}
}
}
Reduced to 30-40 seconds.

Related

Convert list of samaccount names to distinguished names

I have a list of account names (thousands) that I need to add as members of a group, so I need to look up the distinguished names of those accounts so I can add them as members of a group.
There are 21000 user accounts. It takes hours to look up the distinguished names, is there a faster way?
DirectoryEntry deDomain = new DirectoryEntry();
deDomain.Path = "LDAP://DC=my,DC=ca;
deDomain.Username = "me";
deDomain.Password = "mypassword";
DirectorySearcher dsSearch = new DirectorySearcher(deDomain);
dsSearch.CacheResults = false;
dsSearch.PropertiesToLoad.Add("distinguishedname");
foreach (string sam in lstSamAccountNames)
{
dsSearch.Filter = $"(&(objectCategory=person)(objectClass=user)(SAMAccountName={ReplaceSpecialCharacters(sSamAccountName)}))";
SearchResult srPerson = dsSearch.FindOne();
yield return srPerson.Properties["distinguishedname"][0].ToString();
}
I don't have an AD domain handy that I can do performance testing with at the moment, but have you tried the AccountManagement library instead of using DirectorySearcher?
using System.DirectoryServices.AccountManagement;
// ...
var domainName = "MyDomainName";
using (var context = new PrincipalContext(ContextType.Domain, domainName))
{
foreach (var sam in lstSamAccountNames)
{
var userName = ReplaceSpecialCharacters(sam);
using (var user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, $#"{domainName}\{userName}"))
{
yield return user.DistinguishedName;
}
}
}

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();

AD User list from certain OU's

I am trying to print users of only certain OUs in my company AD.
So far I've come up with this:
string groupName = "Domain Users";
string domainName = "domain";
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domainName);
GroupPrincipal grp = GroupPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, groupName);
if (grp != null)
{
foreach (Principal p in grp.GetMembers(false))
{
Console.WriteLine(p.SamAccountName + " - " + p.DisplayName);
}
grp.Dispose();
ctx.Dispose();
Console.ReadLine();
}
else
{
Console.WriteLine("\nWe did not find that group in that domain, perhaps the group resides in a different domain?");
Console.ReadLine();
}
The problem is that it prints every user and not a specific OU like "Employees" or "Students".
How do I add a parameter to specify 1 or 2 OU's that it should cycle through instead of a group?
If you want to limit your search to a single OU / container, you can just bind to that using another PrincipalContext constructor:
string groupName = "Domain Users";
string domainName = "domain";
string ouName = "CN=Users,DC=yourcompany,DC=com";
// bind to the specified container you want
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domainName, ouName);
Then, of course, you're only searching in that CN=Users container - no where else.
I don't know your code exactly so this is pseudocode.
When you have some property e.g. a string to specify the type then use Where:
var groupName = "Student";
foreach (Principal p in grp.GetMembers(false).Where(princ => princ.OUName.Equals(groupName))
{
Console.WriteLine(p.SamAccountName + " - " + p.DisplayName);
}
Or is it some kind of inheritance then OfType can be used:
foreach (Principal p in grp.GetMembers(false).OfType<Student>())
{
Console.WriteLine(p.SamAccountName + " - " + p.DisplayName);
}
You are searching the entire Domain, and "Domain Users" is probably not the OU you would wan´t, change the name of the variable and add:
string domainName = "Domain Users";
string groupName = "Students";
Then add the OU to your PrincipalContext:
var ctx = new PrincipalContext(ContextType.Domain, domainName, groupName);
I use the following code in my Application.
This might be a bit overkill for you, but i think it mostly fits your needs.
public static void DoStuff(UserPrincipal princ) {
var allDomains = Forest.GetCurrentForest().Domains.Cast<Domain>();
var allSearcher = allDomains.Select(domain => {
var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domain.Name));
searcher.Filter = $"(&(&(objectCategory=person)(objectClass=user)(userPrincipalName=*{princ.SamAccountName}*)))";
return searcher;
});
var directoryEntriesFound =
allSearcher.SelectMany(searcher =>
searcher.FindAll()
.Cast<SearchResult>()
.Select(result => result.GetDirectoryEntry()));
var memberOf = directoryEntriesFound.Select(entry => {
using (entry) {
return new {
Name = entry.Name,
GroupName = ((object[])entry.Properties["MemberOf"].Value)
.Select(obj => obj.ToString())
};
}
}
);
var result1 = new List<string>();
foreach (var member in memberOf) {
if(member.GroupName.Contains("Student") )
Console.WriteLine(princ.SamAccountName + " is Student");
if (member.GroupName.Contains("Employee"))
Console.WriteLine(princ.SamAccountName + " is Employee");
}
}
Just call this in your foreach (Principal p in grp.GetMembers(false))

Ldap Query for all members specific to a Group

I am looking to get a list of users that belong to a specific group 'groupName' is passed into the private method.
DirectoryEntry de = new DirectoryEntry("LDAP://DC=xxxx,DC=net"); // Root Directory //
var ds = new DirectorySearcher(de);
ds.PropertiesToLoad.Add("SAMAccountName");
ds.PropertiesToLoad.Add("member");
ds.Filter = "(&(objectClass=group)(SAMAccountName=" + groupName + "))";
SearchResultCollection AllGroupUsers;
AllGroupUsers = ds.FindAll();
The query returns 3 properties :- adspath, accountName and member.
Member is what I am really after.I access the member property and its values as the following piece of code demonstrates:-
if (AllGroupUsers.Count > 0)
{
ResultPropertyValueCollection values = AllGroupUsers[0].Properties["member"];
but something strange happens here. On the RHS of the equal sign, AllGroupUsers has a value for a specific member as "CN=Mike Schoomaker R,........"
While on the LHS of the equal sign, values has "CN=Mike Schoomaker (OR),....."
I am not quite sure how this is possible... It doesn't happen for each and every value under AllGroupUsers... only thing I know is it happens for external users on the active directory... Can anyone show me how I can fix this and get the actual firstName, LastName and MiddleInitial ?
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
using (var group = GroupPrincipal.FindByIdentity(ctx, "groupName"))
{
if (group == null)
{
MessageBox.Show("Group does not exist");
}
else
{
var users = group.GetMembers(true);
foreach (UserPrincipal user in users)
{
//user variable has the details about the user
}
}
}
}
To get a user, not a group you should set DirectoryEntry object and use corresponding properties (e.g. displayName, sn, givenName, initials)
Example:
...
AllGroupUsers = ds.FindAll();
if (AllGroupUsers.Count > 0) {
ResultPropertyValueCollection values = AllGroupUsers[0].Properties["member"];
foreach (string s in values)
{
DirectoryEntry u = new DirectoryEntry("LDAP://" + s);
Console.WriteLine(u.Properties["displayName"].Value);
}
}

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-".

Categories