Más sobre los parámetros

Hasta ahora hemos visto que los parámetros pueden tener atributos, como Mandatory, ValueFromPipeline, etc:

1 ...
2 Param(
3     [Parameters(Mandatory = $true)]
4     [string[]]$ComputerName
5     ...
6 )
7 ...

Pero tenemos muchas otras opciones, como validación de los parámetros y algunos otros que vamos a ver a continuación.

Puedes consultar información adicional sobre los atributos de los parámetros mediante Get-Help about_functions_advanced_parameters

Podemos usar el pipeline para redirigir la ayuda sobre un cmdlet al portapapeles mediante: powershell PS> Get-Help about_functions | clip A continuación, podemos pegar la información de la ayuda en un editor de texto para facilitar su lectura.

Vamos a seguir con el mismo código del cmdlet Get-CompInfo del apartado anterior, pero vamos a centrarnos ahora en los parámetros.

 1 ...
 2 [CmdletBinding()]
 3 Param
 4 (
 5     # Queremos soporte para varios equipos
 6     [string[]]$ComputerName,
 7     # Interruptor para activar el log de errores
 8     [switch]$ErrorLog,
 9     [string]$LogFile = 'c:\errorlog.txt'
10 )
11 ...

Parámetros obligatorios

En primer lugar, indicamos que el parámetro $ComputerName es obligatorio mediante:

 1 ...
 2 [CmdletBinding()]
 3 Param
 4 (
 5     # Queremos soporte para varios equipos
 6     [Parameter(Mandatory=$true)]
 7     [string[]]$ComputerName,
 8     # Interruptor para activar el log de errores
 9     [switch]$ErrorLog,
10     [string]$LogFile = 'c:\errorlog.txt'
11 )
12 ...

Ahora, al ejecutar Get-CompInfo sin especificar nigún parámetro, PowerShell nos pregunta sobre el parámetro obligatorio:

1 PS> Get-CompInfo 
2 cmdlet Get-CompInfo at command pipeline position 1
3 Supply values for the following parameters:
4 ComputerName[0]: 

Parámetros a través del pipeline

Como hemos indicado que $ComputerName puede contener múltiples valores, nos pide el primero. Cuando lo introducimos, nos pide un segundo valor. El cmdlet seguirá preguntando hasta que pulsemos Intro sin proporcionar un valor, indicando que hemos acabado.

Si queremos que el parámetro acepte valores desde el pipeline, lo indicamos mediante:

 1 ...
 2 [CmdletBinding()]
 3 Param
 4 (
 5     # Queremos soporte para varios equipos
 6     [Parameter( Mandatory=$true,
 7                 ValueFromPipeline = $true,
 8                 ValueFromPipelineByPropertyName = $true)]
 9     [string[]]$ComputerName,
10     # Interruptor para activar el log de errores
11     [switch]$ErrorLog,
12     [string]$LogFile = 'c:\errorlog.txt'
13 )
14 ...

Separamos los diferentes atributos para un parámetro mediante comas.

La diferencia entre ValueFromPipeline y ValueFromPipelineByPropertyName estriba en que en el primer caso el cmdlet espera recibir un objeto del mismo tipo, mientras que en el segundo, si no recibe un objecto exactamente del mismo tipo, busca una propiedad que se llame como alguno de los parámetros del cmdlet.

La diferencia se explica en detalle en el curso de la Microsoft Virtual Academy Tools & Scripting with PowerShell 3.0 Jump Start

Ayuda para los parámetros

Otro añadido interesante a los parámetros de la función es proporcionar ayuda al usuario del cmdlet.

 1 ...
 2 [CmdletBinding()]
 3 Param
 4 (
 5     # Queremos soporte para varios equipos
 6     [Parameter( Mandatory=$true,
 7                 ValueFromPipeline = $true,
 8                 ValueFromPipelineByPropertyName = $true,
 9                 HelpMessage = 'Uno o mas nombres de equipos')]
10     [string[]]$ComputerName,
11     # Interruptor para activar el log de errores
12     [switch]$ErrorLog,
13     [string]$LogFile = 'c:\errorlog.txt'
14 )
15 ...

Si ejecutamos de nuevo el cmdlet, como ahora hemos definido un mensaje de ayuda, PowerShell muestra !? para que podamos obtener ayuda al respecto:

