Dominar los bloques Try Catch en PowerShell con ejemplos

¿Alguna vez has ejecutado un script o un cmdlet de PowerShell y te has enfrentado a un muro de texto gritando en rojo, como el que se muestra a continuación?

Example of errors in PowerShell

Los errores pueden volverse abrumadores y confusos. Y, sobre todo, los errores a menudo son difíciles de leer, lo que hace casi imposible determinar qué salió mal y dónde en el script.

Afortunadamente, tienes algunas opciones en PowerShell para mejorar esto a través del manejo de errores. Utilizando el manejo de errores, los errores se pueden filtrar y mostrar de tal manera que sea más fácil entenderlos. Y comprender el error facilita la adición de más lógica al manejo de errores.

En este artículo, aprenderás sobre los errores en PowerShell y cómo se pueden interceptar para realizar el manejo de errores utilizando los bloques Try Catch (y finally) de PowerShell.

Entendiendo cómo funcionan los errores en PowerShell

Antes de profundizar en el manejo de errores, cubramos primero algunos conceptos sobre errores en PowerShell. Comprender los errores puede llevar a mejores estrategias de manejo de errores.

La Variable Automática $Error

En PowerShell, hay muchas variables automáticas, y una de ellas es la variable automática $Error. PowerShell utiliza la variable $Error para almacenar todos los errores que se encuentran en la sesión. La variable $Error es un conjunto de errores ordenados por los más recientes.

Cuando abres una sesión de PowerShell por primera vez, la variable $Error está vacía. Puedes verificarlo llamando a la variable $Error.

The $Error variable is empty

Como puedes ver, la variable $Error comienza vacía. Pero, una vez que se genera un error, el error se agregará y almacenará en la variable $Error.

En el ejemplo a continuación, el error se genera al obtener intencionalmente un nombre de servicio que no existe.

PS> Get-Service xyz
PS> $Error
PS> $Error.Count
The error is added to the $Error variable

Como puedes ver en la salida anterior, el error generado se agregó a la variable $Error.

La variable $Error contiene una colección de errores generados en la sesión de PowerShell. Cada error se puede acceder llamando a su posición en el arreglo. El último error siempre estará en el índice 0.

Por ejemplo, el último error se puede recuperar usando $Error[0].

Las propiedades del objeto $Error

Dado que todo en PowerShell es un objeto, la variable $Error es un objeto y los objetos tienen propiedades. Al pasar la variable $Error al cmdlet Get-Member, deberías ver la lista de propiedades disponibles.

$Error | Get-Member
The $Error object properties

Para determinar la razón del error, puedes ver el contenido de la propiedad InvocationInfo usando el siguiente comando.

$Error[0].InvocationInfo
The InvocationInfo property

¡Ahora puedes hacer lo mismo con las otras propiedades y descubrir qué otra información puedes encontrar!

Errores terminantes

Errores terminales detienen el flujo de ejecución cuando son encontrados por PowerShell en comparación con los errores no terminales. Hay varias formas en que puede ocurrir un error terminal. Un ejemplo es cuando llamas a un cmdlet con un parámetro que no existe.

Como se puede ver en la captura de pantalla a continuación, cuando se ejecuta el comando Get-Process notepad, el comando es válido y se muestran los detalles del proceso notepad.

The notepad process details

Pero, cuando se utiliza un parámetro que no existe como Get-Process notepad -handle 251, el cmdlet muestra un error indicando que el parámetro handle no es válido. Luego, el cmdlet sale sin mostrar los detalles del proceso notepad.

Error is thrown because the parameter is invalid

Errores No Terminales

Los errores no terminales son errores que no detienen la ejecución del script o comando. Por ejemplo, echa un vistazo al código a continuación. Este código obtiene la lista de nombres de archivo del archivo fileslist.txt. Luego, el script recorre cada nombre de archivo, lee el contenido de cada archivo y lo muestra en la pantalla.

$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
    Get-Content $file
}

El contenido del archivo filelist.txt son los nombres de archivo que se muestran en la lista a continuación.

File_1.log
File_2.log
File_3.log
File_4.log
File_5.log
File_6.log
File_7.log
File_8.log
File_9.log
File_10.log

Pero, ¿qué pasa si File_6.log en realidad no existe? Cuando ejecutas el código, esperarías que ocurra un error porque el script no puede encontrar el archivo File_6.log. Se mostrará una salida similar a la que se muestra a continuación.

