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
y cómo auditar errores sin interrumpir
el flujo usando -ErrorAction -ErrorAction . 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.
-ErrorVariable -ErrorVariable
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 -ErrorAction
-ErrorAction -ErrorAction
Cuando defines un cmdlet con ,
PowerShell agrega automáticamente parámetros comunes como [CmdletBinding()] [CmdletBinding()] . 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.
-ErrorAction -ErrorAction
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.
#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 para operaciones internas y -ErrorAction SilentlyContinue -ErrorAction SilentlyContinue para
delegar la política de fallo a quien consume la función.
Write-Error Write-Error
- Pipeline flexible por valor y propiedad.
combina$Path$Path(acepta strings directos) yValueFromPipelineValueFromPipeline(enlaza con propiedadesValueFromPipelineByPropertyNameValueFromPipelineByPropertyNamePath,FullNameoLiteralPathde objetos). Esto permite tantocomo'archivo.txt' |'archivo.txt' |.Get-ChildItemGet-ChildItem - Supresión interna con
. Las llamadas a-ErrorAction-ErrorActionyGet-ItemGet-ItemusanGet-FileHashGet-FileHashIgnorepara evitar que sus errores lleguen al stream. Verificamos el resultado () y emitimos nuestro error con$null -eq $item$null -eq $item: mensaje claro, categoría semántica y ErrorId estructurado.Write-ErrorWrite-Error - Parámetros de
:Write-ErrorWrite-Error-
— texto descriptivo del error (orientado a humanos).-Message-Message -
— clasificación semántica (debe ser un valor de la enumeración-Category-Category1 ). Ayuda a scripts que filtran errores por tipo.[System.Management.Automation.ErrorCategory][System.Management.Automation.ErrorCategory] -
— el objeto que causó el error (aquí: la ruta). Útil para logs estructurados.-TargetObject-TargetObject -
— identificador único con formato-ErrorId-ErrorIdFunctionName.ErrorReason. Permite automatización que distingue errores específicos (ej: reintentar soloItemNotFound, noHashFailed).
-
-
— propiedad booleana que diferencia archivos de directorios. Rechazamos directorios porque no tienen hash de contenido en el sentido de archivo único.$item.PSIsContainer$item.PSIsContainer -
— timestamp UTC de última modificación. Incluirlo en la salida permite detectar cambios sin recalcular hash (más rápido para auditorías incrementales).$item.LastWriteTimeUtc$item.LastWriteTimeUtc
Valores posibles para -ErrorAction -ErrorAction
-ErrorAction -ErrorAction | Valor | Comportamiento |
| Por defecto. El error se registra en el stream de error, pero el pipeline continúa procesando elementos posteriores. |
|
El error se convierte en terminante. El elemento actual falla y se
detiene el procesamiento de elementos posteriores; el bloque
se interrumpe.
|
| 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. |
| 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
# 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 -AutoSizescripts/. Recuerda crear los archivos file1.txt y
file2.txt para probar. 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. Conexión con la pregunta guía
Con , 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.
-ErrorAction -ErrorAction
$ErrorActionPreference $ErrorActionPreference
$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 $ErrorActionPreference $ErrorActionPreference en cada llamada.
-ErrorAction -ErrorAction
- Prefiere
para decisiones locales y funciones reutilizables (no sorprende a quien consume tu código).-ErrorAction-ErrorAction - Usa
en scripts ejecutables cuando quieres una política consistente (p. ej., fail-fast).$ErrorActionPreference$ErrorActionPreference
# 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 -ErrorVariable
-ErrorVariable -ErrorVariable
A diferencia de , que decide si un
error debe detener el flujo, -ErrorAction -ErrorAction 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é).
-ErrorVariable -ErrorVariable
Igual que , -ErrorAction -ErrorAction es un parámetro común: aparece automáticamente
cuando la función se declara con -ErrorVariable -ErrorVariable .
[CmdletBinding()] [CmdletBinding()]
Modos de asignación
tiene dos modos de asignación:
-ErrorVariable -ErrorVariable
- Asignación simple — Si usas
, 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-ErrorVariable variable-ErrorVariable variable. Si no limpias la variable antes de invocar de nuevo, podrías leer estado anterior.[ErrorRecord][ErrorRecord] - Asignación acumulativa — Si usas
, PowerShell acumula errores en la misma variable, agregando cada nuevo error al final de la colección.-ErrorVariable +variable-ErrorVariable +variable
# 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 . Esto permite separar dos
preocupaciones:
producir resultados útiles (los archivos válidos) y auditar fallos al
final.
$errs $errs
-
suprime la impresión de errores en pantalla, pero no elimina el error: igual puede ser capturado con-ErrorAction SilentlyContinue-ErrorAction SilentlyContinue.-ErrorVariable-ErrorVariable -
usa el modo acumulativo: PowerShell agrega cada error como un-ErrorVariable +errs-ErrorVariable +errsal arreglo[ErrorRecord][ErrorRecord].$errs$errs -
suele contener el objeto que causó el error (lo que hicimos con$_.TargetObject$_.TargetObjectenTargetObject = $PathTargetObject = $PathGet-FileFingerprint.ps1). Es útil para enlazar el mensaje a la entrada que falló. -
entrega una descripción humana del problema (p. ej., ruta inexistente), útil para logs o resumen final.$_.Exception.Message$_.Exception.Message
Evita mezclar políticas contradictorias
Si ejecutas con , el pipeline cortará
temprano y -ErrorAction Stop -ErrorAction Stop no tendrá una vista
completa de los errores. Si tu objetivo es auditoría, suele ser mejor
-ErrorVariable errs -ErrorVariable errs + -ErrorAction Continue -ErrorAction Continue .
-ErrorVariable +errs -ErrorVariable +errs
Conexión con la pregunta guía
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.
-ErrorVariable errs -ErrorVariable errs
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:
y[string] $BatchId[string] $BatchId(ambos requeridos). No procesa elementos uno a uno por pipeline: cada batch se procesa completo en una invocación.[string[]] $Paths[string[]] $Paths - Invoca
./pipeline/Get-FileFingerprint.ps1sobre. Ten en cuenta que una invocación podría generar múltiples errores.$Paths$Paths - Emite un objeto resumen con:
,BatchIdBatchId,TotalTotal,SucceededSucceededyFailedFailed(bool).BatchFailedBatchFailed - Si ocurrieron errores, emite un error (uno por batch) indicando que el batch falló. Usa
comoInvokeBatchFingerprint.BatchFailedInvokeBatchFingerprint.BatchFailedy elErrorIdErrorIdcomo$BatchId$BatchId.TargetObjectTargetObject
-
- 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
(modo acumulativo) al invocar el script por cada batch, para acumular errores por batch fallido.-ErrorVariable +batchErrs-ErrorVariable +batchErrs - Al final imprima los
(IDs) de cada batch que falló.TargetObjectTargetObject
-
Uso esperado
# 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
antes de cada invocación y evalúa$batchErr$batchErr.@($batchErr).Count -gt 0@($batchErr).Count -gt 0 - En la Parte 2, usa
para quedarte solo con errores de batch. Toma el prefijo conWhere-ObjectWhere-Objecty compáralo contra($_.FullyQualifiedErrorId -split ',')[0]($_.FullyQualifiedErrorId -split ',')[0]. Ejemplo:'InvokeBatchFingerprint.BatchFailed''InvokeBatchFingerprint.BatchFailed'.$allErrs | Where-Object { ($_.FullyQualifiedErrorId -split ',')[0] -eq 'InvokeBatchFingerprint.BatchFailed' }$allErrs | Where-Object { ($_.FullyQualifiedErrorId -split ',')[0] -eq 'InvokeBatchFingerprint.BatchFailed' }
Solución
#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
}
}$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.
permite delegar la política de
fallo, mientras que -ErrorAction -ErrorAction 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.
-ErrorVariable -ErrorVariable
Puntos clave
- PowerShell distingue entre errores terminantes y no terminantes en el pipeline.
-
delega la política de fallo a quien consume la función.-ErrorAction-ErrorAction -
permite capturar y analizar errores sin interrumpir el flujo.-ErrorVariable-ErrorVariable - 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?
Referencias recomendadas
- “Controlling Error Reporting Behavior and Intercepting Errors” (pp. 7–15) en The Big Book of PowerShell Error Handling por Dave WyattEste capítulo repasa las herramientas principales de PowerShell para controlar y registrar errores: variables automáticas como
, parámetros comunes como$Error$Errory-ErrorAction-ErrorAction, y estructuras para manejar errores terminantes (-ErrorVariable-ErrorVariable/trytry/catchcatch,finallyfinally). También explica cómo interpretar códigos de salida y el estado del último comando (traptrap,$LASTEXITCODE$LASTEXITCODE), con consejos sobre cuándo usar cada mecanismo y qué limitaciones considerar.$?$?
Notas
- Puedes encontrar la lista completa de categorías en la documentación oficial de Microsoft. Volver