Ensayo seguro (-WhatIf / -Confirm)

Encuentra el código de la lección:

Abstract

Esta lección introduce el patrón de ensayo seguro en PowerShell: simular o confirmar acciones antes de aplicarlas. Verás cómo aprovechar el soporte integrado del lenguaje para ejecutar comandos con más seguridad y control.

Exploramos cómo estructurar operaciones para ensayar cambios sin riesgos y decidir cuándo pedir confirmación. Con ejemplos prácticos, aplicarás el patrón para prevenir errores y construir scripts más confiables sin alterar su funcionamiento.

Patrón de ensayo seguro (dry run)

Antes de crear carpetas o archivos, conviene ensayar el cambio: ver qué se haría sin ejecutar nada. En PowerShell esto se habilita con el atributo SupportsShouldProcess , que agrega los parámetros automáticos -WhatIf y -Confirm . Con ellos, el script puede simular o solicitar confirmación de acciones potencialmente destructivas, reduciendo riesgos y aumentando la confianza en su comportamiento.

Initialize-Project (con -WhatIf)
scripts/scaffolding/Initialize-Project.ps1
#Requires -Version 7.5
[CmdletBinding(SupportsShouldProcess,
    ConfirmImpact = 'Medium')]
param(
    [Parameter(Mandatory)]
    [ValidateNotNullOrWhiteSpace()]
    [string] $Name,

    [Parameter(Mandatory)]
    [ValidateNotNullOrWhiteSpace()]
    [ValidateScript({ Test-Path -Path $_ -PathType Container })]
    [string] $Path
)

Set-StrictMode -Version 3.0

$base = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path)
$target = Join-Path $base $Name
$readmePath = Join-Path $target 'README.md'
$helperPath = Join-Path $PSScriptRoot 'New-Readme.ps1' -Resolve

$existsBefore = Test-Path -LiteralPath $readmePath -PathType Leaf
$created = $false
$skipped = $false

if (!(Test-Path -LiteralPath $target -PathType Container)) {
    New-Item -Path $target -ItemType Directory -Force | Out-Null
}

if (!$existsBefore) {
    if ($PSCmdlet.ShouldProcess($readmePath, 'Create README.md')) {
        $content = & $helperPath -Name $Name -Verbose:$PSBoundParameters['Verbose']
        Set-Content -Path $readmePath -Encoding UTF8 -Value $content
        $created = $true
    }
}
else {
    $skipped = $true
}

[PSCustomObject]@{
    BasePath     = $base
    TargetPath   = $target
    ReadmePath   = $readmePath
    HelperPath   = $helperPath
    ExistsBefore = $existsBefore
    Created      = $created
    Skipped      = $skipped
}

¿Qué acabamos de hacer?

  • SupportsShouldProcess indica que el comando admite los parámetros automáticos -WhatIf y -Confirm . PowerShell los agrega y gestiona por ti: -WhatIf nunca ejecuta la acción, sólo la describe; -Confirm solicita confirmación antes de proceder (según reglas de impacto y preferencia del usuario). 1
  • ConfirmImpact declara el impacto de las operaciones del comando (Low, Medium, High). PowerShell lo compara con $ConfirmPreference del usuario (por defecto, High). PowerShell pide confirmación cuando ConfirmImpact >= $ConfirmPreference.
    Si no se supera el umbral, puedes forzar la confirmación con -Confirm .
    En este ejemplo Medium describe creación/modificación de archivos (relevante pero no destructiva).
  • $PSCmdlet.ShouldProcess([string] target, [string] action) es el punto de decisión: aquí se evalúa si una operación debe simularse o ejecutarse. Coloca cada operación con efectos dentro de este if. Con -WhatIf se simula y con -Confirm se solicita permiso -como se explicó arriba-.
  • -Switch:$variable pasa un switch sólo si la variable booleana es $true . Para propagar switches comunes a helpers, usa $PSBoundParameters : por ejemplo -Verbose:$PSBoundParameters['Verbose'] . Si quieres propagar -WhatIf / -Confirm el helper también debe declarar SupportsShouldProcess para que PowerShell gestione su simulación/confirmación correctamente.

Precaución

Si el comando no implementa SupportsShouldProcess , -WhatIf / -Confirm no surten efecto. Compruébalo con
Desde la terminal
$cmd = Get-Command Start-Job
[System.Management.Automation.CommandMetadata]::new($cmd).SupportsShouldProcess # (1)
$cmd = Get-Command Copy-ItemProperty
[System.Management.Automation.CommandMetadata]::new($cmd).SupportsShouldProcess # (2)
(1) Retorna $false ya que Start-Job no acepta -WhatIf / -Confirm .
(2) Retorna $true ya que Copy-ItemProperty acepta -WhatIf / -Confirm .

Regla mental

  • Declarar soporte → SupportsShouldProcess
  • Asignar impacto → ConfirmImpact
  • Envolver efectos → ShouldProcess(target, action)

Confirmación y simulación

SupportsShouldProcess habilita -WhatIf / -Confirm ; el punto de decisión es $PSCmdlet.ShouldProcess(target, action) . -WhatIf simula; -Confirm pide permiso previo (según ConfirmImpact y las preferencias del usuario).

  • Low — operaciones triviales (por ejemplo, lectura o inspección de datos).
  • Medium — operaciones que crean o modifican recursos.
  • High — operaciones críticas o destructivas (por ejemplo, eliminación de archivos o estructuras completas).

Ensayo seguro con -WhatIf y confirmación

