Parámetros de PowerShell: Desbloquea el Poder del Scripting

Todos los comandos de PowerShell pueden tener uno o más parámetros, a veces llamados argumentos. Si no estás utilizando parámetros de PowerShell en tus funciones de PowerShell, no estás escribiendo un buen código en PowerShell.

En este artículo, aprenderás prácticamente todos los aspectos de la creación y el uso de parámetros o argumentos en PowerShell.

Este es un ejemplo de mi libro PowerShell for SysAdmins. Si deseas aprender PowerShell o conocer algunos trucos del oficio, ¡échale un vistazo!

¿Por qué necesitas un parámetro?

Cuando comiences a crear funciones, tendrás la opción de incluir parámetros o no, y cómo funcionarán esos parámetros.

Imagina que tienes una función que instala Microsoft Office. Tal vez llama al instalador de Office de manera silenciosa dentro de la función. Lo que hace la función no importa para nuestros propósitos. La función básica se ve así con el nombre de la función y el bloque de script.

function Install-Office {
    ## ejecutar el instalador silencioso aquí
}

En este ejemplo, acabas de ejecutar Install-Office sin parámetros y hace su trabajo.

No importa si la función Install-Office tenía parámetros o no. Aparentemente, no tenía ningún parámetro obligatorio; de lo contrario, PowerShell no nos habría permitido ejecutarlo sin usar un parámetro.

Cuándo utilizar un parámetro de PowerShell

Office tiene muchas versiones diferentes. Quizás necesites instalar Office 2013 y 2016. Actualmente, no tienes forma de especificar esto. Podrías cambiar el código de la función cada vez que quisieras cambiar el comportamiento.

Por ejemplo, podrías crear dos funciones separadas para instalar diferentes versiones.

function Install-Office2013 {
    Write-Host 'I installed Office 2013. Yippee!'
}

function Install-Office2016 {
    Write-Host 'I installed Office 2016. Yippee!'
}

Hacer esto funciona, pero no es escalable. Te obliga a crear una función separada para cada versión de Office que salga. Vas a tener que duplicar mucho código cuando no tienes que hacerlo.

En su lugar, necesitas una forma de pasar diferentes valores en tiempo de ejecución para cambiar el comportamiento de la función. ¿Cómo haces esto?

¡Sí! Parámetros o como algunos les llaman argumentos.

Dado que nos gustaría instalar diferentes versiones de Office sin cambiar el código cada vez, tienes que agregar al menos un parámetro a esta función.

Antes de que pienses rápidamente en un parámetro de PowerShell para usar, es esencial que primero te preguntes; “¿Cuál es el cambio o cambios más pequeños que esperas serán necesarios en esta función?”.

Recuerda que necesitas volver a ejecutar esta función sin cambiar ninguno de los códigos dentro de la función. En este ejemplo, el parámetro probablemente te sea claro; necesitas agregar un parámetro Version. Pero, cuando tienes una función con decenas de líneas de código, la respuesta no será tan evidente. Mientras respondas esa pregunta con la mayor precisión posible, siempre ayudará.

Así que sabes que necesitas un parámetro Version. ¿Y ahora qué? Ahora puedes agregar uno, pero como en cualquier gran lenguaje de programación, hay múltiples formas de lograr eso.

En este tutorial, te mostraré la “mejor” manera de crear parámetros basados en casi una década de experiencia con PowerShell. Sin embargo, ten en cuenta que esta no es la única forma de crear un parámetro.

Existen los parámetros posicionales. Estos parámetros te permiten pasar valores a los parámetros sin especificar el nombre del parámetro. Los parámetros posicionales funcionan, pero no se consideran la “mejor práctica”. ¿Por qué? Porque son más difíciles de leer, especialmente cuando tienes muchos parámetros definidos en una función.

Creación de un parámetro simple en PowerShell

Crear un parámetro en una función requiere dos componentes principales; un bloque de parámetros y el parámetro en sí. Un bloque de parámetros se define con la palabra clave param seguida de un conjunto de paréntesis.

