J'essaie de comprendre comment rechercher AD dans C # de la même manière que "Rechercher des utilisateurs, des contacts et des groupes" dans l'outil Utilisateurs et ordinateurs Active Directory. J'ai une chaîne qui contient soit un nom de groupe, soit un nom d'utilisateur (généralement au format prenom middleinitial [s'ils en ont un] nom, mais pas toujours). Même si je fais une requête séparée pour les groupes par rapport aux utilisateurs, je ne peux pas trouver un moyen de recherche qui capture la plupart des comptes d'utilisateurs. L'outil Rechercher des utilisateurs, des contacts et des groupes les ramène presque à chaque fois. Quelqu'un a des suggestions?
Je sais déjà comment utiliser la classe DirectorySearcher, le problème est que je ne peux pas trouver une requête qui fasse ce que je voudrais. Ni cn, ni samaccount name n’ont rien à voir avec le nom de l’utilisateur, je ne peux donc pas les rechercher. Le fractionnement et la recherche sur sn et givenName n'attrapent pas autant que cet outil.
Êtes-vous sur .NET 3.5? Si tel est le cas - AD présente de grandes nouvelles fonctionnalités dans .NET 3.5 - consultez cet article Gestion des principaux de sécurité des annuaires dans .NET 3.5 par Ethan Wilanski et Joe Kaplan.
L'une des principales nouvelles fonctionnalités est la classe "PrincipalSearcher", qui devrait grandement simplifier la recherche d'utilisateurs et/ou de groupes dans AD.
Si vous ne pouvez pas utiliser .NET 3.5, une des choses qui pourrait vous faciliter la vie s'appelle "Résolution de nom ambiguë". Il s'agit d'un filtre de recherche spécial peu connu qui permet de rechercher simultanément tout attribut lié au nom.
Spécifiez votre requête de recherche LDAP comme ceci:
searcher.Filter = string.Format("(&(objectCategory=person)(anr={0}))", yourSearchTerm)
Aussi, je recommanderais de filtrer l'attribut "objectCategory", puisqu'il s'agit d'une valeur unique et indexée par défaut dans AD, ce qui est beaucoup plus rapide que d'utiliser "objectClass".
Marc
System.DirectoryServices a deux espaces de noms ... DirectoryEntry et DirectorySearcher.
Plus d'infos sur le DirectorySearcher ici:
http://msdn.Microsoft.com/en-us/library/system.directoryservices.directorysearcher.aspx
Vous pouvez ensuite utiliser la propriété Filter pour filtrer par groupe, utilisateur, etc.
Donc, si vous voulez filtrer par nom de compte, définissez le filtre sur:
"(&(sAMAccountName=bsmith))"
et exécutez la méthode FilterAll. Cela retournera une SearchResultCollection que vous pourrez parcourir en boucle et extraire des informations sur l'utilisateur.
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;
}
Vous devez créer la chaîne de recherche en fonction de la manière dont vous recherchez l'utilisateur.
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 doit être soit sAMAccountName, soit CN, en fonction du format du nom d'utilisateur.
ex:
prenom.nom (ou flastname) sera généralement sAMAccountName
Prénom Nom est généralement le CN
Obtenu par la Joe Kaplan et Ethan Wilansky Article Utilisez cette utilisation (en faisant référence à la DLL System.DirectoryServices.AccountManagement):
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;
}
Les autres réponses étaient mal décrites, ne décrivaient pas comment les mettre en œuvre et la plupart donnaient les mauvaises propriétés de filtrage. Vous n'avez même pas besoin d'utiliser .Filter
- vous pouvez simplement affecter vos propriétés (nom = .Surname
, prénom = .GivenName
) à un objet UserPrincipal
, puis effectuer une recherche sur cet objet à l'aide de PrincipalSearcher
dans l'événement qui déclenche la recherche:
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;
Je suppose que vous avez les zones de texte pour le prénom et le nom pour l'obtenir, avec les identifiants/noms de txtFirstName
et txtLastName
. Notez que si vous n'avez pas de valeur dans la propriété que vous recherchez, ne l'ajoutez pas à la variable UserPrincipal
, sinon cela provoquera une exception. C'est la raison des chèques que j'ai inclus, ci-dessus.
Vous faites ensuite un .FindAll
sur srch
pour obtenir les résultats de la recherche dans une collection PrincipalSearchResult
d'objets 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.
}
}
}
}
Notez que les résultats ne seront pas nuls même si sa .Count()
est 0
, et pourquoi les deux vérifications sont là.
Vous effectuez une itération à l'aide de cette foreach
pour obtenir les propriétés dont vous avez besoin, ce qui répond à la question de savoir comment trouver un utilisateur dans AD en utilisant C #, mais notez que vous ne pouvez accéder qu'à quelques propriétés en utilisant l'objet Principal
. Google (comme je l'ai fait), je serais très découragé. Si vous trouvez que c'est tout ce dont vous avez besoin, c'est parfait! Mais pour obtenir le reste (et reposer ma propre conscience), vous devez plonger, et je vais vous décrire comment faire.
J'ai découvert que vous ne pouvez pas simplement utiliser la variable username
que j'ai indiquée ci-dessus, mais vous devez obtenir le type complet de nom DOMAIN\doej
. Voici comment tu fais ça. Au lieu de cela, mettez ceci dans cette boucle foreach
ci-dessus:
string userId = GetUserIdFromPrincipal(found);
et utiliser cette fonction:
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;
}
Une fois que vous avez cela, vous pouvez appeler cette fonction:
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;
}
Notez que la fonction FormatPhoneNumber
concerne les nombres nord-américains. Il faudra un nombre trouvé (##########
) et le séparer en ###-###-####
.
Vous pouvez alors obtenir les propriétés comme ceci, de retour dans cette boucle foreach
:
string[] userProps = GetUserProperties(userId);
string office = userProps[8];
Mais, en tant que solution complète, vous pouvez même ajouter ces résultats dans une colonne DataRow
et les renvoyer dans le cadre d'une DataTable
que vous pourrez ensuite lier à une ListView
ou GridView
. Voici comment je l'ai fait, en envoyant un List<string>
contenant les propriétés dont j'avais besoin:
/// <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;
}
Vous appelleriez cette fonction comme ceci:
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);
La DataTable
sera remplie avec ces colonnes et une colonne NAME
en tant que première colonne, qui contiendra le .DisplayName
actuel de AD.
Remarque: Vous devez faire référence à System.DirectoryServices
et System.DirectoryServices.AccountManagement
, System.Text.RegularExpressions
, System.Data
pour pouvoir utiliser tout cela.
HTH!
Pour ajouter à la réponse de Miyagi ...
Voici un filtre/requête à appliquer à DirectorySearcher
DirectorySearcher ds = new DirectorySearcher();
ds.Filter = "samaccountname=" + userName;
SearchResult result = ds.FindOne();
Le code que je cherchais dans ce post était:
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();
Il semble que le caractère générique de n'importe quel caractère soit un astérisque (*). C'est pourquoi:
user.GivenName = "*firstname*";
En savoir plus dans Documentation Microsoft