¡Hola, encargados del scripting!El retorno de WinRM

Los encargados del scripting

Cuando los encargados del scripting se embarcaron en crear una serie de dos entregas acerca de la administración remota de Windows® (WinRM), un problema importante pasó inmediatamente al primer plano: la seguridad. A fin de cuentas, nosotros, los encargados del scripting, estábamos bien enterados de los problemas que acompañaron el lanzamiento de la última entrega de la serie Harry Potter: se enviaron por error copias de los libros pedidas con antelación antes de la fecha oficial de lanzamiento del libro. (¿Eso resultó ser un problema? No, claro que no; la editorial simplemente pidió a la gente que no leyera el libro hasta la fecha del lanzamiento.)

Y eso sólo es el principio. El final del libro y de la serie se filtró totalmente antes de que los libros estuvieran disponibles. (Si todavía no ha leído el libro, esto es lo que sucede: resulta que Harry Potter es un mago o algo así.) Asimismo, de inmediato hubo copias digitalizadas de todas las páginas disponibles en Internet, a veces antes de que la autora J. K. Rowling ni tan siquiera las hubiera escrito. En resumen, fue un desastre de seguridad, y los encargados del scripting estaban decididos a no permitir que eso les sucediera a ellos. A fin de cuentas, si la gente tuvo tanto empeño en filtrar el final de la serie de Harry Potter, imagínese lo que no haría por tener acceso al final de la serie en dos partes de los encargados del scripting acerca de WinRM.

Afortunadamente, los encargados del scripting pudieron tomar medidas drásticas y guardar sus secretos, mejor dicho, el secreto. De acuerdo, esto fue principalmente debido al hecho de que no escribieron la segunda parte de esta serie hasta bastante después de la fecha tope para entregar el artículo. Pero qué le vamos a hacer, el encargado del scripting que escribe esta columna estaba de vacaciones durante el mes de agosto y, a diferencia de un porcentaje sorprendentemente elevado de empleados de Microsoft, él ni tan siquiera mira un equipo mientras está de vacaciones, así que mucho menos usarlo.

Bueno, digamos que rara vez mira (ni usa) el equipo incluso cuando no está de vacaciones. Pero ésa es otra historia.

En todo caso, sabemos que muchos de ustedes no han podido dormir durante las últimas cuatro semanas, agitándose, dando vueltas inquietos y preocupándose por cómo acabará la saga de WinRM. Bien, la buena noticia es que esas semanas dolorosamente largas de expectativas insomnes han pasado. He aquí, como primicia, la emocionante conclusión de la serie en dos partes de los encargados del scripting acerca de WinRM. Bueno, en realidad, está todo ahí, en la figura 1.

Figure 1 El desenlace

strComputer = "atl-fs-01.fabrikam.com"

Set objWRM = CreateObject("WSMan.Automation")
Set objSession = objWRM.CreateSession("http://" & strComputer)

strDialect = "https://schemas.microsoft.com/wbem/wsman/1/WQL"
strResource = "https://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/*"
strFilter = "Select Name, DisplayName From Win32_Service Where State = 'Running'"

Set objResponse = objSession.Enumerate(strResource, strFilter, strDialect)

Do Until objResponse.AtEndOfStream
    strXML = objResponse.ReadItem

    Set objXMLDoc = CreateObject("Microsoft.XMLDom")
    objXMLDoc.async=False
    objXMLDoc.loadXML(strXML)

    Set objElement = objXMLDoc.documentElement
    Wscript.Echo "Name: " & objElement.ChildNodes(0).ChildNodes(0).NodeValue 
    Wscript.Echo "Display Name: " &  objElement.ChildNodes(1).ChildNodes(0).NodeValue 
    Wscript.Echo
Loop

Sí, lo sabemos: el espectacular e inesperado giro de la trama también nos ha provocado a nosotros escalofríos que suben y bajan por nuestras espinas dorsales. objElement.ChildNodes(0).ChildNodes(0).NodeValue! ¿Quién lo iba a imaginar? Ya está: eso es porque, como parte de nuestro bloqueo de seguridad, incluso los propios encargados del scripting no tenían la menor idea de cómo terminaría la serie. Pero ahora el secreto ya se conoce.

Antes de continuar, vamos a recapitular rápidamente la serie en su totalidad, por si acaso hubiera alguien por ahí que inexplicablemente no haya leído la parte 1 (que se encuentra disponible en technetmagazine.com/issues/2007/11/HeyScriptingGuy). En la parte 1, presentamos WinRM, una tecnología nueva, que se encuentra en Windows Server® 2003 R2, Windows Vista ® y Windows Server 2008, y facilita sobremanera la administración de equipos por Internet, incluso a través de firewalls. Hay que reconocer que el Instrumental de administración de Windows (WMI) siempre ha tenido la capacidad de administrar equipos de forma remota; sin embargo, WMI se basa en COM distribuido (DCOM) como tecnología de administración remota. Eso es perfecto, sólo que, de forma predeterminada, muchos firewalls bloquean el tráfico DCOM. También es cierto, puede abrir los puertos adecuados y permitir el tráfico DCOM, pero muchos administradores de red se muestran reacios a hacerlo; su mayor preocupación es que, al abrir la puerta a DCOM también se abrirá la puerta a todo tipo de travesuras malintencionadas.

De ahí WinRM, "la implementación de Microsoft del protocolo WS-Management, un protocolo estándar basado en SOAP y compatible con firewalls que permite la interoperación de hardware y sistemas operativos de distintos fabricantes". Lo que es una manera extravagante de decir que ahora puede llevar a cabo la administración remota mediante protocolos estándar de Internet, como HTTP y HTTPS.

Como observamos el mes pasado, WinRM facilita la conexión y la recuperación de información WMI de equipos remotos. ¿Esto significa entonces que es una tecnología absolutamente perfecta? Bueno, no, no exactamente. Como señalamos, cuando WinRM devuelve los datos al script de llamada, esos datos regresan en formato XML. Obviamente XML puede ser un poco complicado de analizar y usar, sobre todo para administradores del sistema con una experiencia limitada en ese ámbito. Por ello, WinRM se entrega con una transformación XSL que convierte los datos devueltos a un formato más legible.

Sería fantástico, si no significara también que su salida siempre tendrá un aspecto parecido a éste:

Win32_Service
    AcceptPause = false
    AcceptStop = true
    Caption = User Profile Service
    CheckPoint = 0
    CreationClassName = Win32_Service

Eso no es necesariamente algo malo, si no fuera porque también significa que su salida siempre se escribirá en la ventana de comandos; de forma predeterminada, no puede guardar fácilmente datos en un archivo de texto, escribir esos datos en una base de datos u hoja de cálculo de Microsoft® Excel®, ni hacer mucho más con los datos que no sea mostrar la información en pantalla. Eso no es lo bastante bueno.

Encima, el problema se agrava si opta por devolver únicamente un número seleccionado de propiedades para una clase WMI (algo que puede hacer para ayudar a reducir el tráfico de red). Cuando se trabaja con solo unas pocas propiedades de una clase (en contraposición a todas las propiedades de una clase), se obtiene una salida parecida a ésta:

XmlFragment
    DisplayName = Windows Event Log
    Name = EventLog

XmlFragment
    DisplayName = COM+ Event System
    Name = EventSystem

Interesante, pero en lo estético no se trata de la presentación de información más agradable que se haya visto (especialmente el encabezado XmlFragment que aparece en todo el informe).

¿Y qué se puede hacer con esto? ¿Escribir código personalizado para analizar y dar formato a esos datos XML? No parece algo a lo que suela dedicarse un creador de scripts de administración del sistema. ¿O sí?

Si quiere algo bien hecho, hágalo usted mismo

