Windows PowerShellUna línea de scripting cada vez

Don Jones

En columnas pasadas, he enfatizado el hecho de que Windows PowerShell es un shell. Está pensado para usarse interactivamente, a diferencia del shell de cmd.exe (o símbolo del sistema) con el que probablemente ya está familiarizado. No obstante, Windows PowerShell es compatible con un lenguaje de scripting, que es más consistente que el lenguaje de lotes de cmd.exe, y es

tan eficaz o más que lenguajes como VBScript. Sin embargo, el hecho de que Windows PowerShell™ sea un shell interactivo hace que el scripting sea notablemente fácil de aprender. De hecho, puede desarrollar scripts interactivamente en el shell, lo que permite escribir una línea de script cada vez y ver de inmediato los resultados del trabajo.

Esta técnica de scripting iterativo también simplifica la depuración. Dado que puede ver directamente los resultados de script, puede revisarlos de forma rápida cuando no obtiene los resultados esperados.

En esta columna, voy a mostrarle un ejemplo de scripting interactivo en Windows PowerShell. Crearé un script que lee los nombres de servicio de un archivo de texto y deshabilita cada modo de inicio de servicio.

Lo que quiero que aprenda de este tutorial es el concepto de crear scripts de Windows PowerShell en pequeñas partes, en lugar de intentar abordar todo un script como una sola pieza. Puede tomar cualquier tarea administrativa que necesite llevar a cabo y descomponerla en sus partes constitutivas y, a continuación, descubrir cómo realizar cada parte de trabajo independientemente. Windows PowerShell ofrece medios excelentes para unir estas partes, como podrá comprobar. Y descubrirá que, al trabajar en partes pequeñas a la vez, le resultará más sencillo desarrollar el script final.

Lectura de nombres de un archivo

Puede resultar frustrante tratar de entender cómo leer archivos de texto en Windows PowerShell. Si ejecuto Help *file*, todo lo que obtengo es ayuda para cmdlet Out-File, que envía texto a un archivo, en lugar de leerlo. No es ninguna ayuda en absoluto. Sin embargo, los nombres de cmdlet de Windows PowerShell siguen una clase de lógica que puedo usar en mi beneficio. Cuándo Windows PowerShell recupera algo, el nombre de cmdlet suele comenzar por Get. De modo que ejecuto Help Get* para ver esos valores de cmdlet y, entonces, al desplazarme por la lista, encuentro Get-Content. Parece prometedor. De modo que ejecuto Help Get-Content para saber más acerca de su función (consulte la Figura 1) y parece que satisfará mis necesidades.

Figura 1 Ejecución de Help Get-Content para obtener más información

Figura 1** Ejecución de Help Get-Content para obtener más información **(Hacer clic en la imagen para ampliarla)

Windows PowerShell trata casi todo como un objeto y los archivos de texto no son una excepción. Un archivo de texto es técnicamente una colección de líneas y cada línea del archivo actúa a modo de objeto independiente. De modo que, si creo un archivo de texto denominado C:\services.txt y lo lleno de nombres de servicio (colocando cada nombre en una línea del archivo), Windows PowerShell podrá leer los nombres individualmente mediante el valor de cmdlet Get-Content. Puesto que la idea de este tutorial es mostrar cómo se pueden desarrollar scripts de forma interactiva, comenzaré ejecutando Get-Content, dándole el nombre de mi archivo de texto y viendo lo que sucede:

PS C:\> get-content c:\services.txt
messenger
alerter
PS C:\>

Como esperaba, Windows PowerShell lee el archivo y muestra el nombre. Por supuesto, mi objetivo no es realmente que se muestren sólo los nombres, pero ahora sé que Get-Content funciona de la manera que esperaba.

Cambio de un servicio

El siguiente paso que deseo realizar es cambiar un modo de inicio de servicio. De nuevo, empiezo tratando de encontrar el valor de cmdlet correcto. De modo que ejecuto Help *Service*. Esto devuelve una lista corta y el valor de cmdlet Set-Service parece ser el único que responderá a mis necesidades. Deseo probar este valor de cmdlet para asegurarme de que comprendo cómo funciona antes de intentar incorporarlo a un script. Al ejecutar Help Set-Service, descubro cómo debe funcionar el cmdlet y una prueba rápida lo confirma:

PS C:\> set-service messenger -startuptype
    disabled
PS C:\>

Combinación de las partes

