Una forma fácil de pasar parámetros en tus scripts de PowerShell
Tags: powershell, script, programming, spanish
Published: 2022-04-04 07:10:05

> _Mi apodo en las redes es [__Coding Bugs__][Dashboard] y llevo desde el año 2004 trabajando como desarrollador y arquitecto de software. En mi [dashboard][Dashboard] encontrarás artículos con información sobre lenguajes de programación, técnicas de programación y otros conceptos que te ayudarán a mejorar._

Ya sabéis que [PowerShell][PowerShell] es el lenguaje de programación que Microsoft pone a nuestra disposición para la _"automatización de tareas multiplataforma compuesta por un shell de línea de comandos, un lenguaje de scripting y un marco de gestión de la configuración"_.

En este artículo mostraré una manera diferente de proporcionar valores a los parámetros de comandos y scripts. Esta es una forma sencilla y simple de declarar parámetros y asignar sus valores que os permitirá organizar vuestros scripts de forma que todo tenga más sentido.

__Forma estándar__

Si conoces [PowerShell][PowerShell] ya sabes como proporcionar parámetros en tus llamadas. Si no lo conoces, simplemente, indica el nombre del parámetro antecedido por un guion y, a continuación, escribe su valor.

```PowerShell
$ Get-ChildItem -Path C:\ -Filter *.txt 
# Devuelve todos los ficheros de texto existentes en C:\
```

Como puedes observar, la forma estándar es muy fácil y para casos en los que hagas invocaciones inmediatas es la mejor manera de proporcionar parámetros.

Sin embargo, imagina que tienes que contar el número de ficheros de un determinado tipo ubicados en una localización específica de tu máquina. En este caso, tendríamos que repetir tantas veces como fuera necesario la invocación al comando anterior, `Get-ChildItem`, proporcionando el parámetro `Filter` en base al tipo de fichero solicitado.

```PowerShell
$ Get-ChildItem -Path C:\ -Filter *.html | Measure | Select Count 
# Devuelve todos los ficheros HTML existentes en C:\

$ Get-ChildItem -Path C:\ -Filter *.css | Measure | Select Count 
# Devuelve todos los ficheros CSS existentes en C:\

$ Get-ChildItem -Path C:\ -Filter *.js | Measure | Select Count 
# Devuelve todos los ficheros JavaScript existentes en C:\
```

Para ahorrarnos tiempo cada vez que nos soliciten esa información vamos a generar un script propio en el que añadamos estas llamadas. Además, vamos a incorporar un parámetro en el script para especificar la ubicación concreta donde contar cada uno de los ficheros HTML, CSS y JS que hay.

```PowerShell
param(
	[Parameter(Mandatory=$true)]
	[String] $Location
)

Return @{
	Html = $(Get-ChildItem -Path $Location -Filter *.html | Measure).Count
	Css = $(Get-ChildItem -Path $Location -Filter *.css | Measure).Count
	Js = $(Get-ChildItem -Path $Location -Filter *.js | Measure).Count
}
```


¿Qué sucede si ahora nos piden incluir más y más tipos de fichero con diferentes extensiones? La respuesta sería incluir más y más líneas a nuestro script para cumplir con lo que nos solicitan. Pero, ¿y si hubiera otra forma más sencilla y más versatil dotando a nuestro script de la capacidad de abstraerse de los tipos de extensión?

__Una `hashtable` para pasar parámetros__

Empezaremos por añadir un nuevo parámetro donde se indiquen las extensiones de los ficheros que tenemos que contar. Ahora sólo tendremos que iterar sobre estos valores y pasar la extensión correspondiente al comando `Get-ChildItem`.

```PowerShell
param(
	[Parameter(Mandatory=$true)]
	[String] $Location,

	[Parameter(Mandatory=$true)]
	[String[]] $Extensions
)

Return $Extensions 
	| Foreach {
		$CurrentExtension = $_.ToLower() 
		# Transformar la extensión a minúsculas
		Return @{ $_ = $(Get-ChildItem -Path $Location -Filter $CurrentExtension | Measure).Count } 
		# Devuelve un objeto con la extensión como clave y el recuento obtenido como valor
}
```

Con esto sería suficiente para nuestro objetivo pero, como _programador_ que somos, queremos que sea aún más abstracto y nos permita mantener la llamada al comando `Get-ChildItem` más limpia, evitando tener que volver a modificarla con nuevos parámetros. 

> _Al ser una línea de comandos un poco compleja es mejor evitar su modificación al máximo posible o hacerla más simple diviéndola en trozos más simples. Por el bien de este artículo, elegimos la primera opción._

[PowerShell](PowerShell) permite el paso de un objeto `hashtable` para proporcionar cada uno de los parámetros que necesitamos del comando a ejecutar. En nuestro caso, el comando es `Get-ChildItem` y los parámetros que estamos utilizando son `Path` y `Filter`.

```PowerShell
# La variable hashtable que crearemos será como la siguiente
$CmdParams = @{
	Path = "."
	Filter = "*.*"
}

# Y la llamada al comando es como la siguiente, importante el uso de "@" en el paso de nuestro hashtable
Get-ChildItem @CmdParams
```

Igual que en el caso anterior, tenemos que actualizar nuestra variable `hashtable` con la extensión correspondiente en cada iteración del `Foreach`. 

