ChatGPT解决这个技术问题 Extra ChatGPT

How can I get a list of users from active directory?

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!

Read the excellent MSDN article Managing Directory Security Principals in the .NET Framework 3.5 for a great intro to using AD with .NET 3.5
Looks like @marc_s's article got archived, here's an updated link
@marc_s I would love to read sir, but the link is a dead one. I tried this blogs.msdn.microsoft.com/msdnmagazine/2008/01/16/… but even the links on that article leads to a genetic page for microsoft magazine
@Malky.Kid I found my way to the article. Use the link of the first comment to this question and download the January 2008 issue. Don't forget to unblock the chm-file in the Explorer properties page before reading.

S
Seymour

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.


What if server not contains Domain
How do you use the same code to list users from an AD group?
Is there a way using this method to narrow the search to only those in the directory that have been assigned an email address?
And what if the current computer does not belong to the domain?
I am using the same approach. I am fetching all users from AD (no filters). The problem is the number of users is over 150,000 so it can take up to 2 hours, is there a faster way to get the whole user list or if we could optimise the above example?
m
marsh-wiggle

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.


I don't think you need searcher.QueryFilter = userPrin; as we already pass user principal to the principal searcher on initialization, but otherwise thanks for the tip on filtering active users only!
Yeah, Andrey is right So basically this could be replaced with adding this property in the second using statement: using (var searcher = new PrincipalSearcher(new UserPrincipal(context){ Enabled = true }))
But I thought you had to test if Enabled had a value first: if (userPrincipal.Enabled.HasValue)
M
MikeZ

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://docs.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);
}

J
Jordan Ryder

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

What is 'e' please?
Thanks, never noticed that. I changed it, was supposed to be "u". I also added ?s to handle null values if the property is missing.
J
JohnB

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

@Fandango68: LOL, yes it is!!! System.Windows.Forms.MessageBox.Show(ex.Message + ex.StackTrace);