Funciones avanzadas
Introducción
La función es una parte esencial de la programación. Es importante dominar esta noción si quiere producir código claro y avanzado. Tanto más en cuanto que las funciones avanzadas de PowerShell ofrecen un sinfín de herramientas para simplificar esta tarea, aunque pueden resultar un poco complicadas de aprender. Por lo tanto, es necesario releer este capítulo varias veces, y probar y volver a probar los ejemplos que se dan.
Las funciones avanzadas están disponibles desde la versión 2.0 de PowerShell. Reproducen un funcionamiento comparable al de los applets de comandos (cmdlet) nativos de PowerShell.
Hoy en día, la mayoría de los módulos creados por la comunidad exportan funciones avanzadas que pueden utilizarse como cmdlets nativos.
Una función avanzada tiene muchas ventajas sobre una función "básica":
-
Implementación de parámetros comunes: -Debug, -Verbose, -WhatIf, -Confirm
-
Utilización de objetos a través del pipeline
-
Definición de varios conjuntos de parámetros
-
Declaración dinámica de parámetros
-
Aplicación de la ayuda integrada
-
Opción y validación de parámetros...
Vamos a hablar de las funciones en este capítulo, pero debe saber que su arquitectura ha sido tomada de los scriptblocks. También se encuentra en los scripts de PowerShell....
Estructura
Lo que define una función como avanzada es su estructura. De hecho, es necesario añadir atributos a la estructura para que se convierta en "avanzada". Entre ellos se encuentra el atributo CmdletBinding().
He aquí un esquema de lo que resulta:
Function <Nombre_de_la_función> {
[CmdletBinding()]
Param (<lista_parámetros>)
DynamicParam {...}
Begin {...}
Process {...}
End {...}
}
También es posible convertir una función en "avanzada" añadiendo el atributo Parameter() a uno de los parámetros, por ejemplo:
Function <Nombre_de_la_función> {
Param
[parameter( mandatory = $true =
)
...
}
La opción mandatory hace que un parámetro sea obligatorio u opcional. Más adelante veremos esto de forma más detallada.
1. Param
El bloque Param se utiliza para definir todos los parámetros de una función. Los parámetros pueden definirse de dos maneras:
#Primer Método:
Function <Nombre_de_la_función> (<Bloque de_parámetros>) {
...
}
#Segundo Método:
Function <Nombre_de_la_función> {
Param (
<Bloque_de parámetros>
)
...
}
En el caso de una función avanzada, solo se puede utilizar el primer método. Basta con rellenar el bloque Param, aunque esté vacío, con [CmdletBinding()].
Se pueden indicar varios elementos para un parámetro:
-
un tipo,
-
un valor por defecto,
-
el atributo Parameter,
-
atributos de validación.
Para los dos primeros, la sintaxis es bastante común. He aquí un ejemplo con un parámetro de tipo cadena de caracteres ([string]) y ’localhost’ como valor por defecto:
Param (
[string]$ComputerName = 'localhost'
)
Veremos...
Preparación del escenario
En esta sección, los escenarios presentados agrupan sintaxis que son más globales a la función, involucrando varios de los elementos ya presentados, en particular la implementación de conjuntos de parámetros, o la aceptación de entrada por el pipeline.
1. Conjuntos de parámetros
Al diseñar una función, resulta útil trabajar con asociaciones de parámetros. Para cada grupo de parámetros, la función tiene diferentes acciones. Esta es una característica común de los cmdlets nativos de PowerShell.
Ejemplo con Get-Process
PS > get-help get-process
NOMBRE
Get-Process
RESUMEN
Gets the processes that are running on the local computer or
a remote computer.
SINTAXIS
Get-Process [[-Name] <String[]>] [-ComputerName <String[]>]
[-FileVersionInfo] [-Module] [<CommonParameters>]
Get-Process [-ComputerName <String[]>] [-FileVersionInfo] -Id
<Int32[]> [-Module] [<CommonParameters>]
Get-Process [-ComputerName <String[]>] [-FileVersionInfo]
-InputObject <Process[]> [-Module] [<CommonParameters>]
Get-Process -Id <Int32[]> -IncludeUserName [<CommonParameters>]
Get-Process [[-Name] <String[]>] -IncludeUserName
[<CommonParameters>]
Get-Process -IncludeUserName -InputObject <Process[]>
[<CommonParameters>]
Se requiere un poco de trabajo de análisis. Los tres parámetros Id, Name e InputObject no pueden utilizarse juntos. En segundo lugar, IncludeUserName no puede utilizarse con el conjunto ComputerName/Module/FileVersionInfo. Sin embargo, cada uno de los tres primeros parámetros puede utilizarse en los dos conjuntos de parámetros. Este es un caso elaborado de conjuntos de parámetros. Es posible alcanzar este nivel de complejidad utilizando funciones avanzadas. Para ello, es necesario utilizar el argumento ParameterSetName y asignarlo a un parámetro. Como recordatorio, un parámetro...
Parámetro dinámico
Hay casos de uso en los que las opciones vistas hasta ahora no son suficientes. Tomemos, por ejemplo, el caso de un parámetro cuyo contenido cambia en función del valor de otro parámetro, o en función del origen de datos (FileSystem, Registry, ActiveDirectory, etc.) en el que se encuentre el usuario.
Es en estas diferentes situaciones en las que los parámetros dinámicos pueden adaptarse y ofrecer parámetros y/o valores que cambian en función del contexto.
1. Declaración
Los parámetros dinámicos se declaran mediante la instrucción DynamicParam{}, que se coloca después del bloque Param():
Param()
DynamicParam{
...
}
Observe que DynamicParam{} se define utilizando llaves en lugar de paréntesis, a diferencia de Param(). De hecho, se trata de un bloque de script que se ejecutará de forma periódica, para generar sobre la marcha los parámetros dinámicos de la función.
Un bloque de script permite utilizar todas las funciones disponibles: comandos, funciones, estructura, comparación, etc.
Sin embargo, antes de presentar un ejemplo complejo, concentrémonos en lo mínimo necesario para crear un parámetro dinámico.
Todos los comandos que veremos en las siguientes secciones deben colocarse, por supuesto, en el bloque de script DynamicParam{...}. La sección Resumen ofrece un resumen de las tres secciones anteriores.
2. Nueva colección de atributos
El uso de atributos se explica en la sección Param de este capítulo, en la que vimos que un parámetro puede tener varios atributos ([Parameter[]], [Alias()], etc.). En el caso de los parámetros dinámicos, utilizamos una colección de atributos para agruparlos. Esto se crea con el siguiente comando:
$attributeCollection = New-Object ` System.Collections.
ObjectModel.Collection[System.Attribute]
El siguiente paso consiste en crear el equivalente del atributo [Parameter()] utilizando la función:
$parameterAttribut = New-Object ` System.Management.
Automation.ParameterAttribute
Es posible cambiar las opciones de este atributo. Encontrará entonces los mismos elementos que antes:
-
Mandatory
-
HelpMessage
-
ParameterSetName...
ArgumentCompleter y Dynamic ValidateSet
Los atributos ArgumentCompletions, ArgumentCompleter y Dynamic ValidateSet se tratan por separado, ya que tienen algunas características especiales. ArgumentCompletion se añadió con la versión 6.0 de PowerShell, y los otros dos tienen una gestión dinámica de validación o finalización.
1. ArgumentCompletions
ArgumentCompletion es similar a ValidateSet, excepto en que no comprueba el valor pasado al parámetro. Los valores que se le pasan solo se utilizan como sugerencias al autocompletar mediante la tecla [Tab].
La sintaxis sigue siendo similar:
Ejemplo
Param (
[Parameter(Mandatory=$true)]
[ArgumentCompletions('Info','Warning','Error')]
[String]$Type
)
Aquí el usuario ve aparecer las sugerencias Info, Warning y Error cuando utiliza la tecla [Tab] (autocompletado) en el parámetro -Type. Sin embargo, es perfectamente posible introducir otro valor, como Información o Advertencia.
2. ArgumentCompleter
El atributo ArgumentCompleter es más complejo de manejar que los atributos anteriores. Al igual que ValidateScript, requiere el uso de un bloque de script (scriptblock), pero le diferencia que debe devolver uno o más valores enumerados. Estos valores se utilizarán como sugerencias para el usuario cuando se llame a autocompletar, con la ventaja de que parte de la entrada del usuario puede tenerse en cuenta cuando se llama a autocompletar.
La enumeración de objetos se refiere a los elementos presentes en una colección/matriz. Estos se devuelven uno a uno en la salida, y no en un solo bloque. En el caso de ArgumentCompleter, debe respetarse la enumeración, ya que de lo contrario se corre el riesgo de acabar con todos los valores en cuanto se pulse la tecla [Tab].
También es posible utilizar una clase PowerShell en lugar de un bloque de script. Para obtener más información, consulte el capítulo Clases - Uso de clases para la validación de parámetros.
a. Estructura
El bloque de script debe incluir un bloque Param con cinco parámetros:
[ArgumentCompleter(
{
param (
$commandName,
$parameterName, ...
Register-ArgumentCompleter
El comando Register-ArgumentCompleter registra un completador de argumentos (argument completer) personalizado. La idea es hacer lo mismo que el atributo ArgumentCompleter visto en la sección anterior.
La diferencia es que el atributo se coloca en un parámetro de una función que usted ha escrito mientras que el comando Register-ArgumentCompleter tiene un alcance más amplio. Es posible colocar Argument Completer en comandos nativos de PowerShell, como Get-Service o Get-Process así como interactuar con binarios como ping.exeo nslookup.exe.
En esta sección, nos centraremos principalmente en la integración con comandos PowerShell y no con ejecutables.
Register-ArgumentCompleter también se puede utilizar para anular el autocompletado ya existente para un parámetro. Por ejemplo, el comando Start-Service tiene autocompletado el parámetro -Name para todos los servicios presentes en la máquina, independientemente de su estado de ejecución. Para simplificar la tarea, es posible registrar un Argument Completer que solo proponga servicios en estado detenido y no en estado de inicio desactivado.
Este es el comando que devuelve los servicios que cumplen estos criterios:
Get-Service | Where-Object {
$_.StartType -ne 'Disabled' -and $_.Status -eq 'Stopped'
}
Register-ArgumentCompleter cumple los mismos requisitos...
Splatting
El splatting es una función disponible desde PowerShell V2.0, pero recomendada para su uso en PowerShell V3.0. ¿Qué hace exactamente?
El primer beneficio directo es la minimización de la entrada de parámetros cuando se utiliza un comando. Algunos comandos tienen un número bastante alto de parámetros obligatorios, que pueden resultar rápidamente ilegibles, a pesar del uso del carácter ` ([Alt Gr]+7).
Ejemplo con el comando Send-MailMessage
Send-MailMessage -Body 'Hello' `
-From nicolas@akalya.fr `
-SmtpServer mail.akalya.fr `
-Subject 'Enviado por PowerShell' `
-To stephane.vangulick@frpsug.fr `
-Port 465 `
-UseSsl
En otro caso, a veces se tiene que reutilizar un comando varias veces con los mismos parámetros. Normalmente, los parámetros se definen uno a uno. Lo mejor es centralizar los valores en variables y luego asignarlos a los parámetros de cada comando.
Ejemplo
$ForegrounColor = 'Yellow'
Write-host 'Hello' -ForegroundColor $ForegrounColor
Write-Host 'World' -ForegroundColor $ForegrounColor
Sin embargo, si necesita añadir un parámetro a todos los comandos...