【问题标题】:How can you find a user in active directory from C#?如何从 C# 中找到活动目录中的用户?
【发布时间】:2010-10-23 22:31:57
【问题描述】:

我试图弄清楚如何从 C# 搜索 AD,类似于“查找用户、联系人和组”在 Active Directory 用户和计算机工具中的工作方式。我有一个包含组名或用户名的字符串(通常格式为 firstname middleinitial [if they have one] lastname,但并非总是如此)。即使我对组和用户进行单独查询,我也无法找到一种可以捕获大多数用户帐户的搜索方式。查找用户、联系人和群组工具几乎每次都能将他们带回来。有人有什么建议吗?

我已经知道如何使用 DirectorySearcher 类,但问题是我找不到符合我要求的查询。 cn 和 samaccount 名称都与这里的用户名无关,所以我无法搜索这些。拆分并在 sn 和 givenName 上搜索并没有像该工具那样捕捉到任何地方。

【问题讨论】:

  • 下面的答案都是关于使用 sAMAccountName 的,我不知道为什么他们有这么多的赞成票。这个问题是关于使用名字和姓氏来获取属性的!只有最高/标记的正确答案更接近。

标签: c# active-directory


【解决方案1】:

您使用的是 .NET 3.5 吗?如果是这样 - AD 在 .NET 3.5 中具有出色的新功能 - 请查看 Ethan Wilanski 和 Joe Kaplan 撰写的这篇文章 Managing Directory Security Principals in .NET 3.5

其中一个重要的新功能是“PrincipalSearcher”类,它应该极大地简化在 AD 中查找用户和/或组的过程。

如果您不能使用 .NET 3.5,可能会让您的生活更轻松的一件事称为“模糊名称解析”,它是一种鲜为人知的特殊搜索过滤器,可以一次搜索几乎所有与名称相关的属性。

像这样指定您的 LDAP 搜索查询:

searcher.Filter = string.Format("(&(objectCategory=person)(anr={0}))", yourSearchTerm)

另外,我建议过滤“objectCategory”属性,因为它在 AD 中默认是单值和索引的,这比使用“objectClass”快得多。

马克

【讨论】:

  • 这正是我想要的!非常感谢!
  • 我希望我能在 6 个月前了解 ANR,我一直在努力编写快速查询。这个解决方案现在为我的用户名查询提供了超快速的结果。本文介绍了它如何为您的搜索创建各种过滤器。 social.technet.microsoft.com/wiki/contents/articles/…
【解决方案2】:

System.DirectoryServices 有两个命名空间...DirectoryEntry 和 DirectorySearcher。

这里有关于 DirectorySearcher 的更多信息:

http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.aspx

然后您可以使用 Filter 属性按组、用户等进行过滤...

因此,如果您想按帐户名称进行过滤,您可以将 .Filter 设置为:

"(&(sAMAccountName=bsmith))"

并运行 FilterAll 方法。这将返回一个 SearchResultCollection,您可以循环并提取有关用户的信息。

【讨论】:

  • 谢谢,虽然我知道 DirectorySearcher。问题是我无法通过查询来查找 AD 中的用户。
  • @Sunookitsune - 所以您正在尝试复制“查找用户、联系人和组”的确切功能?
  • @Miyagi Coder 我不会确切地说出该功能,但至少可以了解它搜索用户的方式。或者找到其他可行的方法,因为我很难过。
【解决方案3】:

您需要根据查找用户的方式构建搜索字符串。

using (var adFolderObject = new DirectoryEntry())
{
     using(var adSearcherObject = new DirectorySearcher(adFolderObject))
     {
          adSearcherObject.SearchScope = SearchScope.Subtree;
          adSearcherObject.Filter = "(&(objectClass=person)(" + userType + "=" + userName + "))";

          return adSearcherObject.FindOne();
     }
}

userType 应该是 sAMAccountName 或 CN,具体取决于用户名的格式。

例如:
firstname.lastname(或姓氏)通常是 sAMAccountName
FirstName LastName 通常是 CN

【讨论】:

  • 不,sAMAccountName 是用户名,即 John Doe 的 doej,并且 OP 没有用户名。如果您说将CN 用于userType 并输入FirstName LastName 而不是username,实际上最好使用过滤器"(&(objectCategory=user)(objectClass=user)(givenName=" + firstName + ")(sn=" + lastName + "))";
