Saltar al contenido principal

Pipelines III: Manejo de errores

Metadatos de la lección

Autoría:
Ignacio Slater-Muñoz
Última actualización:
22 de febrero de 2026

Cambios recientes:

  • 8cb8f65 · 22 de febrero de 2026 · 📝 docs(changelog): add 0.10.0 release notes and lesson metadata refresh ( GitLab / GitHub )
  • 4ad7e55 · 22 de febrero de 2026 · 📝 docs(notes): add bibliography source for pipeline error handling ( GitLab / GitHub )
  • 887ec92 · 22 de febrero de 2026 · ✨ feat(notes): improve pipeline error lesson and code rendering ( GitLab / GitHub )

Abstract

En PowerShell, manejar errores no consiste solo en capturar excepciones, sino en definir explícitamente qué significa fallar dentro de un flujo de elementos. En el contexto del pipeline, esta decisión es especialmente relevante: un error puede afectar únicamente al elemento actual o invalidar todo el procesamiento. Comprender la diferencia entre errores terminantes y no terminantes permite diseñar herramientas más resilientes y reutilizables.

Esta lección explora cómo delegar la política de fallo mediante -ErrorAction y cómo auditar errores sin interrumpir el flujo usando -ErrorVariable . A través de ejemplos y un ejercicio por lotes, se muestra cómo separar el procesamiento de datos de la política de error, permitiendo decidir si se privilegia la continuidad, la atomicidad o la medición de fallos.

Elegir cuándo fallar y cuándo continuar

En automatización, «manejar errores» no es solo capturar excepciones: es decidir qué significa fallar para tu tarea. A veces, un error debe detenerlo todo (para evitar estados inconsistentes). Otras veces, conviene seguir (para procesar lo que sí sirve y reportar lo que no). PowerShell hace explícita esa decisión y, en gran medida, la delega a quien programa.

Esa delegación importa especialmente en el pipeline: en vez de pensar el programa como «una función que devuelve un único resultado,» PowerShell suele trabajar con flujos de elementos. Por eso distingue entre errores no terminantes (falla un elemento, el flujo sigue) y terminantes (se detiene el procesamiento del flujo completo).

¿Por qué este modelo de errores?

En automatización es frecuente encontrar entradas parcialmente malas (archivos corruptos, permisos incompletos, datos ausentes), pero aun así resulta útil procesar lo demás. Por defecto, PowerShell prefiere no perder progreso: continúa procesando, registra cada fallo, y deja que quien programa decida si lo trata como advertencia, métrica o condición de falla. Esta mentalidad favorece la resiliencia en tareas como auditorías, limpiezas masivas y herramientas reutilizables.

Sin embargo, continuar tras errores puede producir resultados incompletos o inconsistentes si tu tarea requiere atomicidad. En esos casos, tiene sentido promover a error terminante y cortar temprano.

Con eso en mente, esta lección busca responder (según el contexto) una pregunta guía:

Pregunta clave

¿Debe un elemento inválido invalidar el resto de los elementos? No existe una única respuesta correcta: depende del objetivo, del costo de fallar, del costo de continuar y del tipo de salida que quieres ofrecer a otras personas (o a tus futuros scripts) que consuman tu pipeline.

Delegar la política de error a quien consume tu función con -ErrorAction

Cuando defines un cmdlet con [CmdletBinding()] , PowerShell agrega automáticamente parámetros comunes como -ErrorAction . Esto significa que quien consume tu función puede decidir si un error debe continuar o detener el flujo, sin que tú tengas que modificar la implementación.

Este parámetro no cambia tu lógica interna: cambia cómo PowerShell interpreta los errores no terminantes que tu función emite. En un pipeline, eso es una diferencia crítica: la política puede ser «tolerante por elemento» o «falla fuerte» según el contexto.

