Mejora el paso de parámetros en tus scripts de PowerShell
Tags: powershell, script, programming, spanish
Published: 2022-02-13 09:23:48

> _Mi apodo en las redes es [__Coding Bugs__][Dashboard] y llevo más de 15 años 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._

El propio Microsoft describe [PowerShell][PowerShell] como _"una solución de 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 otras palabras, es una solución para la creación de scripts que realicen tareas repetitivas, por ejemplo, la creación de una cuenta en el sistema cuando un empleado se incorpora en la compañía, o el borrado de documentos que tienen más de 5 años de antigüedad en una carpeta.  Por tanto, es una herramienta más que tenemos a nuestra disposición para automatizar tareas recurrentes.

En este artículo mostraré como utilizar configuraciones de ámbito general o global para nuestros scripts. Desde una configuración __ad hoc__ en la que establecer de forma fija los valores, hasta la __lectura de un fichero externo de configuración__ con un formato específico como JSON, XML o CSV. El objetivo es hacer la llamada a los scripts de [PowerShell][PowerShell] lo más sencilla posible y evitar cualquier tipo de error. En definitiva, la persona o automatismo que finalmente ejecutará los procesos no tendrá conocimiento del código propio del script y, seguramente, tampoco recordará el valor de los parámetros a pasar después de unas semanas. Cuanto más fácil sea, mejor para todos.

__Ad hoc__

Comenzando con un enfoque en el que desarrollar código basado en parámetros, nos va a permitir tener una batería de scripts que podrán ser utilizados en otras implementaciones simplemente indicando un valor u otro. Un ejemplo de un script genérico puede ser  la descarga de información procedente de Internet. Este comprenderá 2 parámetros: 
* el primero será la dirección URL donde obtener la información y, 
* el segundo dónde queremos guardarla en disco. 

```PowerShell
# Download-Data.ps1

param(
	[Parameter(Mandatory=$true)]
	[String] $Url,
	[Parameter(Mandatory=$true)]
	[String] $FilePath
)

# Save data to the file path specified and returns the response
Invoke-WebRequest -Uri $Url -OutFile $FilePath

# For downloading my Github profile to your machine use the following line
# .\Download-Data.ps1 -Url https://github.com/codbugs -FilePath CodingBugs_Github_Profile.html
```

De esta forma, podremos invocar este proceso tantas veces como queramos y guardar la información de las direcciones URL que le pasemos. El siguiente paso lógico será desarrollar código específico para las necesidades o requisitos que tengamos. En este caso, diremos que queremos guardar una versión de una página web cada día. Siguiendo el ejemplo anterior, tenemos que crear un nuevo script en el que utilizar siempre la misma dirección URL y generar una carpeta en base al día actual. El script guardará la página web de [mi perfil en GitHub][GitHub Profile] en una carpeta con nombre igual al año, mes y día actual.

```PowerShell
# Download-MyGithubProfile.ps1

# My GitHub profile page
$MyGitHubProfileUrl = "https://github.com/codbugs"

# Folder generation based on the current date
$FolderName = Get-Date -Format "yyyyMMdd"
$Folder = New-Item -Path $FolderName -ItemType Directory -Force

# File name
$FileName = "CodingBugsProfile.html"

# Invoking the script for downloading my GitHub profile
.\Download-Data.ps1 -Url $MyGitHubProfileUrl -FilePath "$FolderName\$FileName"
```

Por lo tanto, la necesidad se ha cubierto generando 2 scripts: el primero con una implementación genérica con la posibilidad de ser utilizado en el caso que haya otro requisito parecido y, el segundo, con una implementación específica que no requiere que el operador tenga conocimiento de los parámetros que debe pasar al script.