Ahora necesito combinar la capacidad de leer nombres de servicio de un archivo con el valor de cmdlet Set-Service, y es aquí donde entran en juego las eficaces capacidades de canalización de Windows PowerShell. Con la canalización, la salida de un cmdlet puede pasar como entrada a un segundo cmdlet. La canalización transfiere objetos enteros. En caso de que una colección de objetos se coloque en la canalización, cada objeto pasa a través de ella individualmente. Esto significa que la salida de Get-Content que, recuerde, es una colección de objetos, puede pasar a Set-Service. Puesto que Get-Content transfiere una colección, cada objeto (o línea de texto) de la misma se transfiere a Set-Service individualmente. El resultado es que Set-Service se ejecuta una vez para cada línea del archivo de texto. El comando es similar al siguiente:

PS C:\> get-content c:\services.txt | 
 set-service -startuptype disabled
PS C:\>

Esto es lo que sucede:

  1. El valor de cmdlet Get-Content se ejecuta y lee el archivo entero. Cada línea del archivo se trata como un objeto único y juntas forman una colección de objetos.
  2. La colección de objetos pasa hasta Set-Service.
  3. La canalización ejecuta el valor de cmdlet Set-Service una vez para cada objeto de entrada. Para cada ejecución, el objeto de entrada pasa hasta Set-Service como primer parámetro del cmdlet, que es el nombre del servicio.
  4. Set-Service se ejecuta, usando el objeto de entrada para su primer parámetro y cualquier otro parámetro que se especifique (en este caso, el parámetro -startuptype).

En este momento, resulta interesante observar que he realizado la tarea sin escribir tan siquiera un script. Esta misma acción sería difícil de lograr en el shell de Cmd.exe y ocuparía una docena de líneas de código en VBScript. Sin embargo, Windows PowerShell procesa todo esto en una línea. De todos modos, aún no he acabado. Como puede ver, mi comando no ofrece muchos resultados de estado. Aparte de la falta de errores, es difícil ver si ha sucedido algo realmente. Ahora que he dominado la funcionalidad necesaria para realizar mi tarea, puedo empezar a darle un mejor aspecto mediante el auténtico scripting.

Función de símbolo instantáneo de Windows PowerShell por Michael Murgolo

Una de las aplicaciones Microsoft® PowerToys para Windows® más populares (y una de mis favoritas) es Abrir ventana de comandos aquí. Disponible como parte de Microsoft PowerToys para Windows XP o en las herramientas del kit de recursos de Windows Server® 2003, Abrir ventana de comandos aquí permite hacer clic con el botón secundario del mouse en una carpeta o unidad del Explorador de Windows para abrir una ventana de comandos que señala a la carpeta seleccionada.

A medida que iba conociendo Windows PowerShell, me di cuenta de que deseaba que tuviera la misma funcionalidad. De modo que tomé el archivo setup .inf de Abrir ventana de comandos aquí, cmdhere.inf, de las herramientas del kit de recursos de Windows Server 2003 y lo modifiqué para crear un menú contextual de símbolo instantáneo de Windows PowerShell. Este archivo .inf se incluye con la publicación del blog original en la que se basa esta barra lateral (disponible en leeholmes.com/blog/PowerShellPromptHerePowerToy.aspx). Para instalar la herramienta, sólo tiene que hacer clic con el botón secundario del mouse en el archivo .inf y seleccionar la opción de instalación.

Al crear la versión de Windows PowerShell de esta herramienta, descubrí que la original tenía un error: si se desinstalaba Abrir ventana de comandos aquí, dejaba una entrada de menú contextual sin función. Como resultado, he proporcionado una versión actualizada de cmdhere.inf, también disponible en la publicación del blog original.

Estos dos PowerToys se aprovechan del hecho de que las entradas de menú contextual se configuran en el registro bajo claves asociadas a los tipos de objeto de directorio y unidad. Se trata de la misma forma en que las acciones de menú contextual están asociadas a tipos de archivo. Por ejemplo, al hacer clic con el botón secundario del mouse en un archivo .txt en el Explorador de Windows, se obtienen varias acciones (como Abrir, Imprimir y Editar) en la parte superior de la lista. Para entender cómo están configurados estos elementos, echemos un vistazo al subárbol de registro HKEY_CLASSES_ROOT.

Si abre el Editor del Registro y amplía la rama HKEY_CLASSES_ROOT, verá claves denominadas por tipo de archivo, como .doc, .txt y similares. Si hace clic en la clave .txt, verá que el valor (Predeterminado) es txtfile (consulte la Figura A). Éste es el tipo de objeto asociado a un archivo .txt. Si desplaza y expande la clave txtfile y, a continuación, expande la clave de shell contenida en ésta, verá las claves de algunas entradas de menú contextual de un archivo .txt. (No las verá todas porque hay otros métodos de creación de menús contextuales). Bajo cada una de ellas, hay una tecla de comando. El valor (Predeterminado) de la tecla de comando es la línea de comandos que Windows ejecuta al seleccionar el elemento del menú contextual.

