martes, abril 22, 2008

Usar vbscript para obtener información del directorio activo en Windows

Me he encontrado estos dos últimos días con un par de problemas de administración de máquinas situadas en un directorio activo. Los problemas que tenía que solucionar eran dos:
  • Obtener el dn (distinguised-named) que identifica a una máquina determinada dentro del directorio activo.
  • Obtener la raíz del directorio LDAP.
  • Poder hacer consultas directas al directorio activo usando LDAP y filtrando el contenido que interese.

Información básica de un usuario y máquina

Lo malo de trabajar en entornos de Microsoft, es que a veces, hay varias maneras de realizar lo misma operación, muchas veces, a través API distintas. Para realizar la primera operación recurro a ADSI y concretamente a la interfaz IADsADSystemInfo. Gracias a ella somos capaces de obtener una serie de información básica sobre el ordenador y el usuario que está actualmente conectado. Del ejemplo de Microsoft:
Set objSysInfo = CreateObject("ADSystemInfo")
Wscript.Echo "Usuario: " & objSysInfo.UserName
Wscript.Echo "Nombre de máquina: " & objSysInfo.ComputerName
Wscript.Echo "Sitio: " & objSysInfo.SiteName
Wscript.Echo "Nombre de dominio corto: " & objSysInfo.DomainShortName
Wscript.Echo "Nombre de DNS: " & objSysInfo.DomainDNSName
Wscript.Echo "Nombre de DNS del árbol: " & objSysInfo.ForestDNSName
Wscript.Echo "Máquina con el rol de PDC: " & objSysInfo.PDCRoleOwner
Wscript.Echo "Máquina con el rol de schema owner: " & objSysInfo.SchemaRoleOwner
Wscript.Echo "¿Está en modo nativo?: " & objSysInfo.IsNativeMode
set objSysInfo = nothing

La información más útil es concer los dn del usuario y nombre máquina, para poder usarlos en otras operaciones de ADSI (por ejemplo las contraseñas, averiguar cual es la unidad organizativa a la cual pertenece la máquina o el usuario)
Para que esta interfaz pueda funcionar correctamente, el usuario que ejecuta dicho script, debe de tener permisos de consulta en el directorio activo.

La raíz del directorio LDAP

En LDAP, rootDSE es la raíz del directorio y tiene una serie de información sobre el servidor de directorio. Entre otra información tenemos el distinguished name (dn) para el dominio del cual el servidor de directorio es miembro, y esta información es útil para realizar consultas al LDAP. Obtenerla es muy sencillo:
Set objRootDSE = GetObject("LDAP://RootDSE")
strDomain = objRootDSE.Get("DefaultNamingContext")
Si seguimos la documentación de los enlaces puede obtenerse de este contexto mucha más información como puede ser el tipo de controlador de dominio, el modo en el que se encuentra, el tiempo actual que tiene el controlador, si el controlador está sincronizado,...

Consultas LDAP

A la hora de realizar una query LDAP la herramienta más adecuada en Microsoft Windows es Microsoft ActiveX Data Objects, usando el proveedor de Microsoft OLE DB Provider for Microsoft Active Directory Service, permite realizar consultas al LDAP que implementa el directorio activo.
El lenguaje de consultas que permite este proveedor ADO puede ser bien un dialecto de SQL o bien un filtro LDAP:
Fitro LDAP
"<LDAP://DC=ArcadiaBay,DC=COM>;(objectClass=*);sn, givenName; subtree"
Filtro SQL
"SELECT title, telephoneNumber From 'LDAP://DC=Microsoft, DC=COM' WHERE objectClass='user' AND objectCategory='Person'"
Para crear una consulta contra el directorio activo, lo que hacemos es crear un objeto ADODB.Connection, que usará como proveedor ADsDSOObject, que es el proveedor para ADO que conecta con el directorio activo. Luego, crearemos un objeto ADODB.Command al cual asignaremos la conexión que hemos creado, estableceremos la consulta que deseemos, y la ejecutaremos, lo que nos devolverá un objeto ADODB.RecordSet que nos permitirá iterar por las distintas filas de datos que haya generado la consulta.
Para ver las cosas un poco más claras, un ejemplo que nos va a devolver todas las cuentas de máquinas que existan en el dominio de directorio activo al cual está conectado la máquina:
' Obtener cual es el dominio Set objRootDSE = GetObject("LDAP://RootDSE")
strDomain = objRootDSE.Get("DefaultNamingContext")
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
strCmdText =<"<LDAP://"&strDomain&">" _
+";(&(objectCategory=Computer)(objectClass=Computer));ADsPath,name,cn;Subtree"
'WScript.Echo strCmdText
objCommand.CommandText = strCmdText
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst
Do Until objRecordSet.EOF
    WScript.Echo objRecordSet.Fields("name").Value
    objRecordSet.MoveNext
Loop
El script anterior listaría todos las máquinas presentes en el directorio activo. La parte más interesante es la consulta que ejecuta el proveedor para obtener la información. Esta consulta tiene la forma:
Base de la búsqueda<LDAP://dc=dominio,dc=com>
Filtro LDAP(&(objectCategory=Computer)(objectClass=Computer)
Campos a devolverADsPath,name,cn
Ámbito de búsquedasubtree
En este ejemplo busqueramos desde dc=dominio,dc=com todos los objetos de la categoría computer y de la clase computer, devuelve los campos AdsPath,name y cn. El alcance de la consulta es subtree, busca en la rama donde está el objeto al cual no hemos conectados y recorremos todo el árbol de información hacia abajo.
Una cosa importante en el caso del directorio activo es tener en cuenta que por defecto, el AD está configurado para que devuelva como mucho 1000 objetos (ver el artículo KB 315071, concretamente el parámetro MaxPageSize).

Utilidad

Pues todo esto lo he tenido que buscar para ejecutar ciertas operaciones en función la unidad organizativa esté situada una máquina en el directorio activo, dado las condiciones de contorno del problema - hay una solución un poco más fácil a través d GPO, pero otros ejempos que se me ocurren son:
  • Modificar propiedades de un conjunto de usuarios o máquinas.
  • Ver desde cuando no utilizan la cuenta algunos usuarios.

¿Qué faltaría?

Referencias

No hay comentarios: