Está en la página 1de 44

The Big Book of PowerShell

Error Handling (Spanish)


The DevOps Collective, Inc.
Este libro está a la venta en http://leanpub.com/big-book-of-
powershell-error-handling-spanish

Esta versión se publicó en 2018-07-04

Este es un libro de Leanpub. Leanpub anima a los autores y


publicadoras con el proceso de publicación. Lean Publishing es el
acto de publicar un libro en progreso usando herramientas
sencillas y muchas iteraciones para obtener feedback del lector
hasta conseguir tener el libro adecuado.

© 2018 The DevOps Collective, Inc.


También por The DevOps
Collective, Inc.
Creating HTML Reports in Windows PowerShell
A Unix Person’s Guide to PowerShell
The Big Book of PowerShell Error Handling
DevOps: The Ops Perspective
Ditch Excel: Making Historical and Trend Reports in PowerShell
Secrets of PowerShell Remoting
The Big Book of PowerShell Gotchas
The Monad Manifesto, Annotated
Why PowerShell?
Windows PowerShell Networking Guide
The PowerShell + DevOps Global Summit Manual for Summiteers
Why PowerShell? (Spanish)
Secrets of PowerShell Remoting (Spanish)
DevOps: The Ops Perspective (Spanish)
The Monad Manifesto: Annotated (Spanish)
Creating HTML Reports in PowerShell (Spanish)
The Big Book of PowerShell Gotchas (Spanish)
Índice general

El gran libro de manejo de errores en PowerShell . . . . . 1

Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
¿Qué es el manejo de errores? . . . . . . . . . . . . . . . . 3
¿Cómo está organizado este libro? . . . . . . . . . . . . . 3

Fundamentos para el manejo de errores en PowerShell . 5


ErrorRecords y Exceptions . . . . . . . . . . . . . . . . . 5
Terminating versus Non-Terminating Errors . . . . . . . 6

Controlando el comportamiento de los errores . . . . . . 9


La variable $Error . . . . . . . . . . . . . . . . . . . . . . 9
ErrorVariable . . . . . . . . . . . . . . . . . . . . . . . . . 10
$MaximumErrorCount . . . . . . . . . . . . . . . . . . . 11
ErrorAction y $ErrorActionPreference . . . . . . . . . . . 11
Try/Catch/Finally . . . . . . . . . . . . . . . . . . . . . . . 13
Trap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
La variable $LASTEXITCODE . . . . . . . . . . . . . . . 18
La variable $? . . . . . . . . . . . . . . . . . . . . . . . . . 19
Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

Análisis de los resultados de las pruebas de manejo de


errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Interceptando errores Non-Terminating . . . . . . . . . . 24
Interceptando errores Terminating . . . . . . . . . . . . . 24
Efectos de establecer ErrorAction o $ErrorActionPreference 26
ÍNDICE GENERAL

Cómo se comporta PowerShell cuando se encuentra


errores Terminating no controlados . . . . . . . . 27
Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . 29

Poniéndolo todo junto . . . . . . . . . . . . . . . . . . . . . 31


Supresión de errores (no haga esto) . . . . . . . . . . . . 31
Uso de la variable $? (úselo bajo su propio riesgo) . . . . 31
Determinar qué tipos de errores puede producir un co-
mando . . . . . . . . . . . . . . . . . . . . . . . . . 32
Tratamiento de errores Terminating . . . . . . . . . . . . 35
Tratamiento de errores Non-Terminating . . . . . . . . . 35
Llamando a programas externos . . . . . . . . . . . . . . 37

Epílogo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
El gran libro de manejo
de errores en PowerShell
Escrito por Dave Wyatt
Diseño de portada por Nathan Vonnahme

A pesar del título, este es en realidad un pequeño y conciso libro


diseñado para ayudarle a entender cómo PowerShell genera y
maneja errores. Intentará ayudarle a crear el mejor manejo posible
de errores para sus propios scripts y funciones, en tan solo unas
pocas lecciones.

Esta guía se publica bajo la licencia Creative Commons Attribution-


NoDerivs 3.0 Unported. Los autores le animan a redistribuir este
archivo lo más ampliamente posible, pero le solicitan que no
modifique el documento original.
Obteniendo el código El código de ejemplo, junto con un archivo
que documenta nombres de clases de excepción conocidos, se
puede encontrar en https://github.com/devops-collective-inc/big-
book-of-powershell-error-handling/tree/master/attachments.
¿Ha sido útil este libro? El (los) autor (es) le pide (n) que haga
una donación deducible de impuestos (en los EE.UU., consulte sus
leyes si vive en otro lugar) de cualquier cantidad a The DevOps
Collective¹ para apoyar su trabajo.
¹https://devopscollective.org/donate/
El gran libro de manejo de errores en PowerShell 2

** Revise las actualizaciones! ** Nuestros ebooks se actualizan a


menudo con contenido nuevo y corregido. Los hacemos disponibles
de tres maneras:

• Nuestra rama principal GitHub organization², con un re-


positorio para cada libro. Visite https://github.com/devops-
collective-inc/
• Nuestra GitBook page³, donde puede navegar por los libros
en línea, o descargarlos en formato PDF, EPUB o MOBI. Uti-
lizando el lector en línea, puede saltar a capítulos específicos.
Visite https://www.gitbook.com/@devopscollective
• En LeanPub⁴, donde se pueden descargar como PDF, EPUB, o
MOBI (login requerido), y “comprar” los libros haciendo una
donación a DevOps. También puede elegir recibir notificacio-
nes de actualizaciones. Visite https://leanpub.com/u/devopscollective

GitBook y LeanPub generan la salida del formato PDF ligeramente


