Introducción a PowerShell para desarrolladores Dynamics AX 2012 (PS-I)

PowerShell (lo abreviaremos PS en lo sucesivo) es una herramienta impresionante, entre otras cosas, para la administración de nuestros servidores y aplicaciones, y eso incluye, por supuesto, nuestro Microsoft Dynamics AX 2012. Con esta versión se incluye un módulo propio de PS al que llama “Microsoft Dynamics AX 2012 Management Shell” con algunos comandos que veremos en sucesivas entregas. Estos comandos son muy útiles, pero podemos sacar provecho también de la funcionalidad estándar de PowerShell para muchas tareas.

Todo esto lo veremos en las siguientes entregas de este artículo, de momento es necesario empezar con lo más básico de PS, que nos permitirá comprender y sacar provecho a los siguientes artículos.

Cómo obtener ayuda

Como, a estas alturas, todos debemos saber, PowerShell es una herramienta de administración de Windows a modo de consola de comandos. Una versión mejorada de la antigua consola msdos disponible en versiones anteriores que, como aquella, nos permite crear y reutilizar scripts de comandos.

Dependiendo de la versión de Windows que utilicemos, PowerShell viene instalada por defecto (en las últimas versiones), o habrá que instalarla manualmente. Se puede descargar de manera gratuita de la web oficial de Microsoft. También se puede descargar un editor gratuito (que también viene por defecto en algunas versiones de Windows) que nos permite editar, depurar y ejecutar scripts de PS fácilmente, llamado Windows PowerShell ISE.

Los primeros comandos (en PS llamados: Cmdlets) que debemos aprender son los relativos a la ayuda. Sin duda, nos van a hacer falta. Podemos utilizar el comando Get-Help para obtener ayuda sobre cualquier comando, o para obtener una lista de los comandos disponibles. Para comprobar las posibilidades del sistema de ayuda podemos probar los comandos siguientes y analizar su resultado:

# Ayuda
Get-Help
Get-Help Get-Help -Full

# Ayuda sobre un Cmdlet
Get-Help process
Get-Help Get-Process –Examples
Get-Help Get-Process –Online

# Actualizar ayuda desde internet
Update-Help

Mediante estos comandos vemos cómo obtener una lista de comandos buscando por una palabra clave, cómo buscar información sobre un comando concreto, cómo ir directamente a la página web de Microsoft que nos muestra la ayuda de un comando, y cómo actualizar desde internet la base de datos de ayuda de nuestro sistema local.

001-GetHelp

La curva de aprendizaje de PowerShell es dura, así que seguro que utilizaremos estos comandos a menudo, sobre todo al principio.

Introducción, Pipes y Aliases

A pesar de que se dice de PS que es la nueva versión de la antigua consola msdos, lo cierto es que su utilización no tiene nada que ver, salvo en el simple hecho de que ambas son consolas de comandos, y ambas permiten almacenar y reutilizar scripts.

El resultado de los comandos de PS es siempre un objeto (como los de programación orientada a objetos), no simple texto como en otras consolas. Por tanto, cuando encadenamos la salida de un comando hacia el siguiente, lo que recibe el segundo comando es un objeto o una lista de objetos de los que puede hacer uso de manera completa (no sólo de lo que se muestra por la consola), incluyendo sus propiedades y sus métodos.

Por ejemplo, el comando Get-ItemProperty muestra propiedades de un objeto determinado. Si lo utilizamos con un fichero o una carpeta, mostrará información detallada sobre este objeto en cuestión, de esta forma:

PS C:\> Get-ItemProperty Windows

Lo que se muestra en la consola al ejecutar este comando son ciertas propiedades formateadas de manera similar al clásico comando dir, que muestra todos los ficheros y directorios en un directorio. Sin embargo, la salida del comando Get-ItemProperty la podemos canalizar (Pipe) hacia otro comando para cambiar su formato:

PS C:\> Get-ItemProperty Windows | Format-List

En la siguiente imagen se muestra la salida de ambos comandos. Aquí podemos observar cómo el objeto tiene más propiedades de las que se muestran en un determinado comando, mientras PS utiliza siempre el objeto completo.