¿En qué situaciones podemos utilizar un modelo de scripts como este? A mí me resulta muy útil cuando tengo varios entornos diferentes y tengo que desplegar los mismos elementos en cada uno de ellos. En este caso, creo un script de despliegue genérico con los parámetros que necesito. Después, creo un nuevo script por cada entorno con los parámetros específicos de estos. Cuando tenga que desplegar en un entorno u otro sólo tengo que ejecutar el script correspondiente y estoy seguro que la información de los parámetros es la correcta.

__Fichero de configuración__

Usar ficheros de configuración es una forma sencilla de abstraer el código de aquellos valores necesarios para su ejecución. En vez de pasar todos los parámetros podemos incorporarlos en un fichero de texto y leerlos desde el propio script.

En este caso, aprovecharemos los comandos existentes en [PowerShell][PowerShell] para utilizar el formato de fichero con el que nos sintamos más a gusto: [JSON][JSON], CSV o XML. Todos ellos tienen una forma rápida de leer ficheros y acceder a su información.

Partimos del siguiente fichero [JSON][JSON]:

```JSON
// coding_bugs_configuration.json
{
    "version": "1.0",
    "author": {
        "name": "Coding Bugs",
        "email": "coding.bugs@outlook.com",
        "github": "https://github.com/codbugs"
    },
    "gists": [
        { "name": "README.md", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/README.md" },
        { "name": "coding_bugs_configuration.csv", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.csv" },
        { "name": "coding_bugs_configuration.json", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.json" },
        { "name": "coding_bugs_configuration.xml", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.xml" },
        { "name": "Download-CsvGistFiles.ps1", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-CsvGistFiles.ps1" },
        { "name": "Download-Data.ps1", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-Data.ps1" },
        { "name": "Download-JsonGistFiles.ps1", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-JsonGistFiles.ps1" },
        { "name": "Download-MyGitHubProfile.ps1", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-MyGitHubProfile.ps1" },
        { "name": "Download-XmlGistFiles.ps1", "url": "https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-XmlGistFiles.ps1" }
    ]
}
```

Para obtener la información existente en él tenemos que usar el comando `ConvertFrom-Json` que transforma un código [JSON][JSON] en un objeto nativo de PowerShell. El objetivo es utilizar una notación sencilla e intuitiva para leer los datos. En el siguiente script, utilizamos la información existente para descargarnos cada uno de los _gists_ de este artículo:

```PowerShell
# Download-JsonGistFiles.ps1

param(
	[Parameter(Mandatory=$true)]
	[String]  $ConfigurationFilePath
)

# Reading the configuration file
$Configuration = Get-Content $ConfigurationFilePath | ConvertFrom-Json 

# Creating the folder to download de gists
$FolderName = $Configuration.author.name
$Folder = New-Item -Path $FolderName -Type Directory -Force

# Downloading the gists files
$Configuration.gists | Foreach { .\Download-Data.ps1 -Url $_.url -FilePath "$FolderName\$($_.name)" }

# .\Download-JsonGistFiles.ps1 -ConfigurationFilePath coding_bugs_configuration.json
```

Si queremos tener un fichero XML porque nos resulta más sencillo, no hay problema. El código resultante es bastante similar, sólo tenemos que modificar cómo leer el fichero de configuración y cómo iterar por cada uno de los elementos _gists_.

```xml
<!-- coding_bugs_configuration.xml -->
<xml>
    <version>1.0</version>
    <author>
        <name>Coding Bugs</name>
        <email>coding.bugs@outlook.com</email>
        <github>https://github.com/codbugs</github>
    </author>
    <gists>
        <gist>
            <name>README.md</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/README.md</url>
        </gist>
        <gist>
            <name>coding_bugs_configuration.csv</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.csv</url>
        </gist>
        <gist>
            <name>coding_bugs_configuration.json</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.json</url>
        </gist>
        <gist>
            <name>coding_bugs_configuration.xml</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.xml</url>
        </gist>
        <gist>
            <name>Download-CsvGistFiles.ps1</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-CsvGistFiles.ps1</url>
        </gist>
        <gist>
            <name>Download-Data.ps1</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-Data.ps1</url>
        </gist>
        <gist>
            <name>Download-JsonGistFiles.ps1</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-JsonGistFiles.ps1</url>
        </gist>
        <gist>
            <name>Download-MyGitHubProfile.ps1</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-MyGitHubProfile.ps1</url>
        </gist>
        <gist>
            <name>Download-XmlGistFiles.ps1</name>
            <url>https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-XmlGistFiles.ps1</url>
        </gist>
    </gists>
</xml>
```

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