Antes de crear carpetas o archivos, ejecuta el comando con -WhatIf para comprobar qué haría el script sin realizar cambios reales. Si además deseas que el script pida confirmación antes de ejecutar cada acción, cambia -WhatIf -Confirm .

Desde dibs/scripts
./scaffolding/Initialize-Project.ps1 -Name "Test" -Path "." -Verbose -WhatIf
Salida con -WhatIf
What if: Performing the operation "Create Directory" on target "Destination: /path/to/dibs/scripts/Test".
What if: Performing the operation "Create README.md" on target "/path/to/dibs/scripts/Test/README.md".

BasePath     : /path/to/dibs/scripts
TargetPath   : /path/to/dibs/scripts/Test
ReadmePath   : /path/to/dibs/scripts/Test/README.md
HelperPath   : /path/to/dibs/scripts/scaffolding/New-Readme.ps1
ExistsBefore : False
Created      : False
Skipped      : False

Piensa rápido

Prueba distintos valores de -Name y -Path junto con -WhatIf . ¿Qué ocurre si el proyecto ya existe? ¿Y si la ruta no es válida?

Piensa rápido

Ejecuta el script con -Confirm para observar cómo solicita autorización antes de crear cada archivo.

Ejercicio: Limpieza segura con ConfirmImpact = 'High'

Requisitos

Implementa un script Remove-WorkFolder.ps1 que elimine una carpeta de trabajo específica de forma segura, aplicando el patrón de ensayo/confirmación:
  • Declara [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')] .
  • Parámetro obligatorio $Path, válido y de tipo carpeta existente.
  • Usa $PSCmdlet.ShouldProcess(target, action) para proteger la llamada a Remove-Item -Recurse -Force .
  • Propaga verbosidad y switches comunes con -Verbose:$PSBoundParameters['Verbose'] y, opcionalmente, -WhatIf:$PSBoundParameters['WhatIf'] / -Confirm:$PSBoundParameters['Confirm'] .
  • Devuelve un [PSCustomObject] con TargetPath, ExistsBefore y Deleted.

Uso esperado

Prueba el flujo seguro
./Remove-WorkFolder.ps1 -Path './Test' -Verbose -WhatIf

# Luego, con confirmación interactiva
./Remove-WorkFolder.ps1 -Path './Test' -Verbose -Confirm

Hints

Recuerda resolver rutas con $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path) y usar -LiteralPath en las operaciones de archivo para evitar problemas con caracteres especiales.
  • Con -WhatIf no debe borrarse nada; 'Deleted' debe ser False.
  • Con -Confirm , respeta $ConfirmPreference (aplica confirmación cuando corresponda).

Solución

#Requires -Version 7.5
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param(
    [Parameter(Mandatory)]
    [ValidateNotNullOrWhiteSpace()]
    [ValidateScript({ Test-Path -Path $_ -PathType Container })]
    [string] $Path
)
Set-StrictMode -Version 3.0

$target = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path)
$existsBefore = Test-Path -LiteralPath $target -PathType Container

$deleted = $false

if ($existsBefore -and $PSCmdlet.ShouldProcess($target, 'Remove work folder recursively')) {
    $removeParams = @{
        LiteralPath = $target
        Recurse     = $true
        Force       = $true
        Verbose     = $PSBoundParameters['Verbose']
        WhatIf      = $PSBoundParameters['WhatIf']
        Confirm     = $PSBoundParameters['Confirm']
    }

    Remove-Item @removeParams
    $deleted = $true
}

[PSCustomObject]@{
    TargetPath   = $target
    ExistsBefore = $existsBefore
    Deleted      = $deleted
}

Conclusiones

El patrón ShouldProcess ofrece una base sólida para escribir scripts más seguros, predecibles y confiables. Gracias a los modificadores automáticos -WhatIf y -Confirm , podemos agregar ensayos y confirmaciones sin alterar la estructura ni el comportamiento principal del script, integrando buenas prácticas de seguridad operacional directamente en la herramienta.

Incorporarlo no solo previene errores costosos, sino que además impulsa una forma de pensar centrada en la reversibilidad y en el control del efecto que cada comando tiene sobre el sistema. Con una estructura mínima -declarar SupportsShouldProcess , asignar ConfirmImpact y envolver la operación en ShouldProcess() - obtenemos de forma automática simulación y confirmación interactiva.

Puntos clave

  • SupportsShouldProcess habilita -WhatIf y -Confirm , añadiendo seguridad sin modificar la estructura principal del script.
  • ConfirmImpact define el nivel de riesgo (Low, Medium, High) y cuándo PowerShell pedirá confirmación.
  • ShouldProcess() es el punto donde se decide ejecutar o simular; todo efecto del script debería pasar por él.
  • Los parámetros -WhatIf y -Confirm se aplican automáticamente en cascada a funciones que también declaran soporte para este patrón.
  • Al combinarlo con validaciones ( ValidateScript , LiteralPath ) y $PSBoundParameters , se obtienen scripts robustos y reutilizables.

¿Qué nos llevamos?

El ensayo seguro en PowerShell no es solo una característica técnica: es una filosofía de diseño que fomenta escribir comandos conscientes del contexto y del impacto que generan.

Adoptar este patrón tempranamente te permite crear utilidades más confiables, colaborativas y mantenibles, facilitando pruebas, revisiones y automatización en entornos reales. A partir de aquí, cada comando que modifique el sistema debería preguntarse: «¿debo hacerlo, o solo mostrar lo que haría?»

¿Con ganas de más?

Notas

  1. Nota que -Confirm hace que el script solicite confirmación antes de ejecutar una acción; no significa que tú ya la estés confirmando. Volver