diferente, por lo que puede elegir el que prefiera. LeanPub también
le puede notificar cada vez que liberamos alguna actualización.
Nuestro repositorio de GitHub es el principal; los repositorios
en otros sitios suelen ser sólo espejos utilizados para el proceso
de publicación. GitBook normalmente contendrá nuestra última
versión, incluyendo algunos bits no terminados; LeanPub siempre
contiene la más reciente “publicación liberada” de cualquier libro.
²https://github.com/devops-collective-inc
³https://www.gitbook.com/@devopscollective
⁴https://leanpub.com/u/devopscollective
Introducción
El manejo de errores en Windows PowerShell puede ser un tema
complejo. El objetivo de este libro -que afortunadamente no es tan
“grande” como su nombre lo indica- es ayudar a aclarar algo de
esa complejidad y ayudarle a hacer un trabajo mejor y más conciso
para manejar errores en sus scripts.

¿Qué es el manejo de errores?

Cuando decimos que un script “maneja” un error, significa que


reacciona al error haciendo algo distinto del comportamiento pre-
determinado. En muchos lenguajes de programación y de secuen-
cias de comandos, el comportamiento predeterminado es simple-
mente mostrar un mensaje de error y fallar inmediatamente. En
PowerShell, también se emitirá un mensaje de error, pero a menudo
se seguirá ejecutando el código después de que se produzca el error.
El manejo de errores requiere que el autor de la secuencia de
comandos anticipe dónde pueden ocurrir y que escriba código para
interceptar y analizar dicho errores cuando ocurren. Esto puede
ser un tema complejo y a veces frustrante, particularmente en
PowerShell. El propósito de este libro es mostrarle las herramientas
de manejo de errores que PowerShell pone a su disposición y la
mejor forma de usarlas.

¿Cómo está organizado este libro?


Después de la introducción, el libro se divide en cuatro secciones.
Las dos primeras secciones están escritas asumiendo que usted
Introducción 4

no sabe nada sobre el manejo de errores de PowerShell, y para


proporcionar un sólido contexto sobre el tema. Sin embargo, no hay
nada nuevo en estas secciones que no esté cubierto por los archivos
de ayuda de PowerShell. Si está bastante familiarizado con el objeto
ErrorRecord y los diversos parámetros / variables / declaraciones
relacionados con la generación de errores, de informes y de manejo,
puede pasar directamente a las secciones 3 y 4.
La sección 3 es una mirada objetiva a las características de manejo
de errores de PowerShell, basada en los resultados de algún código
de prueba que escribí para entender su funcionamiento. La idea
era determinar si existían diferencias funcionales entre enfoques
similares para manejar errores ($Error versus ErrorVariable, el uso
o no de $_ en un bloque catch, etc.), lo que generó fuertes opiniones,
durante y después los Scripting Games en 2013.
Estas pruebas revelan un par de dificultades, en particular, al hacer
uso de ErrorVariable.
La sección 4 resume las cosas dándole una visión más orientada a
las tareas de manejo de errores, teniendo en cuenta los hallazgos de
la sección 3.
Fundamentos para el
manejo de errores en
PowerShell
Empecemos por revisar algunos de los conceptos básicos.

ErrorRecords y Exceptions
En .NET Framework, sobre el que se construye PowerShell, el
reporte de errores se realiza en gran medida lanzando excepciones.
Las excepciones son objetos .NET que tienen como tipo base
System.Exception⁵. Estos objetos de excepción contienen suficiente
información para comunicar todos los detalles del error a una
aplicación de .NET Framework (el tipo de error que ocurrió, un
seguimiento de pila de llamadas del método que condujo al error,
etc.) que por sí solo no es suficiente información para proporcionar
a un script de PowerShell. Por eso, PowerShell tiene su propio
seguimiento de la pila de scripts y de llamadas de función de las
que .NET Framework no sabe nada. También es importante saber
qué objetos generaron errores, ya que una única sentencia o tubería
(pipeline) es capaz de producir múltiples errores.
Por estas razones, PowerShell expone el objeto ErrorRecord. Erro-
rRecord contienen una excepción .NET, junto con varias otras
piezas de información específica de PowerShell. Por ejemplo, la
figura 1.1 muestra cómo acceder a las propiedades TargetObject,
CategoryInfo e InvocationInfo de un objeto ErrorRecord; que pro-
⁵http://msdn.microsoft.com/en-us/library/system.exception(v=vs.110).aspx
Fundamentos para el manejo de errores en PowerShell 6

porcionan información útil para la lógica de manejo de errores de


su script.

image003.png

Figura 1.1: Algunas de las propiedades más útiles del objeto Erro-
rRecord.

Terminating versus
Non-Terminating Errors
PowerShell es un lenguaje extremadamente expresivo. Esto signi-
fica que una sola sentencia o pipeline de código PowerShell puede
realizar el trabajo de cientos, o incluso miles de instrucciones crudas
de CPU. Por ejemplo:
Fundamentos para el manejo de errores en PowerShell 7

1 Get-Content .\computers.txt | Restart-Computer

Este pequeño script de PowerShell de tan solo 46 caracteres abre


un archivo en disco, detecta automáticamente su codificación, lee
el texto una línea a la vez, se conecta a cada computadora remota
nombrada en el archivo, se autentica en ese equipo y, si tiene éxito,
reinicia la computadora. Varios de estos pasos pueden encontrar
errores, como en el caso del comando Restart-Computer, que puede
tener éxito para algunos equipos y fallar para otros.
Por esta razón, PowerShell introduce el concepto de un error Non-
Terminating. Un error Non-Terminating es aquel que no impide
que el comando avance y pruebe el siguiente elemento en una
lista de entradas. Por ejemplo, si uno de los equipos del archivo
computers.txt está desconectado, eso no detendrá a PowerShell que
seguirá intentando reiniciar el resto de los equipos del archivo.
Por el contrario, un error Terminating es uno que hace que el
script o tubería (pipeline) falle. Por ejemplo, este comando busca
las direcciones de correo electrónico asociadas con las cuentas de
usuario en un Active Directory:

1 Get-Content .\users.txt |
2 Get-ADUser -Properties mail |
3 Select-Object -Property SamAccountName,mail

En este script, si el comando Get-ADUser no puede comunicarse


