2008-10-10 22 views
11

Ich versuche, alle direkten Berichte eines Benutzers rekursiv über Active Directory abzurufen. Also gegeben ein Benutzer, ich werde mit einer Liste aller Benutzer enden, die diese Person als Manager haben oder die eine Person als Manager haben, der eine Person als Manager hat ... wer hat schließlich den eingegebenen Benutzer als Manager.Alle direkten Berichte aus Active Directory abrufen

Mein aktueller Versuch ist ziemlich langsam:

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

Im Wesentlichen diese Funktion nimmt einen Distinguished Name als Eingang (CN = Michael Stum, OU = Test, DC = sub, DC = Domain, DC = com) und damit ist der Aufruf von ds.FindOne() langsam.

Ich fand, dass es viel schneller ist, nach dem userPrincipalName zu suchen. Mein Problem: sr.Properties ["directReports"] ist nur eine Liste von Strings, und das ist der distinguishedName, der langsam zu suchen scheint.

Ich frage mich, gibt es eine schnelle Möglichkeit, zwischen DistinguishedName und UserPrincipalName zu konvertieren? Oder gibt es eine schnellere Möglichkeit, nach einem Benutzer zu suchen, wenn ich nur den distinguishedName zur Verfügung habe?

Bearbeiten: Dank der Antwort! Durch die Suche im Manager-Feld wurde die Funktion von 90 Sekunden auf 4 Sekunden verbessert. Hier ist die neue und verbesserte Code, die schneller und besser lesbar ist (beachten Sie, dass es wahrscheinlich ein Fehler in der elapsedTime Funktionalität, aber der eigentliche Kern der Funktion funktioniert):

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

Sie können zusätzliche Geschwindigkeit gewinnen, indem Sie DirectoryEntry und DirectorySearcher aus der Rekursion nehmen. Sie ändern sich nicht dazwischen, oder? – Tomalak

+0

Nicht mehr. Was ich nicht gesagt habe: Ich verwende dies in einer Sharepoint-Umgebung, in der der Aufruf in einen SPSecurity.RunWithElevatedPrivileges-Aufruf eingeschlossen ist, was bedeutet, dass ref-Parameter nicht möglich sind, und ich bin mir nicht sicher, ob die Übergabe als normaler Parameter funktioniert (komisch) Sharepoint Security) –

+0

Ich denke, es sollte funktionieren. Objekte werden immer als ref übergeben, AFAIK. Siehe: http://stackoverflow.com/questions/186891/why-use-ref-keyword-when-passing-an-object – Tomalak

Antwort

10

First off, Scope Einstellung zu "Teilbaum" ist unnötig, wenn Sie bereits die DN haben, nach der Sie suchen.

Sie können auch alle Objekte suchen, deren Eigenschaft "manager" die Person ist, nach der Sie suchen, und sie dann iterieren. Dies sollte im Allgemeinen schneller sein als umgekehrt.

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

EDIT: Das folgende ist wichtig, aber wurde nur so weit in den Kommentaren zu dieser Antwort erwähnt: es

Wenn die Filterzeichenfolge aufgebaut ist, wie oben angegeben, besteht die Gefahr des Brechens mit Zeichen, die für einen DN gültig sind, aber in einem Filter eine besondere Bedeutung haben. Diese 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: Einstellen der SearchRoot auf den DN eines Objekts, und die SearchScope-Base ist auch ein schneller Weg, um ein einzelnes Objekt aus AD zu ziehen.

+0

Danke. Ich werde sehen, wie es ohne Subtree funktioniert. Für Ihren zweiten Vorschlag klingt das interessant. Ich muss mein Gehirn ein wenig einwickeln, da die Funktion immer noch rekursiv sein muss, aber ich werde das sofort testen. –

+1

Wenn ich dich 10 mal abstimmen könnte, würde ich es tun. Durch die Suche nach dem Manager wurde die Funktion von 90 auf jetzt nur noch 4 Sekunden verbessert. –

+0

Beachten Sie, dass Sie bei diesem Ansatz das Risiko migrieren müssen, die Filterzeichenfolge mit Zeichen zu unterbrechen, die in einem DN gültig sind, aber in einer Filterzeichenfolge reserviert sind. Auf dem Kopf muss mindestens "#" entgangen sein. – Tomalak

Verwandte Themen