An example of a param block
function Install-Office { [CmdletBinding()] param() Write-Host 'I installed Office 2016. Yippee!' }

En este punto, la funcionalidad real de la función no ha cambiado en absoluto. Simplemente hemos reunido algunas piezas, preparándonos para el primer parámetro.

Una vez que tenemos el bloque de parámetros en su lugar, ahora crearás el parámetro. El método que te sugiero para crear un parámetro incluye el bloque Parameter, seguido del tipo de parámetro, seguido del nombre de la variable del parámetro debajo.

An example of a param block
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host 'I installed Office 2016. Yippee!' }

Ahora hemos creado un parámetro de función en PowerShell, pero ¿qué sucedió exactamente aquí?

El bloque Parameter es una pieza opcional pero recomendada de cada parámetro. Al igual que el bloque param, es un “plumbing” de función que prepara el parámetro para agregar funcionalidad adicional. La segunda línea es donde defines el tipo de parámetro que es.

En este caso, hemos optado por convertir el parámetro Versión a una cadena. Definir un tipo explícito significa que cualquier valor pasado a este parámetro siempre intentará ser “convertido” a una cadena si aún no lo es.

El tipo no es obligatorio pero es muy recomendado. Definir explícitamente el tipo de parámetro que es reducirá significativamente muchas situaciones no deseadas más adelante. Créeme.

Ahora que has definido el parámetro, puedes ejecutar el comando Install-Office con el parámetro Versión pasándole una cadena de versión como 2013. El valor pasado al parámetro Versión a veces se refiere como argumentos o valores de parámetros.

Passing a Parameter to the Function
PS> Install-Office -Version 2013 I installed Office 2016. Yippee!

¿Qué está pasando aquí de todos modos? Le dijiste que querías instalar la versión 2013, pero aún te está diciendo que ha instalado la 2016. Cuando añades un parámetro, entonces debes recordar cambiar el código de la función a una variable. Cuando el parámetro se pasa a la función, esa variable se expandirá para ser cualquier valor que se haya pasado.

Cambia el texto estático de 2016 y reemplázalo con la variable del parámetro Versión y convierte las comillas simples en comillas dobles para que la variable se expanda.

Modifying function code account for a parameter
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" }

Ahora puedes ver que cualquier valor que pases al parámetro Versión será luego pasado a la función como la variable $Versión.

El Atributo de Parámetro Obligatorio

Recuerda que mencioné que la línea [Parameter()] era simplemente “fontanería de funciones” y que era necesario preparar la función para un trabajo posterior. Añadir atributos al parámetro es el trabajo adicional del que hablaba anteriormente.

A parameter doesn’t have to be a placeholder for a variable. PowerShell has a concept called parameter attributes and parameter validation. Parameter attributes change the behavior of the parameter in a lot of different ways.

Por ejemplo, uno de los atributos de parámetro más comunes que configurarás es la palabra clave Mandatory. Por defecto, podrías llamar a la función Install-Office sin usar el parámetro Version y se ejecutaría correctamente. El parámetro de versión sería opcional. Es cierto que no ampliaría la variable $Version dentro de la función porque no había ningún valor, pero aún así se ejecutaría.

En muchas ocasiones, al crear un parámetro, querrás que el usuario siempre use ese parámetro. Dependerás del valor del parámetro en algún lugar del código de la función, y si el parámetro no se pasa, la función fallará. En estos casos, quieres forzar al usuario a pasar este valor de parámetro a tu función. Quieres que ese parámetro se vuelva obligatorio.

Obligar a los usuarios a usar un parámetro es simple una vez que tienes el marco básico construido como lo tienes aquí. Debes incluir la palabra clave Mandatory dentro de los paréntesis del parámetro. Una vez que lo tengas en su lugar, ejecutar la función sin el parámetro detendrá la ejecución hasta que se haya introducido un valor.