Resulta que trabajar con los datos XML sin formato devueltos por WinRM no es ni por asomo una tarea tan desalentadora como pueda parecer. Este mes le mostraremos una manera sencilla de analizar datos XML y darles formato. El enfoque que usaremos no es ni mucho menos la única manera de trabajar con XML, pero eso está bien; nuestro objetivo principal es simplemente demostrar que no tiene que depender de la transformación XSLT. Una vez captada esa idea básica, perfecto; a partir de ahí, todo es posible en cuanto a lo que se puede hacer con datos de WinRM.

Y aquí hacemos una trampa sin importancia: para que esta columna quepa en el espacio asignado, nos saltaremos la mayor parte del script que le acabamos de mostrar en la figura 1. Sin embargo, no creo que ello plantee mayores problemas porque los primeros dos tercios del script se describieron con todo detalle en la columna del mes pasado. Nos centraremos en el último tercio del script, que es la parte donde realmente se trabaja con los datos devueltos.

Como puede ver, este episodio de las crónicas de WinRM reanuda la acción después de haber creado una instancia del objeto WSMan.Automation, de haber consultado un equipo remoto (en este caso, atl-fs-01.fabrikam.com) y de haber recibido información acerca de todos los servicios que se ejecutan en ese equipo. Esto nos lleva a la línea del script donde establecemos un bucle Do Until para leer y procesar los datos XML devueltos; este bucle continuará hasta que no quede nada por leer y procesar. (O, para que suene como si supiéramos de lo que estamos hablando, este bucle continuará hasta el momento en que la propiedad AtEndOfStream del archivo XML sea True).

Esa es la línea de código de la que hablamos y aquí es donde empieza la historia de este mes:

Do Until objResponse.AtEndOfStream

Dentro del bucle, lo primero que hacemos es usar el método ReadItem para leer la primera sección de los datos XML devueltos. (Puesto que estamos trabajando con WinRM y estamos recuperando información de servicio, esta primera sección constará de todos los datos devueltos para el primer servicio de nuestra colección). Después de almacenar esos datos (que, de nuevo, están en formato XML) en una variable denominada strXML, creamos una instancia del objeto Microsoft.XMLDom. De hecho, eso nos da un documento XML en blanco con el que podemos trabajar:

Set objXMLDoc = _
    CreateObject("Microsoft.XMLDom")

Tan pronto como nuestro documento en blanco esté listo, estableceremos el valor de la propiedad Async en False y después llamaremos al método loadXML.

Esto nos lleva a... ¿qué ha dicho? ¿Por qué establecemos el valor de la propiedad Async en False? ¿Y por qué llamamos al método loadXML? Buenas preguntas; ¡ojalá se nos hubieran ocurrido a nosotros!

Para empezar, la propiedad Async indica si el script permite una descarga asincrónica de información XML. Si esta propiedad es True, la descarga se iniciará y el control se devolverá de inmediato al script, incluso si la descarga no se ha completado. Hay que reconocer que parece lo más acertado. Por desgracia en ese caso el script avanzará como si tuviera toda la información que necesita. Y si no tiene toda la información que necesita, pues el desastre será inevitable. Por eso establecemos la propiedad Async en False.

Nota: De acuerdo, se podría escribir código para supervisar periódicamente el estado de la descarga, garantizando así que el script no se precipite prematuramente. Eso funciona, pero un enfoque bastante más fácil es establecer el valor de la propiedad Async en False. Cuando se hace, el script se bloquea hasta que la descarga se haya completado; es decir, una vez que empieza la descarga, el script esperará pacientemente a que ésta termine antes de hacer nada más.

En cuanto al método loadXML, hace lo que el nombre sugiere: carga un documento XML bien formado (o un fragmento de documento) en nuestro documento en blanco. Todo lo que hay que hacer es llamar al método loadXML, pasando la variable strXML como único parámetro del método:

objXMLDoc.loadXML(strXML)

¿El resultado final? Ahora hemos convertido nuestros datos de WinRM en un documento XML virtual. Y eso significa que podemos empezar a usar los métodos XML estándar para analizar ese documento virtual.

