Windows PowerShellEl poder del filtrado

Don Jones

En la columna del mes pasado, hablé sobre la efectividad y flexibilidad de la canalización de Windows PowerShell, que permite pasar un conjunto de datos (o, más exactamente, una secuencia de objetos) de un cmdlet a otro, y posteriormente perfeccionarlo hasta obtener exactamente el que se necesita. En mi análisis, hice referencia al hecho de que los propios scripts del usuario,

no sólo los cmdlets, también pueden sacar provecho de la canalización. Este mes quiero tratar ese tema con todo detalle.

Una de las cosas más frecuentes que hago en Windows PowerShell™ es escribir scripts que actúan en varios equipos remotos, generalmente a través de Instrumental de administración de Windows® (WMI). Como con cualquier tarea relacionada con equipos remotos, siempre existe la posibilidad de que ése u otros equipos no estén disponibles cuando se ejecute el script. Por lo tanto, necesito que mis scripts puedan tratar esta situación.

Por supuesto, hay varias maneras a través de las cuales puedo otorgar a un script la capacidad de controlar el tiempo de espera de una conexión WMI, pero, personalmente, no me gusta ese método, ya que el período de tiempo de espera es muy largo, casi 30 segundos de forma predeterminada. Si tuviese que esperar a que se dieran muchos tiempos de espera, esto haría que mi script se ejecutase mucho más lentamente. En cambio, quiero que mi script realice una comprobación rápida para consultar si un equipo concreto está realmente conectado antes de intentar conectarme a él.

Paradigma de Windows PowerShell

En otros lenguajes de scripting, tal como VBScript, normalmente trataría con un equipo a la vez. Es decir, recuperaría un nombre de equipo, quizás de una lista de nombres almacenada en un archivo de texto, y enviaría un ping al sistema para comprobar si éste está disponible. Si lo está, procedería con la conexión WMI y realizaría cualquier otra labor que necesitase. Esto es un método de uso común de scripting. De hecho, yo escribiría probablemente todo mi código en un bucle y entonces repetiría ese bucle una vez para cada equipo al que necesitase conectarme.

Windows PowerShell, sin embargo, es más idóneo para realizar operaciones por lotes gracias a su base en objetos y su capacidad para trabajar directamente con grupos o colecciones de objetos. El paradigma en Windows PowerShell es que no funciona con objetos únicos ni partes de datos, sino que funciona más bien con grupos enteros, perfeccionando el grupo poco a poco hasta haber alcanzado cualquier objetivo. Por ejemplo, en vez de recuperar un nombre de equipo de mi lista uno a la vez, leería en una acción la recopilación entera de nombres. Y en vez de enviar un ping a cada equipo en un bucle, escribiría una sola rutina que aceptase una recopilación de nombres, les enviaría un ping y, entonces, obtendría aquellos con los que me habría podido poner en contacto. El paso siguiente de mi proceso sería efectuar mi conexión WMI a los nombres restantes, aquellos que se pudieron alcanzar a través de un ping.

Windows PowerShell usa este mismo método exacto en varias tareas. Por ejemplo, para obtener una lista de servicios ejecutados, puedo usar algo como lo siguiente:

Get-Service | Where-Object { $_.Status –eq "Running" }

La figura 1 muestra cómo se ve el resultado en mi equipo. En vez de comprobar cada servicio de uno en uno, usé Get-Service para recuperar todos los servicios, a éstos los canalicé a Where-Object y, a continuación, filtré todos los que no se estaban ejecutando. Esto es más o menos lo que quiero hacer con mi script: obtener una lista de nombres de equipos, filtrar todos a los que no se puede enviar un ping y pasar esta lista de equipos a los que se puede enviar un ping al paso siguiente.

Figura 1 Obtener una lista de equipos a los que se puede enviar un ping

Figura 1** Obtener una lista de equipos a los que se puede enviar un ping **(Hacer clic en la imagen para ampliarla)

Funciones de filtrado

Aunque puedo escribir mi propio cmdlet para hacer esto mismo, no quiero. La creación de un cmdlet requiere el uso de Visual Basic® o C# y bastante experiencia de desarrollo con Microsoft® .NET Framework. Y lo que es más importante, requiere una mayor inversión de la que yo quiero poner en esta tarea. Afortunadamente, Windows PowerShell me permite escribir un tipo especial de función, llamada filtro, que es perfectamente capaz de actuar dentro de la canalización. El diseño básico de una función de filtrado tiene el aspecto siguiente:

function <name> {
  BEGIN {
    #<code>
  }
  PROCESS {
    #<code>
  }
  END {
    #<code>
  }
}

Como puede ver, esta función contiene tres bloques independientes de scripts, denominados BEGIN, PROCESS y END. Una función de filtrado, es decir, una función diseñada para funcionar dentro de la canalización para filtrar objetos, puede disponer de cualquier combinación de estos tres bloques de scripts, en función de lo que se desee hacer. Estos funcionan de la siguiente manera:

  • El bloque BEGIN se ejecuta una vez cuando se llama a la función por primera vez. Puede usarse para hacer el trabajo de instalación, si es necesario.
  • El bloque PROCESS se ejecuta una vez para cada objeto de canalización que se pasa a la función. La variable $_ representa el objeto de entrada de la canalización actual. El bloque PROCESS es obligatorio en una función de filtrado.
  • El bloque END se ejecuta una vez, después de que se hayan procesado todos los objetos de la canalización. Este bloque puede usarse para cualquier trabajo de ultimación, si fuera necesario.

