¡Hola, encargados del scripting!¿Quién es usted?

Los encargados del scripting

Descargar el código de este artículo: HeyScriptingGuy2007-082007_08.exe (152KB)

Hace poco, el encargado del scripting que escribe este artículo miraba un juego del béisbol en televisión. Cuando el juego terminó, decidió leer una revista pero dejó la televisión encendida. Cuando terminó de leer la revista, levantó la vista hacia la televisión y descubrió que estaba mirando una vieja película de la segunda guerra mundial. De hecho, levantó la vista justo en el momento que los actores estaban en medio de una de esas situaciones comprobadas y ciertas: un joven soldado norteamericano está de guardia a última hora de la noche cuando oye un ruido.

"¿Quién es usted?" ladró el centinela.

"Soy yo, el sargento Smith", contestó una voz.

"¿Sargento Smith? No hay ningún sargento Smith en esta unidad."

"Soy nuevo aquí; acabo de ser transferido desde la Compañía A."

"¿Ah, sí? De la Compañía A? Está bien, Smith: ¿Quién ganó la serie mundial de 1934?"

"The New York Yankees."

"¡Respuesta equivocada, Smith!" (Como se puede imaginar, fue detenido de inmediato y arrojado al bergantín). Cualquier buen estadounidense, uno de verdad, sabe que The St. Louis Cardinals, el equipo de Dizzy Dean y Gas House Gang, ganaron la serie mundial de 1934. Quien no lo sepa sólo puede ser una cosa: un espía.

Si quien lee el artículo de este mes no sabía que The St. Louis Cardinals ganaron la serie mundial de 1934, bueno, todo lo que podemos decir es lo siguiente: no hay esperanzas; sabemos que es un espía. Esperamos que tenga la amabilidad de dirigirse a la oficina del FBI más cercana. O que por lo menos nos llame; cuando se trata de espías, el FBI recoge y entrega.

Cuando el encargado del scripting que escribe esta columna miró, se dio cuenta que tenía buenas posibilidades de contestar la segunda pregunta del centinela: ¿Quién ganó la serie mundial de 1966? The Baltimore Orioles. ¿La serie mundial de 1960? Pittsburgh Pirates. ¿La serie mundial de 1994? ¡Ah! Una pregunta con trampa: no hubo serie mundial en 1994.

Sin embargo, hoy en día y en esta época, tendría dificultades para contestar la primera pregunta: ¿quién es usted? Esa no es hoy una pregunta tan fácil como quizás lo era allá en los años cuarenta. Después de todo, sólo en Active Directory® los usuarios pueden tener una gran cantidad de identidades diferentes, que incluyen:

  • Su nombre (givenName).
  • Su apellido (sn).
  • Su nombre para mostrar (displayName).
  • Su nombre principal de usuario (userPrincipalName).
  • Su nombre de inicio de sesión (samAccountName).
  • Su nombre distintivo (distinguishedName).

Todos estos nombres identifican a la misma persona, y todos son nombres que, según las circunstancias, es importante conocer. Y eso es un problema. La mayoría de los usuarios conocen su nombre y apellido. Pero, si le pregunta a un usuario: "¿Cuál es su nombre distintivo?" sólo algunos pocos podrán contestar, "Bueno, ¡eso es sencillo! Soy CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com. Pero todos mis amigos me llaman CN=Ken.Myer".

Como administrador del sistema o como persona de soporte técnico, también necesita conocer estos nombres. Pero, ¿cómo se supone que obtiene esta información? Bueno, un método sería torturar a sus usuarios hasta que finalmente le digan su nombre distintivo. Probablemente esto funcionaría, pero la mayoría de los departamentos de recursos humanos desalientan hoy día ese tipo de cosas. Así que quizás tenga que recurrir al plan B: un script. ¿Qué clase de script? Bueno, para comenzar, ¿qué tal el script de la figura 1?

Figure 1 Recuperación de atributos de usuario con ADSystemInfo

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName

Set objUser = GetObject("LDAP://" & strUser)
WScript.Echo "First Name: " & objUser.givenName
WScript.Echo "Last Name: " & objUser.sn
WScript.Echo "Display Name: " & objUser.displayName
WScript.Echo "User Principal Name: " & objUser.userPrincipalName
WScript.Echo "SAM Account Name: " & objUser.sAMAccountName
WScript.Echo "Distinguished Name: " & objUser.distinguishedName

Vaya, ¿y si sólo el sargento Smith habría estudiado VBScript, eh? Este script aprovecha un objeto ADSI poco conocido (pero muy útil) denominado ADSystemInfo. Se trata de un pequeño objeto ingenioso que puede devolver todo tipo de información sobre el usuario que tiene una sesión iniciada en el equipo local, así como sobre el propio equipo local y el dominio al que pertenece. Por ejemplo, eche un vistazo a la figura 2.