Example of non-terminating error

Como puedes ver en la captura de pantalla del resultado anterior, el script pudo leer los primeros cinco archivos en la lista, pero cuando intentó leer el archivo File_6.txt, se produjo un error. Luego, el script continuó leyendo el resto de los archivos antes de salir. No terminó.

La Variable $ErrorActionPreference

Hasta ahora, has aprendido sobre los errores terminales y no terminales y cómo difieren entre sí. Pero, ¿sabías que se puede forzar a un error no terminal a que se trate como un error terminal?

PowerShell tiene un concepto llamado variables de preferencia. Estas variables se utilizan para cambiar el comportamiento de PowerShell de muchas maneras diferentes. Una de estas variables se llama $ErrorActionPreference.

La variable $ErrorActionPreference se utiliza para cambiar la forma en que PowerShell trata los errores no terminales. De forma predeterminada, el valor de $ErrorActionPreference está configurado en Continue. Cambiar el valor de la variable $ErrorActionPreference a STOP fuerza a PowerShell a tratar todos los errores como errores terminales.

Usa el siguiente código para cambiar el valor de $ErrorActionPreference.

$ErrorActionPreference = "STOP"

Para obtener más información sobre otros valores válidos de la variable $ErrorActionPreference, visita PowerShell ErrorActionPreference.

Ahora, vuelve al ejemplo utilizado en la sección de Errores no terminales en este artículo. El script se puede modificar para incluir el cambio en $ErrorActionPreference como se muestra a continuación:

# Establecer el valor de $ErrorActionPreference en STOP
$ErrorActionPreference = "STOP"
$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
    Get-Content $file
}

Al ejecutar el código modificado anteriormente, se comportará de manera diferente que antes cuando el valor de $ErrorActionPreference se establece en el valor predeterminado de Continuar.

Forcing a terminating error using the $ErrorActionPreference variable

Como se puede ver en la captura de pantalla del resultado anterior, el script pudo leer los primeros cinco archivos en la lista, pero cuando intentó leer el archivo File_6.txt, se devolvió un error porque no se encontró el archivo. Luego, el script se terminó y no se leyeron el resto de los archivos.

El valor de $ErrorActionPreference solo es válido en la sesión actual de PowerShell. Se restablece al valor predeterminado una vez que se inicia una nueva sesión de PowerShell.

El parámetro común ErrorAction

Si se aplica el valor de $ErrorActionPreference a la sesión de PowerShell, el parámetro ErrorAction se aplica a cualquier cmdlet que admita parámetros comunes. El parámetro ErrorAction acepta los mismos valores que la variable $ErrorActionPreference.

El valor del parámetro ErrorAction tiene prioridad sobre el valor de $ErrorActionPreference.

Volviendo al mismo código del ejemplo anterior, pero esta vez, se agrega el parámetro ErrorAction a la línea Get-Content.

# Establecer el valor de $ErrorActionPreference a predeterminado (CONTINUE)
$ErrorActionPreference = "CONTINUE"
$file_list =  Get-Content .\filelist.txt
foreach ($file in $file_list) {
    Write-Output "Reading file $file"
		# Usar el parámetro común -ErrorAction
		Get-Content $file -ErrorAction STOP
}

Después de ejecutar el código modificado, verás que aunque $ErrorActionPreference está configurado en Continue, el script aún se termina cuando encuentra un error. Esto ocurre porque el valor del parámetro ErrorAction en Get-Content está configurado en STOP.

Forcing a terminating error using the PowerShell ErrorAction parameter

Uso de bloques Try Catch en PowerShell

En este punto, has aprendido sobre los errores de PowerShell y cómo funcionan la variable $ErrorActionPreference y los parámetros ErrorAction de PowerShell. Ahora es el momento de aprender sobre lo bueno: los bloques Try Catch Finally de PowerShell.

Los bloques try catch de PowerShell (y el bloque opcional finally) son una forma de envolver un fragmento de código y capturar cualquier error que se produzca.

El código a continuación muestra la sintaxis de la declaración Try.

try {
    <statement list>
}
catch [[<error type>][',' <error type>]*]{
    <statement list>
}
finally {
    <statement list>
}

El bloque Try contiene el código que deseas que PowerShell “intente” y supervise en busca de errores. Si el código en el bloque Try encuentra un error, este se agrega a la variable $Error y luego se pasa al bloque Catch.

