Saturday 13 August 2011

Error 0x80072015 borrando objetos del directorio activo

Hola a todos,

Esta semana no he tenido mucho tiempo así que el post será pequeñito. Hace poco me encontré con un error un tanto extraño al borrar cuentas de máquina del directorio activo y quería hablar un poco sobre esto.

System.DirectoryServices

Para borrar elementos del directorio activo existen muchas maneras pero quizás la más sencilla desde .Net es utilizar las clases del espacio de nombres System.DirectoryServices. Este espacio de nombres está definido en la DLL homónima así que no hay que olvidar añadir la referencia correspondiente.

Bajo System.DirectoryServices nos encontramos una serie de clases que nos permiten realizar todo tipo de operaciones contra el directorio activo encapsulando el acceso a las interfaces ADSI que Windows expone mediante COM. La clase principal a utilizar es DirectoryEntry que representa un objeto del directorio activo ya sea un usuario, un ordenador o cualquier otro tipo de entrada. Si bien esta clase nos permite acceder a todas las propiedades y métodos, es muy genérica y no nos los proporciona de forma fuertemente tipada.

ActiveDS

Si deseáramos esto último podríamos conseguirlo añadiendo una referencia a la DLL ActiveDS que nos proporciona el acceso directo a las interfaces COM de cada tipo de objeto.

En concreto podemos encontrar las siguientes interfaces principales en este ensamblado que se enlazan directamente con objetos del directorio activo:

  • IADsComputer
  • IADsDomain
  • IADsGroup
  • IADsOU
  • IADsService
  • IADsUser

    A continuación muestro como enlazar un objeto DirectoryEntry con una interfaz IADSUser.

    ActiveDS.IADsUser user = de.NativeObject as ActiveDS.IADsUser; 



    En cualquier caso, es recomendable utilizar DirectoryEntry en lugar de acceder desde ActiveDS ya que nos proporciona un nivel de abstracción más sobre la plataforma. Pero nos estamos desviando del objetivo del post o "dispersando como mantequilla untada sobre demasiado pan" como diría Bilbo Bolson, así que vamos a centrarnos.

    Error 0x80072015


    Para eliminar un objeto primero debemos encontrarlo y para eso podemos utilizar DirectorySearcher, la otra clase más importante dentro de System.DirectoryServices. A continuación podéis ver un fragmento de código muy sencillo de como podríamos realizar la búsqueda y la eliminación.

       1:  DirectorySearcher adSearcher = new DirectorySearcher(); 
       2:  adSearcher.Filter = "(&(objectCategory=computer)(name=MyTestComputer*))"; 
       3:  SearchResult adResult = adSearcher.FindOne(); 
       4:  if (adResult != null) 
       5:  { 
       6:      DirectoryEntry entry = adResult.GetDirectoryEntry(); 
       7:      entry.Parent.Children.Remove(entry); 
       8:  }

    Pues bien, en este código hemos incurrido ya en el error, puesto que el método Remove únicamente funciona si el objeto que estamos eliminando no tiene ningún objeto hijo. En el caso de que sí que los tenga se lanzará una COMException con el mensaje "The directory service can perform the requested operation only on a leaf object. (Exception from HRESULT: 0x80072015)" .

    Este fue el error que cometí yo pensando que un objeto "computer" no sería contenedor y por lo tanto siempre sería un "leaf object". Sin embargo, a pesar de que no se puedan ver mediante la herramienta "Active Directory Users and Computers" (dsa.msc) o incluso desde la herramienta ADSI Edit (adsiedit.msc) salvo que realices una búsqueda específica, los objetos computer sí que pueden tener nodos hijos y de hecho, es bastante habitual. En mi caso concreto los elementos que encontré bajo un ordenador fueron del tipo Configuración de MSMQ (MSMQ-Configuration) y es que cuando instalamos el componente de servidor de MSMQ en un ordenador unido al dominio, el sistema operativo publica la información de las colas en la cuenta de la máquina en el directorio activo automáticamente.

    La solución es bastante sencilla, simplemente hemos de sustituir la línea:

       7:      entry.Parent.Children.Remove(entry); 



    por

       7:      entry.DeleteTree() 



    que automáticamente elimina todo el árbol, incluida la entrada desde la que se invoca.

    Claro que hemos de tener cuidado utilizando este peligroso método ya que si lo invocamos sobre el nodo incorrecto podemos causar verdaderos desastres. Por otra parte, existe un inconveniente adicional, el método DeleteTree solo funciona si utilizamos el proveedor LDAP, con lo que si utilizamos otro proveedor tendríamos que implementarnos nosotros mismos un método recursivo para eliminar todos los objetos bajo la entrada que queremos eliminar.






    1. public void DeleteChildren(DirectoryEntry entry)

    2. {

    3.     foreach (DirectoryEntry child in entry.Children)

    4.     {

    5.         DeleteChildren(child);

    6.     }

    7.     entry.CommitChanges();

    8.     entry.Parent.Children.Remove(entry);

    9. }





    Más info en:
    Message Queuing and Active Directory
    MSMQ > LDAP Distinguished Names of Directory Objects 
    ADSI Edit (adsiedit.msc)
    Active Directory Users and Computers
    IADS Reference
    Quitar nodos de Active Directory
    DirectoryEntries.Remove
    DirectoryEntry.DeleteTree