【解决方案4】:
public DirectoryEntry Search(string searchTerm, string propertyName)
{
   DirectoryEntry directoryObject = new DirectoryEntry(<pathToAD>);

   foreach (DirectoryEntry user in directoryObject.Children)
   {
      if (user.Properties[propertyName].Value != null)    
         if (user.Properties[propertyName].Value.ToString() == searchTerm)
             return user;                       
   }

   return null;
}

【讨论】:

    【解决方案5】:

    添加到宫城的答案.​​.....

    这是一个应用于 DirectorySearcher 的过滤器/查询

    DirectorySearcher ds = new DirectorySearcher();
    
    ds.Filter = "samaccountname=" + userName;
    
    SearchResult result = ds.FindOne();
    

    【讨论】:

    • OP 没有用户名,即 John Doe 的 DOMAIN\doej - 他得到了名字和姓氏。这个过滤器对此毫无用处。
    【解决方案6】:

    Joe Kaplan and Ethan Wilansky 文章中得到这个 使用这个 Using(来自引用 System.DirectoryServices.AccountManagement dll):

    using System.DirectoryServices.AccountManagement;
    
    private bool CheckUserinAD(string domain, string username)
    {
        PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, domain);
        UserPrincipal user = new UserPrincipal(domainContext);
        user.Name = username;
        PrincipalSearcher pS = new PrincipalSearcher();
        pS.QueryFilter = user;
        PrincipalSearchResult<Principal> results = pS.FindAll();
        if (results != null && results.Count() > 0)
            return true;
        return false;
    }
    

    【讨论】:

    • 投反对票。 OP 说他有这个人的真实姓名,而不是用户名,就像上面显示的 user.Name 一样。这会搜索用户名,而不是名字,姓氏。
    【解决方案7】:

    其他答案描述不佳,没有描述如何实现它们,并且大多数给出了错误的过滤器属性。你甚至不需要使用.Filter——你可以将你的属性(姓氏=.Surname,名字=.GivenName)分配给UserPrincipal对象,然后使用@987654325搜索那个对象@ 在任何触发搜索的事件中:

    string firstName = txtFirstName.Text;
    string lastName = txtLastName.Text;
    
    PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
    
    UserPrincipal up = new UserPrincipal(ctx);
    if (!String.IsNullOrEmpty(firstName))
        up.GivenName = firstName;
    if (!String.IsNullOrEmpty(lastName))
        up.Surname = lastName;
    
    PrincipalSearcher srch = new PrincipalSearcher(up);
    srch.QueryFilter = up;
    

    我假设您有用于获取名字和姓氏的文本框,ID/名称为txtFirstNametxtLastName。请注意,如果您要查找的属性中没有值,请不要将其添加到UserPrincipal,否则会导致异常。这就是我在上面包含检查的原因。

    然后您对srch 执行.FindAll 以将搜索结果放入PrincipalSearchResult 集合中的Principal 对象:

    using (PrincipalSearchResult<Principal> results = srch.FindAll())
    {
        if (results != null)
        {
            int resultCount = results.Count();
            if (resultCount > 0)  // we have results
            {
                foreach (Principal found in results)
                {
                    string username = found.SamAccountName; // Note, this is not the full user ID!  It does not include the domain.
                }
            }
        }
    }
    

    请注意,即使它的.Count()0,结果也不会为空,以及为什么两个检查都存在。

    您使用 foreach 进行迭代以获取所需的属性,这回答了如何使用 C# 在 AD 中查找用户的问题,但请注意,您只能使用 Principal 对象访问一些属性,如果我通过谷歌(像我一样)提出这个问题,我会非常沮丧。如果你发现这就是你所需要的——太好了,你就完成了!但为了得到其余的(并让我自己的良心安息),你必须潜入水中,我将描述如何做到这一点。

    我发现你不能只使用我上面提到的username,但你必须得到整个DOMAIN\doej 这样的名字。这就是你这样做的方式。相反,将其放在上面的 foreach 循环中:

    string userId = GetUserIdFromPrincipal(found);
    

    并使用此功能:

    private static string GetUserIdFromPrincipal(Principal prin)
    {
        string upn = prin.UserPrincipalName;
        string domain = upn.Split('@')[1];
        domain = domain.Substring(0, domain.IndexOf(".YOURDOMAIN"));
            
        // "domain" will be the subdomain the user belongs to.
        // This may require edits depending on the organization.
    
        return domain + @"\" + prin.SamAccountName;
    }
    

    一旦你有了它,你就可以调用这个函数了:

        public static string[] GetUserProperties(string strUserName)
        {
            UserPrincipal up = GetUser(strUserName);
            if (up != null)
            {
                string firstName = up.GivenName;
                string lastName = up.Surname;
                string middleInit = String.IsNullOrEmpty(up.MiddleName) ? "" : up.MiddleName.Substring(0, 1);
                string email = up.EmailAddress;
                string location = String.Empty;
                string phone = String.Empty;
                string office = String.Empty;
                string dept = String.Empty;
    
                DirectoryEntry de = (DirectoryEntry)up.GetUnderlyingObject();
                DirectorySearcher ds = new DirectorySearcher(de);
                ds.PropertiesToLoad.Add("l"); // city field, a.k.a location
                ds.PropertiesToLoad.Add("telephonenumber");
                ds.PropertiesToLoad.Add("department");
                ds.PropertiesToLoad.Add("physicalDeliveryOfficeName");
    
                SearchResultCollection results = ds.FindAll();
                if (results != null && results.Count > 0)
                {
                    ResultPropertyCollection rpc = results[0].Properties;
                    foreach (string rp in rpc.PropertyNames)
                    {
                        if (rp == "l")  // this matches the "City" field in AD properties
                            location = rpc["l"][0].ToString();
                        if (rp == "telephonenumber")
                            phone = FormatPhoneNumber(rpc["telephonenumber"][0].ToString());                       
                        if (rp == "physicalDeliveryOfficeName")
                            office = rpc["physicalDeliveryOfficeName"][0].ToString();  
                        if (rp == "department")
                            dept = rpc["department"][0].ToString();
                    }
                }
    
                string[] userProps = new string[10];
                userProps[0] = strUserName;
                userProps[1] = firstName;
                userProps[2] = lastName;
                userProps[3] = up.MiddleName;
                userProps[4] = middleInit;
                userProps[5] = email;
                userProps[6] = location;  
                userProps[7] = phone;  
                userProps[8] = office;
                userProps[9] = dept;
    
                return userProps;
            }
            else
                return null;
        }
    
        /// <summary>
        /// Returns a UserPrincipal (AD) user object based on string userID being supplied
        /// </summary>
        /// <param name="strUserName">String form of User ID:  domain\username</param>
        /// <returns>UserPrincipal object</returns>
        public static UserPrincipal GetUser(string strUserName)
        {
            PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Domain);
            try
            {
                UserPrincipal oUserPrincipal = UserPrincipal.FindByIdentity(oPrincipalContext, strUserName);
                return oUserPrincipal;
            }
            catch (Exception ex) { return null; }
        }
    
        public static string FormatPhoneNumber(string strPhoneNumber)
        {
            if (strPhoneNumber.Length > 0)
                //  return String.Format("{0:###-###-####}", strPhoneNumber);  // formating does not work because strPhoneNumber is a string and not a number
                return Regex.Replace(strPhoneNumber, @"(\d{3})(\d{3})(\d{4})", "$1-$2-$3");
            else
                return strPhoneNumber;
        }
    

    请注意,FormatPhoneNumber 函数适用于北美号码。它将获取一个找到的数字 (##########) 并将其拆分为 ###-###-####

    然后您可以在 foreach 循环中获取这样的属性:

    string[] userProps = GetUserProperties(userId);
    string office = userProps[8];
    

    但是,作为一个整体解决方案,您甚至可以将这些结果添加到 DataRow 列中,并将其作为 DataTable 的一部分返回,然后您可以绑定到 ListViewGridView。我就是这样做的,发送一个List&lt;string&gt;,里面填满了我需要的属性:

        /// <summary>
        /// Gets matches based on First and Last Names. 
        /// This function takes a list of acceptable properties:
        /// USERNAME
        /// MIDDLE_NAME
        /// MIDDLE_INITIAL
        /// EMAIL
        /// LOCATION
        /// PHONE
        /// OFFICE
        /// DEPARTMENT
        ///
        /// The DataTable returned will have columns with these names, and firstName and lastName will be added to a column called "NAME"
        /// as the first column, automatically.
        /// </summary>
        /// <param name="firstName"></param>
        /// <param name="lastName"></param>
        /// <param name="props"></param>
        /// <returns>DataTable of columns from "props" based on first and last name results</returns>
        public static DataTable GetUsersFromName(string firstName, string lastName, List<string> props)
        {
            string userId = String.Empty;
            int resultCount = 0;
    
            DataTable dt = new DataTable();
            DataRow dr;
            DataColumn dc;
    
            // Always set the first column to the Name we pass in
            dc = new DataColumn();
            dc.DataType = System.Type.GetType("System.String");
            dc.ColumnName = "NAME";
            dt.Columns.Add(dc);
    
            // Establish our property list as columns in our DataTable
            if (props != null && props.Count > 0)
            {
                foreach (string s in props)
                {
                    dc = new DataColumn();
                    dc.DataType = System.Type.GetType("System.String");
                    if (!String.IsNullOrEmpty(s))
                    {
                        dc.ColumnName = s;
                        dt.Columns.Add(dc);
                    }
                }
            } 
    
            // Start our search
            PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
    
            UserPrincipal up = new UserPrincipal(ctx);
            if (!String.IsNullOrEmpty(firstName))
                up.GivenName = firstName;
            if (!String.IsNullOrEmpty(lastName))
                up.Surname = lastName;
    
            PrincipalSearcher srch = new PrincipalSearcher(up);
            srch.QueryFilter = up;
    
            using (PrincipalSearchResult<Principal> results = srch.FindAll())
            {
                if (results != null)
                {
                    resultCount = results.Count();
                    if (resultCount > 0)  // we have results
                    {
                        foreach (Principal found in results)
                        {
                            // Iterate results, set into DataRow, add to DataTable
                            dr = dt.NewRow();
                            dr["NAME"] = found.DisplayName;
    
                            if (props != null && props.Count > 0)
                            {
                                userId = GetUserIdFromPrincipal(found);
    
                                // Get other properties
                                string[] userProps = GetUserProperties(userId);
    
                                foreach (string s in props)
                                {
                                    if (s == "USERNAME")                   
                                        dr["USERNAME"] = userId;
    
                                    if (s == "MIDDLE_NAME")
                                        dr["MIDDLE_NAME"] = userProps[3];
    
                                    if (s == "MIDDLE_INITIAL")
                                        dr["MIDDLE_INITIAL"] = userProps[4];
    
                                    if (s == "EMAIL")
                                        dr["EMAIL"] = userProps[5];
    
                                    if (s == "LOCATION")
                                        dr["LOCATION"] = userProps[6];
    
                                    if (s == "PHONE")
                                        dr["PHONE"] = userProps[7];
    
                                    if (s == "OFFICE")
                                        dr["OFFICE"] = userProps[8];                                    
    
                                    if (s == "DEPARTMENT")
                                        dr["DEPARTMENT"] = userProps[9];
                                }
                            }
                            dt.Rows.Add(dr);
                        }
                    }
                }
            }
    
            return dt;
        }
    

    你可以这样调用这个函数:

    string firstName = txtFirstName.Text;
    string lastName = txtLastName.Text;
    
    List<string> props = new List<string>();
    props.Add("OFFICE");
    props.Add("DEPARTMENT");
    props.Add("LOCATION");
    props.Add("USERNAME");
    
    DataTable dt = GetUsersFromName(firstName, lastName, props);
    

    DataTable 将填充这些列,NAME 列作为第一列,其中将包含来自 AD 的用户实际 .DisplayName

    注意:您必须引用System.DirectoryServicesSystem.DirectoryServices.AccountManagementSystem.Text.RegularExpressionsSystem.Data 才能使用所有这些。

    HTH!

    【讨论】:

      【解决方案8】:

      我在这篇文章中寻找的代码是:

              string uid = Properties.Settings.Default.uid;
              string pwd = Properties.Settings.Default.pwd;
              using (var context = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", uid, pwd))
              {
                  using (UserPrincipal user = new UserPrincipal(context))
                  {
                      user.GivenName = "*adolf*";
                      using (var searcher = new PrincipalSearcher(user))
                      {
                          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("Mail: " + de.Properties["mail"].Value);
      
                              PrincipalSearchResult<Principal> groups = result.GetGroups();
      
                              foreach (Principal item in groups)
                              {
                                  Console.WriteLine("Groups: {0}: {1}", item.DisplayName, item.Name);
                              }
                              Console.WriteLine();
                          }
                      }
                  }
              }
              Console.WriteLine("End");
              Console.ReadLine();
      

      似乎任何字符的通配符都是星号 (*)。这就是为什么:

      user.GivenName = "*firstname*";
      

      阅读更多Microsoft documentation

      【讨论】:

        猜你喜欢
        • 2018-12-08
        • 2020-10-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多