El bloque Catch contiene las acciones a ejecutar cuando recibe un error del bloque Try. Puede haber varios bloques Catch en una declaración Try.

El bloque Finally contiene el código que se ejecutará al final de la declaración Try. Este bloque se ejecuta independientemente de si se encontró un error o no.

Capturando errores no específicos (Catch-All) con ErrorAction de PowerShell

A simple Try statement contains a Try and a Catch block. The Finally block is optional.

Por ejemplo, para capturar una excepción no específica, el parámetro Catch debe estar vacío. El código de ejemplo a continuación está utilizando el mismo script que se usó en la sección La Variable $ErrorActionPreference pero modificado para usar los bloques Try Catch.

Como puedes ver en el código a continuación, esta vez, la instrucción foreach está encerrada dentro del bloque Try. Luego, un bloque Catch contiene el código para mostrar la cadena Se produjo un error si ocurriera un error. El código en el bloque Finally simplemente borra la variable $Error.

$file_list = Get-Content .\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch {
    Write-Host "An Error Occured" -ForegroundColor RED
}
finally {
    $Error.Clear()
}

El código anterior, después de ejecutarse en PowerShell, te dará esta salida que se muestra a continuación.

Script terminated when an error occurred

La salida anterior muestra que el script encontró un error, ejecutó el código dentro del bloque Catch y luego terminó.

El error fue manejado, que era el propósito del manejo de errores. Sin embargo, el error mostrado fue demasiado genérico. Para mostrar un error más descriptivo, podrías acceder a la propiedad Exception del error que fue pasado por el bloque Try.

El código a continuación está modificado, específicamente el código dentro del bloque Catch, para mostrar el mensaje de excepción del error actual que fue pasado por la canalización – $PSItem.Exception.Message

$file_list = Get-Content .\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch {
    Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
    $Error.Clear()
}

Esta vez, cuando se ejecuta el código modificado anterior, el mensaje mostrado es mucho más descriptivo.

Script terminated with a descriptive error message

Capturando Errores Específicos

Hay momentos en los que un manejo de errores generalizado no es el enfoque más apropiado. Quizás quieras que tu script realice una acción que dependa del tipo de error que se encuentre.

¿Cómo determinas el tipo de error? Revisando el valor TypeName de la propiedad Exception del último error. Por ejemplo, para encontrar el tipo de error del ejemplo anterior, usa este comando:

$Error[0].Exception | Get-Member

El resultado del código anterior se vería como la captura de pantalla a continuación. Como puedes ver, se muestra el valor TypeNameSystem.Management.Automation.ItemNotFoundException.

Getting the error TypeName value

Ahora que conoces el tipo de error que necesitas interceptar, modifica el código para capturarlo de manera específica. Como ves en el código modificado a continuación, ahora hay dos bloques Catch. El primer bloque Catch intercepta un tipo específico de error (System.Management.Automation.ItemNotFoundException). En contraste, el segundo bloque Catch contiene el mensaje de error genérico que atrapa cualquier tipo de error.

$file_list = Get-Content .\filelist.txt
try {
    foreach ($file in $file_list) {
        Write-Output "Reading file $file"
        Get-Content $file -ErrorAction STOP
    }
}
catch [System.Management.Automation.ItemNotFoundException]{
    Write-Host "The file $file is not found." -ForegroundColor RED
}
catch {
    Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
    $Error.Clear()
}

La captura de pantalla a continuación muestra la salida del código modificado anteriormente.

Script terminated with a specific error message

Conclusión

En este artículo, has aprendido sobre los errores en PowerShell, sus propiedades y cómo puedes determinar el tipo específico de un error. También has aprendido la diferencia entre cómo la variable $ErrorActionPreference y el parámetro ErrorAction de PowerShell afectan la forma en que PowerShell trata los errores no terminales.

También has aprendido cómo utilizar los bloques Try Catch Finally de PowerShell para realizar el manejo de errores, ya sea para errores específicos o de enfoque general.

Los ejemplos que se muestran en este artículo solo demuestran lo básico de cómo funcionan los bloques Try Catch Finally. El conocimiento que espero que hayas adquirido en este artículo debería proporcionarte las bases para comenzar a aplicar el manejo de errores en tus scripts.

Lecturas Adicionales

Source:
https://adamtheautomator.com/powershell-try-catch/