con el Active Directory, no hay razón para seguir leyendo las líneas
del archivo de texto o intentando procesar registros adicionales, por
lo que se producirá un error Terminating. Cuando se encuentra este
error Terminating, todo el script o tubería (pipeline) es abortado
inmediatamente. Get-Content detendrá la lectura y cerrará el ar-
chivo.
Es importante conocer la distinción entre estos tipos de errores,
ya que los scripts utilizarán diferentes técnicas para interceptarlos.
Fundamentos para el manejo de errores en PowerShell 8

Como regla general, la mayoría de los errores producidos por los


Cmdlets no Non-Terminating (con algunas excepciones, aquí y
allá).
Controlando el
comportamiento de los
errores
Esta sección muestra brevemente cómo usar cada una de las decla-
raciones, variables y parámetros de PowerShell que están relacio-
nados con el reporte o manejo de errores.

La variable $Error
$Error es una variable global automática en PowerShell que siem-
pre contiene un ArrayList de cero o más objetos ErrorRecord. A
medida que se producen nuevos errores, se agregan al principio de
esta lista, por lo que siempre se puede obtener información sobre el
error más reciente utilizando $Error[0]. Los errores Terminating y
Non-Terminating se incluirán en esta lista.
Aparte de acceder a los objetos de la lista con la sintaxis de matriz,
hay otras dos tareas comunes que se realizan con la variable $Error:
Se puede comprobar cuántos errores están actualmente en la lista
utilizando la propiedad $Error.Count y puede eliminar todos los
errores de la lista con el método $Error.Clear(). Por ejemplo:
Controlando el comportamiento de los errores 10

image004.png

Figura 2.1: Utilizando $Error para acceder a la información de error,


verificar el recuento y borrar la lista.
Si está planeando hacer uso de la variable $Error en sus scripts,
tenga en cuenta que puede contener información sobre errores
que ocurrieron en la sesión actual de PowerShell, pero antes de
que se iniciara la ejecución de su secuencia de comandos. Algunas
personas consideran una mala práctica borrar la variable $Error
dentro de un script. Como se trata de una variable global para
la sesión de PowerShell, la persona que llamó a su secuencia de
comandos podría revisar el contenido de $Error después de que su
comando haya terminado la ejecución..

ErrorVariable
El parámetro común ErrorVariable proporciona una alternativa
al uso de la colección $Error anterior. A diferencia de $Error,
ErrorVariable sólo contendrá los errores que se produjeron desde
el comando que se está llamando, en lugar de tener potencialmente
errores de otras partes en la sesión PowerShell. Esto también evita
tener que borrar el contenido de $Error (con los problemas que esto
podría ocasionar).
Cuando se utiliza ErrorVariable, si desea anexar a la variable de
error en lugar de sobrescribirla, coloque un signo + delante del
Controlando el comportamiento de los errores 11

nombre de la variable. Tenga en cuenta que no se utiliza un


signo de moneda cuando pasa un nombre de variable al parámetro
ErrorVariable, pero si utiliza el signo de moneda más adelante
cuando comprueba su valor.
La variable asignada al parámetro ErrorVariable nunca será nula.
Si no se produjeron errores, contendrá un objeto ArrayList con un
recuento de 0, como se ve en la figura 2.2:

image005.png

Figura 2.2: Demostración del uso del parámetro ErrorVariable.

$MaximumErrorCount
De forma predeterminada, la variable $Error sólo puede contener
un máximo de 256 errores antes de que comience a desechar los ele-
mentos más antiguos de la lista. Puede ajustar este comportamiento
modificando la variable $MaximumErrorCount.

ErrorAction y
$ErrorActionPreference
Hay varias maneras en las que puede controlar el comportamiento
de PowerShell. Las que probablemente utilizará con más frecuencia
Controlando el comportamiento de los errores 12

son los parámetro ErrorAction y la variable $ErrorActionPreferen-


ce.
El parámetro ErrorAction se puede pasar a cualquier Cmdlet o
función avanzada y puede tener uno de los siguientes valores:
Continue (el valor por defecto), SilentlyContinue, Stop, Inquire,
Ignore (sólo en PowerShell 3.0 o posterior), y Suspend (sólo para
workflows, pero no se discutirá aquí). Este valor afecta el cómo se
comporta el Cmdlet cuando produce un error Non-Terminating.

• El valor predeterminado Continue provoca que el error se


escriba en la secuencia de errores y se agregue a la variable
$Error. Entonces, el Cmdlet continuara su ejecución.
• El valor SilentlyContinue sólo agrega el error a la variable
$Error. No escribe el error en la secuencia de errores (por lo
que no se mostrará en la consola).
• El valor Ignore suprime el mensaje de error y no lo agrega a
la variable $Error. Esta opción se agregó con PowerShell 3.0.
• El valor Stop hace que los errores Non-Terminating se traten
como errores Terminating, deteniendo inmediatamente la
ejecución del Cmdlet. Esto también permite interceptar estos
errores en una sentencia try / catch o trap, como se describe
más adelante.
• El valor Inquire provoca que PowerShell pregunte al usuario
si el script debe continuar o no cuando se produce un error.

La variable $ErrorActionPreference se puede utilizar igual que


el parámetro ErrorAction, con un par de excepciones: no puede
establecer $ErrorActionPreference en Ignore o Suspend. Además,
$ErrorActionPreference afecta su alcance actual además de cual-
quier comando secundario que se llame. Esta sutil diferencia tiene
el efecto de permitirle controlar el comportamiento de los errores
producidos por los métodos .NET, u otras causas como cuando
PowerShell se encuentra con un error del tipo “comando no en-
contrado”.
Controlando el comportamiento de los errores 13

La Figura 2.3 muestra los efectos de las tres configuraciones de


ErrorActionPreference más utilizadas.

image006.png

Figura 2.3: Comportamiento de $ErrorActionPreference