Using a mandatory parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" } PS> Install-Office cmdlet Install-Office at command pipeline position 1 Supply values for the following parameters: Version:

La función esperará hasta que especifiques un valor para el parámetro Version. Una vez que lo hagas y presiones Enter, PowerShell ejecutará la función y continuará. Si proporcionas un valor para el parámetro, PowerShell no te pedirá el parámetro cada vez.

Atributos de Validación de Parámetros en PowerShell

Hacer obligatorio un parámetro es uno de los atributos de parámetro más comunes que puedes agregar, pero también puedes usar atributos de validación de parámetros. En programación, siempre es esencial restringir la entrada del usuario lo más posible. Limitar la información que los usuarios (¡o incluso tú!) pueden pasar a tus funciones o scripts eliminará código innecesario dentro de tu función que debe tener en cuenta todo tipo de situaciones.

Aprendizaje a través de Ejemplos

Por ejemplo, en la función Install-Office, demostré pasar el valor 2013 porque sabía que funcionaría. ¡Yo escribí el código! Estoy asumiendo (¡nunca hagas esto en el código!) que es obvio que cualquiera que sepa algo especificaría la versión como 2013 o 2016. Bueno, lo que es obvio para ti puede no ser tan obvio para otras personas.

Si quieres ser técnico acerca de las versiones, probablemente sería más preciso especificar la versión 2013 como 15.0 y 2016 como 16.0 si Microsoft todavía estuviera utilizando el esquema de versiones que han usado en el pasado. Pero, ¿qué pasa si, ya que estás asumiendo que van a especificar una versión de 2013 o 2016, tienes código dentro de la función que busca carpetas con esas versiones o algo más?

A continuación, se muestra un ejemplo de cómo podrías estar usando la cadena $Version en una ruta de archivo. Si alguien pasa un valor que no completa un nombre de carpeta de Office2013 o Office2016, fallará o hará algo peor, como eliminar carpetas inesperadas o cambiar cosas que no tuviste en cuenta.

Assuming Parameter Values
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version '15.0' Get-ChildItem : Cannot find path '\SRV1\Installers\Office15.0' because it does not exist. At line:7 char:5 Get-ChildItem -Path "\\SRV1\Installers\Office$Version" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CategoryInfo : ObjectNotFound: (\SRV1\Installers\Office15.0:String) [Get-ChildItem], ItemNotFoundExcep tion FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

Para limitar al usuario a lo que esperas que ingrese, puedes agregar alguna validación de parámetros en PowerShell.

Utilizando el Atributo de Validación de Parámetros ValidateSet

Existen varios tipos de validación de parámetros que puedes utilizar. Para obtener una lista completa, ejecuta Get-Help about_Functions_Advanced_Parameters. En este ejemplo, probablemente el atributo ValidateSet sería el más adecuado.

El atributo de validación ValidateSet te permite especificar una lista de valores permitidos como valor del parámetro. Dado que solo estamos considerando la cadena 2013 o 2016, me gustaría asegurarme de que el usuario solo pueda especificar estos valores. De lo contrario, la función fallará de inmediato, notificándoles el motivo.

Puedes agregar atributos de validación de parámetros justo debajo de la palabra clave original Parameter. En este ejemplo, dentro de los paréntesis del atributo del parámetro, tienes un array de elementos; 2013 y 2016. Un atributo de validación de parámetros le indica a PowerShell que los únicos valores válidos para Versión son 2013 o 2016. Si intentas pasar algo que no esté en el conjunto, recibirás un error que te notificará que solo tienes un número específico de opciones disponibles.

Using the ValidateSet parameter validation attribute
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version 15.0 Install-Office : Cannot validate argument on parameter 'Version'. The argument "15.0" does not belong to the set "2013,2016" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again. At line:1 char:25 Install-Office -Version 15.0 ~~~~ CategoryInfo : InvalidData: (:) [Install-Office], ParameterBindingValidationException FullyQualifiedErrorId : ParameterArgumentValidationError,Install-Office

