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();
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);
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.
I'm trying to recive all computers in my AD and also which of them whos currently logged in. I've tryed doing this by checking the "lastLogonStamp" but that returns the wrong value, saying my server was logged into AD eight days ago. Even if I restart the server it says the same. I got the code from another question here:
How to list all computers and the last time they were logged onto in AD?
public DataTable GetListOfComputers(string domain, string userName, string password)
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain,
userName, password, AuthenticationTypes.Secure);
DirectorySearcher search = new DirectorySearcher(entry);
string query = "(objectclass=computer)";
search.Filter = query;
search.PropertiesToLoad.Add("name");
search.PropertiesToLoad.Add("lastLogonTimestamp");
SearchResultCollection mySearchResultColl = search.FindAll();
DataTable results = new DataTable();
results.Columns.Add("name");
results.Columns.Add("lastLogonTimestamp");
foreach (SearchResult sr in mySearchResultColl)
{
DataRow dr = results.NewRow();
DirectoryEntry de = sr.GetDirectoryEntry();
dr["name"] = de.Properties["Name"].Value;
dr["lastLogonTimestamp"] = DateTime.FromFileTimeUtc(long.Parse(sr.Properties["lastLogonTimestamp"][0].ToString()));
results.Rows.Add(dr);
de.Close();
}
return results;
}
If you're using .NET 3.5 and up, you can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for a ComputerPrincipal
ComputerPrincipal qbeComputer = new ComputerPrincipal(ctx);
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeComputer);
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" - it could be user, group, computer.....
ComputerPrincipal cp = found as ComputerPrincipal;
if(cp != null)
{
string computerName = cp.Name;
DateTime lastLogon = cp.LastLogon;
}
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement. Or see the MSDN documentation on the System.DirectoryServices.AccountManagement namespace.
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 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#