Figure 2 Muestre todo tipo de información de dominio

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")

Wscript.Echo "User name: " & objSysInfo.UserName
Wscript.Echo "Computer name: " & objSysInfo.ComputerName
Wscript.Echo "Site name: " & objSysInfo.SiteName
Wscript.Echo "Domain short name: " & objSysInfo.DomainShortName
Wscript.Echo "Domain DNS name: " & objSysInfo.DomainDNSName
Wscript.Echo "Forest DNS name: " & objSysInfo.ForestDNSName
Wscript.Echo "PDC role owner: " & objSysInfo.PDCRoleOwner
Wscript.Echo "Schema role owner: " & objSysInfo.SchemaRoleOwner
Wscript.Echo "Domain is in native mode: " & objSysInfo.IsNativeMode

No obstante, por hoy, la única propiedad que nos interesa es la propiedad UserName. ¿Qué tiene de especial UserName? Bueno, resulta que corresponde a la propiedad distinguishedName. ¿Y qué tiene de especial distinguishedName? Bueno, distinguishedName es similar a una ruta de acceso de archivo UNC: Al igual que una ruta de acceso UNC nos permite identificar exclusivamente un archivo en alguna parte de nuestra red, distinguishedName (por ejemplo, CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com) nos permite identificar exclusivamente una cuenta de usuario en Active Directory. A su vez, nos permite enlazar esa cuenta de usuario. Y una vez que hemos hecho esa conexión, podemos recuperar cualquier información que deseamos acerca de un usuario, incluso quién es.

Y es exactamente eso lo que hacemos en el primer script que le mostramos. Para empezar, creamos una instancia del objeto ADSystemInfo y le asignamos el valor de la propiedad UserName a una variable denominada strUser:

Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName

Observe que aquí no tenemos mucho para hacer. Por ejemplo, no tenemos que especificar el nombre de usuario, dominio de usuario, ni la unidad organizativa en la que reside la cuenta de usuario; ADSystemInfo hace todo eso por nosotros. Una vez que tenemos el distinguishedName del usuario, lo podemos enlazar a su cuenta de usuario por medio de esta línea de código:

Set objUser = GetObject("LDAP://" & strUser)

Y, nuevamente, una vez que realizamos la conexión podemos devolver los valores de cualquiera de los atributos de Active Directory de esa cuenta. En nuestro script de ejemplo, simplemente hemos devuelto algunas propiedades del nombre del usuario, pero también podríamos haber recuperado el número de teléfono, la ubicación de la oficina, la dirección de correo electrónico, etc.

Eso es fantástico. Todo lo que tiene que hacer es repartir este script a todos sus usuarios y nunca más tendrán que preguntarse: "¿Quién soy"? (O, si lo hacen, tendrán una manera rápida y fácil de resolverlo). Pero qué sucede con una pregunta relacionada: ¿quién es usted? Una cosa es poder identificar el usuario que ha iniciado sesión en equipo local. ¿Pero cómo puede determinar quién ha iniciado sesión en un equipo remoto? Como se puede imaginar, ese es el hueso más duro de roer.

Y, disculpe, también lo pensamos. Lamentablemente no puede apuntar nuestro script de ADSystemInfo a un equipo remoto. La razón por la que no puede es porque el objeto ADSystemInfo sólo se puede crear de forma local. Eso nos deja tres posibilidades:

  • Crear un script de inicio de sesión que registre el nombre del usuario que ha iniciado sesión en alguna ubicación de fácil acceso.
  • Usar la clase Win32_ComputerSystem de WMI y la propiedad UserName.
  • Hacer otra cosa.

La primera opción suena muy atrayente. Como se habrá dado cuenta, el objeto ADSystemInfo puede devolver el nombre distintivo del equipo (la propiedad ComputerName) así como el nombre distintivo del usuario. Considere, si le parece, el script de ejemplo que se muestra en la figura 3.

Figure 3 Script de inicio de sesión

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")

strUser = objSysInfo.UserName
strComputer = objSysInfo.ComputerName

Set objUser = GetObject("LDAP://" & _
    strUser)
strUserName = objUser.displayName

Set objComputer = GetObject("LDAP://" & _
    strComputer)
objComputer.Description = strUserName
objComputer.SetInfo

¿Qué hacemos aquí? Para comenzar, tomamos los valores de las propiedades UserName y ComputerName y los almacenamos en un par de variables (strUser y strComputer). A continuación, los enlazamos a la cuenta de usuario en Active Directory (igual que antes), recuperamos el valor del atributo displayName (igual que antes) y almacenamos ese valor en una variable denominada strUserName. ¿Bastante sencillo, no?

A continuación, podemos usar esta línea de código para realizar la conexión a la cuenta de equipo de Active Directory:

Set objComputer = GetObject("LDAP://" & _
    strComputer)

