2008-10-10 4 views
11

J'essaie d'obtenir tous les rapports directs d'un utilisateur via Active Directory, de manière récursive. Donc, vu un utilisateur, je finirai avec une liste de tous les utilisateurs qui ont cette personne en tant que manager ou qui ont une personne en tant que manager qui a une personne en tant que manager ... qui a finalement l'utilisateur en entrée en tant que manager.Obtention de tous les rapports directs à partir d'Active Directory

Ma tentative actuelle est plutôt lent:

private static Collection<string> GetDirectReportsInternal(string userDN, out long elapsedTime) 
{ 
    Collection<string> result = new Collection<string>(); 
    Collection<string> reports = new Collection<string>(); 

    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 

    long allSubElapsed = 0; 
    string principalname = string.Empty; 

    using (DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}",userDN))) 
    { 
     using (DirectorySearcher ds = new DirectorySearcher(directoryEntry)) 
     { 
      ds.SearchScope = SearchScope.Subtree; 
      ds.PropertiesToLoad.Clear(); 
      ds.PropertiesToLoad.Add("directReports"); 
      ds.PropertiesToLoad.Add("userPrincipalName"); 
      ds.PageSize = 10; 
      ds.ServerPageTimeLimit = TimeSpan.FromSeconds(2); 
      SearchResult sr = ds.FindOne(); 
      if (sr != null) 
      { 
       principalname = (string)sr.Properties["userPrincipalName"][0]; 
       foreach (string s in sr.Properties["directReports"]) 
       { 
        reports.Add(s); 
       } 
      } 
     } 
    } 

    if (!string.IsNullOrEmpty(principalname)) 
    { 
     result.Add(principalname); 
    } 

    foreach (string s in reports) 
    { 
     long subElapsed = 0; 
     Collection<string> subResult = GetDirectReportsInternal(s, out subElapsed); 
     allSubElapsed += subElapsed; 

     foreach (string s2 in subResult) 
     { 
     result.Add(s2); 
     } 
    } 



    sw.Stop(); 
    elapsedTime = sw.ElapsedMilliseconds + allSubElapsed; 
    return result; 
} 

Essentiellement, cette fonction prend un nom distinctif en entrée (CN = Michael Stum, OU = test, DC = sous, DC = domain, DC = com) , et avec cela, l'appel à ds.FindOne() est lent.

J'ai trouvé qu'il est beaucoup plus rapide de rechercher le userPrincipalName. Mon problème: sr.Properties ["directReports"] est juste une liste de chaînes, et c'est le distinguishedName, qui semble lent à chercher.

Je me demande, existe-t-il un moyen rapide de convertir entre distinguishedName et userPrincipalName? Ou y a-t-il un moyen plus rapide de rechercher un utilisateur si je n'ai que le distinguishedName avec lequel travailler?

Modifier: Grâce à la réponse! La recherche dans Manager-Field a amélioré la fonction de 90 secondes à 4 secondes. Voici le nouveau et l'amélioration du code, ce qui est plus rapide et plus lisible (notez qu'il ya très probablement un bug dans la fonctionnalité elapsedTime, mais le véritable noyau de la fonction fonctionne):

private static Collection<string> GetDirectReportsInternal(string ldapBase, string userDN, out long elapsedTime) 
{ 
    Collection<string> result = new Collection<string>(); 

    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    string principalname = string.Empty; 

    using (DirectoryEntry directoryEntry = new DirectoryEntry(ldapBase)) 
    { 
     using (DirectorySearcher ds = new DirectorySearcher(directoryEntry)) 
     { 
      ds.SearchScope = SearchScope.Subtree; 
      ds.PropertiesToLoad.Clear(); 
      ds.PropertiesToLoad.Add("userPrincipalName"); 
      ds.PropertiesToLoad.Add("distinguishedName"); 
      ds.PageSize = 10; 
      ds.ServerPageTimeLimit = TimeSpan.FromSeconds(2); 
      ds.Filter = string.Format("(&(objectCategory=user)(manager={0}))",userDN); 

      using (SearchResultCollection src = ds.FindAll()) 
      { 
       Collection<string> tmp = null; 
       long subElapsed = 0; 
       foreach (SearchResult sr in src) 
       { 
        result.Add((string)sr.Properties["userPrincipalName"][0]); 
        tmp = GetDirectReportsInternal(ldapBase, (string)sr.Properties["distinguishedName"][0], out subElapsed); 
        foreach (string s in tmp) 
        { 
        result.Add(s); 
        } 
       } 
      } 
      } 
     } 
    sw.Stop(); 
    elapsedTime = sw.ElapsedMilliseconds; 
    return result; 
} 
+0

Vous pouvez obtenir une vitesse supplémentaire en supprimant DirectoryEntry et DirectorySearcher de la récursivité. Ils ne changent pas entre eux, n'est-ce pas? – Tomalak

+0

Plus maintenant. Ce que je n'ai pas dit: Je l'utilise dans un Sharepoint Environment où l'appel est enveloppé dans un appel SPSecurity.RunWithElevatedPrivileges, ce qui signifie que ref Parameters n'est pas possible, et je ne suis pas sûr que le passer comme un paramètre normal fonctionne (bizarre Sharepoint Security) –

+0

Je pense que ça devrait marcher. Les objets sont toujours passés comme ref, AFAIK. Voir: http://stackoverflow.com/questions/186891/why-use-ref-keyword-when-passing-an-object – Tomalak

Répondre

10

Tout d'abord, la mise Portée à "sous-arbre" est inutile lorsque vous avez déjà le DN que vous recherchez.

En outre, que diriez-vous de trouver tous les objets dont la propriété "manager" est la personne que vous recherchez, puis de les parcourir. Cela devrait généralement être plus rapide que l'inverse.

(&(objectCategory=user)(manager=<user-dn-here>)) 

EDIT: Voici est importante, mais n'a été mentionné dans les commentaires à cette réponse jusqu'à présent:

Lorsque la chaîne de filtre est construit comme indiqué ci-dessus, il y a le risque de casser avec des caractères valides pour un DN, mais qui ont une signification particulière dans un filtre. Ces must be escaped:

* as \2a 
( as \28 
) as \29 
\ as \5c 
NUL as \00 
/ as \2f 

// Arbitrary binary data can be represented using the same scheme. 

EDIT: Réglage du SearchRoot au DN d'un objet, et le SearchScope-Base est également un moyen rapide de tirer un seul objet sur AD.

+0

Merci. Je vais voir comment ça se passe sans Subtree. Pour votre deuxième suggestion, cela semble intéressant. J'ai besoin d'envelopper un peu mon cerveau car la fonction doit toujours être récursive, mais je vais le tester immédiatement. –

+1

Si je pouvais vous voter 10 fois, je le ferais. Chaging la fonction pour rechercher le gestionnaire l'a amélioré de courir 90 secondes à seulement 4 secondes maintenant. –

+0

Attention, avec cette approche, vous devez migrer le risque de casser votre chaîne de filtre avec des caractères valides dans un DN mais réservés dans une chaîne de filtre. Sur le dessus de ma tête, au moins «#» doit être échappé. – Tomalak

Questions connexes