2

I'm trying to query Active Directory and return an IEnumerable but with properties like Title and EmailAddress, which aren't shown doing PrincipalSearcher.Findll(). If I use PrincipalSearcher.FindOne(), it has much more properties (still not Title, though) so I'm trying to figure out what I'm doing differently or how to get the info I need out. I've worn out Google trying to find more info and it seems like UserPrincipal.GetUnderlyingObject() might be the ticket but I'm not understanding how to involve that into a foreach loop in order to populate it into the List.

    public class ADUser
    {
        public string SamAccountName { get; set; }
        public string DisplayName { get; set; }
        public string Title { get; set; }

        public IEnumerable<ADUser> Get(string username)
        {
            var users = new List<ADUser>();

            var principalContext = new PrincipalContext(ContextType.Domain, "domain.com");
            var userPrincipal = new UserPrincipal(principalContext)
            {
                SamAccountName = username
            };

            var principalSearcher = new PrincipalSearcher(userPrincipal);

            foreach (var user in principalSearcher.FindAll())
            {
                users.Add(new ADUser
                {
                    SamAccountName = user.SamAccountName,
                    DisplayName = user.DisplayName,
                    //Title = user.Title //Won't work, no Title property
                });
            }

            return users;
        }
    }

This works but only returns a fraction of the properties .FindOne() does, but if I do FindOne(), I won't be able to search for partial usernames, such as "jsm" returning "John Smith" and "James Smoth".

wjhanna
  • 27
  • 6
  • 1
    Possible duplicate of [How to get Active Directory Attributes not represented by the UserPrincipal class](https://stackoverflow.com/questions/3929561/how-to-get-active-directory-attributes-not-represented-by-the-userprincipal-clas) – Camilo Terevinto Apr 11 '18 at 23:42
  • Also: https://stackoverflow.com/questions/19492883/get-an-extension-attribute-from-ad – Camilo Terevinto Apr 11 '18 at 23:44
  • These are putting me in the right direction, but I'm still not able to get the Title property inside of the foreach loop. I can see it outside, by doing "userPrincipal.Title", but that isn't helping me if I can't put it into the List for later iteration. – wjhanna Apr 12 '18 at 00:26

1 Answers1

1

The short answer is that you can do this:

        foreach (var user in principalSearcher.FindAll())
        {
            var userDe = (DirectoryEntry) user.GetUnderlyingObject();
            users.Add(new ADUser
            {
                SamAccountName = user.SamAccountName,
                DisplayName = user.DisplayName,
                Title = userDe.Properties["title"]?.Value.ToString()
            });
        }

This is part of the reason I never use the AccountManagement namespace anymore. It uses DirectoryEntry in the background and hides its complexity to make the basic things easier for you, but you still have to revert to using DirectoryEntry directly for some things. And it actually performs slower than using DirectoryEntry/DirectorySearcher directly.

Here's an example of how you can do the same thing via DirectorySearcher. It is a little more complicated, but I bet you'll find it performs faster:

public class ADUser
{
    public string SamAccountName { get; set; }
    public string DisplayName { get; set; }
    public string Title { get; set; }

    public IEnumerable<ADUser> Get(string username)
    {
        var users = new List<ADUser>();

        var search = new DirectorySearcher(
            new DirectoryEntry("LDAP://domain.com"),
            $"(&(objectClass=user)(objectCategory=person)(sAMAccountName={username}))",
            new [] { "sAMAccountName", "displayName", "title" } //The attributes you want to see
        ) {
            PageSize = 1000 //If you're expecting more than 1000 results, you need this otherwise you'll only get the first 1000 and it'll stop
        };

        using (var results = search.FindAll()) {
            foreach (SearchResult result in results)
            {
                users.Add(new ADUser
                {
                    SamAccountName = result.Properties.Contains("sAMAccountName") ? result.Properties["sAMAccountName"][0].ToString() : null,
                    DisplayName = result.Properties.Contains("displayName") ? result.Properties["displayName"][0].ToString() : null,
                    Title = result.Properties.Contains("title") ? result.Properties["title"][0].ToString() : null
                });
            }
        }
        return users;
    }
}
Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • I guess you'll never expect more than 1000 results when you're searching by username :) It just threw me off since you're returning a list. It's a good thing to know anyway. – Gabriel Luci Apr 12 '18 at 00:33
  • I'm getting an error: "ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index" when I try to run that code on both exact usernames as well as partial.. I'm thinking it may be related to the "new []" ? – wjhanna Apr 12 '18 at 00:54
  • I'm not getting any exception. Which line is throwing the exception for you? – Gabriel Luci Apr 12 '18 at 00:58
  • If you want it to work for partial usernames too, you have to add a `*` to the filter: `$"(&(objectClass=user)(objectCategory=person)(sAMAccountName={username}*))"` – Gabriel Luci Apr 12 '18 at 01:00
  • Line 35 of ADUser.cs, the "users.Add( new ADUser" line. – wjhanna Apr 12 '18 at 01:00
  • I figured it out, I assumed the question mark in ["title"]?[0] meant it was able to be nullable, but I'm getting the error because one account has no Title field populated. How can I make it able to be null? – wjhanna Apr 12 '18 at 01:06
  • Right, sorry. You have to add a check using `.Properties.Contains()`. I've updated my code. – Gabriel Luci Apr 12 '18 at 01:09
  • That's the kind of stuff the `AccountManagement` namespace protects you from :) but at a cost... – Gabriel Luci Apr 12 '18 at 01:14
  • Thanks so much for the help, it's exactly what I needed. I've seriously been fighting trying to get this working all day. – wjhanna Apr 12 '18 at 01:17
  • No problem. I've been there! :) – Gabriel Luci Apr 12 '18 at 01:20