002-Get-ItemProperty

De la misma forma, podríamos obtener esta información detallada de todos los elementos en el directorio activo, canalizando varios de estos comandos (para más comandos de formato: Get-Help format):

PS C:\> dir | Get-ItemProperty | Format-List

Como hemos comprobado, los comandos de PS tienen nombres compuestos en la forma Verbo-Nombre. El número de verbos es limitado, y nos da una idea de para qué sirve cada comando. El nombre nos va a indicar las “familias” de comandos que tenemos disponibles, como son: Servicios, Procesos, etc. Algunas de ellas las veremos más adelante.

También hemos visto que disponemos de comandos más “clásicos”, heredados de otras consolas, que nos facilitan el uso. Por ejemplo dir, ls, rm, etc. Estos comandos son en realidad aliases a comandos propios de PowerShell. Podemos crear aliases nuevos y modificar los existentes. Para obtener información d todos los comandos disponibles para el manejo de alias: Get-Help alias. También podemos consultar los alias existentes mediante el comando alias, que puede recibir opcionalmente una cadena de búsqueda para limitar el resultado:

003-alias

Scripts

Como que en el resto de consolas de comandos, una parte de la gran potencia de PowerShell es la posibilidad de escribir scripts y guardarlos en ficheros para ejecutarlos repetidamente. Puesto que PS maneja, como hemos dicho, objetos, los scripts de PS pueden ser mucho más complejos y potentes que en la antigua consola msdos.

Por ejemplo, con los comandos que ya hemos visto hasta ahora, podríamos obtener una lista con los nombres de los ficheros del directorio activo de esta forma:

$list = dir
foreach ($item in $list)
{
    $item.Name
}

En este pequeño script se muestran varias cosas interesantes. Por un lado, cómo hacemos uso del valor devuelto del comando dir para almacenarlo en una variable (una lista de objetos). También vemos cómo manejar esa lista de objetos para obtener cada uno de sus elementos en otra variable (que representará a un objeto individual) de manera similar a otros lenguajes; y cómo mostrar por la pantalla sólo una propiedad del objeto, el nombre. En esta última línea vemos como no hay ningún comando, sólo una variable, y es que en PS se muestra por la consola cualquier elemento que no se encadene hacia otros comandos.

Este script se puede guardar en un fichero de texto para poder utilizarlo cuando sea necesario. Por ejemplo, si guardamos el fichero con nombre Ejemplo1.ps1, podrá ser ejecutado cuantas veces queramos mediante el comando:

PS C:\> .\Ejemplo.ps1

Y si queremos importar este script dentro de otro script, para reutilizar su funcionalidad, se puede importar mediante la sintaxis:

PS C:\> . (C:\Pruebas\Ejemplo.ps1)

Los scripts pueden contener comentarios de una sola línea precedidos por el carácter # y comentarios de varias líneas entre los símbolos <# y #>

Sintaxis avanzada

Curiosamente, utilizando la sintaxis propia de PS (a menudo, no muy intuitiva), obtenemos el mismo resultado que el script anterior con el comando:

PS C:\> (dir).Name

Al encerrar el comando dir entre paréntesis estamos tratando todos los elementos devueltos por ese comando como objetos individuales, accediendo a su propiedad Name. Como todos esos objetos no se canalizan hacia ningún otro comando, se muestran por pantalla.

Otro ejemplo, para borrar todos los ficheros de una determinada carpeta podríamos utilizar las sintaxis equivalentes siguientes (cuidado con este código!!):

PS C:\> dir | Remove-item
PS C:\> (dir).delete()

Sin embargo borrar todo el contenido de un directorio no siempre es lo más útil. Si quisiéramos borrar sólo algunos ficheros en base a una condición, podríamos incluir esa condición en alguno de los cmdlets, de esta forma (de nuevo ambas son equivalentes):

PS C:\Pruebas> dir “Prueba B” | Remove-item
PS C:\Pruebas> (dir “Prueba B”).delete()

