How can I better query multiple domains in Active

2019-02-15 07:03发布

问题:

I am attempting to expand a LDAP / AD search from only searching in the currently logged in domain to searching all domains in the AD. The method takes in the string with the query and returns and return an LDAPInformation object.

While I am asking, is there any better way to search for the name than in this way? It is user unfriendly due to needing to use wildcards if looking for a person by last name (example: Doe*).

    public static LDAPInformation[] GetGlobalAddressListVIAName(string nameQuery)
    {
        var currentForest = Forest.GetCurrentForest();
        var globalCatalog = currentForest.FindGlobalCatalog();

        using (var searcher = globalCatalog.GetDirectorySearcher())
        {
            using (var entry = new DirectoryEntry(searcher.SearchRoot.Path))
            {
                searcher.Filter = "(&(mailnickname=*)(objectClass=user)(displayName=" + nameQuery + "))";
                searcher.PropertyNamesOnly = true;
                searcher.SearchScope = SearchScope.Subtree;
                searcher.Sort.Direction = SortDirection.Ascending;
                searcher.Sort.PropertyName = "displayName";
                return searcher.FindAll().Cast<SearchResult>().Select(result => new LDAPInformation(result.GetDirectoryEntry())).ToArray();
            }
        }
    }

Here is the object:

    class LDAPInformation
{
    internal LDAPInformation(DirectoryEntry entry)
    {
        //Section: HASH
        this.sAMAccountName = (string)entry.Properties["sAMAccountName"].Value;

        //Section: Email
        this.Mail = (string)entry.Properties["mail"].Value;

        //Section: Organziation
        this.Description = (string)entry.Properties["description"].Value;
        this.Company = (string)entry.Properties["company"].Value;
        this.Title = (string)entry.Properties["title"].Value;
        this.Department = (string)entry.Properties["department"].Value;

        //Section: Name
        this.DisplayName = (string)entry.Properties["displayName"].Value;
        this.FirstName = (string)entry.Properties["firstName"].Value;
        this.MiddleName = (string)entry.Properties["middleName"].Value;
        this.LastName = (string)entry.Properties["lastName"].Value;

        //Section: Address
        this.StreetAddress = (string)entry.Properties["streetAddress"].Value;
        this.City = (string)entry.Properties["city"].Value;
        this.State = (string)entry.Properties["state"].Value;
        this.PostalCode = (string)entry.Properties["postalCode"].Value;
        this.TelephoneNumber = (string)entry.Properties["telephoneNumber"].Value;
    }

    public string DisplayName
    {
        get;
        private set;
    }

    public string Mail
    {
        get;
        private set;
    }

    public string sAMAccountName
    {
        get;
        private set;
    }

    public string Description
    {
        get;
        private set;
    }

    public string Company
    {
        get;
        private set;
    }

    public string Title
    {
        get;
        private set;
    }

    public string Department
    {
        get;
        private set;
    }

    public string FirstName
    {
        get;
        private set;
    }

    public string MiddleName
    {
        get;
        private set;
    }

    public string LastName
    {
        get;
        private set;
    }

    public string StreetAddress
    {
        get;
        private set;
    }

    public string City
    {
        get;
        private set;
    }

    public string State
    {
        get;
        private set;
    }

    public string PostalCode
    {
        get;
        private set;
    }

    public string TelephoneNumber
    {
        get;
        private set;
    }
}

回答1:

Querying the global catalog is the correct approach.

You might want to look into Ambigous Name Resolution (ANR) - http://support.microsoft.com/kb/243299.



回答2:

@Brian Desmond and @lordzero. Possibly a bit late to the part here, but I've just been working on this sort of stuff myself recently so thought I share.

In response to your question 'What's your search root?", lordzero, here's what you can do to find it from the AD servers without knowing them. A Domain registered PC will know the AD infrastructure/Forest etc.

First you create a DirectoryEntry for "GC://rootDSE" from that you extract a Naming Context, rootDomainNamingContext or another. You can dump out all the attributes of the first root DSE DirectoryEntry to see what's available.

This is my implementation for a Directory Services service provider we use.

This is taken from a DirectorySearcherWrapper class

I have a member var. and instantiate it in the constructor.

DirectorySearcher directorySearcher;

In my initialisation method, I do this

        using (DirectoryEntry directoryEntry = new DirectoryEntry(DirectoryConstants.RootDSE))
        {
            // Create a Global Catalog Directory Service Searcher
            string strRootName = directoryEntry.Properties[DirectoryConstants.RootDomainNamingContext].Value.ToString();
            using (DirectoryEntry usersBinding = new DirectoryEntry(DirectoryConstants.GlobalCatalogProtocol + strRootName))
            {
                directorySearcher.SearchRoot = usersBinding;
                directorySearcher.ClientTimeout = timeout;
                directorySearcher.CacheResults = true;
                result = true;
                initialized = true;
            }
        }

DirectoryConstants class properties

public static string RootDSE { get { return @"GC://rootDSE"; } }
public static string RootDomainNamingContext { get { return "rootDomainNamingContext"; } }
public static string GlobalCatalogProtocol { get { return @"GC://"; } }

I'm sure this only works for Domain users logged in to a Domain registered PC. Authentication is automatically handled behind the scenes. If your user is logged in to a local account or the machine isn't on a Domain, you'll most likely get a DirectoryServicesCOMException.

Hope this helps. Ditch the 'using' constructs if you're not bothered about disposal and StyleCop/Sonar coding violations!

The DirectorySearcherWrapper class where the code above resides is then used by the service provider code. It's separated like this so it can easily be Mocked out as there no guarantee the build machines are registered to a domain when it executes our Unit Tests.

...most of it comes from the MSDN/.Net documentation.