12

I am using the System.DirectoryServices.AccountManagement part of the .Net library to interface into ActiveDirectory.

Having called GetMembers() on a GroupPrincipal object and filter the results, I now have a collection of UserPrincipal objects

GroupPrincipal myGroup;  // population of this object omitted here 

foreach (UserPrincipal user in myGroup.GetMembers(false).OfType<UserPrincipal>())
{
    Console.WriteLine(user.SamAccountName);
}

The above code sample will print out usernames like "TestUser1". I need to compare these to a list coming from another application in "DOMAIN\TestUser1" format.

How do I get the "DOMAIN" part from the UserPrincipal object?

I can't just append a known domain name as there are multiple domains involved and I need to differentiate DOMAIN1\TestUser1 and DOMAIN2\TestUser2.

Will Marcouiller
  • 23,773
  • 22
  • 96
  • 162
Grhm
  • 6,726
  • 4
  • 40
  • 64
  • @marc_s The UserPrincipleName contains the name in name@fully.qualified.domain.name format - I can't see how to easily convert that into DOMAIN\user format (especially as the domains involved are a known list - each production environment will be a different list of domains than my dev environment) – Grhm Nov 26 '10 at 11:03
  • You can also use the `msDS-PrincipalName` property as described here http://stackoverflow.com/questions/10702188/ – Greg Bray Oct 29 '13 at 16:42
  • 2
    Or use `user.Sid.Translate(typeof(System.Security.Principal.NTAccount)).ToString()` to get the Domain\Username of each group member. See http://stackoverflow.com/questions/6759463 – Greg Bray Oct 29 '13 at 17:32
  • 1
    @GregBray msDs-Principalname did not exist in Win2003 for AD LDS systems (see http://msdn.microsoft.com/en-us/library/cc221208.aspx) and on AD DS *may* contain the NETBIOS name (see http://msdn.microsoft.com/en-us/library/cc223404.aspx). I no longer have access to the domains I originally had this problem with to check - but your suggestion may be useful to those that follow... – Grhm Oct 30 '13 at 09:44

5 Answers5

4

You have two choices that I can think of.

  1. Parse, or take everything that is on, the right of name@fully.qualified.domain.name;
  2. Use the System.DirectoryServices namespace.

I don't know about UserPrincipal, neither do I about GroupPrincipal. On the other hand, I know of a working way to achive to what you want.

[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("INTRA\TESTUSER1", string.Concat(netBiosName, "\", foundLogin).ToUpperInvariant())
}

Other related information or links available in this SO question.
C# Active Directory: Get domain name of user?
How to find the NetBIOS name of a domain

Community
  • 1
  • 1
Will Marcouiller
  • 23,773
  • 22
  • 96
  • 162
  • Looks good but doesn't work. The result.GetDirectoryEntry() doesn't have a "nETBIOSName" property - it is always null. (Plus I had to use Properties["nETBIOSName"].Value and my result object doesn't have a Dispose() - using .Net4 if that makes a difference) – Grhm Nov 29 '10 at 13:56
  • @Grmh: I edited my answer. Please take a look at it and tell me whether it works. I have encapsulated it within a NUnit TestCase for ease of testing. – Will Marcouiller Nov 29 '10 at 17:36
  • @Will: I don't appear to have a "CN=Partitions,CN=Configuration" in my domain. My domain is apparently a child domain and the parent has a CN=Copnfiguration section. I'll have to investigate.... – Grhm Nov 30 '10 at 09:46
  • If I query the parent domain, it get two results. results[0].Properties["cn"][0] gives the parent domains NetBIOS name, whilst results[1].Properties["cn"][0] gives the child domains NetBIOS name. This gives a list of possible NETBIOS names. I guess I'll have to look for each netbiosname\loginname combo to see if it returns a valid user and that it matches the original user. Does seem kinda tortuous though. :( – Grhm Nov 30 '10 at 09:58
  • @Grhm: Agreed, it is tortuous. I think the simplest possible way would be to take the right part of the `@` sign on the `login@fully.qualified.domain.name` and parse concatenate it to form `fully.qualified.domain.name\login`. Would this work for best for you? – Will Marcouiller Nov 30 '10 at 17:24
  • @Will: Unfortunately I think I'll have to. I was hoping to compare a list of users to a list of logon names and report any discrepanices. The list of logons is supplied in DOMIAN\user format and user was expecting output in that format. – Grhm Dec 01 '10 at 10:15
  • It is no wrong to tell the user to provide with a list of `full domain\login` due to the limitations or or knowledge. The best thing possible is to stay transparent to the user. =) Hope I helped at least a bit. – Will Marcouiller Dec 01 '10 at 12:41
  • @Will: With your help, I now attempt to get the nETBIOSName property from the user, and if not present fallback to user@fqdn format. The first option may work in the customer environment and if not the code will return a sane known format. I'l markj this as answered since there doesn't appear to be a better answer. – Grhm Dec 01 '10 at 17:43
2

Use the ActiveDs COM library, it has built-in name translation that works and does not make any assumptions (like other answers here).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ActiveDs;

namespace Foo.Repository.AdUserProfile
{
    public class ADUserProfileValueTranslate
    {
        public static string ConvertUserPrincipalNameToNetBiosName(string userPrincipleName)
        {
            NameTranslate nameTranslate = new NameTranslate();
            nameTranslate.Set((int)ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_USER_PRINCIPAL_NAME, userPrincipleName);
            return nameTranslate.Get((int) ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_NT4);
        }
    }
}
Jeff
  • 5,913
  • 2
  • 28
  • 30
0

You could look for the possible domains in the user.DistinguishedName property. A user in Domain 1 should contain the string "DC=DOMAIN1". It definitely shouldn't contain the string "DC=DOMAIN2".

GC.
  • 1,214
  • 1
  • 10
  • 26
  • 1
    Not necessarily. I had a domain that was user1@dev.project.local or PROJECTDEV\User1 The user.DistinguishedName ended with DC=dev,DC=project,DC=local it does not contain DC=PROJECTDEV – Grhm Aug 22 '12 at 11:41
  • Ah - thx for the correction. I had thought distinguished name was in some sense a calculated field - and it's consistent in my environment. – GC. Aug 23 '12 at 11:00
0

As mentioned in one of the comments to the question I think this is a good answer for more recent times:

 user.Sid.Translate(typeof(System.Security.Principal.NTAccount)).ToString()
Jeff
  • 352
  • 3
  • 11
-1

Have you tried passing the fully qualified domain name to this other app? Most windows API's won't complain if you do fully_qualified_domain\USER.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • Unfortunately the other app stores the usernames in a database and expects the username to be unique. It also interfaces to a number of other apps that I dont have access to. – Grhm Nov 29 '10 at 11:22