En mi ejemplo, pretendo producir una función de filtrado que acepte una recopilación de nombres como objetos de entrada y que, entonces, intente enviar un ping a cada uno. Los que reciban el ping correctamente pasarán a la canalización, mientras que los sistemas que no puedan recibir el ping simplemente se descartarán. Dado que la funcionalidad de ping no requiere ninguna instalación ni ultimación especiales, simplemente usaré el bloque de script PROCESS. El código en la figura 2 proporciona el script completo.

Figure 2 Ping-Address y Restart-Computer

1  function Ping-Address {
2    PROCESS {
3      $ping = $false
4      $results = Get-WmiObject -query `
5      "SELECT * FROM Win32_PingStatus WHERE Address = '$_'"
6      foreach ($result in $results) {
7        if ($results.StatusCode -eq 0) {
8          $ping = $true
9        }
10      }
11      if ($ping -eq $true) {
12        Write-Output $_
13      }
14    }
15  }
16
17  function Restart-Computer {
18    PROCESS {
19      $computer = Get-WmiObject Win32_OperatingSystem -computer $_
20      $computer.Reboot()
21    }
22  }
23
24  Get-Content c:\computers.txt | Ping-Address | Restart-Computer

Observe que definí dos funciones: Ping-Address y Restart-Computer. En Windows PowerShell, las funciones deben definirse antes de ser invocadas. Como resultado, la primera línea ejecutable de mi script es la línea 24, que usa el cmdlet Get-Content para obtener una lista de nombres de equipo de un archivo (un nombre de equipo por línea). Esa lista, que técnicamente es una colección de objetos de cadena, se canaliza a la función que dirige el ping, la cual filtra los equipos que no pueden recibir un ping. Los resultados se canalizan a la función Restart-Computer, que usa la conexión para reiniciar de forma remota los equipos que sí pueden recibir un ping.

La función Ping-Address implementa un bloque de script PROCESS, los que significa que la función espera una recopilación de entrada de objetos. El bloque de script PROCESS trata con esa entrada automáticamente; no tuve que definir ningún argumento de entrada para incluirla. Inicio el proceso en la línea 3 al establecer la variable $ping en $false, que es una variable integrada de Windows PowerShell que representa el valor booleano False.

A continuación, uso la clase local WMI Win32_PingStatus para enviar el ping al equipo especificado. Observe en la línea 5 que la variable $_, que representa el objeto de canalización actual, está incrustada dentro de la cadena de consulta WMI. Cuando una cadena se incluye entre comillas dobles, Windows PowerShell siempre intentará reemplazar las variables tal como $_ con su contenido, de modo que no es necesario preocuparse por la concatenación de la cadena. Uso esa capacidad en la línea 5.

La línea 6 es un bucle que comprueba los resultados de mi ping. Si cualquiera de los resultados es correcto (es decir, el StatusCode es cero), establezco el valor $ping igual a $true para indicar ese resultado correcto. En la línea 11, compruebo si $ping se estableció en $true. En caso afirmativo, envío el objeto de entrada original a la secuencia de salida predeterminada. Windows PowerShell administra la secuencia de salida predeterminada de manera automática. Si esta función se da al final de la canalización, entonces la secuencia de salida se convierte en una representación de texto. Si hay más comandos en la canalización, entonces los objetos de secuencia de salida (en este caso, los objetos de cadena que contienen los nombres de equipo) pasan al comando siguiente de la canalización.

La función Restart-Computer es algo más sencilla, pero también usa un bloque PROCESS para que pueda participar fácilmente en la canalización. En la línea 19, la función se conecta al equipo denominado y recupera su clase WMI Win32_OperatingSystem. La línea 20 ejecuta el método de la clase Reboot para reiniciar el equipo remoto.

De nuevo, la línea 24 es donde de hecho se ejecuta todo. Por supuesto, se debe ser muy cauteloso al ejecutar este script, ya que está diseñado para rearrancar todos los equipos denominados en c:\computers.txt, lo cual podría tener definitivamente unos resultados desastrosos si no se presta atención a los nombres de ese archivo de texto.

Pasos siguientes

Este script no está totalmente a prueba de balas. Debo reconocer también de que existe la posibilidad de que se dé un error WMI no relacionado con la conectividad básica, como por ejemplo, que el firewall bloquee los puertos necesarios en el equipo remoto o que se produzca un error de seguridad de WMI. Estos problemas se pueden controlar si se implementa un controlador de capturas, cosa que suena a tema perfecto para una próxima columna.

Asimismo, este script realiza una acción bastante importante: reiniciar un equipo remoto. Cualquier script que intenta dicha acción potencialmente peligrosa debe implementar dos parámetros comunes de Windows PowerShell: –Confirm y –WhatIf. Explicar cómo se realiza esta operación requiere más espacio del que dispongo aquí, pero también sería otro gran tema para una columna en el futuro. Mientras tanto, puede visitar el blog del equipo de Windows PowerShell: el arquitecto Jeffrey Snover cubre este tema.

Incluso sin entrar en detalle con estas características, puede obtener una buena idea de lo que pueden hacer las funciones de filtrado. Voy a perfeccionar esta técnica en columnas venideras, para así mostrar que estas funciones pueden proporcionar una buena manera de modularizar código que se puede volver a usar, así como mejorar la funcionalidad de sus scripts en general.

Don Jones es el principal gurú de scripting de SAPIEN Technologies y coautor de Windows PowerShell: TFM (SAPIEN Press, 2007). Póngase en contacto con Don en la dirección www.ScriptingAnswers.com.

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