```PowerShell
param(
	[Parameter(Mandatory=$true)]
	[String] $Location,

	[Parameter(Mandatory=$true)]
	[String[]] $Extensions
)

$CmdParams = @{
	Path = $Location
	Filter = "*.*"
}

Return $Extensions 
	| Foreach {
		$CurrentExtension = $_.ToLower() 
		# Transformar la extensión a minúsculas
		$CmdParams.Filter = "*.$CurrentExtension" 
		# Establecer el filtro en la extensión actual
		Return @{ $_ = $(Get-ChildItem @CmdParams | Measure).Count } 
		# Devuelve un objeto con la extensión como clave y el recuento obtenido como valor
}
```

Ya hemos conseguido lo que buscabamos, hemos abstraido la llamada al comando `Get-ChildItem` con respecto a los parámetros que necesitamos pasarle. De esta forma, podemos modificar la variable `CmdParams` añadiendo, cambiando o quitando cualquier par _clave-valor_  en base a las necesidades funcionales que tengamos.

> Podemos ver aquí el segundo principio del paradigma SOLID, [__Open Closed Principle__](https://dev.to/codbugs/learning-open-closed-principle-by-example-1628), en el que se indica que el código debe ser cerrado a cualquier modificación pero abierto para extender su funcionalidad. Podemos decir que el propósito de la iteración sobre cada una de las extensiones es retornar el número de ficheros existentes en la ubicación indicada. El comando `Get-ChildItem` puede ser invocado con otros parámetros que modificará el número de ficheros que retorne pero su resultado seguirá siendo el mismo (cerrado a modificación), por tanto, nuestra variable `CmdParams` permite que los ficheros a devolver pueda ser diferente dependiendo de los parámetros que reciba (abierto a extensión).

Imagina que nos solicitan otra nueva funcionalidad. Ahora se necesita contabilizar los ficheros de la ubicación proporcionada y los ficheros de las carpetas que se encuentran inmediatamente dentro, es decir, el primer nivel.

Déjame explicarte que el comando `Get-ChildItem` dispone de parámetros que nos permiten implementar este tipo de requisitos sin necesidad de hacer ningún tipo de ingeniería avanzada, usaremos los parámetros `Recurse` para establecer que se realice la búsqueda en la localización indicada y en todas las que hay por debajo y, `Depth` para establecer el máximo número de recursividad que necesatimos, en nuestro caso, el valor es de 1.

Seguro que ya has visualizado el cambio que hay que realizar al código anterior. Desde mi punto de vista, algo muy sencillo que no nos llevará más de 5 minutos entre el cambio y las pruebas para verificar que es correcto.

En nuestro código tenemos que incluir los dos parámetros anteriores en la variable `CmdParams` para que se aplique. Es un cambio limpio, sólo añadir nuevos elementos en nuestro `hashtable` sin tener que modificar la línea de comandos donde se encuentra el comando que, como hemos dicho, es ciertamente compleja.

```PowerShell
param(
	[Parameter(Mandatory=$true)]
	[String] $Location,

	[Parameter(Mandatory=$true)]
	[String[]] $Extensions
)

$CmdParams = @{
	Path = $Location
	Filter = "*.*"
	Recurse = $true
	Depth = 1
}

Return $Extensions 
	| Foreach {
		$CurrentExtension = $_.ToLower() 
		# Transformar la extensión a minúsculas
		$CmdParams.Filter = "*.$CurrentExtension" 
		# Establecer el filtro en la extensión actual
		Return @{ $_ = $(Get-ChildItem @CmdParams | Measure).Count } 
		# Devuelve un objeto con la extensión como clave y el recuento obtenido como valor
}
```


__Resumiendo__

El paso de parámetros en comandos y/o scripts en [PowerShell](PowerShell)es sencillo. Podemos utilizar la forma estándar para llamadas a comandos o en scripts sencillos. Y, también, podemos usar técnicas más avanzadas que nos permitan tener un código más limpio e implementar modificaciones de forma más sencilla.

El uso de `hashtables` es una forma fácil de visualizar los valores de los parámetros de los comandos que invocamos. Además, estructura nuestro código haciendo que sea más legible para los demás o, incluso, para nosotros mismos despues de pasado un tiempo.

El código de este artículo lo tenéis en este [_gist_](https://gist.github.com/codbugs/d18b9b2d958c48d94f253535bb3896fe).

__Feedback__

Si te ha gustado este artículo no olvides clicar en el corazón o el unicornio y compartirlo con tus amigos y compañeros. Déjame tus comentarios en la parte inferior sobre lo que te ha gustado o como mejorar y si quieres que escriba acerca de otros temas mándame un mensaje directo a mi cuenta de [twitter][Twitter].

__Créditos__

La imagen de cabecera procede de [Unsplash] y es cortesía de [Markus Spiske](https://unsplash.com/@markusspiske)


[PowerShell]: https://docs.microsoft.com/es-es/powershell/scripting/overview?view=powershell-7.2 "PowerShell"
[GitHub Profile]: https://github.com/codbugs "Mi perfil en GitHub"
[Unsplash]: https://unsplash.com/ "Unsplash"
[Twitter]: https://twitter.com/BugsCoding
[Dashboard]: https://dev.to/codbugs "Dashboard"