Primer script y validación de parámetros
Abstract
En esta lección damos el salto de ejecutar comandos aislados a construir un script reutilizable con
parámetros y validación. Empezaremos diferenciando cmdlets, funciones y scripts, y
luego crearemos un generador de README.md que ilustra buenas prácticas: entrada tipada,
mensajes de diagnóstico activables con y salida limpia que puede
encadenarse por el pipeline.
-Verbose -Verbose
Verás cómo aplicar validaciones declarativas para “fallar pronto”, y cómo usar , here-strings y el operador Join-Path Join-Path para generar texto robusto. Al
finalizar, tendrás un patrón de script listo para extender y aplicar en proyectos reales.
-f -f
Cmdlets, funciones y scripts
Antes de escribir nuestro primer script, vale la pena aclarar qué entendemos por cmdlet y cómo se relaciona con las funciones y los scripts de PowerShell. Aunque los tres ejecutan acciones en la terminal, difieren en su nivel de integración y propósito dentro del ecosistema.
Cmdlets
Un cmdlet (command-let) es la unidad básica de ejecución en PowerShell. Cada cmdlet
implementa una acción bien definida (por ejemplo, o
Get-Process Get-Process ) y devuelve objetos .NET al pipeline, lo que permite
encadenarlos fácilmente.
Set-Content Set-Content
Funciones
Una función es un bloque de código definido dentro de PowerShell que encapsula una tarea
específica. Puede comportarse como un cmdlet si se le añade el atributo
, lo que habilita características avanzadas como el manejo
de
[CmdletBinding()] [CmdletBinding()] , -Verbose -Verbose o
-ErrorAction -ErrorAction .
-WhatIf -WhatIf
Las funciones son ideales para reutilizar lógica sin necesidad de crear archivos externos. Además,
pueden agruparse en módulos (.psm1) para distribuirlas y cargarlas de forma controlada.
Scripts
Un script es un archivo .ps1 que contiene una secuencia de comandos, expresiones y
funciones. Al igual que las funciones, puede incluir para
comportarse como un cmdlet, pero se ejecuta desde archivo en lugar de estar cargado en memoria.
[CmdletBinding()] [CmdletBinding()]
Los scripts son útiles para automatizar flujos de trabajo completos, y suelen actuar como punto de entrada para tareas complejas o repetitivas. Su estructura puede incluir parámetros, validaciones y salida tipada, igual que los cmdlets.
Nota
Tu primer script: generar un README.md básico
Hasta ahora hemos trabajado ejecutando comandos directamente en la terminal. El siguiente paso es encapsular
esa lógica en un script sencillo y reutilizable, guardado dentro de dibs/scripts. Este
ejemplo genera el contenido inicial de un README.md para un proyecto —un caso común que
demuestra cómo automatizar tareas repetitivas de manera reproducible y documentada.
Incluye parámetros obligatorios y una opción de verbosity para mostrar mensajes adicionales durante la ejecución. Te recomiendo editar y mantener estos scripts en un entorno cómodo como VS Code, que ofrece resaltado de sintaxis, integración con terminal y depuración básica.
README.md con PowerShell #Requires -Version 7.0
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $Name
)
Write-Verbose "Creating README.md for project '$Name'"
@'
# {0}
Project initialized on {1}.
Learn more about READMEs at https://www.makeareadme.com/.
'@ -f $Name, (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ¿Qué acabamos de hacer?
-
: garantiza que el script se ejecute en PowerShell 7 o superior. 1#Requires -Version 7.0#Requires -Version 7.0 -
convierte el script en un cmdlet, lo que permite usar parámetros como[CmdletBinding()][CmdletBinding()]para mostrar mensajes de diagnóstico controlados con-Verbose-Verbose.Write-VerboseWrite-Verbose
Esto hace que tanto scripts como funciones puedan comportarse como cmdlets, con soporte para common parameters y confirmaciones interactivas. - El bloque
define los parámetros del script. La sintaxisparam(...)param(...)permite aplicar metadatos y validaciones:[Atributo1][Atributo2][Tipo] $Parametro[Atributo1][Atributo2][Tipo] $Parametro-
: indica que el parámetro debe proporcionarse obligatoriamente al ejecutar el script.[Parameter(Mandatory)][Parameter(Mandatory)] -
: evita que se pasen valores vacíos o[ValidateNotNullOrEmpty()][ValidateNotNullOrEmpty()].$null$null -
: especifica el tipo del parámetro, mejorando la validación y el autocompletado.[string][string]
-
- Se usa un here-string para devolver texto multilínea. La sintaxis con
preserva los saltos de línea.@' ... '@@' ... '@ - El operador
: es el format operator de PowerShell. Reemplaza los marcadores-f-f,{0}{0}, ... por los valores que se pasan a continuación, en orden. En este caso,{1}{1}y la fecha formateada se insertan en el texto del README.$Name$Name - En PowerShell, la última expresión evaluada en un script o función se devuelve automáticamente como salida, incluso sin usar la palabra clave
.returnreturn
Aquí, el here-string formateado es la última expresión, por lo que se devuelve como resultado del script. Este comportamiento sigue la filosofía de diseño de PowerShell: todo es un objeto y todo puede fluir por el pipeline.
Formato de cadenas en PowerShell: guía rápida
En los ejemplos del curso usaremos cadenas literales ( ) junto
con composite formatting ('...' '...' ). Esta combinación es uniforme,
legible y predecible, especialmente útil al dividir líneas largas o mantener estilos de salida
consistentes.
-f -f
- Composite formatting: usa el operador
o el método-f-fpara reemplazar marcadores[string]::Format()[string]::Format(),{0}{0}, etc., por los argumentos que se pasan en orden.{1}{1}Formato compuesto'Hello, {0}! Today is {1:yyyy-MM-dd}.' -f $User, (Get-Date) # o bien [string]::Format('Hello, {0}! Today is {1:yyyy-MM-dd}.', $User, (Get-Date))Ventaja principal: separa claramente la plantilla del contenido, facilitando la lectura y el mantenimiento.
- Interpolación: las comillas dobles (
) expanden variables ("...""...") y expresiones ($var$var), como en el siguiente ejemplo:$(...)$(...)Interpolación de cadenas"Hello, $User! Today is $(Get-Date -Format 'yyyy-MM-dd')."Es más concisa y natural en scripts pequeños, pero puede ser menos práctica en textos largos o con muchas inserciones, donde el formato compuesto ofrece más control.
Sobre return return en PowerShell
return return
Si bien en PowerShell existe, no usaremos la palabra clave
en este curso. Es redundante en la mayoría de los scripts, ya que toda expresión evaluada produce
salida automáticamente. De hecho, usar return return puede provocar resultados
inesperados 2 en pipelines o cuando se combinan funciones y scripts.
return return
Su único uso obligatorio es dentro de métodos de clases para devolver valores de forma explícita. Dado que no trabajaremos con clases en este curso, puedes ignorarlo completamente.
Get-Help about_Return -Online -Online para consultar la documentación en línea.
Ejecutar el script y guardar el resultado
dibs/scripts $content = .\core\New-Readme.ps1 -Name 'Utility Scripts - DIBS' -Verbose
Set-Content -Value $content -Path README.md -Encoding UTF8 ¿Qué acabamos de hacer?
-
ejecuta el script desde la carpeta actual (.\core\New-Readme.ps1 -Name '...'.\core\New-Readme.ps1 -Name '...'dibs/scripts). -
activa mensajes de diagnóstico generados por-Verbose-Verbose. Aunque no definimos el parámetroWrite-VerboseWrite-Verboseexplícitamente, se agrega automáticamente gracias a-Verbose-Verbose.[CmdletBinding()][CmdletBinding()] - La salida del script (el texto del README) se asigna a una variable mediante
. Esto permite inspeccionarla o modificarla antes de escribirla en disco.$content = ...$content = ... -
crea o sobrescribeSet-Content -Value $content -Path README.md -Encoding UTF8Set-Content -Value $content -Path README.md -Encoding UTF8README.mdcon codificación UTF-8. Es buena práctica incluir siemprepara evitar inconsistencias entre sistemas.-Encoding UTF8-Encoding UTF8
Validación básica de parámetros
PowerShell permite validar los valores de los parámetros directamente en su definición. Esto evita errores comunes y mantiene los scripts más seguros y predecibles sin necesidad de escribir lógica adicional.
param(
# No vacío o nulo
[ValidateNotNullOrEmpty()]
[string] $NotNullOrEmptyString,
# Solo valores permitidos
[ValidateSet('debug','info','warn','error')]
[string] $OnlyTheseLevels = 'info',
# Rango numérico válido
[ValidateRange(1, 10)]
[int] $NumberInRange = 5,
# Coincidir un patrón (minúsculas, dígitos y guiones)
[ValidatePattern('^[a-z0-9-]{3,30}$')]
[string] $MatchesPattern = 'valid-slug',
# Validación personalizada con script block
[ValidateScript({ $_ % 2 -eq 0 })]
[int] $EvenNumber = 2
) ¿Qué acabamos de hacer?
-
— evita que se pasen valores vacíos o nulos.[ValidateNotNullOrEmpty()][ValidateNotNullOrEmpty()] -
— limita el valor a una lista predefinida de opciones.[ValidateSet(...)][ValidateSet(...)] -
— asegura que el valor esté dentro del rango indicado.[ValidateRange(min, max)][ValidateRange(min, max)] -
— valida el formato del valor usando una expresión regular.[ValidatePattern('regex')][ValidatePattern('regex')] -
— ejecuta un bloque de script por cada valor recibido. Dentro del bloque,[ValidateScript({ ... })][ValidateScript({ ... })]representa el valor actual del parámetro que se está validando.$_$_
Si el bloque devuelve, la validación pasa; si devuelve$true$trueo lanza un error, la validación falla.$false$false
Por ejemplo,solo acepta números pares. Este tipo de validación es útil para comprobaciones personalizadas, como verificar que un archivo exista o que una cadena cumpla reglas específicas.{ $_ % 2 -eq 0 }{ $_ % 2 -eq 0 }
Tip
Las validaciones permiten “fallar pronto”: si un valor no cumple con las reglas, PowerShell detiene la ejecución y muestra un mensaje claro sin ejecutar el resto del script.
Ejercicio: Test-Readme.ps1
Requisitos
Implementa un script de validación que verifique un README.md mínimo en el directorio
indicado. Debe devolver si pasa todas las validaciones y
$true $true en caso contrario (sin usar $false $false ).
return return
- Parámetros:
-
(carpeta a validar).[Parameter(Mandatory)][Validation1(...)][Validation2(...)][string] $Path[Parameter(Mandatory)][Validation1(...)][Validation2(...)][string] $Path
La ruta debe:- no ser nula ni vacía y
- existir y contener un archivo
README.md.
-
-
(mensajes de diagnóstico).[switch] $Verbose[switch] $Verbose
-
- Validaciones mínimas sobre
README.mden:$Path$Path- Existe el archivo
README.md. - Contiene un título H1 en la primera línea:
.# Nombre# Nombre - Contiene la frase
(línea generada por el script de la lección).Project initialized onProject initialized on
-
Uso esperado
./path/to/Test-Readme.ps1 -Path '.' -Verbose.) no contiene un README.md entonces el script falla
por validación de parámetros. Si existe pero no cumple las reglas, devuelve $false $false . Si todo está correcto, devuelve $true $true .
Hints
- Usa
para construir rutas.Join-Path $Path 'README.md'Join-Path $Path 'README.md' -
habilita mensajes emitidos con-Verbose-Verbose; el parámetro se expone automáticamente al usarWrite-VerboseWrite-Verboseen el script.[CmdletBinding()][CmdletBinding()]
Cmdlets útiles
# ¿Es un directorio?
Test-Path -Path "." -PathType Container
# ¿Existe un archivo?
Test-Path -Path "README.md" -PathType Leaf
# Leer todo el contenido como string
Get-Content -Path "README.md" -Raw
# Verificar coincidencias
"# Título" -match '^#\s+.+' # H1 en la primera línea
"Project initialized on ..." -match 'Project initialized on'
# Operadores lógicos
$true -and !$false Solución
#Requires -Version 7.0
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[ValidateScript({ Test-Path (Join-Path $_ 'README.md') -PathType Leaf })]
[string] $Path
)
$readmePath = Join-Path $Path 'README.md'
Write-Verbose ('Checking for README.md at {0}' -f $readmePath)
$content = Get-Content -Path $readmePath -Raw
$hasH1 = $content -match '^#\s+.+'
$hasMarker = $content -match 'Project initialized on'
if ($hasH1 -and $hasMarker) {
Write-Verbose 'README.md looks good.'
$true
}
else {
Write-Verbose 'README.md does not follow expected format.'
$false
}Conclusiones
En esta lección pasaste de ejecutar comandos sueltos a estructurar automatizaciones con un script claro y validado. Diferenciaste cmdlets, funciones y scripts; construiste un generador deREADME.md; y aplicaste validaciones de parámetros para prevenir errores desde el
inicio. Además, incorporaste diagnósticos con -Verbose -Verbose (expuesto por
[CmdletBinding()] [CmdletBinding()] ) y buenas prácticas como Join-Path Join-Path , here-strings y el operador de formato -f -f .
Puntos clave
- Cmdlets, funciones y scripts: misma filosofía, distintos contenedores y niveles de integración.
- Primer script útil: generar contenido de
README.mdcomo plantilla reproducible. - Validación declarativa:
,[ValidateNotNullOrEmpty()][ValidateNotNullOrEmpty()],[ValidateSet(...)][ValidateSet(...)],[ValidateRange(...)][ValidateRange(...)]y[ValidatePattern(...)][ValidatePattern(...)]para “fallar pronto”.[ValidateScript(...)][ValidateScript(...)] - Diagnóstico integrado:
expone[CmdletBinding()][CmdletBinding()]; usa-Verbose-Verbosepara trazas controladas.Write-VerboseWrite-Verbose - Rutas portables: preferir
frente a concatenar separadores.Join-PathJoin-Path - Salida de texto robusta: combinar here-strings con
para separar plantilla y datos.-f-f
¿Qué nos llevamos?
La productividad en PowerShell surge de pequeños scripts bien diseñados: reciben parámetros
válidos, explican lo que hacen con y devuelven resultados
previsibles. Cada verificación que agregas al inicio te ahorra depurar al final.
-Verbose -Verbose
Adopta una mentalidad de automatización incremental: comienza por una tarea concreta (p. ej., generar un README), formaliza sus entradas con validaciones y compártela como base para el siguiente paso. Así conviertes acciones puntuales en herramientas reutilizables que crecen contigo y con tu equipo.
¿Con ganas de más?
Referencias recomendadas
- “Improving your parameterized script” (pp. 249–258) en Learn PowerShell in a month of lunches, fourth edition: Covers Windows, Linux, and MacOS por Travis PlunkCapítulo práctico sobre cómo llevar un script parametrizado de PowerShell “de básico a sólido”. Parte desde un ejemplo funcional (
) con ayuda embebida y dos parámetros, y muestra por qué conviene emitir objetos conGet-DiskInventoryGet-DiskInventoryen lugar de salida formateada, permitiendo usarSelect-ObjectSelect-Objecto exportar a CSV sin cambios. ConFormat-TableFormat-Tablese vuelve “avanzado” y habilita[CmdletBinding()][CmdletBinding()]para trazas activables. También introduce mejoras en la definición de parámetros: obligatorios con-Verbose-Verbose, alias con[Parameter(Mandatory=$true)][Parameter(Mandatory=$true)]y validaciones con[Alias('host')][Alias('host')]. Aunque centrado en Windows, los principios son transferibles. El capítulo introduce conceptos más avanzados que se explicarán más adelante o que quedan fuera del alcance de este curso, pero resulta ideal para quienes buscan crear scripts en PowerShell que sean profesionales, reutilizables y claros.[ValidateSet(...)][ValidateSet(...)]