1 PS> Get-CompInfo
2 cmdlet Get-CompInfo at command pipeline position 1
3 Supply values for the following parameters:
4 (Type !? for Help.)
5 ComputerName[0]: !?
6 Uno o mas nombres de equipos
7 ComputerName[0]: 

Al introducir !? se muestra el mensaje de ayuda que hemos especificado. Por tanto, es conveniente especificar un mensaje de ayuda que sea útil y claro.

Alias

Como PowerShell tiene una política de nombres estricta, para ayudar a aquellas personas que están acostumbradas a usar, por ejemplo hostname y no ComputerName, se pueden definir alias para los parámetros.

Además, definir alias para los parámetros también permite que los cmdlets encajen unos con otros más fácilmente a través del pipeline.

Si un cmdlet envía a través del pipeline un objeto que no dispone de una propiedad con el nombre que espera el cmdlet de destino, el cmdlet fallaría. Sin embargo, podemos crear un alias para la propiedad que contiene la información que nos interesa del objeto que llega a través del pipeline, de manera que, gracias al alias creado para el parámetro, pueda pasar la información al cmdlet de destino.

 1 ...
 2 [CmdletBinding()]
 3 Param
 4 (
 5     # Queremos soporte para varios equipos
 6     [Parameter( Mandatory=$true,
 7                 ValueFromPipeline = $true,
 8                 ValueFromPipelineByPropertyName = $true,
 9                 HelpMessage = 'Uno o mas nombres de equipos')]
10     [Alias('Hostname')]
11     [string[]]$ComputerName,
12     # Interruptor para activar el log de errores
13     [switch]$ErrorLog,
14     [string]$LogFile = 'c:\errorlog.txt'
15 )
16 ...

Al haber definido el alias Hostname para el parámetro ComputerName, ahora podemos ejecutar el cmdlet usando cualquiera de los dos nombres para el parámetro:

1 PS> Get-CompInfo -ComputerName
1 PS> Get-CompInfo -Hostname

En ISE, IntelliSense permite autocompletar el nombre del parámetro con cualquiera de los dos nombres asignados al parámetro.

Validación de los parámetros

Podemos establecer un conjunto de valores válidos para los valores introducidos en los parámetros. Si el valor introducido no es uno de los valores válidos, se genera un error.

Establecemos la validación de un parámetro mediante alguna de las reglas de validación que nos ofrece PowerShell; por ejemplo, que el valor introducido sea alguno de los valores especificado mediante ValidateSet():

 1 ...
 2 [CmdletBinding()]
 3 Param
 4 (
 5     # Queremos soporte para varios equipos
 6     [Parameter( Mandatory=$true,
 7                 ValueFromPipeline = $true,
 8                 ValueFromPipelineByPropertyName = $true,
 9                 HelpMessage = 'Uno o mas nombres de equipos')]
10     [Alias('Hostname')]
11     [ValidateSet('localhost', '.')]
12     [string[]]$ComputerName,
13     # Interruptor para activar el log de errores
14     [switch]$ErrorLog,
15     [string]$LogFile = 'c:\errorlog.txt'
16 )
17 ...

Si ejecutamos el cmdlet Get-CompInfo en la consola de ISE, observamos que se proporciona IntelliSense con las diferentes opciones válidas para el parámetro que estamos especificando.

Si pese a todo introducimos un valor diferente, obtenemos un error:

1 PS> Get-CompInfo -ComputerName dc
2 Get-CompInfo : Cannot validate argument on parameter 'ComputerName'. The argu\
3 ment "dc" does not belong to the set "localhost,." specified by the ValidateS\
4 et attribute. Supply an argument that is in the set and then try the command \
5 again.

En el caso de disponer únicamente de un conjunto válido de opciones para un parámetro, las especificamos como un conjunto de validación.

Otros conjuntos de validación sería, por ejemplo, el número de parámetros que pueden validarse.

Por ejemplo si sólo queremos admitir un máximo de 2 nombres de equipos remotos, podemos hacer:

1 ...
2 [ValidateCount(0,2)]
3 [string[]]$ComputerName,
4 ...

Podemos validar la entrada de forma general mediante ValidatePattern(), usando expresiones regulares.

