Primer script y validación de parámetros

Encuentra el código de la lección:

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 -Verbose y salida limpia que puede encadenarse por el pipeline.

Verás cómo aplicar validaciones declarativas para “fallar pronto”, y cómo usar Join-Path , here-strings y el operador -f para generar texto robusto. Al finalizar, tendrás un patrón de script listo para extender y aplicar en proyectos reales.

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, Get-Process o Set-Content ) y devuelve objetos .NET al pipeline, lo que permite encadenarlos fácilmente.

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 [CmdletBinding()] , lo que habilita características avanzadas como el manejo de -Verbose , -ErrorAction o -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 [CmdletBinding()] para comportarse como un cmdlet, pero se ejecuta desde archivo en lugar de estar cargado en memoria.

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

En este apunte usaremos scripts en lugar de funciones o módulos para mantener las explicaciones simples. Todos los conceptos de scripting —validación de parámetros, pipelines, salida tipada, etc.— se aplican por igual a funciones y scripts, por lo que podrás migrar este conocimiento fácilmente más adelante.

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.

Generar README.md con PowerShell
scripts/core/New-Readme.ps1
#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?

  • #Requires -Version 7.0 : garantiza que el script se ejecute en PowerShell 7 o superior. 1
  • [CmdletBinding()] convierte el script en un cmdlet, lo que permite usar parámetros como -Verbose para mostrar mensajes de diagnóstico controlados con Write-Verbose .
    Esto hace que tanto scripts como funciones puedan comportarse como cmdlets, con soporte para common parameters y confirmaciones interactivas.
  • El bloque param(...) define los parámetros del script. La sintaxis [Atributo1][Atributo2][Tipo] $Parametro permite aplicar metadatos y validaciones:
    • [Parameter(Mandatory)] : indica que el parámetro debe proporcionarse obligatoriamente al ejecutar el script.
    • [ValidateNotNullOrEmpty()] : evita que se pasen valores vacíos o $null .
    • [string] : especifica el tipo del parámetro, mejorando la validación y el autocompletado.
  • Se usa un here-string para devolver texto multilínea. La sintaxis con @' ... '@ preserva los saltos de línea.
  • El operador -f : es el format operator de PowerShell. Reemplaza los marcadores {0} , {1} , ... por los valores que se pasan a continuación, en orden. En este caso, $Name y la fecha formateada se insertan en el texto del README.
  • 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 return .
    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 ( -f ). Esta combinación es uniforme, legible y predecible, especialmente útil al dividir líneas largas o mantener estilos de salida consistentes.

  • Composite formatting: usa el operador -f o el método [string]::Format() para reemplazar marcadores {0} , {1} , etc., por los argumentos que se pasan en orden.
    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 ( $var ) y expresiones ( $(...) ), 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 en PowerShell

Si bien en PowerShell existe, no usaremos la palabra clave return 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 puede provocar resultados inesperados 2 en pipelines o cuando se combinan funciones y scripts.

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.

Desde la terminal
Get-Help about_Return
Usa -Online para consultar la documentación en línea.

Ejecutar el script y guardar el resultado

Desde dibs/scripts
$content = .\core\New-Readme.ps1 -Name 'Utility Scripts - DIBS' -Verbose
Set-Content -Value $content -Path README.md -Encoding UTF8
Generar y guardar el archivo README

¿Qué acabamos de hacer?

  • .\core\New-Readme.ps1 -Name '...' ejecuta el script desde la carpeta actual (dibs/scripts).
  • -Verbose activa mensajes de diagnóstico generados por Write-Verbose . Aunque no definimos el parámetro -Verbose explícitamente, se agrega automáticamente gracias a [CmdletBinding()] .
  • La salida del script (el texto del README) se asigna a una variable mediante $content = ... . Esto permite inspeccionarla o modificarla antes de escribirla en disco.
  • Set-Content -Value $content -Path README.md -Encoding UTF8 crea o sobrescribe README.md con codificación UTF-8. Es buena práctica incluir siempre -Encoding UTF8 para evitar inconsistencias entre sistemas.

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.

Ejemplo: validaciones declarativas
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?

  • [ValidateNotNullOrEmpty()] — evita que se pasen valores vacíos o nulos.
  • [ValidateSet(...)] — limita el valor a una lista predefinida de opciones.
  • [ValidateRange(min, max)] — asegura que el valor esté dentro del rango indicado.
  • [ValidatePattern('regex')] — valida el formato del valor usando una expresión regular.
  • [ValidateScript({ ... })] — ejecuta un bloque de script por cada valor recibido. Dentro del bloque, $_ representa el valor actual del parámetro que se está validando.
    Si el bloque devuelve $true , la validación pasa; si devuelve $false o lanza un error, la validación falla.
    Por ejemplo, { $_ % 2 -eq 0 } 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.

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 $true si pasa todas las validaciones y $false en caso contrario (sin usar return ).

  • Parámetros:
    • [Parameter(Mandatory)][Validation1(...)][Validation2(...)][string] $Path (carpeta a validar).
      La ruta debe:
      • no ser nula ni vacía y
      • existir y contener un archivo README.md.
    • [switch] $Verbose (mensajes de diagnóstico).
  • Validaciones mínimas sobre README.md en $Path :
    • Existe el archivo README.md.
    • Contiene un título H1 en la primera línea: # Nombre .
    • Contiene la frase Project initialized on (línea generada por el script de la lección).

Uso esperado

Desde la terminal
./path/to/Test-Readme.ps1 -Path '.' -Verbose
Si la carpeta actual (.) no contiene un README.md entonces el script falla por validación de parámetros. Si existe pero no cumple las reglas, devuelve $false . Si todo está correcto, devuelve $true .

Hints

  • Usa Join-Path $Path 'README.md' para construir rutas.
  • -Verbose habilita mensajes emitidos con Write-Verbose ; el parámetro se expone automáticamente al usar [CmdletBinding()] en el script.

Cmdlets útiles

Ejemplos de uso
# ¿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

Solución de referencia
#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
}
Un enfoque posible; cualquier solución que cumpla los requisitos es válida.

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 de README.md; y aplicaste validaciones de parámetros para prevenir errores desde el inicio. Además, incorporaste diagnósticos con -Verbose (expuesto por [CmdletBinding()] ) y buenas prácticas como Join-Path , here-strings y el operador de formato -f .

Puntos clave

  • Cmdlets, funciones y scripts: misma filosofía, distintos contenedores y niveles de integración.
  • Primer script útil: generar contenido de README.md como plantilla reproducible.
  • Validación declarativa: [ValidateNotNullOrEmpty()] , [ValidateSet(...)] , [ValidateRange(...)] , [ValidatePattern(...)] y [ValidateScript(...)] para “fallar pronto”.
  • Diagnóstico integrado: [CmdletBinding()] expone -Verbose ; usa Write-Verbose para trazas controladas.
  • Rutas portables: preferir Join-Path frente a concatenar separadores.
  • Salida de texto robusta: combinar here-strings con -f para separar plantilla y datos.

¿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 -Verbose y devuelven resultados previsibles. Cada verificación que agregas al inicio te ahorra depurar al final.

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?

Notas

  1. Puedes ajustar #Requires a otra versión, pero no garantizamos compatibilidad con PowerShell anterior. Volver
  2. Al menos cuando estamos comenzando con PowerShell. Volver