El try catch en PowerShell es la forma de decidir qué hace un script cuando algo falla: avisar, reintentar, saltar al siguiente equipo o detenerse con un código de salida que entienda quien lo ejecuta. Es la base del manejo de errores en PowerShell y depende de un parámetro, ErrorAction, que decide si un error llega al catch. Sin él, un script puede fallar a mitad de camino y terminar «bien». Lo medimos: un script cuya primera orden falla termina con el código 0, el mismo de un script que funcionó.
Esta guía reúne lo que hace falta para el trabajo diario: por qué a veces el catch no se ejecuta, cómo usar -ErrorAction, cómo capturar cada tipo de error, qué hacer con los programas externos y con los códigos de salida, y ocho trampas que no avisan. Todo lo probamos en este mes de noviembre de 2026 en Windows 11, con Windows PowerShell 5.1 (5.1.26100.9818) y con PowerShell 7.6.9, ambos con la interfaz en español.
Si es su primer contacto con la consola, empiece por nuestra guía de PowerShell. Si ya escribe scripts y quiere que fallen con orden, siga leyendo.
Errores que terminan y que no: la clave del try catch en PowerShell
PowerShell distingue dos clases de error, y esa diferencia explica casi todas las dudas sobre el try catch en PowerShell.
| Clase | Qué pasa | Ejemplos medidos | ¿Entra en el catch? |
|---|---|---|---|
| Error que no termina | Se escribe el mensaje en rojo y el comando sigue con lo demás | Get-ChildItem o Get-Item sobre una ruta que no existe, Write-Error | No, salvo con -ErrorAction Stop |
| Error que termina | Se detiene la instrucción o el script | throw, división por cero, un método de .NET que falla, -ErrorAction Stop | Sí |
| Código de salida de un programa externo | No es un error de PowerShell; solo cambia $LASTEXITCODE | cmd.exe /c exit 3, robocopy | No, salvo con la preferencia de PowerShell 7.4 o posterior |
Por ejemplo, la duda más repetida en los foros, «mi try catch no funciona», casi siempre es esto:
# No entra en el catch: Get-ChildItem solo escribe el error y sigue
try { Get-ChildItem -Path 'C:\NoExiste' } catch { 'Capturado' }
# Entra en el catch: -ErrorAction Stop convierte el error en uno que termina
try { Get-ChildItem -Path 'C:\NoExiste' -ErrorAction Stop } catch { "Capturado: $($_.Exception.Message)" }
En las dos versiones, el primer bloque muestra el error en rojo y no imprime «Capturado». El segundo imprime el mensaje No se encuentra la ruta de acceso 'C:\NoExiste' porque no existe.
Sintaxis de try catch en PowerShell: try, catch y finally
Un bloque try contiene lo que puede fallar. La referencia de Microsoft sobre try, catch y finally describe la sintaxis completa; aquí va lo que se usa en la práctica. Detrás puede haber uno o varios catch y, opcionalmente, un finally.
- try: el código que se vigila.
- catch: se ejecuta si dentro del
tryse produce un error que termina. Dentro, la variable$_es el error (un objetoErrorRecord). - catch con tipo:
catch [System.IO.FileNotFoundException]solo atrapa ese tipo de excepción. Se revisan en orden y gana el primero que coincide, así que elcatchgenérico va al final. - finally: se ejecuta siempre, haya error o no. Lo comprobamos también con un
returndentro deltryy con un error que nadie captura: en los dos casosfinallycorrió.
Este es el esquema que conviene copiar: leer un archivo de configuración y, si no existe, seguir con valores por defecto.
$ruta = 'C:\Scripts\config.json'
try {
$config = Get-Content -Path $ruta -Raw -ErrorAction Stop | ConvertFrom-Json
}
catch [System.Management.Automation.ItemNotFoundException] {
Write-Warning "No existe $ruta. Se usan los valores por defecto."
$config = [pscustomobject]@{ DiasDeRetencion = 30 }
}
catch {
Write-Warning "Error inesperado: $($_.Exception.GetType().FullName)"
throw
}
finally {
Write-Verbose 'Lectura de la configuración terminada'
}
$config.DiasDeRetencion
El throw sin argumentos dentro de un catch vuelve a lanzar el mismo error. Así se registra o se avisa sin tragarse un fallo que no se esperaba.
ErrorAction y ErrorActionPreference: cuándo entra el catch
El try catch en PowerShell depende de esta pieza. El parámetro común -ErrorAction decide qué hace un comando con sus errores que no terminan. La variable $ErrorActionPreference hace lo mismo para todos los comandos del ámbito donde se define. Estos son los valores, medidos en las dos versiones y descritos en la documentación de las variables de preferencia:
| Valor | Qué hace | ¿Queda en $Error? | Versión |
|---|---|---|---|
Continue | Muestra el error y sigue. Es el valor por defecto | Sí | 5.1 y 7 |
SilentlyContinue | No muestra nada y sigue | Sí | 5.1 y 7 |
Ignore | No muestra nada, sigue y no deja rastro | No | 5.1 y 7 |
Stop | Convierte el error en uno que termina; el catch lo atrapa | Sí | 5.1 y 7 |
Inquire | Pregunta qué hacer. No sirve en scripts desatendidos | Sí | 5.1 y 7 |
Break | Entra en el depurador | Sí | Solo 7 |
Break no existe en Windows PowerShell 5.1: lo comprobamos en la lista de valores de cada versión. Un script que lo use falla en 5.1.
En la práctica, hay tres formas de aplicar Stop, de la más localizada a la más amplia:
# 1. Solo en un comando
Get-Item -Path 'C:\NoExiste' -ErrorAction Stop
# 2. En todo el script o la función (se pone al principio)
$ErrorActionPreference = 'Stop'
# 3. Como valor por defecto del parámetro en todos los comandos de la sesión
$PSDefaultParameterValues['*:ErrorAction'] = 'Stop'
Por lo tanto, recomendamos la segunda al principio de los scripts desatendidos, y la primera cuando solo un comando debe detener el proceso. Con la tercera, cualquier error detiene lo que se esté haciendo, también en la consola interactiva.
SilentlyContinue frente a Ignore
Los dos callan el error; sin embargo, no son iguales. Con SilentlyContinue el error queda en $Error; con Ignore, no. Lo medimos vaciando $Error antes de cada prueba: tras un Get-Item fallido, SilentlyContinue dejó 1 error y Ignore dejó 0. Si quiere comprobar después qué falló, use SilentlyContinue con -ErrorVariable:
Get-Item -Path 'C:\NoExiste', $env:TEMP -ErrorAction SilentlyContinue -ErrorVariable fallos
"Fallaron $($fallos.Count) rutas"
$fallos | ForEach-Object { $_.TargetObject }
Capturar por tipo de error con try catch en PowerShell
Atrapar por tipo es más seguro que comparar el texto del mensaje, por dos razones que medimos. La primera: el texto cambia según la versión. En PowerShell 7 los mensajes del propio PowerShell salen en español, pero los de .NET salen en inglés. El mismo archivo inexistente dio «No se pudo encontrar el archivo» en 5.1 y «Could not find file» en 7.6. La segunda: algunos tipos también cambian. Una petición web a un puerto cerrado lanzó System.Net.WebException en 5.1 y System.Net.Http.HttpRequestException en 7.6, así que un catch [System.Net.WebException] escrito para 5.1 no atrapa ese error en 7.
Estos son los tipos que obtuvimos en el laboratorio, con el mensaje de 5.1:
| Situación | Tipo de la excepción | Mensaje en 5.1 |
|---|---|---|
Ruta que no existe (Get-Item, Get-ChildItem) | System.Management.Automation.ItemNotFoundException | No se encuentra la ruta de acceso … porque no existe. |
| Archivo que no existe, leído con .NET | System.IO.FileNotFoundException | No se pudo encontrar el archivo … |
Servicio que no existe (Stop-Service) | Microsoft.PowerShell.Commands.ServiceCommandException | No se encuentra ningún servicio con el nombre … |
| Clave del registro sin permiso | System.Security.SecurityException | Acceso denegado al Registro solicitado. |
| Servidor web que no responde | System.Net.WebException (en 7: System.Net.Http.HttpRequestException) | No es posible conectar con el servidor remoto |
| División por cero | System.DivideByZeroException | |
Conversión imposible, como [int]'abc' | System.Management.Automation.RuntimeException | |
throw con un texto | System.Management.Automation.RuntimeException | El propio texto |
Write-Error con -ErrorAction Stop | Microsoft.PowerShell.Commands.WriteErrorException | El propio texto |
Así que, para saber qué tipo atrapar, provoque el error una vez y lea el nombre completo:
try { Stop-Service -Name 'ServicioInexistente' -ErrorAction Stop }
catch { $_.Exception.GetType().FullName }
Cuando el tipo es demasiado general, como RuntimeException, la propiedad FullyQualifiedErrorId distingue mejor. En el servicio inexistente vale NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.StopServiceCommand. Y CategoryInfo.Category da una categoría corta: ObjectNotFound para una ruta y PermissionDenied para el registro sin permiso.
Qué hay dentro de un error capturado
Dentro del catch, $_ es un ErrorRecord. Estas son las propiedades que más sirven para un registro o un aviso:
| Propiedad | Qué contiene | Ejemplo medido |
|---|---|---|
$_.Exception.Message | El mensaje | No se encuentra la ruta de acceso … porque no existe. |
$_.Exception.GetType().FullName | El tipo de la excepción | System.Management.Automation.ItemNotFoundException |
$_.FullyQualifiedErrorId | Identificador estable del error y del comando | PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand |
$_.CategoryInfo.Category | Categoría | ObjectNotFound |
$_.TargetObject | Sobre qué se trabajaba | La ruta que no existía |
$_.InvocationInfo.ScriptLineNumber | Línea del script donde ocurrió | Un número de línea |
$_.ScriptStackTrace | Cadena de llamadas hasta el error | Funciones y líneas |
Además de $_, hay tres herramientas para revisar errores después:
$Error: la lista de los últimos errores de la sesión, con el más reciente en$Error[0]. Guarda 256: lo comprobamos provocando 300 errores seguidos, y en las dos versiones quedaron 256. En PowerShell 7 la variable$MaximumErrorCount, que la documentación da como el límite, no existe, y asignarla no cambia nada.$?: vale$truesi el último comando terminó bien. Hay que leerla justo después del comando, porque cualquier otra instrucción la cambia.Get-Error: muestra el último error con todo su detalle. Solo existe en PowerShell 7.
Por otra parte, la forma de mostrar los errores en la consola también cambia. PowerShell 7 usa por defecto ConciseView, una sola línea con el mensaje, y Windows PowerShell 5.1 usa NormalView, con la línea, el carácter, la categoría y el identificador. Si un compañero le envía un error copiado de la consola de 7, falta información: pídale el resultado de Get-Error.
throw en PowerShell, Write-Error y ThrowTerminatingError
Hay tres formas de generar un error propio. El throw en PowerShell es la más conocida, pero cada una sirve para algo distinto:
| Forma | Clase de error | Cuándo usarla |
|---|---|---|
throw | Termina el script, salvo que lo atrape un catch | Cuando no tiene sentido seguir: falta la configuración, no hay conexión |
Write-Error | No termina | Cuando un elemento falla pero los demás deben procesarse |
$PSCmdlet.ThrowTerminatingError() | Termina la instrucción | En funciones avanzadas, para dar un error con categoría y objeto, como los cmdlets de Microsoft |
Con throw en PowerShell se puede lanzar un texto o una excepción concreta. Sin embargo, la segunda forma es mejor porque quien llama puede atraparla por tipo. En la prueba, una excepción ArgumentException lanzada con el parámetro Ruta llegó al catch [System.ArgumentException] con la propiedad ParamName intacta.
function Get-ConfiguracionKH {
[CmdletBinding()]
param([Parameter(Mandatory)][string]$Ruta)
if (-not (Test-Path -LiteralPath $Ruta)) {
throw [System.IO.FileNotFoundException]::new('No existe el archivo de configuración', $Ruta)
}
Get-Content -LiteralPath $Ruta -Raw | ConvertFrom-Json
}
try { Get-ConfiguracionKH -Ruta 'C:\Scripts\no-existe.json' }
catch [System.IO.FileNotFoundException] { "Falta: $($_.Exception.FileName)" }
Write-Error no entra en un catch por sí solo. Lo medimos: con el valor por defecto, el catch no se ejecutó; con -ErrorAction Stop, sí, y el tipo fue WriteErrorException.
Programas externos y try catch en PowerShell: $LASTEXITCODE
El try catch en PowerShell no ve los fallos de un programa externo. Un programa externo (robocopy, cmd.exe, winget, msiexec) no lanza excepciones de PowerShell. Cuando falla, deja un número en $LASTEXITCODE y pone $? en $false, pero el catch no se entera. Lo medimos con cmd.exe /c exit 3: en 5.1 y en 7.6 el catch no se ejecutó, $LASTEXITCODE valió 3 y $? quedó en $false.
Por eso, la solución clásica, válida en las dos versiones, es revisar el código y lanzar un error propio. Con Robocopy hay que hacerlo bien, porque sus códigos del 1 al 7 no son errores: indican qué se copió. A partir del 8 hay al menos un fallo: así lo dice la página de códigos de Robocopy de Microsoft (códigos de retorno de Robocopy), aunque la del comando escribe «mayor que 8», y el 8 ya significa que algo no se copió. Lo explicamos en la guía de Robocopy.
$origen = 'D:\Datos'
$destino = '\\nas01\copias\datos'
robocopy $origen $destino /E /R:2 /W:5 /NP /NFL /NDL
if ($LASTEXITCODE -ge 8) {
throw "Robocopy falló con el código $LASTEXITCODE"
}
"Copia terminada con el código $LASTEXITCODE"
La novedad de PowerShell 7.4
Desde PowerShell 7.4 existe $PSNativeCommandUseErrorActionPreference. Con ella en $true, un programa externo que termina con un código distinto de 0 produce un error de PowerShell que sigue a $ErrorActionPreference. En 7.6 la medimos: viene en $false, y al activarla junto con Stop, el catch atrapó un System.Management.Automation.NativeCommandExitException con el mensaje El programa "cmd.exe" finalizó con un código de salida distinto de cero: 3 (0x00000003). En 5.1 la variable no existe.
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
try {
cmd.exe /c exit 3
}
catch {
"Falló un programa externo: $($_.Exception.Message)"
}
Con Robocopy no conviene activarla de forma global: un código 1 (archivos copiados) pasaría a ser un error. La propia documentación de Microsoft pone ese ejemplo y la desactiva solo para esa parte del script.
Códigos de salida en PowerShell: lo que ve el programador de tareas
Si el script lo lanza un programador de tareas, un sistema de monitoreo o una herramienta de despliegue, lo que cuenta es su código de salida: 0 significa éxito y cualquier otro número, fallo. Medimos cinco scripts de prueba lanzados de dos formas, con -File y con -Command, y los resultados fueron iguales en 5.1 y en 7.6:
| Qué hace el script | Con -File | Con -Command |
|---|---|---|
Un comando falla sin Stop y el script sigue | 0 | 0 |
Termina con throw | 1 | 1 |
Termina con exit 3 | 3 | 1 |
Usa $ErrorActionPreference = 'Stop' y un comando falla | 1 | 1 |
Atrapa el error y termina con exit 2 | 2 | 1 |
Hay dos conclusiones. En primer lugar, la primera fila es la más peligrosa: un script que falla sin Stop termina con 0, y el programador de tareas lo da por bueno. En segundo lugar, con -Command, el código propio se pierde y queda en 1. La documentación de pwsh lo confirma y propone añadir exit $LASTEXITCODE al final del comando. Por eso, en una tarea programada, use -File. Lo detallamos en la guía de tareas programadas con PowerShell.
# Acción de una tarea programada: -File conserva el código de salida del script
pwsh.exe -NoProfile -ExecutionPolicy Bypass -File 'C:\Scripts\copia-nocturna.ps1'
Ocho trampas del try catch en PowerShell
Estas son las situaciones del laboratorio en las que el script se comportó distinto de lo que parece. Ninguna muestra un error.
1. continue dentro de ForEach-Object termina el script
Es la trampa más grave del try catch en PowerShell. Dentro de un bucle foreach, continue en un catch salta al siguiente elemento. Dentro de ForEach-Object no hay un bucle al que volver, y PowerShell sale del script entero. Lo medimos con tres elementos que fallan en el segundo: el script imprimió «1» y «fallo», no procesó el tercero, no ejecutó la línea siguiente y terminó con el código 0. Dentro de ForEach-Object, use return, que pasa al siguiente elemento (medido: «1, fallo, 3»).
1..3 | ForEach-Object {
try {
if ($_ -eq 2) { throw 'falla el elemento 2' }
"Procesado $_"
}
catch {
Write-Warning $_.Exception.Message
return # no use continue aquí
}
}
2. -ErrorAction no hace nada en una función sin [CmdletBinding()]
Una función sencilla, sin [CmdletBinding()], no tiene los parámetros comunes. Si alguien la llama con -ErrorAction Stop, PowerShell no da ningún aviso, el error no se detiene y la función sigue. En la prueba, la función imprimió la línea que venía después del error. Con [CmdletBinding()] el mismo llamado entró en el catch. Toda función que vaya a usarse con try catch debe llevarlo.
3. La preferencia de una función no llega a un módulo
$ErrorActionPreference = 'Stop' dentro de una función no afecta a los comandos de un módulo que esa función llama: el módulo tiene su propio ámbito. En la prueba, la función del módulo falló y siguió adelante sin pasar por el catch. En cambio, un cmdlet de Windows llamado desde la misma función sí se detuvo. Por lo tanto, si llama funciones de un módulo, páseles -ErrorAction Stop de forma explícita.
4. $_ se pisa dentro del catch
Dentro del catch, $_ es el error. Pero si dentro del catch usa una tubería con ForEach-Object o Where-Object, $_ pasa a ser cada elemento de esa tubería. Guarde el error en una variable al empezar:
try { Get-Item -Path 'C:\NoExiste' -ErrorAction Stop }
catch {
$fallo = $_
'servidor1', 'servidor2' | ForEach-Object { "Aviso para $_" }
"Error original: $($fallo.Exception.Message)"
}
5. SilentlyContinue en todo el script
Poner $ErrorActionPreference = 'SilentlyContinue' al principio hace que nada falle a la vista y, como mostramos, que el script termine con 0. Es la causa típica de «la tarea dice que terminó bien, pero no hizo nada». Si un comando concreto puede fallar sin importancia, ponga -ErrorAction SilentlyContinue solo en ese comando.
6. $? y los programas externos
$? solo informa del último comando. Si entre el programa externo y la comprobación hay otra instrucción, aunque sea una asignación, $? vuelve a $true. Para programas externos, compruebe $LASTEXITCODE justo después.
7. Un tipo que no existe en 5.1 rompe el catch
En el try catch en PowerShell, un catch puede nombrar varios tipos, y es tentador poner el de 5.1 y el de 7 juntos. Pero si uno de ellos no existe en la versión que ejecuta el script, Windows PowerShell 5.1 no ignora esa línea: la instrucción entera falla con No se encuentra el tipo [System.Net.Http.HttpRequestException]., aunque el error real fuera otro. Lo medimos lanzando un throw cualquiera: en 7 llegó al catch genérico y en 5.1 dio ese error. Si el script debe funcionar en las dos versiones, compare el nombre del tipo dentro de un catch genérico, como en el escenario de reintentos.
8. .Count con el modo estricto en 5.1
Con Set-StrictMode -Version Latest, si un comando devuelve un solo objeto, pedirle .Count falla en Windows PowerShell 5.1 con No se encuentra la propiedad 'Count' en este objeto. Lo medimos con una búsqueda que devolvía un único archivo. Envuelva el resultado en @() para tener siempre una lista: $archivos = @(Get-ChildItem -Path $Carpeta -File).
trap y Set-StrictMode
trap es la forma antigua de atrapar errores: un bloque que se ejecuta ante cualquier error que termina en su ámbito. Con continue dentro, el script sigue en la instrucción siguiente. Funciona en las dos versiones, pero es más difícil de leer que un try catch y hoy casi no se usa.
Set-StrictMode -Version Latest convierte en error cosas que PowerShell deja pasar en silencio, como leer una variable que no existe. En la prueba, con modo estricto, leer $variableQueNoExiste lanzó un error que el catch atrapó, con el mensaje La variable '$variableQueNoExiste' no se puede recuperar porque no se ha establecido. Sin modo estricto, la variable vale $null y el script sigue. Es útil en scripts largos, donde un nombre mal escrito puede pasar meses sin notarse.
Escenarios de try catch en PowerShell
Recorrer una lista con try catch en PowerShell sin detenerse
Un caso diario de la mesa de ayuda TI: revisar varios servicios o equipos y obtener un informe de todos, incluidos los que fallan. El try catch va dentro del bucle, no fuera.
$servicios = 'Spooler', 'W32Time', 'ServicioQueNoExiste'
$informe = foreach ($nombre in $servicios) {
try {
$s = Get-Service -Name $nombre -ErrorAction Stop
[pscustomobject]@{ Servicio = $nombre; Estado = "$($s.Status)"; Error = '' }
}
catch {
[pscustomobject]@{ Servicio = $nombre; Estado = 'No disponible'; Error = $_.Exception.Message }
}
}
$informe | Format-Table -AutoSize
$informe | Export-Csv -Path "$env:TEMP\servicios.csv" -NoTypeInformation -Encoding UTF8
El informe tiene una fila por servicio, y la del servicio inexistente lleva el mensaje del error en lugar de detener la revisión. Si quiere hacer lo mismo en equipos remotos, en la guía de PowerShell remoto está Invoke-Command.
Reintentar ante fallos pasajeros
Por ejemplo, una red que se corta un segundo o un servicio en la nube que responde tarde no deberían detener una tarea. Este patrón reintenta solo los errores de red y deja pasar los demás:
function Invoke-ConReintento {
[CmdletBinding()]
param(
[Parameter(Mandatory)][scriptblock]$Accion,
[int]$Intentos = 3,
[int]$EsperaSegundos = 5
)
for ($i = 1; $i -le $Intentos; $i++) {
try {
return & $Accion
}
catch {
# Se compara el nombre: el tipo HttpRequestException no existe en Windows PowerShell 5.1
$esDeRed = $_.Exception.GetType().Name -in 'WebException', 'HttpRequestException'
if (-not $esDeRed -or $i -eq $Intentos) { throw }
Write-Warning "Intento $i de $Intentos falló: $($_.Exception.Message). Se reintenta en $EsperaSegundos s."
Start-Sleep -Seconds $EsperaSegundos
}
}
}
Invoke-ConReintento -Accion { Invoke-RestMethod -Uri 'https://www.kharonte.com/wp-json/' -TimeoutSec 10 } -Intentos 3
El catch reconoce por su nombre los dos tipos de error web, el de 5.1 y el de 7, para que funcione igual en las dos versiones. Si una URL inexistente o un error de permisos lanzan otro tipo de excepción, no se reintentan y salen enseguida, que es lo correcto. Antes de culpar al script, compruebe la conexión con Test-NetConnection.
Limpiar siempre con finally
finally es el lugar para cerrar lo que se abrió: una transcripción, una sesión remota, un archivo temporal o una conexión. Se ejecuta aunque el try falle:
$temporal = Join-Path $env:TEMP 'informe-parcial.csv'
try {
Get-Process | Select-Object Name, Id, CPU | Export-Csv -Path $temporal -NoTypeInformation
# ... procesar el archivo ...
}
finally {
if (Test-Path -LiteralPath $temporal) { Remove-Item -LiteralPath $temporal }
}
Plantilla de manejo de errores en PowerShell
Esta plantilla para scripts desatendidos reúne lo anterior: modo estricto, Stop como preferencia, un registro con Start-Transcript, un catch que anota el error con su línea, y códigos de salida que entiende el programador de tareas. La probamos tal cual, con éxito y con fallo, en las dos versiones.
#Requires -Version 5.1
[CmdletBinding()]
param([string]$Carpeta = 'C:\Scripts\Datos')
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$registro = Join-Path $env:TEMP ("tarea-{0:yyyyMMdd-HHmmss}.log" -f (Get-Date))
Start-Transcript -Path $registro | Out-Null
$codigo = 0
try {
$archivos = @(Get-ChildItem -Path $Carpeta -File)
"Archivos encontrados: $($archivos.Count)"
# ... el trabajo del script ...
}
catch {
$codigo = 1
Write-Warning ("Error en la línea {0}: {1}" -f $_.InvocationInfo.ScriptLineNumber, $_.Exception.Message)
Write-Warning $_.ScriptStackTrace
}
finally {
Stop-Transcript | Out-Null
}
exit $codigo
El exit va después del finally para que el registro se cierre siempre. De este modo, si la carpeta no existe, el script termina con 1 y el registro guarda la línea y el mensaje. Ese registro, más el código de salida, es lo que permite saber qué pasó sin repetir la ejecución. Para los errores que deja Windows, y no el script, la herramienta es el visor de eventos.
Recomendaciones para el try catch en PowerShell
- Ponga
$ErrorActionPreference = 'Stop'al principio de todo script desatendido, y relájela solo en el comando que lo necesite. - Atrape por tipo, no por texto. Los mensajes cambian de idioma entre versiones; los tipos son más estables, aunque también conviene revisarlos, como el error web.
- No deje un
catchvacío. Uncatch {}oculta el error y el script sigue como si nada. Como mínimo, anote el error. - Vuelva a lanzar lo que no sabe resolver con
throwdentro delcatch. - Revise
$LASTEXITCODEdespués de cada programa externo, o active$PSNativeCommandUseErrorActionPreferenceen 7.4 o posterior, salvo con programas como Robocopy. - Termine con un código de salida explícito y lance los scripts con
-File. - Use
[CmdletBinding()]en sus funciones para que acepten-ErrorAction. - Dentro de
ForEach-Object, usereturn, nocontinue. - Pruebe el camino del error, no solo el que funciona: provoque el fallo una vez, con una ruta o un servicio que no existe, y compruebe qué registra el script y con qué código termina.
- Revise los scripts con PSScriptAnalyzer, que avisa de los bloques
catchvacíos con su reglaPSAvoidUsingEmptyCatchBlocky de otros descuidos. Lo explicamos en la guía de PowerShell ISE vs Visual Studio Code.
Errores frecuentes con try catch en PowerShell
| Síntoma | Causa | Solución |
|---|---|---|
El catch no se ejecuta | El error no termina | -ErrorAction Stop o $ErrorActionPreference = 'Stop' |
| La tarea programada dice «correcto» y no hizo nada | El script falla sin Stop y termina con 0 | Stop y exit 1 en el catch |
| El código de salida siempre es 1 | El script se lanza con -Command | Lanzarlo con -File |
| El script se corta en mitad de una lista sin error | continue dentro de ForEach-Object | return |
-ErrorAction Stop no detiene la función | Falta [CmdletBinding()] | Añadirlo |
Un catch por tipo funciona en 5.1 y no en 7 | El tipo de la excepción cambió | Atrapar los dos tipos |
| Robocopy aparece como fallo tras copiar bien | Su código 1 no es un error | Fallo solo a partir del 8 |
No se encuentra el tipo en 5.1 | El catch nombra un tipo que no existe en esa versión | Comparar el nombre del tipo |
Conclusión
El try catch en PowerShell es corto de escribir, pero solo funciona si el error termina. Por eso la línea más importante de un script desatendido suele ser $ErrorActionPreference = 'Stop', y la segunda, un exit con el código correcto. Con eso, más un registro y la costumbre de probar el camino del error, un script deja de fallar en silencio.
Esta es la base de cómo trabajamos los scripts de mantenimiento en el soporte técnico para empresas: tareas que avisan cuando algo sale mal y registros que permiten saber qué pasó sin repetirlo. Si su equipo dedica horas a revisar tareas que «terminaron bien» sin hacer nada, nuestros servicios administrados de TI cubren la mesa de ayuda, los equipos y los servidores. Puede ver todo lo que hacemos en servicios de TI para empresas.




