27

I know that this type of question has been asked before, but other methods are failing me right now.

As it stands our windows service polls AD, given an LDAP (i.e. LDAP://10.32.16.80) and a list of usergroups within that AD server to search for. It retrieves all users within those given groups, recursively searching those groups for more groups as well. Each user is then added to another applications authenticated users list.

This part of the application is running successfully. However, we're in need of each user's friendly domain name (i.e. the part of their login DOMAIN/username)

So if there is a user that is part of TEST domain, named Steve: TEST/steve is his login. I'm able to find steve in the AD, however I also need "TEST" to be stored along with his AD information.

Again, I can find 'steve' fine by using a directory searcher and the LDAP IP I'm given, but given the LDAP IP, how can I find the friendly domain name?

When I try the following code I'm given an error when attempting to access the 'defaultNamingContext':

System.Runtime.InteropServices.COMException (0x8007202A): The authentication mechanism is unknown.

Here is the code:

    private string SetCurrentDomain(string server)
    {
        string result = string.Empty;
        try
        {
            logger.Debug("'SetCurrentDomain'; Instantiating rootDSE LDAP");
            DirectoryEntry ldapRoot = new DirectoryEntry(server + "/rootDSE", username, password);
            logger.Debug("'SetCurrentDomain'; Successfully instantiated rootDSE LDAP");

            logger.Debug("Attempting to retrieve 'defaultNamingContext'...");
            string domain = (string)ldapRoot.Properties["defaultNamingContext"][0]; //THIS IS WHERE I HIT THE COMEXCEPTION
            logger.Debug("Retrieved 'defaultNamingContext': " + domain);
            if (!domain.IsEmpty())
            {

                logger.Debug("'SetCurrentDomain'; Instantiating partitions/configuration LDAP entry");
                DirectoryEntry parts = new DirectoryEntry(server + "/CN=Partitions,CN=Configuration," + domain, username, password);

                logger.Debug("'SetCurrentDomain'; Successfully instantiated partitions/configuration LDAP entry");
                foreach (DirectoryEntry part in parts.Children)
                {
                    if (part.Properties["nCName"] != null && (string)part.Properties["nCName"][0] != null)
                    {
                        logger.Debug("'SetCurrentDomain'; Found property nCName");
                        if ((string)part.Properties["nCName"][0] == domain)
                        {
                            logger.Debug("'SetCurrentDomain'; nCName matched defaultnamingcontext");
                            result = (string)part.Properties["NetBIOSName"][0];
                            logger.Debug("'SetCurrentDomain'; Found NetBIOSName (friendly domain name): " + result);
                            break;
                        }
                    }
                }
            }
            logger.Debug("finished setting current domain...");
        }
        catch (Exception ex)
        {
            logger.Error("error attempting to set domain:" + ex.ToString());
        }
        return result;
    }

edit

I added this sample method in order to attempt a suggestion but am getting an exception: "Unspecified error" when I hit the "FindAll()" call on the searcher. The string being passed in is: "CN=TEST USER,CN=Users,DC=tempe,DC=ktregression,DC=com"

        private string GetUserDomain(string dn)
    {
        string domain = string.Empty;
        string firstPart = dn.Substring(dn.IndexOf("DC="));
        string secondPart = "CN=Partitions,CN=Configuration," + firstPart;
        DirectoryEntry root = new DirectoryEntry(secondPart, textBox2.Text, textBox3.Text);
        DirectorySearcher searcher = new DirectorySearcher(root);
        searcher.SearchScope = SearchScope.Subtree;
        searcher.ReferralChasing = ReferralChasingOption.All;
        searcher.Filter = "(&(nCName=" + firstPart + ")(nETBIOSName=*))";
        try
        {
            SearchResultCollection rs = searcher.FindAll();
            if (rs != null)
            {
                domain = GetProperty(rs[0], "nETBIOSName");
            }
        }
        catch (Exception ex)
        {

        }


        return domain;
ghost_mv
  • 1,170
  • 4
  • 20
  • 43
  • Is the `TEST` domain under the same Forest as the current domain? If so, you can query the right domain for this user, as the user may exist in your current domain, but not in the other. – Will Marcouiller Nov 22 '10 at 19:05
  • Yes, the TEST domain will be in the same forest as the current domain. In that respect, how do I go about querying the domain for a given user? Keep in mind my AD knowledge is limited, in that I'm not well versed on building LDAP strings and such... – ghost_mv Nov 22 '10 at 19:08
  • possible duplicate of [Get NT style domain\user given DN](http://stackoverflow.com/questions/1796426/get-nt-style-domain-user-given-dn) - this provides info on how to convert a user's DN to its NETBIOS domain name, which is what you want here. – Steve Townsend Nov 22 '10 at 19:17
  • I would then query each domain for user *Steve* and return a `IList` of the domains where *Steve* has an account. Hence, you cannot say that the absolute username will be `TEST\Steve`, since *Steve* might have another user account of the same name for a different domain, let's say `PROD\Steve`. – Will Marcouiller Nov 22 '10 at 19:26
  • Steve Townsend, I attempted that link as a solution but am getting an unspecified error when making the call "FindOne()" on the searcher. – ghost_mv Nov 22 '10 at 21:47

4 Answers4

30

This article helped me much to understand how to work with the Active Directory.
Howto: (Almost) Everything In Active Directory via C#

From this point forward, if you require further assitance, please let me know with proper questions in comment, and I shall answer them for you to the best of my knowledge.

EDIT #1

You had better go with this example's filter instead. I have written some sample code to briefly show how to work with the System.DirectoryServices and System.DirectoryServices.ActiveDirectory namespaces. The System.DirectoryServices.ActiveDirectory namespace is used to retrieve information about the domains within your Forest.

private IEnumerable<DirectoryEntry> GetDomains() {
    ICollection<string> domains = new List<string>();

    // Querying the current Forest for the domains within.
    foreach(Domain d in Forest.GetCurrentForest().Domains)
        domains.Add(d.Name);

    return domains;
}

private string GetDomainFullName(string friendlyName) {
    DirectoryContext context = new DirectoryContext(DirectoryContextType.Domain, friendlyName);
    Domain domain = Domain.GetDomain(context);
    return domain.Name;
}

private IEnumerable<string> GetUserDomain(string userName) {
    foreach(string d in GetDomains()) 
        // From the domains obtained from the Forest, we search the domain subtree for the given userName.
        using (DirectoryEntry domain = new DirectoryEntry(GetDomainFullName(d))) {
            using (DirectorySearcher searcher = new DirectorySearcher()){
                searcher.SearchRoot = domain;
                searcher.SearchScope = SearchScope.Subtree;
                searcher.PropertiesToLoad.Add("sAMAccountName");
                // The Filter is very important, so is its query string. The 'objectClass' parameter is mandatory.
                // Once we specified the 'objectClass', we want to look for the user whose login
                // login is userName.
                searcher.Filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", userName);

                try {
                    SearchResultCollection  results = searcher.FindAll();

                    // If the user cannot be found, then let's check next domain.
                    if (results == null || results.Count = 0)
                        continue;

                     // Here, we yield return for we want all of the domain which this userName is authenticated.
                     yield return domain.Path;
                } finally {
                    searcher.Dispose();
                    domain.Dispose();
                }
            }
}

Here, I didn't test this code and might have some minor issue to fix. This sample is provided as-is for the sake of helping you. I hope this will help.

EDIT #2

I found out another way out:

  1. You have first to look whether you can find the user account within your domain;
  2. If found, then get the domain NetBIOS Name; and
  3. concatenate it to a backslash (****) and the found login.

The example below uses a NUnit TestCase which you can test for yourself and see if it does what you are required to.

[TestCase("LDAP://fully.qualified.domain.name", "TestUser1")] 
public void GetNetBiosName(string ldapUrl, string login)
    string netBiosName = null;
    string foundLogin = null;

    using (DirectoryEntry root = new DirectoryEntry(ldapUrl))
        Using (DirectorySearcher searcher = new DirectorySearcher(root) {
            searcher.SearchScope = SearchScope.Subtree;
            searcher.PropertiesToLoad.Add("sAMAccountName");
            searcher.Filter = string.Format("(&(objectClass=user)(sAMAccountName={0}))", login);

            SearchResult result = null;

            try {
                result = searcher.FindOne();

                if (result == null) 
                    if (string.Equals(login, result.GetDirectoryEntry().Properties("sAMAccountName").Value)) 
                        foundLogin = result.GetDirectoryEntry().Properties("sAMAccountName").Value
            } finally {
                searcher.Dispose();
                root.Dispose();
                if (result != null) result = null;
            }
        }

    if (!string.IsNullOrEmpty(foundLogin)) 
        using (DirectoryEntry root = new DirectoryEntry(ldapUrl.Insert(7, "CN=Partitions,CN=Configuration,DC=").Replace(".", ",DC=")) 
            Using DirectorySearcher searcher = new DirectorySearcher(root)
                searcher.Filter = "nETBIOSName=*";
                searcher.PropertiesToLoad.Add("cn");

                SearchResultCollection results = null;

                try {
                    results = searcher.FindAll();

                    if (results != null && results.Count > 0 && results[0] != null) {
                        ResultPropertyValueCollection values = results[0].Properties("cn");
                        netBiosName = rpvc[0].ToString();
                } finally {
                    searcher.Dispose();
                    root.Dispose();

                    if (results != null) {
                        results.Dispose();
                        results = null;
                    }
                }
            }

    Assert.AreEqual("FULLY\TESTUSER1", string.Concat(netBiosName, "\", foundLogin).ToUpperInvariant())
}

The source from which I inspired myself is:
Find the NetBios Name of a domain in AD

danijelk
  • 6,749
  • 2
  • 16
  • 11
Will Marcouiller
  • 23,773
  • 22
  • 96
  • 162
  • Will, I've used that site many times, but it doesn't seem to give any specifics on the question at hand. Thanks though. – ghost_mv Nov 22 '10 at 21:50
  • @Micheal Velasquez: Please see my edit for a code sample which, I hope, will help you. – Will Marcouiller Nov 23 '10 at 14:51
  • I ported your code sample over to my C# test app and with a few tweaks (i.e. the return type of the GetDomains() method, etc.) I'm running into an issue with the yield return of the GetUserDomain() method. The DirectoryEntry object "domain" does not have a property "Domain" (i.e. domain.Domain) – ghost_mv Nov 23 '10 at 15:46
  • Also, your sample gets the current Forest's domains. I'm in need of of the info of a specified LDAP server, not necessarily the one that the application is running under. – ghost_mv Nov 23 '10 at 15:57
  • this would actually be `domain.Path`, sorry for my mistake. - Edited my code to reflect the change. – Will Marcouiller Nov 23 '10 at 17:23
  • 2
    While this is a great example, I will point out that samAccountName is not guaranteed to be unique across domains. It is only unique within a specific domain. Learned that the hard way. – ChrisW Mar 01 '12 at 23:07
  • Please look into this http://stackoverflow.com/questions/26043624/active-directory-authentication url, if possible please suggest me some solution – Vivekh Sep 25 '14 at 16:30
  • The last block of code is full of errors, it took me 10 min to fix all of them. I wonder if you really ran this code... :-( – sɐunıɔןɐqɐp Feb 29 '20 at 09:33
9

Since I could not find any example code I would like to share my own solution. This will search the parents of the DirectoryEntry object until it hits the domainDNS class.

using System.DirectoryServices;

public static class Methods
{
    public static T ldap_get_value<T>(PropertyValueCollection property)
    {
        object value = null;
        foreach (object tmpValue in property) value = tmpValue;
        return (T)value;
    }

    public static string ldap_get_domainname(DirectoryEntry entry)
    {
        if (entry == null || entry.Parent == null) return null;
        using (DirectoryEntry parent = entry.Parent)
        {
            if (ldap_get_value<string>(parent.Properties["objectClass"]) == "domainDNS") 
                return ldap_get_value<string>(parent.Properties["dc"]);
            else 
                return ldap_get_domainname(parent);
        }
    }
}

Use it like this:

string[] _properties = new string[] { "objectClass", "distinguishedName", "samAccountName", "userPrincipalName", "displayName", "mail", "title", "company", "thumbnailPhoto", "useraccountcontrol" };
string account = "my-user-name";
// OR even better:
// string account = "my-user-name@DOMAIN.local";

using (DirectoryEntry ldap = new DirectoryEntry())
{
    using (DirectorySearcher searcher = new DirectorySearcher(ldap))
    {
        searcher.PropertiesToLoad.AddRange(_properties);
        if (account.Contains('@')) searcher.Filter = "(userPrincipalName=" + account + ")";
        else searcher.Filter = "(samAccountName=" + account + ")";
        var user = searcher.FindOne().GetDirectoryEntry();

        Console.WriteLine("Name: " + Methods.ldap_get_value<string>(user.Properties["displayName"]));
        Console.WriteLine("Domain: " + Methods.ldap_get_domainname(user));
        Console.WriteLine("Login: " + Methods.ldap_get_domainname(user) + "\\" + Methods.ldap_get_value<string>(user.Properties["samAccountName"]));
    }
}

I haven't got a forest to test it on but in theory this should cut it.

Tiele Declercq
  • 2,070
  • 2
  • 28
  • 39
  • It works well but there's a more elegant implementation for `ldap_get_value`: `return entry.Properties [propertyName].OfType ().LastOrDefault ();` – Ohad Schneider Jan 11 '16 at 15:17
  • Thanks Tiele, your code works for me with small changes, but it looks much faster as compare to other code i found online. – Anand Patel Feb 16 '16 at 02:13
  • I am getting "Username or password is incorrect" exception in var `user = searcher.FindOne().GetDirectoryEntry();` line. Whether it is authentication error ? – Shesha Jul 22 '16 at 09:28
  • Someone please describe me the meaning of the first function there called `ldap_get_value`. What's the purpose iterating over all the properties and reassigning them all to the `value` object? Wouldn't we get the last item there in the end? – Just Shadow Mar 24 '20 at 18:55
5

You can retrieve the name of the domain that the current user is on using the Environment.UserDomainName Property.

string domainName;
domainName = System.Environment.UserDomainName;
Manual5355
  • 981
  • 10
  • 27
Mark
  • 218
  • 1
  • 4
  • 12
2

Maybe not entirely correct but...

DirectoryEntry dirEntry = new DirectoryEntry();         
DirectorySearcher dirSearcher = new DirectorySearcher(dirEntry);
dirSearcher.SearchScope = SearchScope.Subtree;
dirSearcher.Filter = string.Format("(&(objectClass=user)(|(cn={0})(sn={0}*)(givenName={0})(sAMAccountName={0}*)))", userName);
var searchResults = dirSearcher.FindAll();

foreach (SearchResult sr in searchResults)
{
     var de = sr.GetDirectoryEntry();
     string user = de.Properties["SAMAccountName"][0].ToString();               
     string domain = de.Path.ToString().Split(new [] { ",DC=" },StringSplitOptions.None)[1];
     MessageBox.Show(domain + "/" + user);
}

Because the value of de.Path is

LDAP://CN=FullName,DC=domain,DC=local

DartAlex
  • 264
  • 1
  • 2
  • 9