Llegados a este punto, debemos usar la siguiente línea de código para crear una referencia de objeto al elemento raíz en el archivo XML:

Set objElement = objXMLDoc.documentElement

Ahora ya estamos listos para divertirnos un poco. (Suponiendo, por supuesto, que su idea de diversión sea analizar un archivo XML. Eso es, sin duda alguna, lo que nosotros, los encargados del scripting, consideramos diversión).

Como quizá recuerde, nuestra consulta del lenguaje de consulta de WMI (WQL) (o usando terminología de WinRM, nuestro filtro) tiene este aspecto:

strFilter = "Select Name, DisplayName " & _
  "From Win32_Service Where State = 'Running'"

Como puede ver, hemos solicitado dos propiedades de la clase Win32_Service: Name y DisplayName. (Hemos incluido también una cláusula Where que limita los datos devueltos a los servicios que se están ejecutando. Sin embargo, eso no es nada por lo que tengamos que preocuparnos ahora mismo). ¿Es importante que hayamos solicitado dos propiedades? ¿Es importante el orden de esa solicitud? Puede ser. ¿Cómo demonios vamos saberlo?

Ah, buena pregunta. Quizá, como autores de este artículo deberíamos saber las respuestas a esas preguntas. Está bien. Resulta que sabemos que la respuesta a ambas preguntas es sí. ¿Es importante que hayamos solicitado dos propiedades en nuestra consulta de WQL? Sí, lo es; a fin de cuentas, serán los únicos valores de propiedad que se nos devuelvan. (A diferencia de hacer una consulta Select * From, que devuelve valores para todas las propiedades de una clase).

¿Entonces, el orden de esas dos propiedades también es importante? Por supuesto que sí. El orden con el que especificamos los nombres de propiedad es el mismo orden con el que se devuelven los valores de propiedad. Eso es importante porque cada valor de propiedad se devolverá como nodo (o sección) secundario de nuestro elemento raíz. ¿Qué valor de propiedad se devolverá como primer nodo secundario? Eso es una pregunta fácil. En este caso, el primer nodo secundario será la propiedad Name, porque es la primera propiedad enumerada en nuestra consulta de WQL. ¿Qué propiedad se devolverá como segundo nodo secundario? Es cierto, será la propiedad DisplayName, porque es el segundo elemento enumerado en nuestra consulta.

Espere un segundo. ¿Lo ha resuelto usted solo o alguien le ha filtrado la información de esta columna por adelantado? Hmmmm...

En todo caso, esto facilita la devolución del valor de la propiedad Name. Lo único que queda es hacer referencia a NodeValue del primer elemento (elemento 0) en la primera colección de ChildNodes (elemento 0), de este modo:

Wscript.Echo "Name: " & _
objElement.ChildNodes(0).ChildNodes(0).NodeValue

¿Y cómo hacemos referencia al valor de la propiedad DisplayName? En este caso, hacemos referencia al NodeValue del primer elemento en la segunda colección de ChildNodes (elemento 1):

Wscript.Echo "Display Name: " & _
objElement.ChildNodes(1).ChildNodes(0).NodeValue 

¿Y qué pasaría si tuviéramos una tercera propiedad (Status, por ejemplo) en nuestra consulta de WQL? Entonces, simplemente haríamos referencia al NodeValue del primer elemento de la tercera colección de ChildNodes (elemento 2):

Wscript.Echo "Status: " & _
objElement.ChildNodes(2).ChildNodes(0).NodeValue 

Y así sucesivamente hasta llegar a la última propiedad.

Entonces, ¿qué aspecto tendrá nuestra salida de script ahora? Algo parecido a lo siguiente:

Display Name: Windows Event Log
Name: EventLog

Display Name: COM+ Event System
Name: EventSystem

