如何从C#中找到活动目录中的用户?

时间:2009-05-05 14:51:25

标签: c# active-directory

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

我已经知道如何使用DirectorySearcher类,问题是我找不到我想做的查询。 cn和samaccount名称都没有与用户的名字有关,所以我无法搜索这些名称。拆分和搜索sn和givenName并没有像该工具那样接近任何地方。

8 个答案:

答案 0 :(得分:20)

您使用的是.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”快得多。

马克

答案 1 :(得分:10)

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

此处有关DirectorySearcher的更多信息:

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

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

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

"(&(sAMAccountName=bsmith))"

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

答案 2 :(得分: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(或flastname)通常是sAMAccountName
     FirstName LastName通常是CN

答案 3 :(得分:3)

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;
}

答案 4 :(得分:2)

来自Joe Kaplan and Ethan Wilansky文章 使用此使用(从引用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;
}

答案 5 :(得分:1)

添加Miyagi的回答......

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

DirectorySearcher ds = new DirectorySearcher();

ds.Filter = "samaccountname=" + userName;

SearchResult result = ds.FindOne();

答案 6 :(得分:1)

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

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,否则会导致异常。这就是我在上面进行检查的原因。

然后在.FindAll上执行srch,将搜索结果转换为PrincipalSearchResultPrincipal个对象的集合:

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中查找用户的问题,但请注意,您只能使用{{1}获取一些属性对象,如果我通过Google(正如我所做的那样)达到这个问题,我会非常沮丧。如果您发现这就是您所需要的一切 - 那就太棒了!但是为了得到其余的(并保持自己的良心),你必须潜入,我将描述如何做到这一点。

我发现你不能只使用我在上面提到的那个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的一部分返回,然后您可以将其绑定到DataTableListView。我就这样做了,发送了一个填充了我需要的属性的GridView

List<string>

您可以这样调用此函数:

    /// <summary>
    /// Gets matches based on First and Last Names. 
    /// This function takes a list of acceptable properties:
    /// USERNAME
    /// MIDDLE_NAME
    /// MIDDLE_INITIAL
    /// EMAIL
    /// LOCATION
    /// POST
    /// 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列将作为第一列,其中包含用户来自AD的实际NAME

注意:您必须引用.DisplayNameSystem.DirectoryServicesSystem.DirectoryServices.AccountManagementSystem.Text.RegularExpressions才能使用这一切。

HTH!

答案 7 :(得分:0)

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

        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();

似乎任何字符的通配符都是Asterisk(*)。这就是原因:

user.GivenName = "*firstname*";

Microsoft documentation

中了解详情