2008-10-10 10 views
11

Estoy tratando de obtener todos los informes directos de un Usuario a través de Active Directory, recursivamente. Por lo tanto, dado un usuario, terminaré con una lista de todos los usuarios que tienen a esta persona como gerente o que tienen una persona como gerente que tiene una persona como gerente ... que finalmente tiene el usuario de entrada como administrador.Obteniendo todos los Informes Directos desde el Directorio Activo

Mi intento actual es bastante lento:

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

En esencia, esta función toma un nombre distinguido como entrada (CN = Michael Stum, OU = prueba, DC = sub DC = dominio, DC = com) , y con eso, la llamada a ds.FindOne() es lenta.

Descubrí que es mucho más rápido buscar userPrincipalName. Mi problema: sr.Properties ["directReports"] es solo una lista de cadenas, y ese es el distinguishedName, que parece lento de buscar.

Me pregunto, ¿hay una manera rápida de convertir entre distinguishedName y userPrincipalName? ¿O hay una manera más rápida de buscar un usuario si solo tengo el distinguishedName para trabajar?

Editar: ¡Gracias por la respuesta! La búsqueda en el Manager-Field mejoró la función de 90 Segundos a 4 Segundos. Esta es la nueva y mejorada código, que es más rápido y más fácil de leer (tenga en cuenta que no es muy probable que un error en la funcionalidad elapsedTime, pero el núcleo real de la función trabaja):

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

Puede obtener velocidad adicional si toma DirectoryEntry y DirectorySearcher fuera de la recursión. No cambian en el medio, ¿verdad? – Tomalak

+0

ya no. Lo que no dije: estoy usando esto en un entorno Sharepoint donde la llamada está envuelta en una llamada SPSecurity.RunWithElevatedPrivileges, lo que significa que los parámetros ref no son posibles, y no estoy seguro si pasarlo como un parámetro normal funciona (raro Sharepoint Security) –

+0

Creo que debería funcionar. Los objetos siempre pasan como ref, AFAIK. Ver: http://stackoverflow.com/questions/186891/why-use-ref-keyword-when-passing-an-object – Tomalak

Respuesta

10

En primer lugar, el establecimiento de Alcance "subárbol" no es necesario cuando ya tienes el DN que estás buscando.

Además, ¿qué le parece encontrar todos los objetos cuya propiedad de "administrador" es la persona que busca, y luego iterarlos. En general, esto debería ser más rápido que al revés.

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

EDIT: La siguiente es importante, pero sólo se ha mencionado en los comentarios a esta respuesta hasta el momento:

Cuando la cadena de filtro se construye como se indicó anteriormente, existe el riesgo de romperla con caracteres que son válidos para un DN, pero que tienen un significado especial en un filtro. Estos 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: Ajuste de la SearchRoot al DN de un objeto, y la SearchScope a Base también es una forma rápida de tirar de un solo objeto de AD.

+0

Gracias. Veré cómo funciona sin Subtree. Para su segunda sugerencia, eso suena interesante. Necesito ajustar mi cerebro un poco ya que la función aún necesita ser recursiva, pero lo probaré de inmediato. –

+1

Si pudiera votar 10 veces, lo haría. Colocar la función para buscar el administrador mejoró la ejecución de 90 Segundos a solo 4 Segundos ahora. –

+0

Tenga en cuenta que con este enfoque, debe migrar el riesgo de romper la cadena de filtro con caracteres que son válidos en un DN pero reservados en una cadena de filtro. En la parte superior de mi cabeza, al menos '#' necesita ser escapado. – Tomalak

Cuestiones relacionadas