De acuerdo, no tiene un aspecto tan diferente de la salida de WinRM predeterminada (aunque nos hemos deshecho de aquel encabezado tonto de XmlFragment). La diferencia es ésta: al trabajar con los valores de propiedad individuales, ya no estamos limitados al formato predeterminado (observe que usamos la etiqueta Display Name y no DisplayName), ni estamos limitados a mostrar información únicamente en la ventana de comandos.

¿Preferiría escribir estos datos en una hoja de cálculo de Excel? Eso es bastante fácil de hacer. Para empezar, inserte el siguiente bloque de código (que crea y configura una nueva hoja de cálculo de Excel) inmediatamente después de la línea en el script de WinRM que llama al método Enumerate:

Set objExcel = _
  CreateObject("Excel.Application")
objExcel.Visible = True
Set objWorkbook = objExcel.Workbooks.Add()
Set objWorksheet = objWorkbook.Worksheets(1)

i = 2
objWorksheet.Cells(1,1) = "Name"
objWorksheet.Cells(1,2) = "Display Name"

Ahora, reemplace el bucle Do Until original por el que se muestra en la figura 2. Inténtelo y verá lo que sucede.

Figure 2 Nuevo bucle Do Until

Do Until objResponse.AtEndOfStream
  strXML = objResponse.ReadItem

  Set objXMLDoc = CreateObject("Microsoft.XMLDom")
  objXMLDoc.async=False
  objXMLDoc.loadXML(strXML)

  Set objElement = objXMLDoc.documentElement
  objExcel.Cells(i, 1) = objElement.ChildNodes(0).ChildNodes(0).NodeValue 
  objExcel.Cells(i, 2) = objElement.ChildNodes(1).ChildNodes(0).NodeValue 
  i = i + 1
Loop

¿WinRM, parte 3?

Entre la columna de este mes y la del último, debería tener suficiente para empezar con WinRM. Esperamos que sí; WinRM es una tecnología nueva y fascinante que promete hacer de la administración remota de sus equipos algo muy fácil (al tiempo que mantiene la seguridad). Lo que, por supuesto, conduce directamente hacia la pregunta que todo el mundo se plantea: ¿esto significa que habrá una tercera entrega de la saga de WinRM?

Lo siento, no podemos revelar esa información. No por problemas de seguridad, sino simplemente debido a que el proceso habitual de planeación y toma de decisiones de los encargados del scripting nos impide tener la menor idea de si escribiremos algo más acerca de WinRM. Como dicen ellos: ¡no deje de leernos!

Dr. Scripto's Scripting Perplexer

El doctor Scripto ha tenido un pequeño accidente. Dejó uno de sus scripts tirado por ahí (en vez de ponerlo, como buen creador de scripts, a buen recaudo) y tropezó con él sin querer, haciendo que los pedazos del script volaran por los aires. Se las ha apañado para encontrar todas las variables, palabras clave, símbolos y demás, y lo ha dispuesto todo por orden alfabético, pero ahora necesita volver a recomponerlo para lograr que el script esté de nuevo completo. Tiene para rato, pero espera tener la respuesta lista para cuando aparezca TechNet Magazine el próximo mes. Mientras tanto, no deje de intentarlo y vea si usted puede convertir este conjunto aparentemente aleatorio de pedazos de script en un script completo. ¡Buena suerte!

Pista: Bueno, un poco de ayuda no le vendrá mal. El script final eliminará todos los archivos del equipo local anteriores a una fecha especificada.

ANSWER:

Dr. Scripto's Scripting Perplexer

Respuesta: El torpe doctor Scripto, diciembre de 2007

Sí, Dr. Scripto logró recomponer su script. Aquí está el script reconstruido que, además, funciona y elimina todos los archivos del equipo local anteriores a una fecha especificada:

strDate = "20060102000000.000000+000"

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colFiles = objWMIService.ExecQuery("Select * From CIM_DataFile Where CreationDate < '" & strDate & "'")
For Each objFile in colFiles
    Wscript.Echo objFile.Name
Next

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.