El atributo ValidateSet es un atributo de validación común para usar. Para obtener un desglose completo de todas las formas en que se pueden restringir los valores de los parámetros, consulte el tema de ayuda Functions_Advanced_Parameters ejecutando Get-Help about_Functions_Advanced_Parameters.

Conjuntos de parámetros

Digamos que solo desea que se utilicen ciertos parámetros de PowerShell con otros parámetros. Tal vez haya agregado un parámetro Path a la función Install-Office. Este camino instalará cualquier versión que tenga el instalador. En este caso, no desea que el usuario use el parámetro Version.

Necesitas conjuntos de parámetros.

Los parámetros se pueden agrupar en conjuntos que solo se pueden usar con otros parámetros en el mismo conjunto. Usando la función a continuación, ahora puede usar tanto el parámetro Version como el parámetro Path para obtener el camino hacia el instalador.

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory)]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

Esto plantea un problema, sin embargo, porque el usuario podría usar ambos parámetros. Además, dado que ambos parámetros son obligatorios, se verán obligados a usar ambos cuando eso no es lo que desea. Para solucionar esto, podemos colocar cada parámetro en un conjunto de parámetros como se muestra a continuación.

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ParameterSetName = 'ByVersion')]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory, ParameterSetName = 'ByPath')]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

Al definir un nombre de conjunto de parámetros en cada parámetro, esto le permite controlar grupos de parámetros juntos.

El conjunto de parámetros predeterminado

¿Qué pasa si el usuario intenta ejecutar Install-Office sin parámetros? Esto no está previsto, y verá un mensaje de error amigable.

No parameter set

Para solucionar esto, necesitarás definir un conjunto de parámetros predeterminado dentro del área de CmdletBinding(). Esto le indica a la función que elija un conjunto de parámetros para usar si no se usaron parámetros explícitamente cambiando [CmdletBinding()] a [CmdletBinding(DefaultParameterSetName = 'ByVersion')]

Ahora, cada vez que ejecutes Install-Office, solicitará el parámetro Version ya que estará utilizando ese conjunto de parámetros.

Entrada en canalización

En los ejemplos hasta ahora, has estado creando funciones con un parámetro de PowerShell que solo se puede pasar usando la sintaxis típica -NombreParámetro Valor. Pero, como ya has aprendido, PowerShell tiene una canalización intuitiva que te permite pasar objetos de un comando a otro sin usar la sintaxis “típica”.

Cuando usas la canalización, “encadenas” comandos con el símbolo de tubería | permitiendo a un usuario enviar la salida de un comando como Get-Service a Start-Service como un atajo para pasar el parámetro Nombre a Start-Service.

La “Antigua” Manera Usando un Bucle

En la función personalizada en la que estás trabajando; estás instalando Office y tienes un parámetro Versión. Digamos que tienes una lista de nombres de equipos en un archivo CSV en una fila con la versión de Office que necesita ser instalada en ellos en la segunda fila. El archivo CSV se ve algo así:

ComputerName,Version
PC1,2016
PC2,2013
PC3,2016

Te gustaría instalar la versión de Office que está al lado de cada equipo en ese equipo.

Primero, tendrás que agregar un parámetro ComputerName a la función para pasar un nombre de computadora diferente en cada iteración de la función. A continuación, he creado un seudocódigo que representa algún código que puede estar en la función ficticia y agregado una instancia de Write-Host para ver cómo se expanden las variables dentro de la función.

Adding the ComputerName parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version, [Parameter()] [string]$ComputerName ) <# ## Conéctate al remoto con algún código aquí Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Haz cosas para instalar la versión de Office en esta computadora Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

Una vez que hayas agregado el parámetro ComputerName a la función, podrías hacer que esto suceda leyendo el archivo CSV y pasando los valores del nombre de la computadora y la versión a la función Install-Office.