Las herramientas para el símbolo de cmd y el símbolo de Windows PowerShell usan esta técnica para configurar las entradas de menú contextual de Abrir ventana de comandos aquí y la función de símbolo instantáneo de Windows PowerShell. No hay ningún tipo de archivo asociado a unidades ni directorios, aunque hay claves de unidad y directorio en HKEY_CLASSES_ROOT asociadas a los objetos.

Figura A Entradas de menú contextual configuradas en el registro

Figura A** Entradas de menú contextual configuradas en el registro **(Hacer clic en la imagen para ampliarla)

Michael Murgolo es consultor jefe de infraestructuras de Servicios de consultoría de Microsoft (Microsoft Consulting Services, MCS) Está especializado en las áreas de sistemas operativos, implementación, servicios de red, Active Directory, administración de sistemas, automatización y administración de revisiones.

Scripting interactivo

Uno de los procedimientos que necesito realizar es hacer que los valores de cmdlet se ejecuten para los servicios de C:\services.txt. De esta forma, podré obtener el nombre de servicio así como cualquier otra información que desee y podré seguir el avance del script.

Anteriormente, usé la canalización para pasar objetos de un cmdlet a otro. Esta vez, sin embargo, voy a usar una técnica más parecida al script, denominada construcción de Foreach (que presenté en la columna del mes pasado). La construcción de Foreach puede aceptar una colección de objetos y ejecutar varios cmdlets para cada objeto de la colección. Designo una variable que representa el objeto actual en cada momento a lo largo del bucle. Por ejemplo, la construcción puede empezar así:

foreach ($service in get-content c:\services.txt)

Sigo ejecutando el mismo valor de cmdlet Get-Content para recuperar el contenido del archivo de texto. Esta vez, solicito que la construcción de Foreach se repita a través de la colección de objetos devueltos por Get-Content. El bucle se ejecuta una vez en cada objeto y el objeto actual se coloca en la variable $service. Ahora sólo tengo que especificar el código que deseo ejecutar con el bucle. Comenzaré intentando duplicar mi comando de una sola línea original (esto minimizará la complejidad y asegurará que no pierda ninguna funcionalidad):

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> }
>>
PS C:\>