Try/Catch/Finally
Las sentencias Try/ Catch/ Finally, agregadas en PowerShell 2.0,
son la forma preferida de manejar los errores Terminating. No se
pueden utilizar para manejar errores Non-Terminating, a menos
que fuerce esos errores a convertirse en errores Terminating con
ErrorAction o $ErrorActionPreference establecido en Stop.
Para usar Try/Catch/ Finally, comience con la palabra clave “Try”
seguida de un solo bloque de secuencia de comandos de PowerShell.
Después del bloque Try puede haber cualquier número de bloques
Controlando el comportamiento de los errores 14

Catch y cero o un bloque Finally. Debe haber un mínimo de un


bloque Catch o un bloque Finally. Un bloque Try no puede ser
utilizado por sí mismo, debe tener al menos un bloque Catch.
El código dentro del bloque Try se ejecuta hasta que se completa o se
produce un error Terminating. Si se produce un error Terminating,
se detiene la ejecución del código en el bloque Try. PowerShell
escribe el error Terminating en la lista $Error y busca un bloque
Catch coincidente (ya sea en el ámbito actual o en cualquier ámbito
superior). Si no existe un bloque Catch para manejar el error,
PowerShell escribe el error en la secuencia Error, lo mismo que
habría hecho si el error hubiera ocurrido fuera de un bloque Try.
Los bloques Catch se pueden escribir para capturar sólo tipos
específicos de excepciones, o para capturar todos los errores Termi-
nating. Si define varios bloques de captura para diferentes tipos de
excepciones, asegúrese de colocar los bloques más específicos en la
parte superior de la lista. Las búsquedas de PowerShell analizan los
bloques de arriba abajo, y se detienen tan pronto como encuentran
la primera coincidencia.
Si se incluye un bloque Finally, ese código se ejecuta después de
que los bloques Try y Catch estén completos (se hayan ejecutado),
independientemente de si se ha producido o no un error. Esto está
destinado principalmente a realizar una limpieza de los recursos
(liberar memoria, llamar a métodos Close () o Dispose (), etc.)
La Figura 2.4 muestra el uso de un bloque Try/Catch/Finally:
Controlando el comportamiento de los errores 15

image007.png

Figura 2.4: Ejemplo del uso de Try/Catch/Finally.


Observe que el texto “Statement after the error” nunca se muestra,
porque se produjo un error Terminating en la línea anterior. Dado
que el error se produjo por una excepción IOException, se ejecutó
ese bloque Catch, en lugar del bloque general “catch-all” que
aparece al final. Después, el bloque Finally se ejecuta, cambiando
el valor de $testVariable.
Fíjese también que mientras el bloque Catch especificaba un ti-
po [System.IO.IOException], el tipo de excepción real fue [Sys-
tem.IO.DirectoryNotFoundException]. Esto funciona porque Di-
rectoryNotFoundException hereda de IOException, de la misma
manera que todas las excepciones comparten el mismo tipo base
[System.Exception]. Puede ver esto en la figura 2.5:
Controlando el comportamiento de los errores 16

image008.png

Figura 2.5: Mostrando que IOException es el tipo base para Direc-


toryNotFoundException.

Trap
Las sentencias Trap fueron el método para manejar los errores
Terminating en PowerShell 1.0. Al igual que con Try/Catch/Finally,
la instrucción Trap no tiene ningún efecto en los errores Non-
Terminating.
Trap es un poco incómodo de usar, ya que se aplica a todo el ámbito
donde se define (y los ámbitos hijos también), en lugar de tener la
lógica de manejo de errores cerca del código que podría producir
el error como cuando se utiliza Try/Catch/Finally. Para aquellos
de ustedes familiarizados con Visual Basic, Trap es parecido a “On
Error Goto”. Por eso, las sentencias Trap no ven mucho uso en los
scripts de PowerShell modernos, y no los incluí en los scripts de
prueba ni en el análisis de la Sección 3 de este libro.
En aras de mantener la integridad, he aquí un ejemplo de cómo usar
Trap:
Controlando el comportamiento de los errores 17

image009.png

Figura 2.6: Uso de la sentencia Trap


Como puede ver, los bloques Trap se definen de la misma forma
que los bloques Catch, especificando opcionalmente un tipo Ex-
ception. Los bloques Trap pueden terminar opcionalmente con una
instrucción Break o Continue. Si no se utiliza ninguno de estos, el
error se escribe en la secuencia Error (Error Stream) y el bloque
de secuencia de comandos actual continúa con la siguiente línea
después del error. Si utiliza Break, como se ve en la figura 2.5, el
error se escribe en la secuencia Error (Error Stream) y el resto del
bloque de secuencia de comandos actual no se ejecuta. Si utiliza
Continue, el error no se escribe en la secuencia de errores y el
bloque de secuencia de comandos continúa la ejecución con la
siguiente instrucción.
Controlando el comportamiento de los errores 18

La variable $LASTEXITCODE
Cuando llama a un programa ejecutable externo en lugar de un
Cmdlet, un Script o una función de PowerShell, la variable $LASTE-
XITCODE contiene automáticamente el código de salida de dicho
proceso. La mayoría de los procesos utilizan por convención un
código de salida con valor cero cuando el proceso finaliza con éxito
y un valor diferente a cero si se produce un error, pero esto no está
garantizado. Depende del desarrollador del ejecutable determinar
qué significan sus códigos de salida.
Tenga en cuenta que la variable $LASTEXITCODE sólo se establece
cuando llama a un ejecutable directamente o a través del operador
de llamadas de PowerShell (&) o del Cmdlet Invoke-Expression.
Si utiliza otro método, como Start-Process o WMI para iniciar
el ejecutable, estos tienen sus propias maneras de comunicar su
código de salida, por lo que no se afectará el valor actual de
$LASTEXITCODE.

image010.png

Figura 2.7: Uso de $ LASTEXITCODE


Controlando el comportamiento de los errores 19