Una vez establecida esa conexión, asignamos el displayName del usuario a la propiedad Description del equipo y, a continuación, llamamos al método SetInfo para escribir ese cambio en Active Directory:

objComputer.Description = strUserName
objComputer.SetInfo

¿Por qué hacemos esto? Muy fácil. Suponga que Ken Myer se conecta al equipo atl-ws-01. ¿Adivina cuál será el valor de la propiedad Description del equipo atl-ws-01? Exacto: Ken Myer, la misma persona que tiene una sesión iniciada en el equipo. ¿Desea saber quién tiene una sesión iniciada en atl-ws-01? Sólo compruebe la propiedad Description.

Ahora, en general, este escenario funciona bastante bien y funciona aún mejor si dispone de un script de cierre de sesión que desactiva la propiedad Description cada vez que el usuario cierre sesión. Sin embargo, no es una solución infalible. ¿Por qué? Bueno, los scripts de inicio de sesión no siempre se ejecutan. Por ejemplo, normalmente no se ejecutan cuando los usuarios inician sesión mediante RAS. De forma similar, un script de inicio de sesión no se ejecuta si un usuario desconecta su conexión de red, inicia sesión con credenciales almacenadas en la memoria caché y luego vuelve a conectar el equipo a la red. Y suponga que un usuario apaga su equipo sin cerrar sesión. Eso significa que su script de cierre de sesión nunca se ejecutará. En ese caso, Ken Myer supuestamente todavía tendrá una sesión iniciada en atl-ws-01 aunque el equipo ni siquiera está en funcionamiento. Es decir, es una técnica de gran utilidad, pero...

Entonces, ¿qué le parece la opción 2, usar la clase Win32_ComputerSystem? Nuevamente, a menudo funcionará, pero el problema con la clase Win32_ComputerSystem es que no siempre devuelve el nombre del usuario que tiene una sesión iniciada, especialmente para los usuarios que no tienen derechos de administrador (y especialmente, para equipos con Windows® 2000). El script en la figura 4 probablemente le dirá quién tiene una sesión iniciada en un equipo pero, nuevamente, sin ninguna garantía.

Figure 4 Averigüe con WMI quién tiene una sesión iniciada

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * From Win32_ComputerSystem")

For Each objItem in colItems
  Wscript.Echo objItem.UserName
Next

A propósito, en este caso la propiedad UserName se obtiene con el formato dominio\nombre de usuario. En otras palabras: FABRIKAM\kenmyer.

Ah, casi nos olvidamos: incluso si se obtiene un nombre, eso no significa que un usuario tenga realmente una sesión iniciada en el equipo. Cuando Ken Myer sale del sistema atl-ws-01, su nombre se retiene como valor de la propiedad UserName y no se reemplaza hasta que alguien inicia nuevamente una sesión.

Vaya.

Pero espere; eso no nos convierte en espías. Este es otro método que podemos aplicar. Si alguien tiene una sesión iniciada en el equipo, es muy probable que el proceso Explorer.exe se esté ejecutando. En general, si Explorer.exe no se ejecuta, significa que nadie tiene una sesión iniciada en el equipo. Y dado que Explorer.exe se ejecuta bajo las credenciales del usuario que tiene una sesión iniciada, casi siempre podemos determinar quién ha iniciado sesión en un equipo por medio de un script similar al que se muestra en la figura 5.

Figure 5 Determinación del propietario de Explorer.exe

strComputer = "atl-ws-01"

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where Name = 'explorer.exe'")

If colItems.Count = 0 Then
  Wscript.Echo "No one is logged on to the computer."
Else
  For Each objProcess in colItems
    objProcess.GetOwner strUser, strDomain
    Wscript.Echo strDomain & "\" & strUser
  Next
End If

Como puede ver, en este caso realizamos una conexión al servicio WMI en un equipo remoto (atl-ws-01, para ser precisos). A continuación, usamos esta línea de código para recuperar una colección de todas las instancias de la clase Win32_Process con el nombre Explorer.exe:

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where " & _
  "Name = 'explorer.exe'")

Y ahora, ¿qué? Bien, como mencionamos hace un instante, si Explorer.exe no se ejecuta, es muy probable de que nadie tenga una sesión iniciada en el equipo. ¿Cómo sabemos si Explorer.exe se encuentra en ejecución? Una manera muy sencilla es comprobar el valor de la propiedad Count de la colección. Si Count es igual a 0, tenemos una colección vacía y la única manera que podemos tener una colección vacía es si no hay sesiones de Explorer.exe en ejecución en atl-ws-01. En este caso, devolvemos un mensaje que dice que nadie ha iniciado sesión en el equipo:

Wscript.Echo "No one is logged on " & _
"to the computer."