Huella de archivos por elemento (pipeline-aware)
scripts/pipeline/Get-FileFingerprint.ps1
#Requires -Version 7.5
[CmdletBinding()]
param(
    [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
    [Alias('Id')]
    [string] $BatchId,

    [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('FullName', 'LiteralPath')]
    [string[]] $Paths
)

begin {
    Set-StrictMode -Version 3.0
}

process {
    $batchErr = $null

    $childErrorParams = @{
        ErrorAction   = 'SilentlyContinue'
        ErrorVariable = 'batchErr'
    }
    $results = $Paths | & "$PSScriptRoot/Get-FileFingerprint.ps1" @childErrorParams

    $batchErrors = @($batchErr)
    $lastBatchError = if ($batchErrors.Count -gt 0) {
        $batchErrors[-1]
    }
    else {
        $null
    }

    $total = $Paths.Count
    $succeeded = @($results).Count
    $failed = $total - $succeeded
    $batchFailed = $null -ne $lastBatchError

    if ($batchFailed) {
        $batchErrorParams = @{
            Message      = ("Batch '{0}' failed (at least one item failed)." -f $BatchId)
            Category     = 'InvalidOperation'
            TargetObject = $BatchId
            ErrorId      = 'InvokeBatchFingerprint.BatchFailed'
        }
        Write-Error @batchErrorParams
    }

    [pscustomobject]@{
        BatchId     = $BatchId
        Total       = $total
        Succeeded   = $succeeded
        Failed      = $failed
        BatchFailed = $batchFailed
    }
}

Detalles clave

Este cmdlet genera una «huella digital» de archivos: calcula su hash SHA256, tamaño y fecha de modificación. El diseño permite procesar lotes de archivos por pipeline, reportando errores por elemento sin detener toda la operación. La clave está en usar -ErrorAction SilentlyContinue para operaciones internas y Write-Error para delegar la política de fallo a quien consume la función.

  • Pipeline flexible por valor y propiedad. $Path combina ValueFromPipeline (acepta strings directos) y ValueFromPipelineByPropertyName (enlaza con propiedades Path, FullName o LiteralPath de objetos). Esto permite tanto 'archivo.txt' | como Get-ChildItem .
  • Supresión interna con -ErrorAction . Las llamadas a Get-Item y Get-FileHash usan Ignore para evitar que sus errores lleguen al stream. Verificamos el resultado ( $null -eq $item ) y emitimos nuestro error con Write-Error : mensaje claro, categoría semántica y ErrorId estructurado.
  • Parámetros de Write-Error :
    • -Message — texto descriptivo del error (orientado a humanos).
    • -Category — clasificación semántica (debe ser un valor de la enumeración [System.Management.Automation.ErrorCategory] 1 ). Ayuda a scripts que filtran errores por tipo.
    • -TargetObject — el objeto que causó el error (aquí: la ruta). Útil para logs estructurados.
    • -ErrorId — identificador único con formato FunctionName.ErrorReason. Permite automatización que distingue errores específicos (ej: reintentar solo ItemNotFound, no HashFailed).
  • $item.PSIsContainer — propiedad booleana que diferencia archivos de directorios. Rechazamos directorios porque no tienen hash de contenido en el sentido de archivo único.
  • $item.LastWriteTimeUtc — timestamp UTC de última modificación. Incluirlo en la salida permite detectar cambios sin recalcular hash (más rápido para auditorías incrementales).

Valores posibles para -ErrorAction

Valor Comportamiento
Continue Por defecto. El error se registra en el stream de error, pero el pipeline continúa procesando elementos posteriores.
Stop El error se convierte en terminante. El elemento actual falla y se detiene el procesamiento de elementos posteriores; el bloque process se interrumpe.
SilentlyContinue El error se suprime: no se registra en el stream de error, el pipeline continúa silenciosamente. Peligroso porque es fácil pasar por alto problemas.
Inquire PowerShell pausa y pregunta al usuario: «¿Continuar?» (muestra opciones: sí/sí a todo/detener/suspender). Rara vez se usa en scripts, más común en modo interactivo.

Ejemplos de uso

Modo tolerante (por defecto)
# 1) Entradas como strings (ValueFromPipeline), reporta errores pero continúa
Write-Host "Modo tolerante (por defecto)" -ForegroundColor Cyan
'file1.txt', 'missing.txt', 'file2.txt' |
    ./pipeline/Get-FileFingerprint.ps1 -ErrorAction Continue |
    Format-Table -AutoSize
# 2) Misma entrada, pero política estricta
Write-Host "Modo estricto: un error corta el pipeline" -ForegroundColor Cyan
'file1.txt', 'missing.txt', 'file2.txt' |
    ./pipeline/Get-FileFingerprint.ps1 -ErrorAction Stop |
    Format-Table -AutoSize
Desde scripts/. Recuerda crear los archivos file1.txt y file2.txt para probar.
Output
Modo tolerante (por defecto)

Path                              SizeBytes LastWriteUtc    Sha256
----                              --------- ------------    ------
path/to/scripts/file1.txt         0         2/21/2026 ...   E3B...
Write-Error:
Line |
   5 |  … ./pipeline/Get-FileFingerprint.ps1 -ErrorAction Continue  …
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Failed to fingerprint 'missing.txt': Cannot find path 'missing.txt' because it does not exist.
path/to/scripts/file2.txt         0         2/21/2026 ...   E3B...

Modo estricto: un error corta el pipeline

Write-Error:
Line |
  10 |  … ./pipeline/Get-FileFingerprint.ps1 -ErrorAction Stop |
     |    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Failed to fingerprint 'missing.txt': Cannot find path 'missing.txt' because it does not exist.
En el primer caso, el error afecta únicamente al elemento actual y el flujo continúa. En el segundo caso, el error se convierte en terminante y detiene el bloque process: los elementos que aún no han sido evaluados nunca llegan a procesarse.

Conexión con la pregunta guía

Con -ErrorAction , quien consume tu función decide si «un elemento inválido invalida el resto» (modo estricto) o si cada elemento se trata como independiente (modo tolerante). Tu función expone ambos comportamientos sin duplicar lógica ni forzar una única política.

$ErrorActionPreference

$ErrorActionPreference define la política de error global para la sesión. Afecta a cmdlets que emiten errores no terminantes y puede usarse para evitar repetir -ErrorAction en cada llamada.

  • Prefiere -ErrorAction para decisiones locales y funciones reutilizables (no sorprende a quien consume tu código).
  • Usa $ErrorActionPreference en scripts ejecutables cuando quieres una política consistente (p. ej., fail-fast).
Preferencia global vs. decisión local
# Solo para esta sesión
$ErrorActionPreference = 'Stop'

# Equivalente a pasar `-ErrorAction Stop`
Get-Item missing.txt

# Se puede sobrescribir localmente
Get-Item missing.txt -ErrorAction Continue

Auditar fallos sin romper el pipeline con -ErrorVariable

A diferencia de -ErrorAction , que decide si un error debe detener el flujo, -ErrorVariable sirve para capturar errores no terminantes en una variable. Esto te permite procesar todos los elementos posibles y, al final, inspeccionar qué falló (y por qué).

Igual que -ErrorAction , -ErrorVariable es un parámetro común: aparece automáticamente cuando la función se declara con [CmdletBinding()] .

Modos de asignación

-ErrorVariable tiene dos modos de asignación:

  • Asignación simple — Si usas -ErrorVariable variable , PowerShell asigna en la variable los errores de esa invocación del comando. Si el comando emite varios errores en una sola ejecución, la variable puede contener varios [ErrorRecord] . Si no limpias la variable antes de invocar de nuevo, podrías leer estado anterior.
  • Asignación acumulativa — Si usas -ErrorVariable +variable , PowerShell acumula errores en la misma variable, agregando cada nuevo error al final de la colección.
Procesar lo válido y auditar los fallos al final
# Captura y acumula errores no terminantes del script
$errs = @()

'file1.txt', 'missing.txt', 'file2.txt', 'missing2.txt' |
    ./pipeline/Get-FileFingerprint.ps1 -ErrorVariable +errs -ErrorAction SilentlyContinue |
    Format-Table -AutoSize

Write-Host "Errores capturados: $($errs.Count)" -ForegroundColor Yellow

$errs |
    ForEach-Object {
        Write-Host "Error en $($_.TargetObject): $($_.Exception.Message)"
    }

Detalles clave

En este ejemplo, el pipeline sigue procesando todos los elementos y los errores se guardan en $errs . Esto permite separar dos preocupaciones: producir resultados útiles (los archivos válidos) y auditar fallos al final.

  • -ErrorAction SilentlyContinue suprime la impresión de errores en pantalla, pero no elimina el error: igual puede ser capturado con -ErrorVariable .
  • -ErrorVariable +errs usa el modo acumulativo: PowerShell agrega cada error como un [ErrorRecord] al arreglo $errs .
  • $_.TargetObject suele contener el objeto que causó el error (lo que hicimos con TargetObject = $Path en Get-FileFingerprint.ps1). Es útil para enlazar el mensaje a la entrada que falló.
  • $_.Exception.Message entrega una descripción humana del problema (p. ej., ruta inexistente), útil para logs o resumen final.

Evita mezclar políticas contradictorias

Si ejecutas con -ErrorAction Stop , el pipeline cortará temprano y -ErrorVariable errs no tendrá una vista completa de los errores. Si tu objetivo es auditoría, suele ser mejor -ErrorAction Continue + -ErrorVariable +errs .

Conexión con la pregunta guía

-ErrorVariable errs encaja cuando la respuesta es: «no, un elemento inválido no debe invalidar el resto» , pero aun así necesitas medir y reportar los fallos para decidir si la ejecución fue aceptable.

Ejercicio: Auditoría por lotes: fallos por lote vs fallos del pipeline

Requisitos

  • Buenas prácticas. Aplica las convenciones que hemos visto hasta ahora.
  • Contexto. Vas a procesar lotes de rutas. Te importa saber:
    • si un lote falló (da lo mismo cuántos elementos fallaron dentro), y
    • cuántos lotes fallaron en total en un pipeline mayor.
  • Parte 1 — Script por lote. Implementa pipeline/Invoke-BatchFingerprint.ps1.
    • Recibe dos parámetros: [string] $BatchId y [string[]] $Paths (ambos requeridos). No procesa elementos uno a uno por pipeline: cada batch se procesa completo en una invocación.
    • Invoca ./pipeline/Get-FileFingerprint.ps1 sobre $Paths . Ten en cuenta que una invocación podría generar múltiples errores.
    • Emite un objeto resumen con: BatchId , Total , Succeeded , Failed y BatchFailed (bool).
    • Si ocurrieron errores, emite un error (uno por batch) indicando que el batch falló. Usa InvokeBatchFingerprint.BatchFailed como ErrorId y el $BatchId como TargetObject .
  • Parte 2 — Pipeline que cuenta batches fallidos. Escribe un snippet que:
    • Construya al menos 3 batches con IDs (p. ej. B1, B2, B3) donde algunos contengan rutas inválidas a propósito.
    • Use -ErrorVariable +batchErrs (modo acumulativo) al invocar el script por cada batch, para acumular errores por batch fallido.
    • Al final imprima los TargetObject (IDs) de cada batch que falló.

Uso esperado

Uso esperado (acumulación por batch)
# Construye batches (cada uno es un objeto con Id y Paths)
$batches = @(
    [pscustomobject]@{ Id = 'B1'; Paths = @('file1.txt', 'missing.txt') },
    [pscustomobject]@{ Id = 'B2'; Paths = @('file2.txt') },
    [pscustomobject]@{ Id = 'B3'; Paths = @('missing2.txt', '.') }
)

$allErrs = @()

# Tu pipeline aquí

Hints

  • Dentro del script, limpia $batchErr antes de cada invocación y evalúa @($batchErr).Count -gt 0 .
  • En la Parte 2, usa Where-Object para quedarte solo con errores de batch. Toma el prefijo con ($_.FullyQualifiedErrorId -split ',')[0] y compáralo contra 'InvokeBatchFingerprint.BatchFailed' . Ejemplo: $allErrs | Where-Object { ($_.FullyQualifiedErrorId -split ',')[0] -eq 'InvokeBatchFingerprint.BatchFailed' } .

Solución

Parte 1
#Requires -Version 7.5
[CmdletBinding()]
param(
    [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
    [Alias('Id')]
    [string] $BatchId,

    [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('FullName', 'LiteralPath')]
    [string[]] $Paths
)

begin {
    Set-StrictMode -Version 3.0
}

process {
    # Importante: limpiar en cada batch para evitar arrastrar estado previo.
    $batchErr = @()

    $childErrorParams = @{
        ErrorAction   = 'SilentlyContinue'
        ErrorVariable = 'batchErr'
    }
    $results = $Paths | & "$PSScriptRoot/Get-FileFingerprint.ps1" @childErrorParams

    $total = $Paths.Count
    $succeeded = @($results).Count
    $failed = $total - $succeeded
    $batchErrors = @($batchErr)
    $batchFailed = $batchErrors.Count -gt 0

    if ($batchFailed) {
        $batchErrorParams = @{
            Message      = ("Batch '{0}' failed (at least one item failed)." -f $BatchId)
            Category     = 'InvalidOperation'
            TargetObject = $BatchId
            ErrorId      = 'InvokeBatchFingerprint.BatchFailed'
        }
        Write-Error @batchErrorParams
    }

    [pscustomobject]@{
        BatchId     = $BatchId
        Total       = $total
        Succeeded   = $succeeded
        Failed      = $failed
        BatchFailed = $batchFailed
    }
}
Parte 2
$batches = @(
    [pscustomobject]@{ Id = 'B1'; Paths = @('file1.txt', 'missing.txt') },
    [pscustomobject]@{ Id = 'B2'; Paths = @('file2.txt') },
    [pscustomobject]@{ Id = 'B3'; Paths = @('missing2.txt', '.') }
)

$allErrs = @()

$errorParams = @{
    ErrorAction   = 'SilentlyContinue'
    ErrorVariable = '+allErrs'
}

$batches | ./pipeline/Invoke-BatchFingerprint.ps1 @errorParams | Out-Null

$allErrs |
    Where-Object {
        ($_.FullyQualifiedErrorId -split ',')[0] -eq 'InvokeBatchFingerprint.BatchFailed'
    } |
    ForEach-Object {
        Write-Host "Batch fallido: $($_.TargetObject)" -ForegroundColor Red
    }

Conclusiones

El modelo de errores de PowerShell está diseñado para trabajar con flujos, no con resultados únicos. Por eso distingue entre fallos que afectan a un elemento y fallos que deben detener todo el proceso. Elegir entre continuar o detener no es una decisión técnica aislada: depende del objetivo de la automatización y del contrato que tu herramienta ofrece a quien la consume.

-ErrorAction permite delegar la política de fallo, mientras que -ErrorVariable habilita auditorías posteriores sin perder progreso. Juntas, estas herramientas permiten construir pipelines expresivos, controlados y semánticamente claros, tanto a nivel de elemento como de lote.

Puntos clave

  • PowerShell distingue entre errores terminantes y no terminantes en el pipeline.
  • -ErrorAction delega la política de fallo a quien consume la función.
  • -ErrorVariable permite capturar y analizar errores sin interrumpir el flujo.
  • Separar el procesamiento de datos de la política de error mejora la reutilización y claridad.

¿Qué nos llevamos?

Diseñar pipelines robustos implica decidir conscientemente cuándo fallar y cuándo continuar. No existe una única política correcta: lo importante es que la decisión sea explícita, coherente con el objetivo del script y visible para quien lo reutilice en otros contextos.

¿Con ganas de más?

Notas

  1. Puedes encontrar la lista completa de categorías en la documentación oficial de Microsoft. Volver