I am trying to reproduce some of the functionality of the app 'Active Directory Users and Computers", and am not finding an easy way to retrieve the A/D information for a given user, using the account name, or "samaccountname".
Currently, I create a DirectoryEntry using the domain user and password, then use the entry to instantiate a DirectorySearcher to perform a FindAll(). I then run through the resulting SearchResultCollection's SearchResults, resolving UserPrincipals and their various properties to assemble all the A/D users in the OU. This takes way too long.
DirectoryEntry entry = new DirectoryEntry(
LDAPString, domainuserid, password, AuthenticationTypes.Secure);
DirectorySearcher srch = new DirectorySearcher(entry);
SearchResultCollection results = srch.FindAll();
foreach (SearchResult sr in results)
{
DirectoryEntry de = sr.GetDirectoryEntry();
if (de.Name.IndexOf("CN=") > -1)
{
foreach (string propKey in sr.Properties.PropertyNames)
{
user = new ADUser();
UserPrincipal up = GetServiceUser(sr.Properties["samaccountname"][0].ToString());
user.AccountName = up.SamAccountName;
user.Name = up.Name;
foreach (string propKey in sr.Properties.PropertyNames)
{
switch (propKey.Trim().ToLower())
{
case "givenname":
user.SurName = sr.Properties[propKey][0].ToString();
break;
ETC...
}
}
}
}
}
I've done timings and this part of the process seems to take way more time than it should. It occurs to me that I might retrieve all the SamAccountNames of A/D users, display them in a list, and only when selected to retrieve all the relevant data for that user. I've not run into a way to do this retrieval one at a time, as-needed, and it seems that would be more efficient. Is there a programmatic way to get an A/D user's information solely by use of the SamAccountName?
Edited to Add:
I thought I had found an answer to this, but then found it to be not quite what I was looking for.
Once I wrote a little AD tool, I used FindOne to retrieve and modify detail data via the SamAccountName. I don't know exactly how performant it was compared to your solution (it really was some time ago), but it worked pretty well by then (also with plenty of items):
// get the user entry
var s = new DirectorySearcher(entry);
s.Filter = "(samaccountname=" + username + ")";
SearchResult user = s.FindOne();
// read / do some changes
var d = user.GetDirectoryEntry();
d.Properties[...]
d.Invoke(...);
d.CommitChanges();
You'll want to make use of the srch.Filter to refine your LDAP query:
DirectorySearcher srch = new DirectorySearcher(entry); // You already have this line
srch.Filter = string.Format("(&(objectCategory=person)(objectClass=user)(sAMAccountName={0}))", samAccountName);
Now you can actually just use srch.FindOne() instead of srch.FindAll() since samaccountname is unique.
Related
How can I get a list of users from active directory? Is there a way to pull username, firstname, lastname? I saw a similar post where this was used:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN");
I have never done anything with active directory so I am completely lost. Any help would be greatly appreciated!
If you are new to Active Directory, I suggest you should understand how Active Directory stores data first.
Active Directory is actually a LDAP server. Objects stored in LDAP server are stored hierarchically. It's very similar to you store your files in your file system. That's why it got the name Directory server and Active Directory
The containers and objects on Active Directory can be specified by a distinguished name. The distinguished name is like this CN=SomeName,CN=SomeDirectory,DC=yourdomain,DC=com. Like a traditional relational database, you can run query against a LDAP server. It's called LDAP query.
There are a number of ways to run a LDAP query in .NET. You can use DirectorySearcher from System.DirectoryServices or SearchRequest from System.DirectoryServices.Protocol.
For your question, since you are asking to find user principal object specifically, I think the most intuitive way is to use PrincipalSearcher from System.DirectoryServices.AccountManagement. You can easily find a lot of different examples from google. Here is a sample that is doing exactly what you are asking for.
using (var context = new PrincipalContext(ContextType.Domain, "yourdomain.com"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
Console.WriteLine("First Name: " + de.Properties["givenName"].Value);
Console.WriteLine("Last Name : " + de.Properties["sn"].Value);
Console.WriteLine("SAM account name : " + de.Properties["samAccountName"].Value);
Console.WriteLine("User principal name: " + de.Properties["userPrincipalName"].Value);
Console.WriteLine();
}
}
}
Console.ReadLine();
Note that on the AD user object, there are a number of attributes. In particular, givenName will give you the First Name and sn will give you the Last Name. About the user name. I think you meant the user logon name. Note that there are two logon names on AD user object. One is samAccountName, which is also known as pre-Windows 2000 user logon name. userPrincipalName is generally used after Windows 2000.
If you want to filter y active accounts add this to Harvey's code:
UserPrincipal userPrin = new UserPrincipal(context);
userPrin.Enabled = true;
after the first using. Then add
searcher.QueryFilter = userPrin;
before the find all. And that should get you the active ones.
PrincipalContext for browsing the AD is ridiculously slow (only use it for .ValidateCredentials, see below), use DirectoryEntry instead and .PropertiesToLoad() so you only pay for what you need.
Filters and syntax here:
https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
Attributes here:
https://learn.microsoft.com/en-us/windows/win32/adschema/attributes-all
using (var root = new DirectoryEntry($"LDAP://{Domain}"))
{
using (var searcher = new DirectorySearcher(root))
{
// looking for a specific user
searcher.Filter = $"(&(objectCategory=person)(objectClass=user)(sAMAccountName={username}))";
// I only care about what groups the user is a memberOf
searcher.PropertiesToLoad.Add("memberOf");
// FYI, non-null results means the user was found
var results = searcher.FindOne();
var properties = results?.Properties;
if (properties?.Contains("memberOf") == true)
{
// ... iterate over all the groups the user is a member of
}
}
}
Clean, simple, fast. No magic, no half-documented calls to .RefreshCache to grab the tokenGroups or to .Bind or .NativeObject in a try/catch to validate credentials.
For authenticating the user:
using (var context = new PrincipalContext(ContextType.Domain))
{
return context.ValidateCredentials(username, password);
}
Certainly the credit goes to #Harvey Kwok here, but I just wanted to add this example because in my case I wanted to get an actual List of UserPrincipals. It's probably more efficient to filter this query upfront, but in my small environment, it's just easier to pull everything and then filter as needed later from my list.
Depending on what you need, you may not need to cast to DirectoryEntry, but some properties are not available from UserPrincipal.
using (var searcher = new PrincipalSearcher(new UserPrincipal(new PrincipalContext(ContextType.Domain, Environment.UserDomainName))))
{
List<UserPrincipal> users = searcher.FindAll().Select(u => (UserPrincipal)u).ToList();
foreach(var u in users)
{
DirectoryEntry d = (DirectoryEntry)u.GetUnderlyingObject();
Console.WriteLine(d.Properties["GivenName"]?.Value?.ToString() + d.Properties["sn"]?.Value?.ToString());
}
}
Include the System.DirectoryServices.dll, then use the code below:
DirectoryEntry directoryEntry = new DirectoryEntry("WinNT://" + Environment.MachineName);
string userNames="Users: ";
foreach (DirectoryEntry child in directoryEntry.Children)
{
if (child.SchemaClassName == "User")
{
userNames += child.Name + Environment.NewLine ;
}
}
MessageBox.Show(userNames);
Good Day All,
I have been asked to write out some code to find a computer object in AD I haven't been given the OU's that these objects could be in (which isn't helpful at all - obviously). I've determined that I can remotely connect to the AD server with the lines:
DirectoryEntry startingPoint = new DirectoryEntry("LDAP://DC=Zone,DC=Corp,DC=COM", "UserName", "Password");
//Find all OUs
DirectorySearcher searcher = new DirectorySearcher(startingPoint);
searcher.Filter = "(objectCategory=Computer)";
foreach (SearchResult result in searcher.FindAll())
{
}
Unfortunately, the only information I do have is that all the objects will be no further than 3 OU's deep. So I'd like to know how to go through each of the OU's going that deep in each one I come to until I find the designated host name. I'm not exactly sure how to work that out. Any assistance would be appreciated.
First, I would change the SearchScope to Subtree and then include the hostname as part of the filter. I would then use FindOne() instead of FindAll():
DirectoryEntry startingPoint = new DirectoryEntry("LDAP://DC=Zone,DC=Corp,DC=COM", "UserName", "Password");
DirectorySearcher searcher = new DirectorySearcher(startingPoint)
{
SearchScope = SearchScope.Subtree,
Filter = $"(&(objectCategory=Computer)(cn={hostname}))"
};
SearchResult result = searcher.FindOne();
How can I get a list of users from active directory? Is there a way to pull username, firstname, lastname? I saw a similar post where this was used:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN");
I have never done anything with active directory so I am completely lost. Any help would be greatly appreciated!
If you are new to Active Directory, I suggest you should understand how Active Directory stores data first.
Active Directory is actually a LDAP server. Objects stored in LDAP server are stored hierarchically. It's very similar to you store your files in your file system. That's why it got the name Directory server and Active Directory
The containers and objects on Active Directory can be specified by a distinguished name. The distinguished name is like this CN=SomeName,CN=SomeDirectory,DC=yourdomain,DC=com. Like a traditional relational database, you can run query against a LDAP server. It's called LDAP query.
There are a number of ways to run a LDAP query in .NET. You can use DirectorySearcher from System.DirectoryServices or SearchRequest from System.DirectoryServices.Protocol.
For your question, since you are asking to find user principal object specifically, I think the most intuitive way is to use PrincipalSearcher from System.DirectoryServices.AccountManagement. You can easily find a lot of different examples from google. Here is a sample that is doing exactly what you are asking for.
using (var context = new PrincipalContext(ContextType.Domain, "yourdomain.com"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
Console.WriteLine("First Name: " + de.Properties["givenName"].Value);
Console.WriteLine("Last Name : " + de.Properties["sn"].Value);
Console.WriteLine("SAM account name : " + de.Properties["samAccountName"].Value);
Console.WriteLine("User principal name: " + de.Properties["userPrincipalName"].Value);
Console.WriteLine();
}
}
}
Console.ReadLine();
Note that on the AD user object, there are a number of attributes. In particular, givenName will give you the First Name and sn will give you the Last Name. About the user name. I think you meant the user logon name. Note that there are two logon names on AD user object. One is samAccountName, which is also known as pre-Windows 2000 user logon name. userPrincipalName is generally used after Windows 2000.
If you want to filter y active accounts add this to Harvey's code:
UserPrincipal userPrin = new UserPrincipal(context);
userPrin.Enabled = true;
after the first using. Then add
searcher.QueryFilter = userPrin;
before the find all. And that should get you the active ones.
PrincipalContext for browsing the AD is ridiculously slow (only use it for .ValidateCredentials, see below), use DirectoryEntry instead and .PropertiesToLoad() so you only pay for what you need.
Filters and syntax here:
https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
Attributes here:
https://learn.microsoft.com/en-us/windows/win32/adschema/attributes-all
using (var root = new DirectoryEntry($"LDAP://{Domain}"))
{
using (var searcher = new DirectorySearcher(root))
{
// looking for a specific user
searcher.Filter = $"(&(objectCategory=person)(objectClass=user)(sAMAccountName={username}))";
// I only care about what groups the user is a memberOf
searcher.PropertiesToLoad.Add("memberOf");
// FYI, non-null results means the user was found
var results = searcher.FindOne();
var properties = results?.Properties;
if (properties?.Contains("memberOf") == true)
{
// ... iterate over all the groups the user is a member of
}
}
}
Clean, simple, fast. No magic, no half-documented calls to .RefreshCache to grab the tokenGroups or to .Bind or .NativeObject in a try/catch to validate credentials.
For authenticating the user:
using (var context = new PrincipalContext(ContextType.Domain))
{
return context.ValidateCredentials(username, password);
}
Certainly the credit goes to #Harvey Kwok here, but I just wanted to add this example because in my case I wanted to get an actual List of UserPrincipals. It's probably more efficient to filter this query upfront, but in my small environment, it's just easier to pull everything and then filter as needed later from my list.
Depending on what you need, you may not need to cast to DirectoryEntry, but some properties are not available from UserPrincipal.
using (var searcher = new PrincipalSearcher(new UserPrincipal(new PrincipalContext(ContextType.Domain, Environment.UserDomainName))))
{
List<UserPrincipal> users = searcher.FindAll().Select(u => (UserPrincipal)u).ToList();
foreach(var u in users)
{
DirectoryEntry d = (DirectoryEntry)u.GetUnderlyingObject();
Console.WriteLine(d.Properties["GivenName"]?.Value?.ToString() + d.Properties["sn"]?.Value?.ToString());
}
}
Include the System.DirectoryServices.dll, then use the code below:
DirectoryEntry directoryEntry = new DirectoryEntry("WinNT://" + Environment.MachineName);
string userNames="Users: ";
foreach (DirectoryEntry child in directoryEntry.Children)
{
if (child.SchemaClassName == "User")
{
userNames += child.Name + Environment.NewLine ;
}
}
MessageBox.Show(userNames);
I am trying to get a user's email address in AD without success.
String account = userAccount.Replace(#"Domain\", "");
DirectoryEntry entry = new DirectoryEntry();
try {
DirectorySearcher search = new DirectorySearcher(entry);
search.PropertiesToLoad.Add("mail"); // e-mail addressead
SearchResult result = search.FindOne();
if (result != null) {
return result.Properties["mail"][0].ToString();
} else {
return "Unknown User";
}
} catch (Exception ex) {
return ex.Message;
}
Can anyone see the issue or point in the right direction?
Disclaimer: This code doesn't search for a single exact match, so for domain\j_doe it may return domain\j_doe_from_external_department's email address if such similarly named account also exists. If such behaviour is undesirable, then either use a samAccountName filter intead of an anr one used below or filter the results additionally.
I have used this code successfully (where "account" is the user logon name without the domain (domain\account):
// get a DirectorySearcher object
DirectorySearcher search = new DirectorySearcher(entry);
// specify the search filter
search.Filter = "(&(objectClass=user)(anr=" + account + "))";
// specify which property values to return in the search
search.PropertiesToLoad.Add("givenName"); // first name
search.PropertiesToLoad.Add("sn"); // last name
search.PropertiesToLoad.Add("mail"); // smtp mail address
// perform the search
SearchResult result = search.FindOne();
You guys are working too hard:
// Look up the current user's email address
string eMail = UserPrincipal.Current.EmailAddress;
You can try the below GetUserEmail method. If You are looking out to find the email address for logged-in user in MVC then call the GetUserEmail() function with User.Identity.Name
using System.DirectoryServices;
using System.Linq;
public string GetUserEmail(string UserId)
{
var searcher = new DirectorySearcher("LDAP://" + UserId.Split('\\').First().ToLower())
{
Filter = "(&(ObjectClass=person)(sAMAccountName=" + UserId.Split('\\').Last().ToLower() + "))"
};
var result = searcher.FindOne();
if (result == null)
return string.Empty;
return result.Properties["mail"][0].ToString();
}
GetUserEmail(User.Identity.Name) //Get Logged in user email address
You forgot a filter.
Try adding this before calling FindOne:
search.Filter = String.Format("(sAMAccountName={0})", account);
What about this
public string GetEmailFromSamAccountName(string samAccountName, string domain="YOURCOMPANY")
{
using (var principalContext = new PrincipalContext(ContextType.Domain, domain))
{
var userPrincipal = UserPrincipal.FindByIdentity(principalContext, samAccountName);
return userPrincipal.EmailAddress;
}
}
Also, where do you pull the username from (stored, user input, current identity)? A username can change (be renamed) easily - the SID/Windows Logon Identity on the other hand does not change - so you would be better off doing filters/searches by SID rather than samaccountname - if possible and/or needed design-wise...
You need to add references for System.DirectoryServices.AccountManagement and include this same references in your using statement. Now you will have access to the current users login details as listed below include the email address.
string loginname = Environment.UserName;
string firstname = UserPrincipal.Current.GivenName;
string lastname = UserPrincipal.Current.Surname;
string name = UserPrincipal.Current.Name;
string eMail = UserPrincipal.Current.EmailAddress;
update: fredrick nailed it....
Jakob is right. You need to filter your search. You can do all sorts of ands and ors there too if you need to, but I think sAMAccountName is enough. You might want to fire up the ADSI tool (it's in the resource kit I think), which lets you walk AD like the registry. it's great for looking at properties. Then find a user, work out what prop you want (mail in this case) and what it's primary key is - sAMAccountName is a good one, but you may also want to filter on the node type.
I'm on a mac, so I can't check it for you, but each node in AD has a type, and you can add that to your filter. I think it looks like this:
((sAMAccountName=bob) & (type=User))
Again, check that - I know it's not type=user, but something LIKE that.
I have this code to connect to Active Directory and get all the groups that exist, it works and returns all the groups in results :
DirectoryEntry dirEnt = new DirectoryEntry();
using (DirectorySearcher srch = new DirectorySearcher(dirEnt, "(objectClass=Group)"))
{
srch.PageSize = 1000;
SearchResultCollection results = srch.FindAll();
}
I now want to return users of a specific group i.e. Administrators, how would I go about this?
I had tried changing (objectClass=Group) to (objectClass=Group)(cn=admin) but then it returns no results.
All the best
Here's a reference about how to in Active Directory:
Howto: (Almost) Everything In Active Directory via C#