Si Count no es igual a 0, establecemos un bucle For Each para avanzar por pasos por la colección de procesos con el nombre Explorer.exe (y sí, suponemos que nuestra colección tendrá invariablemente sólo un elemento). En cada sesión de Explorer.exe llamamos al método GetOwner para determinar de quién es la cuenta con la que se ejecuta Explorer.exe:

objProcess.GetOwner strUser, strDomain

Observe que pasamos a GetOwner un par de parámetros de salida: strUser y strDomain. Los parámetros de salida simplemente son variables a las que asignamos un nombre y suministramos a un método; el método asignará valores a esos parámetros de salida. En este caso, a strUser se asignará el nombre de inicio de sesión del usuario conectado (kenmyer) y a strDomain se asignará el nombre de dominio del usuario conectado (FABRIKAM). Todo lo que debemos hacer es devolver los valores de estos dos parámetros de salida:

Wscript.Echo strDomain & "\" & strUser

¿Sabe qué? Eso es bastante bueno. Pero podemos dar un paso más. Cuando usamos el método GetOwner, obtenemos el nombre de inicio de sesión (samAccountName) para el usuario que tiene una sesión iniciada en el equipo. Eso es bueno, pero como mencionamos antes, los usuarios tienen todo tipo de nombres además de samAccountName. Para responder sinceramente a la pregunta "¿Quién es usted?" sería útil conocer, digamos, el displayName del usuario. Pero ¿podemos determinar estos otros nombres usando GetOwner?

No, no podemos. Sin embargo, podemos tomar samAccountName, insertarlo en un script de búsqueda de Active Directory y, a continuación, ubicar la cuenta de usuario con ese nombre de inicio de sesión y enlazarla (una tarea que resulta más fácil por el hecho que samAccountName debe ser exclusivo en un dominio). Y una vez que enlazamos la cuenta de usuario, podemos devolver los valores de cualquier propiedad de Active Directory, incluido displayName.

No tenemos tiempo para explicar el script de la figura 6 con todo detalle. Si desea obtener más información acerca de cómo realizar búsquedas en Active Directory, eche un vistazo a "Amigo, ¿dónde está mi impresora?". Es suficiente decir que este script determina el nombre de inicio de sesión del usuario conectado, busca en Active Directory el usuario con ese nombre de inicio de sesión (samAccountName), enlaza la cuenta de usuario en cuestión y, a continuación, devuelve el displayName del usuario. ¡Y todo con una mano atada detrás de su espalda!

Figure 6 Enlace al usuario que ha iniciado la sesión

strComputer = "atl-ws-01"

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where Name = 'explorer.exe'")

If colItems.Count = 0 Then
  Wscript.Echo "No one is logged on to the computer."
Else
  For Each objProcess in colItems
    objProcess.GetOwner strUser,strDomain
  Next
End If

Const ADS_SCOPE_SUBTREE = 2

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
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = "SELECT displayName FROM " & _
  "'LDAP://DC=wingroup,DC=fabrikam,DC=com' WHERE " & _
    "objectCategory='user' " & _
    "AND samAccountName = '" & strUser & "'"
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst

Do Until objRecordSet.EOF
  Wscript.Echo objRecordSet.Fields("displayName").Value
  objRecordSet.MoveNext
Loop

¿Así que cuáles son los elementos clave que debemos retener del artículo de hoy? (A propósito, ese es el modo en que habla la gente de Microsoft. Si quiere volver totalmente loco al encargado del scripting, simplemente dígale algo como: "Necesitamos seleccionar los elementos clave y de acción, así como los objetivos obsoletos para todos los interesados"). Bueno, en primer lugar, ahora sabemos cómo obtener información acerca del usuario que ha iniciado sesión en un equipo (tanto en el equipo local o como en uno remoto). Más importante, sabemos que si alguna vez nos damos un salto en el tiempo y nos encontramos en la segunda guerra mundial: hay que tener una copia de este artículo (para que pueda contestar la pregunta "¿Quién es usted?"), y haga lo que haga, siempre lleve una lista de los ganadores de la serie mundial de béisbol. Después de todo, nunca sabe cuándo alguien le va a preguntar, "¿Quién ganó la serie mundial de 1903?".

Nota: The Boston Red Sox. A propósito, esta fue la primera de todas las series mundiales. Ahora, ¿quién se supone que ganó la serie mundial de 1904? Qué quiere decir, ¿no lo sabe? Discúlpenos un segundo; necesitamos realizar una llamada telefónica...

Los encargados del scripting trabajan para Microsoft, mejor dicho, están contratados por Microsoft. Cuando no juegan al béisbol, ni entrenan ni lo ven (u otras actividades varias), dirigen el TechNet Script Center. Visite la página web www.scriptingguys.com.

© 2008 Microsoft Corporation and CMP Media, LLC. Reservados todos los derechos; queda prohibida la reproducción parcial o total sin previa autorización.