La variable $?
La variable $? es un valor booleano que se establece automática-
mente después de cada instrucción PowerShell o tubería (pipeline)
finaliza la ejecución. Estará establecida en True si el comando
anterior se ha ejecutado correctamente o en False si se produjo
un error. Si el comando anterior era una llamada a un exe nativo,
$? se establecerá en True si la variable $ LASTEXITCODE es
igual a cero, de lo contrario, False. Cuando el comando anterior
era una sentencia de PowerShell, $? Se establecerá en False si
se han producido errores (incluso si ErrorAction se estableció en
SilentlyContinue o Ignore).
Sólo tenga en cuenta que el valor de esta variable se restablece des-
pués de cada instrucción. Debe comprobar su valor inmediatamente
después del comando que le interesa o se sobrescribirá (probable-
mente en True). La Figura 2.8 muestra este comportamiento. La
primera vez $? se establece en False, porque el Get-Item encontró
un error. La segunda vez $? Se comprobó y se estableció en True,
porque el comando anterior finalizo correctamente. En este caso,
el comando anterior fue “$?” cuando se visualizó el valor de la
variable.

image011.png

Figura 2.8: Demostración del comportamiento de la variable $?


La variable $? no da ningún detalle sobre el error que ocurrió.
Simplemente una bandera que indica que algo salió mal. En el
caso de llamar a programas ejecutables, debe asegurarse de que
devuelven un código de salida de 0 para indicar una operación
exitosa y un valor distinto de cero para indicar un error antes de
Controlando el comportamiento de los errores 20

poder confiar en el contenido de $ ?.

Resumen
Esto cubre todas las técnicas que puede utilizar para controlar,
interceptar o manejar errores en un script de PowerShell. Resu-
miendo:

• Para interceptar y reaccionar ante errores Non-Terminating,


comprueba el contenido de la colección automática $Error
o de la variable que ha especificado como ErrorVariable.
Esto se hace después de que el comando se completa. No
puede reaccionar a un error Non-Terminating antes de que
el Cmdlet o Función termine su trabajo.
• Para interceptar y reaccionar a la terminación de errores,
utilice Try/Catch/Finally (preferido) o Trap (antiguo y en
desuso). Ambos le permiten especificar diferentes bloques de
secuencias de comandos para reaccionar a diferentes tipos de
excepciones.
• Mediante el parámetro ErrorAction, puede cambiar la forma
en que los Cmdlets y las funciones de PowerShell informan de
errores Non-Terminating. Establecer a un valor de Stop hace
que se conviertan en errores Terminating y entonces pueden
interceptarse con Try/Catch/Finally o Trap.
• $ErrorActionPreference funciona como ErrorAction, excepto
que también puede afectar al comportamiento de PowerShell
cuando se produce un error Terminating, incluso si esos
errores fueron ocasionados por el llamado a un método .NET
en lugar de un Cmdlet.
• $LASTEXITCODE contiene el código de salida de ejecutables
externos. Un código de salida cero normalmente indica una
operación exitosa, pero eso depende del autor del programa.
Controlando el comportamiento de los errores 21

• $? puede decirle si el comando anterior finalizo de forma


exitosa, aunque debe tener cuidado al utilizarlo con comandos
externos, si no siguen la convención de usar un código de sa-
lida con valor cero como indicador de éxito. También necesita
asegurarse de comprobar el contenido de $? inmediatamente
después del comando que le interesa.
Análisis de los resultados
de las pruebas de manejo
de errores
Como se mencionó en la introducción, el código de prueba y
sus archivos de salida están disponibles para su descarga. Vea la
sección “acerca de”, al comienzo de este libro, para conocer la
ubicación. Son un montón de datos, no muy bien formateados en un
documento de Word, por lo que no serán incluidos en el contenido
de los archivos en este libro. Si te cuestionas acerca de cualquiera
de los análisis o conclusiones que he presentado en esta sección, te
animo a descargar y revisar tanto el código como los archivos de
resultados.
El código de prueba consta de dos archivos. El primero es un
módulo de PowerShell (ErrorHandlingTestCommands.psm1) que
contiene un Cmdlet, una clase .NET y varias funciones avanzadas
para producir errores Terminating y Non-Terminating a demanda,
o para probar el comportamiento de PowerShell cuando se produ-
cen tales errores. El segundo archivo es el script ErrorTests.ps1, que
importa el módulo, llama a sus comandos con varios parámetros
y produce la salida que fue redirigida (incluyendo la secuencia
de errores) a los tres archivos de resultados: ErrorTests.v2.txt,
ErrorTests.v3. Txt y ErrorTests.v4.txt.
Hay tres secciones principales en el script ErrorTests.ps1. La prime-
ra sección llama a los comandos para generar errores Terminating y
Non-Terminating, y envía información sobre el contenido de $_ (en
bloques Catch solamente), $Error y ErrorVariable. Estas pruebas
tenían como objetivo responder a las siguientes preguntas:
Análisis de los resultados de las pruebas de manejo de errores 23

• Cuando se trata sólo de errores Non-Terminating, ¿hay di-


ferencias entre cómo $Error y ErrorVariable presentan la in-
formación acerca de los errores que ocurrieron? ¿Hay alguna
diferencia si los errores provienen de un Cmdlet o función
avanzada?
• Cuando se utiliza un bloque Try/Catch, ¿Hay diferencias en el
comportamiento entre la forma en como $Error, ErrorVaria-
ble y $_ proporcionan información sobre el error Terminating
que se produjo? ¿Hay alguna diferencia si los errores proce-
den de un Cmdlet, función avanzada o un método .NET?
• Cuando se producen errores Non-Terminating además del
error, ¿Hay diferencias entre cómo $Error y ErrorVariable
presentan la información? ¿Hay alguna diferencia cuando los
errores provienen de un Cmdlet o función avanzada?
• En las pruebas anteriores, ¿Hay alguna diferencia entre un
error Terminating que se produjo normalmente, en compa-
ración con un error Non-Terminating que se produjo cuan-
do ErrorAction o $ErrorActionPreference se establecieron a
Stop?