O bien podríamos utilizar la sintaxis propia de PS, que nos permite filtros más potentes:

PS C:\Pruebas> dir | Where-Object { $_.Name.StartsWith("Prueba B") } | Remove-Item

El cmdlet Where-Object (alias: ?) es especial porque su utilidad es filtrar un conjunto de elementos entrante, y devolver sólo el conjunto de elementos que satisfacen una determinada expresión true o false. Este cmdlet no recibe un parámetro, si no el cuerpo de una función, esto es, un lambda. La expresión se escribe directamente entre llaves después del cmdlet.

Esta función dispone del objeto $_ que representa a cada elemento de la colección entrante. Con este elemento se pueden hacer cuantas validaciones se quieran. Si estas validaciones resultan en true, el elemento pasará al siguiente cmdlet; Si no, se ignora y no será canalizado al siguiente comando.

Puesto que los cmdlets utilizan objetos .NET para representar a cada elemento, podemos utilizar sus propiedades y métodos para estas evaluaciones. En este ejemplo, estamos filtrando todos los elementos cuyo nombre (Name) empiece (StartsWith) por la cadena “Prueba B”. Esta sintaxis podría ser más legible (aunque no es así como suele representarse) de esta forma:

dir
| Where-Object
{
    $_.Name.StartsWith("Prueba B")
}
| Remove-Item

Podemos forzar la utilización de lambdas mediante el cmdlet ForEach-Object (alias: %), de forma que podamos ejecutar diferentes comandos sobre cada uno de los elementos entrantes, de esta forma:

PS C:\> dir | ForEach-Object { $_.Name }

Como ya hemos dicho, estos cmdlets pueden utilizarse juntos en una misma pipe, y habitualmente nos los encontraremos en su forma abreviada (utilizando sus alias), de forma similar a esta:

PS C:\> dir | ? Name –like "Prueba B*" | % { $_.Name }

Normalmente utilizaremos una sintaxis que nos resulte legible y fácil de usar, dependiendo de nuestras costumbres y de nuestra habilidad con otros lenguajes y herramientas. Sin embargo es importante conocer la sintaxis propia y estricta de PowerShell para poder interpretar correctamente scripts que encontremos en instalaciones existentes.

Parámetros

Para que nuestros scripts y cmdlets sean realmente útiles, lo razonable es que utilicen valores recibidos por parámetro. De nuevo, PowerShell tiene su propia manera de tratar este asunto, permitiéndonos desde la manera más sencilla de declarar métodos con parámetros, hasta opciones más potentes para especificar valores por defecto e incluso validaciones complejas de los valores recibidos, en la propia declaración.

La forma sencilla y obvia es incluir los parámetros y su tipo de datos en la declaración de los métodos, tal como se hace en otros lenguajes:

function Show-Message([string] $message)
{
    $message
}

Pero PS tiene, como de costumbre, su propia manera de hacerlo. La siguiente sintaxis se puede utilizar tanto en funciones como en scripts completos:

function Show-Message
{
    Param(
        [string] $message
    )
    $message
}

La ventaja de utilizar este método es que podemos incluir diferentes modificadores, valores por defecto y validaciones extra a cada parámetro:

function Get-FileContent
{
    Param(
        [Parameter(Mandatory=$false), HelpMessage='Ruta del fichero']
        [ValidateScript({ Test-Path $_ -PathType Leaf })]
        [string] $fileName = 'c:\Test.ps1'
    )
    Get-Content $fileName #lee el contenido del fichero
}

Para utilizar los ejemplos anteriores podríamos hacer las siguientes llamadas a la función (todas son válidas):

Get-FileContent #Recibirá el valor por defecto
Get-FileContent 'c:\Test.ps1' #Parámetro posicional
Get-FileContent -fileName 'c:\Test.ps1' #Parámetro por nombre

Hay mucha información sobre los diferentes atributos que podemos incluir en nuestros parámetros en la documentación y en la ayuda de PowerShell.

Hasta aquí la introducción. En los próximos capítulos entraremos en materia con más profundidad, pero ya tenemos una base de la que partir 🙂