$computers = Import-Csv -Path 'C:\ComputerOfficeVersions.csv'
foreach ($pc in $computers) {
    Install-Office -Version $_.Version -ComputerName $_.ComputerName
}

Creación de Entrada de Tubería para Parámetros

Este método de lectura de las filas del CSV y usando un bucle para pasar las propiedades de cada fila a la función es la forma “antigua” de hacerlo. En esta sección, deseas prescindir completamente del bucle foreach y usar la tubería en su lugar.

Tal como está, la función no admite la tubería en absoluto. Tendría sentido intuitivo asumir que podrías pasar cada nombre de computadora y versión a la función usando la tubería. A continuación, estamos leyendo el CSV y pasándolo directamente a Install-Office, pero esto no funciona.

No function pipeline input defined
PS> Import-Csv -Path 'C:\ComputerOfficeVersions.csv' | Install-Office

Puedes asumir lo que quieras, pero eso no significa que funcione automáticamente. Nos están pidiendo el parámetro Version cuando sabes que Import-Csv lo está enviando como una propiedad del objeto. ¿Por qué no funciona? Porque aún no has añadido soporte para el pipeline.

Existen dos tipos de entrada de pipeline en una función de PowerShell; ByValue (objeto completo) y ByPropertyName (una única propiedad del objeto). ¿Cuál crees que es la mejor manera de unir la salida de Import-Csv con la entrada de Install-Office?

Sin ninguna refactorización, podrías usar el método ByPropertyName, ya que, después de todo, Import-Csv ya está devolviendo las propiedades Version y ComputerName, ya que son columnas en el CSV.

Agregar soporte para el pipeline a una función personalizada es mucho más simple de lo que puedas estar pensando. Es simplemente un atributo de parámetro representado con una de dos palabras clave; ValueFromPipeline o ValueFromPipelineByPropertyName.

En el ejemplo, quieres vincular las propiedades ComputerName y Version devueltas por Import-Csv a los parámetros Version y ComputerName de Install-Office, así que usarás ValueFromPipelineByPropertyName.

Dado que deseas vincular ambos parámetros, añadirás esta palabra clave a ambos parámetros, como se muestra a continuación, y volverás a ejecutar la función utilizando el pipeline.

Adding Pipeline Support
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) <# ## Conéctese al remoto con algún código aquí Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Realice acciones para instalar la versión de Office en esta computadora Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

Eso es extraño. Solo se ejecutó para la última fila en el CSV. ¿Qué está pasando? Simplemente ejecutó la función para la última fila porque omitió un concepto que no es necesario al construir funciones sin soporte de canalización.

¡No olvides el bloque de proceso!

Cuando necesitas crear una función que involucra soporte de canalización, debes incluir (como mínimo) un bloque “incorporado” dentro de tu función llamado process. Este bloque de proceso le indica a PowerShell que al recibir entrada de canalización, ejecute la función para cada iteración. Por defecto, solo ejecutará la última.

Técnicamente podrías agregar otros bloques como begin y end también, pero los guionistas no los utilizan tan a menudo.

Para indicarle a PowerShell que ejecute esta función para cada objeto que ingrese, agregaré un bloque process que incluya el código dentro de eso.

Adding the process block
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) process { <# ## Conéctese al remoto con algún código aquí Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## Realice acciones para instalar la versión de Office en esta computadora Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" } }

Ahora puedes ver que las propiedades Version y ComputerName de cada objeto devuelto por Import-Csv se pasaron a Install-Office y se vincularon a los parámetros Version y ComputerName.

Recursos

Para profundizar en cómo funcionan los parámetros de función, echa un vistazo a mi publicación en el blog sobre funciones de PowerShell.

I also encourage you to check my Pluralsight course entitled Building Advanced PowerShell Functions and Modules for an in-depth breakdown of everything there is to know about PowerShell functions, function parameters, and PowerShell modules.

Source:
https://adamtheautomator.com/powershell-parameter/