La segunda sección consiste en algunas pruebas para determinar


si ErrorAction o $ ErrorActionPreference afectan a los errores
Terminating, o sólo a los errores Non-Terminating.
La sección final prueba cómo se comporta PowerShell cuando
encuentra errores Terminating no controlados de cada origen po-
sible (un Cmdlet que utiliza PSCmdlet.ThrowTerminatingError(),
una función avanzada utiliza la sentencia Throw de PowerShell,
un método .NET que genera una excepción, un Cmdlet o una
Función avanzada que produce errores Non-Terminating cuando
ErrorAction se establece en Stop en un comando desconocido).
Los resultados de todas las pruebas fueron idénticos en PowerShell
3.0 y 4.0. Powershell 2.0 tuvo un par de diferencias, que veremos en
el análisis.
Análisis de los resultados de las pruebas de manejo de errores 24

Interceptando errores
Non-Terminating
Comencemos hablando de errores Non-Terminating.

ErrorVariable versus $Error

Cuando se trata de errores Non-Terminating, sólo hay una dife-


rencia entre $Error y ErrorVariable: el orden de los errores en las
listas se invierte. El error más reciente que se produce siempre se
encuentra al principio de la variable $Error (índice cero) mientras
que el error más reciente se encuentra al final de ErrorVariable.

Interceptando errores Terminating

Esta es la verdadera “carne de la tarea”: Trabajar con errores


Terminating, o excepciones.

$_

Al principio de un bloque Catch, la variable $_ siempre se refiere


a un objeto ErrorRecord para el error Terminating, independiente-
mente de cómo se produjo ese error.

$Error

Al principio de un bloque Catch, $Error[0] siempre se refiere a un


objeto ErrorRecord para el error Terminating, independientemente
de cómo se produjo ese error.
Análisis de los resultados de las pruebas de manejo de errores 25

ErrorVariable
Aquí, las cosas empiezan a complicarse. Cuando un error Termi-
nating se produce por un Cmdlet o una función y está utilizando
ErrorVariable, la variable contendrá algunos elementos inesperados
y los resultados son bastante diferentes en las distintas pruebas
realizadas:

• Cuando se llama a una función avanzada que genera un error


Terminating, ErrorVariable contiene dos objetos de Erro-
rRecord idénticos para el error. Además, si está ejecutando
PowerShell 2.0, estos registros de errores son seguidos por dos
objetos idénticos de tipo System.Management.Automation.RuntimeException.
Estos objetos RuntimeException contienen una propiedad
ErrorRecord, que hace referencia a los objetos ErrorRecord
idénticos al par que también figuraba en la lista ErrorVariable.
Los objetos adicionales RuntimeException no están presentes
en PowerShell 3.0 o posterior.
• Cuando se llama a un Cmdlet que genera un error Termi-
nating, ErrorVariable contiene un solo registro, pero no es
un objeto ErrorRecord. En su lugar, es una instancia de Sys-
tem.Management.Automation.CmdletInvocationException. Co-
mo los objetos RuntimeException mencionados en el último
punto, CmdletInvocationException tiene una propiedad Erro-
rRecord y esa propiedad se refiere al objeto ErrorRecord que
se esperaba que estuviera contenido en la lista ErrorVariable.
• Cuando se llama a una función avanzada con ErrorAction
establecido a Stop, ErrorVariable contiene un objeto del tipo
System.Management.Automation.ActionPreferenceStopException,
seguido por dos objetos de ErrorRecord idénticos. Como con
los tipos RuntimeException y CmdletInvocationException,
ActionPreferenceStopException todos contiene una propie-
dad ErrorRecord, que se refiere a un objeto ErrorRecord que
es idéntico a los dos que se incluyeron directamente en la lista
ErrorVariable. Además, si se ejecuta PowerShell 2.0, hay dos
Análisis de los resultados de las pruebas de manejo de errores 26

objetos más idénticos al tipo ActionPreferenceStopException,


para un total de 5 entradas relacionadas con el mismo error
de Terminating.
• Cuando se llama a un Cmdlet con ErrorAction establecido
a Stop, ErrorVariable contiene un único objeto del tipo Sys-
tem.Management.Automation.ActionPreferenceStopException.
La propiedad ErrorRecord de este objeto ActionPreferenceS-
topException contiene el objeto ErrorRecord que se esperaba
que estuviera directamente en la lista ErrorVariable.

Efectos de establecer ErrorAction o


$ErrorActionPreference
Cuando se ejecuta un Cmdlet o una función avanzada y establece
el parámetro ErrorAction, se afecta el comportamiento de todos los
errores Non-Terminating. Sin embargo, también parece afectar a
los errores Terminating producidos por la sentencia Throw en una
función avanzada (aunque no afecta los procedentes de los Cmdlets
a través del método PSCmdlet.ThrowTerminatingError())
Si establece la variable $ErrorActionPreference antes de llamar
al comando, su valor afecta a los errores Terminating and Non-
Terminating.
Esto es comportamiento no se encuentra documentado. Los ar-
chivos de ayuda de PowerShell indican que tanto la variable de
preferencia como el parámetro sólo deberían afectar a los errores
Non-Terminating.
Análisis de los resultados de las pruebas de manejo de errores 27

Cómo se comporta PowerShell


cuando se encuentra errores
Terminating no controlados
Esta sección del código demostró ser un poco molesta de probar,
porque el manejó de los errores en el alcance del padre (el script),
afectó el comportamiento del código dentro de las funciones. Si el
ámbito de la secuencia de comandos no tenía ningún tratamiento
de errores, en muchos casos, el error no controlado abortó el script
también. Como resultado, el script ErrorTests.ps1 y los archivos de
texto que contienen su salida se escriben para mostrar sólo los casos
en que se produce un error Terminating, pero la ejecución de la
función continua y pasa al siguiente comando.
Si desea ejecutar la batería completa de pruebas para este comporta-
miento, importe el módulo ErrorHandlingTests.psm1 y ejecute ma-
nualmente los siguientes comandos en una consola de PowerShell.
Como los va a ejecutar uno a la vez, no encontrará problemas con
que algunos de los comandos fallen su ejecución debido a un error
no controlado anterior. Caso distinto seria si estuvieran todos en
un script.