Aquí se producen algunos procesos interesantes. Observe que he terminado la primera línea de la construcción de Foreach con una llave de apertura. Todo lo que hay dentro de las llaves de apertura y cierre se considera dentro del bucle de Foreach y se ejecutará una vez para cada objeto de la colección de entrada. Después de escribir { y presionar Entrar, observe que Windows PowerShell ha cambiado su símbolo por >> (consulte la Figura 2). Esto indica que sabe que he empezado una construcción de alguna clase y que está esperando a que la termine. A continuación, he escrito el valor de cmdlet Set-Service. Esta vez, he usado $service como primer parámetro, ya que representa el nombre del servicio actual leído en mi archivo de texto. En la línea siguiente, concluyo la construcción con una llave de cierre. Después de presionar Entrar dos veces, Windows PowerShell ejecuta de inmediato mi código.

Figura 2 Windows PowerShell sabe que se ha iniciado una construcción

Figura 2** Windows PowerShell sabe que se ha iniciado una construcción **(Hacer clic en la imagen para ampliarla)

Correcto, de inmediato. Lo que he escrito parece un script, pero Windows PowerShell realmente se está ejecutando en tiempo real y no se ha almacenado en ningún archivo de texto. Ahora volveré a introducirlo todo, agregando una línea de código para obtener el nombre de servicio actual:

PS C:\> foreach ($service in get-content c:\services.txt) {
>> set-service $service -startuptype disabled
>> "Disabling $service"
>> }
>>
Disabling messenger
Disabling alerter
PS C:\>

Observe que estoy solicitando que muestre algo y he encerrado ese algo entre comillas dobles. Las comillas indican a Windows PowerShell que se trata de una cadena de texto y no de otro comando. Sin embargo, al usar comillas dobles (frente a las simples), Windows PowerShell busca cualquier variable en la cadena de texto. Si encuentra alguna, sustituye el valor real de la variable por el nombre de la misma. De esta forma, al ejecutarse el código, puede comprobar que se muestra el nombre de servicio actual.

¡Y esto todavía no es un script!

Hasta ahora, he usado Windows PowerShell interactivamente, que es una forma estupenda de ver de inmediato los resultados de lo que escribo. Sin embargo, en algún momento, el hecho de volver a escribir estas líneas de código se volverá algo tedioso. De ahí que Windows PowerShell también pueda ejecutar scripts. De hecho, los scripts de Windows PowerShell siguen la definición más literal de la palabra script: Windows PowerShell básicamente sólo lee en el archivo de texto de script y "escribe" cada línea que encuentra, exactamente como si el usuario escribiera las líneas manualmente. Esto significa que todo lo que he realizado hasta este momento se puede pegar en un archivo de script. Así, usaré Bloc de notas para crear un archivo denominado disableservices.ps1 y pegar lo siguiente en él:

foreach ($service in get-content c:\services.txt) {
 set-service $service -startuptype disabled
 "Disabling $service"
 }

He colocado este archivo en una carpeta denominada C:\test. Ahora trataré de ejecutarlo desde Windows PowerShell:

PS C:\test> disableservices
'disableservices' is not recognized as a cmdlet, function, operable program, or
<script file.
At line:1 char:15
+ disableservices <<<<
PS C:\test>

¡Ay! ¿Qué ha salido mal? Windows PowerShell se ha centrado en la carpeta C:\test, pero no ha encontrado mi script. ¿Por qué? Debido a restricciones de seguridad, Windows PowerShell está diseñado para no ejecutar ningún script de la carpeta actual, con lo cual evita que un script se apropie de cualquier comando del sistema operativo. Por ejemplo, no puedo crear un script con el nombre dir.ps1 y que invalide el comando normal dir. Si necesito ejecutar un script desde la carpeta actual, tengo que especificar una ruta de acceso relativa:

PS C:\test> ./disableservices
The file C:\test\disableservices.ps1 cannot be loaded. 
The execution of scripts is disabled on this system. 
Please see "get-help about_signing" for more details.
At line:1 char:17
+ ./disableservices <<<<
PS C:\test>

Y ahora, ¿qué? Sigue sin funcionar. He obtenido la ruta de acceso correcta, pero Windows PowerShell dice que no puede ejecutar ningún script. Esto se debe a que, de forma predeterminada, Windows PowerShell no puede ejecutar scripts. De nuevo, se trata de una precaución de seguridad diseñada para evitar los problemas que todos hemos tenido con VBScript. Los scripts malintencionados no se pueden ejecutar en Windows PowerShell de forma predeterminada porque ningún script se puede ejecutar de forma predeterminada. Para ejecutar mi script, necesito cambiar explícitamente la directiva de ejecución:

PS C:\test> set-executionpolicy remotesigned

La directiva de ejecución RemoteSigned permite a los scripts sin firmar que se ejecuten desde el equipo local (para ejecutar los scripts descargados, sigue siendo necesaria una firma). Allsigned es una directiva más adecuada que sólo ejecuta scripts firmados digitalmente con un certificado emitido por un editor de confianza. Sin embargo, no tengo ningún certificado a mano, por lo que no puedo firmar mis scripts y RemoteSigned será una elección bastante buena para esta situación. Ahora trataré de ejecutar mi script de nuevo:

PS C:\test> ./disableservices
Disabling messenger
Disabling alerter
PS C:\test>

Quiero indicar que la directiva de ejecución RemoteSigned que vamos a usar no es una gran elección; es solamente una buena. Aunque existe una solución mucho mejor. Sería más seguro obtener un certificado de firma de código, usar el valor de cmdlet Set-AuthenticodeSignature de Windows PowerShell para firmar los scripts y establecer la directiva de ejecución AllSigned, mucho más segura.

Hay también otra directiva, Unrestricted, que debe evitar sin duda. Esta directiva permite que todos los scripts (incluso scripts malintencionadas de ubicaciones remotas) se ejecuten libremente en el equipo, lo que puede ponerle en una situación muy peligrosa. Por consiguiente, no recomiendo usar la directiva Unrestricted en ningún caso.

Resultados inmediatos

Las capacidades de scripting interactivo de Windows PowerShell permiten crear prototipos de script de forma rápida o incluso pequeñas partes de script. Obtendrá resultados inmediatos, de modo que puede ajustar los scripts para obtener exactamente los resultados que desee. Cuando termine, puede pasar el código a un archivo .ps1, para conservarlo y que sea fácilmente accesible en el futuro. Y recuerde: lo ideal es que firme digitalmente los archivos .ps1 para poder dejar que Windows PowerShell establezca la directiva de ejecución AllSigned, la directiva más segura que permite la ejecución de scripts.

Don Jones es el principal gurú de scripting de SAPIEN Technologies y coautor de Windows PowerShell: TFM (en inglés) (consulte www.SAPIENPress.com). Póngase en contacto con él en la dirección don@sapien.com.

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