I'm trying to figure out how to search AD from C# similarly to how "Find Users, Contacts, and Groups" works in the Active Directory Users and Computers tool. I have a string that either contains a group name, or a user's name (usually in the format firstname middleinitial [if they have one] lastname, but not always). Even if I do a seperate query for groups vs. users, I can't come up with a way to search that captures most user accounts. The Find Users, Contacts, and Groups tool brings them back almost every time. Anyone have any suggestions?
我试图弄清楚如何从C#中搜索AD,类似于“查找用户,联系人和组”在“Active Directory用户和计算机”工具中的工作方式。我有一个字符串,其中包含一个组名或一个用户名(通常格式为firstname middleinitial [如果他们有一个]姓氏,但并非总是如此)。即使我对群组与用户进行单独查询,我也无法想出一种捕获大多数用户帐户的搜索方式。查找用户,联系人和组工具几乎每次都会将它们带回来。有人有什么建议吗?
I already know how to use the DirectorySearcher class, the issue is that I can't find a query that does what I'd like. Neither cn nor samaccount name has anything to do with the user's name in this, so I'm unable to search on those. Splitting things up and searching on sn and givenName doesn't catch anywhere near as much as that tool does.
我已经知道如何使用DirectorySearcher类,问题是我找不到我想做的查询。 cn和samaccount名称都没有与用户的名字有关,所以我无法搜索那些。拆分和搜索sn和givenName并没有像工具那样接近任何地方。
8 个解决方案
#1
Are you on .NET 3.5 ? If so - AD has great new features in .NET 3.5 - check out this article Managing Directory Security Principals in .NET 3.5 by Ethan Wilanski and Joe Kaplan.
你是.NET 3.5吗?如果是这样 - AD在.NET 3.5中有很多新功能 - 请参阅本文由Ethan Wilanski和Joe Kaplan管理.NET 3.5中的目录安全主体。
One of the big new features is a "PrincipalSearcher" class which should greatly simplify finding users and/or groups in AD.
其中一个重要的新功能是“PrincipalSearcher”类,它应该极大地简化在AD中查找用户和/或组的过程。
If you cannot use .NET 3.5, one thing that might make your life easier is called "Ambiguous Name Resolution", and it's a little known special search filter that will search in just about any name-related attribute all at once.
如果你不能使用.NET 3.5,那么可能会让你的生活更轻松的一件事叫做“模糊名称解析”,这是一个鲜为人知的特殊搜索过滤器,可以同时搜索任何与名称相关的属性。
Specify your LDAP search query like this:
像这样指定您的LDAP搜索查询:
searcher.Filter = string.Format("(&(objectCategory=person)(anr={0}))", yourSearchTerm)
Also, I would recommend filtering on the "objectCategory" attribute, since that's single-valued and indexed by default in AD, which is a lot faster than using "objectClass".
另外,我建议对“objectCategory”属性进行过滤,因为这是AD中的单值和默认索引,这比使用“objectClass”快得多。
Marc
#2
System.DirectoryServices has two namespaces...DirectoryEntry and DirectorySearcher.
System.DirectoryServices有两个名称空间... DirectoryEntry和DirectorySearcher。
More info on the DirectorySearcher here:
有关DirectorySearcher的更多信息:
http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.aspx
You can then use the Filter property to filter by Group, user etc...
然后,您可以使用Filter属性按组,用户等进行筛选...
So if you wanted to filter by account name you would set the .Filter to:
因此,如果您想按帐户名称进行过滤,则可以将.Filter设置为:
"(&(sAMAccountName=bsmith))"
and run the FilterAll method. This will return a SearchResultCollection that you can loop through and pull information about the user.
并运行FilterAll方法。这将返回一个SearchResultCollection,您可以循环并提取有关该用户的信息。
#3
You need to build the search string based on how you're looking for the user.
您需要根据您查找用户的方式构建搜索字符串。
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 should either be sAMAccountName or CN depending on how the username is formatted.
userType应该是sAMAccountName或CN,具体取决于用户名的格式。
ex:
firstname.lastname (or flastname) will usually be the sAMAccountName
FirstName LastName will usually be the CN
例如:firstname.lastname(或flastname)通常是sAMAccountName FirstName LastName通常是CN
#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
Got this from the Joe Kaplan and Ethan Wilansky Article Use this Using (from referencing the System.DirectoryServices.AccountManagement dll):
来自Joe Kaplan和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;
}
#6
To add onto Miyagi's answer....
添加到Miyagi的答案....
Here's a filter/query to apply to DirectorySearcher
这是一个应用于DirectorySearcher的过滤器/查询
DirectorySearcher ds = new DirectorySearcher();
ds.Filter = "samaccountname=" + userName;
SearchResult result = ds.FindOne();
#7
The other answers were poorly described, didn't describe how to implement them, and most gave the wrong filter properties. You don't even need to use .Filter
-- you can just assign your properties (last name = .Surname
, first name = .GivenName
) to a UserPrincipal
object, then search on that object using a PrincipalSearcher
in whatever event that triggers the search:
其他答案描述不清楚,没有描述如何实现它们,并且大多数都给出了错误的过滤器属性。您甚至不需要使用.Filter - 您只需将属性(姓氏=。姓氏,名字= .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;
I'm assuming you have textboxes for First and Last Name to get it, with IDs/Names of txtFirstName
and txtLastName
. Note that if you do not have a value in the property you are looking for, do not add it to the UserPrincipal
, or it will cause an exception. That's the reason for the checks I included, above.
我假设你有第一个和最后一个名字的文本框来获取它,ID /名称为txtFirstName和txtLastName。请注意,如果您在要查找的属性中没有值,请不要将其添加到UserPrincipal,否则将导致异常。这就是我在上面进行检查的原因。
You then do a .FindAll
on srch
to get search results into a PrincipalSearchResult
collection of Principal
objects:
然后,您在srch上执行.FindAll,以将搜索结果转换为Principal对象的PrincipalSearchResult集合:
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.
}
}
}
}
Note that results won't be null even if its .Count()
is 0
, and why both checks are there.
请注意,即使其.Count()为0,结果也不会为null,并且为什么两个检查都存在。
You iterate using that foreach
to get the properties you need, and this answers the question of how to find a user in AD using C#, but note you can only get to a few properties using the Principal
object, and if I reached this question through Google (as I did), I would be very disheartened. If you find that's all you need - great, you're done! But in order to get the rest (and rest my own conscience), you have to dive down, and I'll describe how to do that.
您使用该foreach迭代以获取所需的属性,这回答了如何使用C#在AD中查找用户的问题,但请注意,您只能使用Principal对象获取一些属性,如果我通过谷歌(就像我一样),我会非常沮丧。如果您发现这就是您所需要的一切 - 那就太棒了!但是为了得到休息(并保持自己的良心),你必须潜入,我将描述如何做到这一点。
I found you can't just use that username
I put above, but you have to get the whole DOMAIN\doej
kind of name. This is how you do that. Instead, put this in that foreach
loop, above:
我发现你不能只使用我上面提到的那个用户名,但是你必须得到整个DOMAIN \ doej这样的名字。这就是你如何做到的。相反,把它放在foreach循环中,上面:
string userId = GetUserIdFromPrincipal(found);
and use this function:
并使用此功能:
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;
}
Once you have that, you can call this function:
完成后,您可以调用此函数:
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;
}
Note that the FormatPhoneNumber
function is for North American numbers. It will take a number it finds (##########
) and separate it into ###-###-####
.
请注意,FormatPhoneNumber函数适用于北美数字。它将找到它找到的数字(##########)并将其分成### - ### - ####。
You can then get the properties like this, back in that foreach
loop:
然后你可以在foreach循环中获得这样的属性:
string[] userProps = GetUserProperties(userId);
string office = userProps[8];
But, as a whole solution, you can even add these results into a DataRow
column, and return it as part of a DataTable
that you could then bind to a ListView
or GridView
. This is how I did it, sending in a List<string>
filled with the properties I needed:
但是,作为一个完整的解决方案,您甚至可以将这些结果添加到DataRow列中,并将其作为DataTable的一部分返回,然后您可以将其绑定到ListView或GridView。我就是这样做的,发送一个List
/// <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;
}
You would call this function like this:
您可以像这样调用此函数:
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);
The DataTable
will be filled with those columns, and a NAME
column as the first column, that will have the user's actual .DisplayName
from AD.
DataTable将填充这些列,并将NAME列填充为第一列,它将具有AD中用户的实际.DisplayName。
Note: You must reference System.DirectoryServices
and System.DirectoryServices.AccountManagement
, System.Text.RegularExpressions
, System.Data
to use all this.
注意:您必须引用System.DirectoryServices和System.DirectoryServices.AccountManagement,System.Text.RegularExpressions,System.Data才能使用所有这些。
HTH!
#8
The code I were looking for in this post was:
我在这篇文章中寻找的代码是:
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();
It seems that wildcard for any character is Asterisk (*). That's why:
似乎任何字符的通配符都是Asterisk(*)。这就是为什么:
user.GivenName = "*firstname*";
Read more in Microsoft documentation
阅读Microsoft文档中的更多内容
#1
Are you on .NET 3.5 ? If so - AD has great new features in .NET 3.5 - check out this article Managing Directory Security Principals in .NET 3.5 by Ethan Wilanski and Joe Kaplan.
你是.NET 3.5吗?如果是这样 - AD在.NET 3.5中有很多新功能 - 请参阅本文由Ethan Wilanski和Joe Kaplan管理.NET 3.5中的目录安全主体。
One of the big new features is a "PrincipalSearcher" class which should greatly simplify finding users and/or groups in AD.
其中一个重要的新功能是“PrincipalSearcher”类,它应该极大地简化在AD中查找用户和/或组的过程。
If you cannot use .NET 3.5, one thing that might make your life easier is called "Ambiguous Name Resolution", and it's a little known special search filter that will search in just about any name-related attribute all at once.
如果你不能使用.NET 3.5,那么可能会让你的生活更轻松的一件事叫做“模糊名称解析”,这是一个鲜为人知的特殊搜索过滤器,可以同时搜索任何与名称相关的属性。
Specify your LDAP search query like this:
像这样指定您的LDAP搜索查询:
searcher.Filter = string.Format("(&(objectCategory=person)(anr={0}))", yourSearchTerm)
Also, I would recommend filtering on the "objectCategory" attribute, since that's single-valued and indexed by default in AD, which is a lot faster than using "objectClass".
另外,我建议对“objectCategory”属性进行过滤,因为这是AD中的单值和默认索引,这比使用“objectClass”快得多。
Marc
#2
System.DirectoryServices has two namespaces...DirectoryEntry and DirectorySearcher.
System.DirectoryServices有两个名称空间... DirectoryEntry和DirectorySearcher。
More info on the DirectorySearcher here:
有关DirectorySearcher的更多信息:
http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.aspx
You can then use the Filter property to filter by Group, user etc...
然后,您可以使用Filter属性按组,用户等进行筛选...
So if you wanted to filter by account name you would set the .Filter to:
因此,如果您想按帐户名称进行过滤,则可以将.Filter设置为:
"(&(sAMAccountName=bsmith))"
and run the FilterAll method. This will return a SearchResultCollection that you can loop through and pull information about the user.
并运行FilterAll方法。这将返回一个SearchResultCollection,您可以循环并提取有关该用户的信息。
#3
You need to build the search string based on how you're looking for the user.
您需要根据您查找用户的方式构建搜索字符串。
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 should either be sAMAccountName or CN depending on how the username is formatted.
userType应该是sAMAccountName或CN,具体取决于用户名的格式。
ex:
firstname.lastname (or flastname) will usually be the sAMAccountName
FirstName LastName will usually be the CN
例如:firstname.lastname(或flastname)通常是sAMAccountName FirstName LastName通常是CN
#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
Got this from the Joe Kaplan and Ethan Wilansky Article Use this Using (from referencing the System.DirectoryServices.AccountManagement dll):
来自Joe Kaplan和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;
}
#6
To add onto Miyagi's answer....
添加到Miyagi的答案....
Here's a filter/query to apply to DirectorySearcher
这是一个应用于DirectorySearcher的过滤器/查询
DirectorySearcher ds = new DirectorySearcher();
ds.Filter = "samaccountname=" + userName;
SearchResult result = ds.FindOne();
#7
The other answers were poorly described, didn't describe how to implement them, and most gave the wrong filter properties. You don't even need to use .Filter
-- you can just assign your properties (last name = .Surname
, first name = .GivenName
) to a UserPrincipal
object, then search on that object using a PrincipalSearcher
in whatever event that triggers the search:
其他答案描述不清楚,没有描述如何实现它们,并且大多数都给出了错误的过滤器属性。您甚至不需要使用.Filter - 您只需将属性(姓氏=。姓氏,名字= .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;
I'm assuming you have textboxes for First and Last Name to get it, with IDs/Names of txtFirstName
and txtLastName
. Note that if you do not have a value in the property you are looking for, do not add it to the UserPrincipal
, or it will cause an exception. That's the reason for the checks I included, above.
我假设你有第一个和最后一个名字的文本框来获取它,ID /名称为txtFirstName和txtLastName。请注意,如果您在要查找的属性中没有值,请不要将其添加到UserPrincipal,否则将导致异常。这就是我在上面进行检查的原因。
You then do a .FindAll
on srch
to get search results into a PrincipalSearchResult
collection of Principal
objects:
然后,您在srch上执行.FindAll,以将搜索结果转换为Principal对象的PrincipalSearchResult集合:
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.
}
}
}
}
Note that results won't be null even if its .Count()
is 0
, and why both checks are there.
请注意,即使其.Count()为0,结果也不会为null,并且为什么两个检查都存在。
You iterate using that foreach
to get the properties you need, and this answers the question of how to find a user in AD using C#, but note you can only get to a few properties using the Principal
object, and if I reached this question through Google (as I did), I would be very disheartened. If you find that's all you need - great, you're done! But in order to get the rest (and rest my own conscience), you have to dive down, and I'll describe how to do that.
您使用该foreach迭代以获取所需的属性,这回答了如何使用C#在AD中查找用户的问题,但请注意,您只能使用Principal对象获取一些属性,如果我通过谷歌(就像我一样),我会非常沮丧。如果您发现这就是您所需要的一切 - 那就太棒了!但是为了得到休息(并保持自己的良心),你必须潜入,我将描述如何做到这一点。
I found you can't just use that username
I put above, but you have to get the whole DOMAIN\doej
kind of name. This is how you do that. Instead, put this in that foreach
loop, above:
我发现你不能只使用我上面提到的那个用户名,但是你必须得到整个DOMAIN \ doej这样的名字。这就是你如何做到的。相反,把它放在foreach循环中,上面:
string userId = GetUserIdFromPrincipal(found);
and use this function:
并使用此功能:
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;
}
Once you have that, you can call this function:
完成后,您可以调用此函数:
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;
}
Note that the FormatPhoneNumber
function is for North American numbers. It will take a number it finds (##########
) and separate it into ###-###-####
.
请注意,FormatPhoneNumber函数适用于北美数字。它将找到它找到的数字(##########)并将其分成### - ### - ####。
You can then get the properties like this, back in that foreach
loop:
然后你可以在foreach循环中获得这样的属性:
string[] userProps = GetUserProperties(userId);
string office = userProps[8];
But, as a whole solution, you can even add these results into a DataRow
column, and return it as part of a DataTable
that you could then bind to a ListView
or GridView
. This is how I did it, sending in a List<string>
filled with the properties I needed:
但是,作为一个完整的解决方案,您甚至可以将这些结果添加到DataRow列中,并将其作为DataTable的一部分返回,然后您可以将其绑定到ListView或GridView。我就是这样做的,发送一个List
/// <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;
}
You would call this function like this:
您可以像这样调用此函数:
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);
The DataTable
will be filled with those columns, and a NAME
column as the first column, that will have the user's actual .DisplayName
from AD.
DataTable将填充这些列,并将NAME列填充为第一列,它将具有AD中用户的实际.DisplayName。
Note: You must reference System.DirectoryServices
and System.DirectoryServices.AccountManagement
, System.Text.RegularExpressions
, System.Data
to use all this.
注意:您必须引用System.DirectoryServices和System.DirectoryServices.AccountManagement,System.Text.RegularExpressions,System.Data才能使用所有这些。
HTH!
#8
The code I were looking for in this post was:
我在这篇文章中寻找的代码是:
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();
It seems that wildcard for any character is Asterisk (*). That's why:
似乎任何字符的通配符都是Asterisk(*)。这就是为什么:
user.GivenName = "*firstname*";
Read more in Microsoft documentation
阅读Microsoft文档中的更多内容