# Reading the configuration file
[XML]$Configuration = Get-Content  $ConfigurationFilePath

# Creating the folder to download de gists
$FolderName = $Configuration.xml.author.name
$Folder = New-Item -Path $FolderName -Type Directory -Force

# Downloading the gists files
$Configuration.xml.gists.gist | Foreach { .\Download-Data.ps1 -Url $_.url -FilePath "$FolderName\$($_.name)" }

# .\Download-XmlGistFiles.ps1 -ConfigurationFilePath coding_bugs_configuration.xml
```

Por último, si queremos tener un fichero CSV tenemos que modificar cómo leer el fichero de configuración y cómo iterar por cada una de las filas.

```CSV
"version";"author_name";"author_email";"author_github";"gist_name";"gist_url"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"README.md";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/README.md"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"coding_bugs_configuration.csv";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.csv"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"coding_bugs_configuration.json";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.json"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"coding_bugs_configuration.xml";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/coding_bugs_configuration.xml"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"Download-CsvGistFiles.ps1";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-CsvGistFiles.ps1"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"Download-Data.ps1";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-Data.ps1"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"Download-JsonGistFiles.ps1";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-JsonGistFiles.ps1"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"Download-MyGitHubProfile.ps1";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-MyGitHubProfile.ps1"
"1.0";"Coding Bugs";"coding.bugs@outlook.com";"https://github.com/codbugs";"Download-XmlGistFiles.ps1";"https://gist.githubusercontent.com/codbugs/3e3abe4c7709e992c819327cb0b9c0c0/raw/67b1a5e520d0ebaac8c678a43b9dda5638a147e1/Download-XmlGistFiles.ps1"
```

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

# Reading the configuration file
$Configuration = Get-Content $ConfigurationFilePath
$Configuration = Import-Csv -Path $ConfigurationFilePath -Delimiter ';'

$Configuration | Foreach  {
	# Creating the folder to download de gists
	$FolderName = $_.author_name
	$Folder = New-Item -Path $FolderName -Type Directory -Force

	# Downloading the gists files
	.\Download-Data.ps1 -Url $_.gist_url -FilePath "$FolderName\$($_.gist_name)"
}

# .\Download-CsvGistFiles.ps1 -ConfigurationFilePath coding_bugs_configuration.csv
```

__Resumiendo__

A la hora de proporcionar los valores a los parámetros de nuestros scripts en PowerShell, podemos seguir 2 estrategias que nos faciliten la labor de su ejecución:
1. Generar un script genérico que nos sirva en cualquier situación que necesitemos y un conjunto de scripts con los parámetros establecidos directamente en código e invocando al primero,
2. Introducir un fichero de configuración con todos los parámetros necesarios para el script.

Os estaréis imaginando en una __tercera posibilidad__ . Efectivamente, se trata de la combinación de las dos anteriores, es decir, tener un script genérico con una implementación que cubra la ejecución del propio proceso mediante un fichero de configuración, y un conjunto de scripts especificando el fichero de configuración concreto a utilizar. De esta forma, facilitamos mucho más las acciones que haya que tomar en la ejecución o modificación de nuestros scritps.

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

__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][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"
[JSON]: https://json.org/json-es.html "JSON"
[Unsplash]: https://unsplash.com/ "Unsplash"
[Twitter]: https://twitter.com/BugsCoding
[Dashboard]: https://dev.to/codbugs "Dashboard"