1 Test-WithoutRethrow -Cmdlet -Terminating


2
3 Test-WithoutRethrow -Function -Terminating
4
5 Test-WithoutRethrow -Cmdlet -NonTerminating
6
7 Test-WithoutRethrow -Function -NonTerminating
8
9 Test-WithoutRethrow -Method
10
11 Test-WithoutRethrow -UnknownCommand
Análisis de los resultados de las pruebas de manejo de errores 28

También hay una función Test-WithRethrow que se puede llamar


con los mismos parámetros, para comprobar que los resultados son
consistentes en los 6 casos cuando se maneja cada error y se elige
si se aborta la función.

PowerShell continúa la ejecución después


de producirse un error Terminating,
cuando:

• Un Cmdlet genera un error Terminating


• Un método .NET genera una excepción
• PowerShell encuentra un comando desconocido

PowerShell detiene la ejecución después de


producirse un error Terminating, cuando:

• Una función utiliza la sentencia Throw


• Cualquier error Non-Terminating en conjunto con ErrorAc-
tion establecido a Stop
• En cualquier momento cuando $ErrorActionPreference se
establece a Stop en el ámbito del llamador

Con el fin de lograr un comportamiento coherente entre estas dife-


rentes fuentes de errores Terminating, puede colocar los comandos
que potencialmente podrían producir un error de terminación en
un bloque try. En el bloque catch, puede decidir si aborta o no la
ejecución del bloque de secuencia de comandos actual. La figura 3.1
muestra un ejemplo de cómo forzar una función a abortar cuando
se genera una excepción de terminación desde un Cmdlet (una
situación en la que PowerShell normalmente solo continuaría y
ejecuta la sentencia “after terminating error”), volviendo a lanzar
el error del bloque Catch. Cuando se usa Throw sin argumentos
Análisis de los resultados de las pruebas de manejo de errores 29

dentro de un bloque Catch, se pasa el mismo error hacia el ámbito


padre.

image013.png

Figura 3.1: Volviendo a lanzar un error Terminating para forzar a


una función a detener la ejecución.

Conclusiones
Para errores Non-Terminating, puede utilizar $Error o ErrorVaria-
ble sin distinción. Solo debe tener presente en que el orden de los
ErrorRecords se invierte, pero usted puede fácilmente controlar eso
en su código, suponiendo que considere que eso sea un problema.
Sin embargo, tan pronto como los errores Terminating entran en
juego, ErrorVariable tiene un comportamiento muy molesto: a
veces contiene objetos de excepción en lugar de ErrorRecords, y en
otros casos, tiene uno o más objetos duplicados, todos relacionados
con el error Terminating. Si bien es posible codificar alrededor
de estas peculiaridades, realmente no parece que valga la pena el
esfuerzo cuando se puede utilizar fácilmente $_ o $Error[0].
Cuando está llamando a un comando que puede producir un error
Terminating y no maneja ese error dentro una sentencia Try/-
Catch o Trap, el comportamiento de PowerShell es inconsistente,
dependiendo de cómo se generó el error Terminating. Para lograr
Análisis de los resultados de las pruebas de manejo de errores 30

resultados consistentes, independientemente de los comandos que


esté llamando, coloque dichos comandos en un bloque Try y elija
si desea volver a lanzar el error en el bloque Catch.

image018.png
Poniéndolo todo junto
Ahora que hemos examinado todas las herramientas de manejo de
errores e identificado algunos posibles escenarios de “engañosos”,
he aquí algunos consejos y ejemplos de cómo abordar el manejo de
errores en sus propios scripts.

Supresión de errores (no haga esto)


Hay ocasiones en las que puede “procesar” un error sin la in-
tención de manejarlo. En realidad, las situaciones válidas para
estos escenarios son pocas. Procure no establecer ErrorAction o
$ErrorActionPreference en SilentlyContinue a menos que tenga la
intención de examinar y verificar cada posible error, usted mismo
más adelante en su código. Utilizar un bloque Try/Catch con un
bloque Catch vacío equivale a la misma cosa. Por lo general esto no
es lo correcto.
Es mejor al menos mostrar al usuario la salida de error por defecto
en la consola, que tener un comando que falle sin indicación alguna
de que algo salió mal.

Uso de la variable $? (úselo bajo su


propio riesgo)
La variable $? parece una buena idea al principio, pero hay muchas
cosas que podrían salir mña como para simplemente confiar en
esta variable en un script de producción. Por ejemplo, si el error
es generado por un comando que está entre paréntesis o una sub-
expresión, la variable $? se establecerá en true en lugar de false:
Poniéndolo todo junto 32

image015.png

Figura 4.1: Falsos positivos con la variable $?

Determinar qué tipos de errores


puede producir un comando
Antes de que pueda decidir la mejor manera de manejar los errores
de un comando en particular, a menudo necesitará saber qué tipo de
errores puede producir. ¿Terminating o Non-Terminating? ¿Cuáles
son los tipos de excepción que se pueden producir? Desafortunada-
mente, la documentación del Cmdlet de PowerShell no proporciona
esta información, por lo que necesita recurrir a algún tipo de prueba
y error. Aquí hay un ejemplo de cómo puede averiguar si los errores
de un Cmdlet son Terminating or Non-Terminating:
Poniéndolo todo junto 33

image016.png

Figura 4.2: Identificación de errores