Por ejemplo, para realizar una primera aproximación para la validación de una dirección IP podemos usar una expresión regular como (aunque sigue permitiendo introducir direcciones no válidas como 999.999.999.999:

1 ...
2 [ValidatePattern("\b\d{1,3}\.\d{1,3}\.\d{1,3}\b")]
3 [string]$ip
4 ...

Otra de las reglas de validación extremadamente potente es usar un script para validar la entrada de datos, con lo que las posibilidades de validación son infinitas.

1 ...
2 [ValidateScript( { test-path $_ } )]
3 [string]$path
4 ...

En la ayuda: Get-Help about_functions_advanced_parameters disponemos de la lista completa de reglas de validación de parámetros que podemos usar en PowerShell.

Escribiendo ayuda

Habitualmente escribir ayuda es una de esas tareas que cuando se empieza en el mundo del scripting no se tiene en cuenta: simplemente se escribe el script que soluciona el problema y se olvida.

Pero con PowerShell escribir ayuda es tan sencillo que ya no hay excusas para no hacerlo.

Una de las manera más sencillas de escribir ayuda en un script PowerShell es mediante la ayuda basada en los comentarios. Es decir, el sistema de ayuda de PowerShell obtiene información relativa al script a través de los comentarios en el propio script.

Puedes encontrar toda la información al respecto mediante get-help about_Comment_Based_Help.

La ayuda basada en comentarios ofrece la información acerca de tu script estructurada de la misma forma que la ayuda de cualquier otro cmdlet.

Sin embargo, hay algunas limitaciones en cuanto a las posibilidades que ofrece la ayuda basada en comentarios.

La actualización de la ayuda mediante update-help no funciona con la ayuda basada en comentarios; sólo funciona para ayuda escrita en formato XML (que es la otra forma de escribir ayuda para los scripts en PowerShell). La ayuda en formato XML se puede colgar en una URL pública, de manera que los clientes pueden descargar una versión actualizada en sus equipos.

Sin embargo, la ayuda basada en XML es más complicada de escribir y, en general, sólo tiene sentido si hay una compañía que vende los scripts e interesada en mantenerlos por dinero.

Ayuda basada en comentarios

Lo primero a destacar de la ayuda basada en comentarios es que existen dos maneras de comentar en PowerShell.

En primer lugar, podemos comentar una línea predeciendo el comentario del símbolo #.

Pero para la ayuda basada en comentarios vamos a usar los bloques de comentarios, que se extienden varias líneas:

Este fragmento de código viene del snippet para una función avanzada de ISE.

 1 <#
 2 .Synopsis
 3    Short description
 4 .DESCRIPTION
 5    Long description
 6 .EXAMPLE
 7    Example of how to use this cmdlet
 8 .EXAMPLE
 9    Another example of how to use this cmdlet
10 #>
11 function Verb-Noun
12 {
13     [CmdletBinding()]
14     [Alias()]
15     [OutputType([int])]
16     Param
17     (
18         # Param1 help description
19         [Parameter(Mandatory=$true,
20                    ValueFromPipelineByPropertyName=$true,
21                    Position=0)]
22         $Param1,
23 
24         # Param2 help description
25         [int]
26         $Param2
27     )
28 
29     Begin {}
30     Process {}
31     End {}
32 }

Si ejecutamos Get-Help verb-noun (que es el nombre del cmdlet en esta función), obtenemos:

 1 PS C:\Windows\system32> Get-Help Verb-Noun
 2 
 3 NAME
 4     Verb-Noun
 5     
 6 SYNOPSIS
 7     Short description
 8     
 9     
10 SYNTAX
11     Verb-Noun [-Param1] <Object> [-Param2 <Int32>] [<CommonParameters>]
12     
13 DESCRIPTION
14     Long description
15 
16 RELATED LINKS
17 
18 REMARKS
19     To see the examples, type: "get-help Verb-Noun -examples".
20     For more information, type: "get-help Verb-Noun -detailed".
21     For technical information, type: "get-help Verb-Noun -full".
22 
23 PS C:\Windows\system32> 

Es decir, sin haber tenido que realizar ninguna acción especial, vemos que el sistema de ayuda integrado en PowerShell ha descubierto y formateado la información proporcionada en el bloque de inicial de comentarios.

En la ayuda vemos que se indican los nombres y tipos de los parámetros, si son obligatorios u opcionales e incluso cómo obtener información adicional sobre ejemplos o la ayuda detallada.

Toda esta información se obtiene directamente del bloque de comentarios del script.

Puedes comprobar que esta información se actualiza a medida que se modifica el script (por ejemplo cuando especificas un nuevo parámetro, el tipo de una variable, etc).

Volviendo al script Get-CompInfo:

 1 <#
 2 .Synopsis
 3    Obtiene información sobre los equipos indicados.
 4 .DESCRIPTION
 5    El script obtiene información detallada de:
 6    * El sistema operativo
 7    * La versión de build del SO.
 8    * El tamaño del disco c:
 9    * El espacio libre en el disco
10 .EXAMPLE
11     Get-CompInfo -ComputerName localhost
12 #>
13 ...

Al ejecutar get-hep get-compinfo:

 1 PS> Get-Help Get-CompInfo
 2 NAME
 3     Get-CompInfo
 4 SYNOPSIS
 5     Obtiene información sobre los equipos indicados.
 6 SYNTAX
 7     Get-CompInfo [-ComputerName] <String[]> [-ErrorLog] [[-LogFile] <String>]\
 8  [<CommonParameters>]
 9 DESCRIPTION
10     El script obtiene información detallada de:
11     * El sistema operativo
12     * La versión de build del SO.
13     * El tamaño del disco c:
14     * El espacio libre en el disco
15 RELATED LINKS
16 
17 REMARKS
18     To see the examples, type: "get-help Get-CompInfo -examples".
19     For more information, type: "get-help Get-CompInfo -detailed".
20     For technical information, type: "get-help Get-CompInfo -full".

En el bloque de comentarios hemos incluido un ejemplo de uso; vamos a usar get-help get-compinfo -examples, como se sugiere al final de la ayuda para obtener ejemplos:

1 PS> Get-Help Get-CompInfo -Examples
2 NAME
3     Get-CompInfo
4 SYNOPSIS
5     Obtiene información sobre los equipos indicados.
6     -------------------------- EXAMPLE 1 --------------------------
7     PS C:\>Get-CompInfo -ComputerName localhost

Vemos que obtenemos la descripción contenida en el bloque de ayuda, pero además se muestra información acerca del uso del comando.

Gracias al sistema de ayuda de PowerShell, sin tener que escribir más que algunas líneas de comentarios en el script -que deberíamos incluir en cualquier caso-, podemos sacar el máximo rendimiento de esta ayuda implícita basada en los comentarios del script.

La separación entre el bloque de comentarios y la función que documenta puede ser como máximo de una línea en blanco; si hay dos o más líneas en blanco, PowerShell no es capaz de relacionar el bloque de comentarios como ayuda para la función que lo sucede.

Además de colocar el bloque de ayuda antes de la función, también es posible insertar el bloque de ayuda en la función:

 1 ...
 2 Function Verbo-Nombre {
 3     <#
 4     .Synopsis
 5        Obtiene información sobre los equipos indicados.
 6     ...
 7     #>
 8 
 9     # code
10 }
11 ...

Finalmente, también puede colocarse al final de la función, siempre y cuando el cierre del bloque de comentarios esté en la línea anterior al cierre de la función.

Aunque no hay una forma mejor que otra, como en ISE el snippet para la función avanzada incluye el bloque de ayuda al principio de la función, ésta es la forma más extendida.

La idea tras el sistema de ayuda, especialmente con los ejemplos, es proporcionar ejemplos de cómo se supone que se va a usar el cmdlet en situaciones reales.

Uno de los parámetros de get-help es -ShowCommand. Este parámetro muestra la ayuda en una ventana independiente, lo que facilita su lectura. Además, esta ventana contiene una caja de búsqueda, con la que podemos encontrar con más facilidad el contenido que buscamos.

El sistema de ayuda no sólo obtiene información de los bloques de comentarios al princio (o al final) de la función. En el caso de los parámetros, si antes de la definición del parámetro añadimos un comentario, el sistema de ayuda lo interpreta como información interesante sobre el parámetro y lo muestra en la ayuda:

1 ...
2 	# Indica el nombre del equipo del que obtendremos la información
3     [string[]]$ComputerName
4 ...

En la ayuda detallada get-help get-compinfo -detailed, observaremos:

1 ...
2 PARAMETERS
3     -ComputerName <String[]>
4         Indica el nombre del equipo del que obtendremos la información
5 ...