Irónicamente, este era un lugar práctico tanto para usar la sentencia
Trap como para establecer $ErrorActionPreference a SilentlyCon-
tinue, cosas que casi nunca haría en un script de producción.
Como se puede ver en la figura 4.2, Get-Acl produce excepciones
Terminating cuando el archivo existe, pero el Cmdlet no puede leer
el ACL. Get-Item y Get-Acl producen errores Non-Terminating si
el archivo no existe.
Pasar por este tipo de ensayo y error puede ser un proceso que
consume mucho tiempo, sin embargo, es necesario que conozca
las diferentes formas en que un comando puede fallar y, a conti-
nuación, reproducir esas condiciones para ver si el error resultante
era Terminating o Non-Terminating. Como resultado de lo molesto
que puede ser, además de este libro electrónico, el repositorio de
Github contendrá una hoja de cálculo con una lista de errores
Terminating conocidos de algunos Cmdlets. Será un documento
“vivo”, posiblemente convertido en un wiki en algún momento,
pero probablemente nunca será una referencia completa, debido a
la gran cantidad de Cmdlets de PowerShell que existen por ahí,
Poniéndolo todo junto 34

aunque esto es mucho mejor que nada.


Además de saber si los errores son Terminating o Non- Terminating,
es posible que también desee conocer qué tipos de excepciones
se producen. La figura 4.3 muestra cómo puede enumerar los
tipos de excepción que están asociados con diferentes tipos de
errores. Cada objeto de excepción puede contener opcionalmente
una InnerException, y puede usar cualquiera de ellos en un bloque
Catch o Trap:

image017.png

Figura 4.3: Visualización de los tipos de Excepciones y de cualquier


InnerException.
Poniéndolo todo junto 35

Tratamiento de errores Terminating


Esta es la parte fácil. Sólo use try/catch, y consulte $_ o $Error[0]
en sus bloques Catch para obtener información sobre el error.

Tratamiento de errores
Non-Terminating
Tiendo a clasificar los comandos que pueden producir errores Non-
Terminating (Cmdlets, funciones y secuencias de comandos) de
una de tres maneras: comandos que necesitan procesar un solo
objeto de entrada, comandos que sólo pueden producir errores Non-
Terminating y comandos que podrían producir errores Terminating
o Non-Terminating. Suelo manejar cada una de estas categorías de
las siguientes formas:
Si el comando sólo necesita procesar un único objeto de entrada,
como en la figura 4.4, uso ErrorAction en Stop y manejo los errores
en un bloque Try /Catch. Debido a que el Cmdlet sólo trata con un
único objeto de entrada, el concepto de un error Non-Terminating
no es terriblemente útil de todos modos.

image018.png

Figura 4.4: Utilizar Try/Catch y ErrorAction en Stop cuando se trata


de un solo objeto.
Poniéndolo todo junto 36

Si el comando sólo produce errores Non-Terminating, utilizó Erro-


rAction, pero esta categoría es más grande de lo que usted pensaría.
La mayoría de los errores de un Cmdlet de PowerShell son Non-
Terminating:

image019.png

Figura 4.5: Uso de ErrorVariable en errores Terminating.


Cuando está examinando el contenido de ErrorVariable, recuerde
que normalmente puede obtener información útil acerca de lo que
falló al examinar la propiedad CategoryInfo.Activity del objeto
ErrorRecord (cuyo Cmdlet produjo el error) y la propiedad Targe-
tObject (cuyo objeto estaba procesando cuando el error ocurrió).
Sin embargo, no todos los Cmdlets rellenan el ErrorRecord con un
TargetObject, por lo que querrá realizar algunas comprobaciones
para determinar cuán útil será esta técnica. Si encuentra una
situación en la que un Cmdlet debe estar informándole sobre el
TargetObject pero no lo hace, considere cambiar su estructura de
código para procesar un objeto a la vez, como se muestra en la figura
4.4. De esa manera, ya sabrá qué objeto se está procesando.
Surge un escenario más complicado si un comando en particular
puede producir errores Terminating y Non- Terminating. En esas
situaciones, si es práctico, intentó cambiar mi código para llamar al
comando en un objeto a la vez. Si se encuentra en una situación en
la que esto no es deseable (aunque me parece difícil de encontrar un
ejemplo), recomiendo el siguiente enfoque para evitar el comporta-
miento peculiar de ErrorVariable y evitar llamar a $Error.Clear():
Poniéndolo todo junto 37

image020.png

Figura 4.6: usando $Error sin llamar a Clear() e ignorando los


registros de errores previamente existentes.
Como se puede ver, la estructura de este código es casi igual que
cuando se utiliza el parámetro ErrorVariable, con la adición de
un bloque Try alrededor del código “problemático” y el uso de
la variable $ previousError para asegurarnos de que sólo estamos
reaccionando a nuevos errores en la colección $Error. En este caso,
tengo un bloque Catch vacío, porque el error Terminating (si se
produce) va a ser añadido también a $Error y manejado en el bucle
foreach de todos modos. Es posible que prefiera manejar el error
Terminating en el bloque Catch y los errores Non- Terminating en
el bucle. De cualquier manera funciona.

Llamando a programas externos


Cuando necesite llamar a un ejecutable externo, la mayor parte
del tiempo obtendrá los mejores resultados comprobando $LASTE-
XITCODE, sin embargo, tendrá que asegurarse que el programa
externo devuelve información útil a través de su código de salida.
Poniéndolo todo junto 38

Hay algunos ejecutables “raros” por ahí que siempre devuelven 0,


independientemente de si se encontraron o no errores.
Si un ejecutable externo escribe algo en el flujo StdErr, PowerShell a
veces se percata de esto y envuelve el texto en un ErrorRecord, pero
este comportamiento no parece ser consistente. No estoy seguro
aún en qué condiciones se producirán estos errores, por lo que
tiendo a utilizar $LASTEXITCODE cuando necesito establecer si
un comando externo funcionó o no.
Epílogo
¡Esperamos que haya encontrado útil esta guía! Esto siempre va a
ser un trabajo en progreso; así que a medida que la gente aporte
materiales y sugerencias, incorporaremos lo mejor que podamos y
publicaremos una nueva edición.

También podría gustarte