- 05/23/2020
- 14 minutes pour lire
-
- j
- f
- s
la gestion des Erreurs est juste une partie de la vie quand il s’agit de l’écriture de code. Nous pouvons souvent vérifier et validerconditions pour le comportement attendu. Quand l’inattendu se produit, nous nous tournons vers la gestion des exceptions., Vous pouvez facilement gérer les exceptions générées par le code d’autres personnes ou vous pouvez générer vos propres exceptions pour que les autres puissent les gérer.
Remarque
La version originale de cet article est parue sur le blog écrit par @KevinMarquette. L’équipe de PowerShell remercie Kevin d’avoir partagé ce contenu avec nous. Veuillez consulter son blog atPowerShellExplained.com.
terminologie de base
Nous devons couvrir quelques termes de base avant de sauter dans celui-ci.
Exception
Une Exception est comme un événement qui est créé lorsque la gestion normale des erreurs ne peut pas traiter le problème.,Essayer de diviser un nombre par zéro ou manquer de mémoire sont des exemples de quelque chose qui crée une exception. Parfois, l’auteur du code que vous utilisez crée des exceptions pour certains problèmes lorsqu’ils se produisent.
à Lancer et à Attraper
Lorsqu’une exception se produit, nous disons qu’une exception est levée. Pour gérer une exception lancée, vousbesoin de l’attraper. Si une exception est levée et qu’elle n’est pas attrapée par quelque chose, le script stopsexecuting.
la pile d’appels
la pile d’appels est la liste des fonctions qui se sont appelées., Lorsqu’une fonction est appelée, elle est ajoutée à la pile ou en haut de la liste. Lorsque la fonction se termine ou revient, elle est supprimée de la pile.
Lorsqu’une exception est levée, cette pile d’appels est vérifiée pour qu’un gestionnaire d’exceptions puisse catchit.
erreurs de terminaison et de non-terminaison
Une exception est généralement une erreur de terminaison. Une exception levée est soit être interceptée, soit itterminates l’exécution en cours. Par défaut, une erreur non terminante est générée par Write-Error
et elle ajoute une erreur au flux de sortie sans lancer d’exception.,
je signale cela parce que Write-Error
et d’autres erreurs non terminantes ne déclenchent pas lecatch
.
avaler une exception
c’est quand vous attrapez une erreur juste pour la supprimer. Faites-le avec prudence car cela peut rendre les problèmes de tournage très difficiles.
syntaxe de commande de base
Voici un aperçu rapide de la syntaxe de base de gestion des exceptions utilisée dans PowerShell.
Jet
Pour créer notre propre événement d’exception, on lance une exception avec la balise throw
mot clé.,
function Start-Something{ throw "Bad thing happened"}
Cela crée une exception d’exécution qui est une terminaison d’erreur. Il est géré par un catch
dans la fonction acalling ou quitte le script avec un message comme celui-ci.
write-Error-ErrorAction Stop
j’ai mentionné queWrite-Error
ne génère pas d’erreur de fin par défaut. Si vous spécifiez-ErrorAction Stop
, Write-Error
génère une terminaison d’erreur qui peuvent être traitées avec uncatch
.,
Write-Error -Message "Houston, we have a problem." -ErrorAction Stop
Merci à Lee Dailey pour le rappel sur l’utilisation de la balise -ErrorAction Stop
de cette façon.
Cmdlet-ErrorAction Stop
Si vous spécifiez-ErrorAction Stop
sur n’importe quelle fonction ou cmdlet avancée, Toutes les instructionsWrite-Error
se terminent par des erreurs qui arrêtent l’exécution ou qui peuvent être gérées par uncatch
.,
Start-Something -ErrorAction Stop
Try/Catch
La façon dont la gestion des exceptions travaille en PowerShell (et beaucoup d’autres langues) est que vous devez d’abord try
asection de code, et si elle déclenche une erreur, vous pouvez catch
c’. Voici un échantillon rapide.
le script catch
ne s’exécute que s’il y a une erreur de fin. Si le try
s’exécute correctement, alors il saute sur le catch
.,
Try/Finally
parfois, vous n’avez pas besoin de gérer une erreur mais vous avez toujours besoin de code pour exécuter si un exceptionhappens ou non. Un scriptfinally
fait exactement cela.
regardez cet exemple:
chaque fois que vous ouvrez ou vous connectez à une ressource, vous devez la fermer. Si l’exceptionExecuteNonQuery()
throwsan, la connexion n’est pas fermée. Voici le même code dans un bloctry/finally
.
dans cet exemple, la connexion est fermée en cas d’erreur. Il est également fermé s’il n’y a pas d’erreur., Le scriptfinally
s’exécute à chaque fois.
parce que vous n’attrapez pas l’exception, elle est toujours propagée dans la pile d’appels.
Try/Catch/finally
Il est parfaitement valide pour utiliser catch
et finally
ensemble. La plupart du temps, vous utiliserez l’un oul’autre, mais vous pouvez trouver des scénarios où vous utilisez à la fois.
$PSItem
Maintenant que nous avons les bases, on peut creuser un peu plus profond.,
dans le bloccatch
, il y a une variable automatique ($PSItem
ou$_
) de typeErrorRecord
qui contient les détails sur l’exception. Voici un aperçu rapide de certaines des propriétés keyproperties.
pour ces exemples, j’ai utilisé un chemin non valide dansReadAllText
pour générer cette exception.
::ReadAllText( '\\test\no\filefound.log')
PSItem.ToString ()
cela vous donne le message le plus propre à utiliser dans la journalisation et la sortie générale., ToString()
est automatiquement appelé si $PSItem
est placé dans une chaîne.
catch{ Write-Output "Ran into an issue: $($PSItem.ToString())"}catch{ Write-Output "Ran into an issue: $PSItem"}
PS PSItem.InvocationInfo
Cette propriété contient des informations supplémentaires collectées par PowerShell sur la fonction ou le script où l’exception a été levée. Voici le InvocationInfo
de L’exemple d’exception Icreated.
Les détails importants ici montrent leScriptName
, leLine
du code et leScriptLineNumber
où l’appel a commencé.
PS PSItem.,ScriptStackTrace
Cette propriété affiche l’ordre des appels de fonction qui vous ont conduit au code où l’exception a été générée.
Je ne fais que des appels à des fonctions dans le même script, mais cela suivrait les appels si des multiplescripts étaient impliqués.
PS PSItem.Exception
c’est l’exception réelle qui a été levée.
PS PSItem.Exception.Message
ceci est le message général qui décrit l’exception et est un bon point de départ whentroubleshooting. La plupart des exceptions ont un message par défaut, mais peuvent également être définies sur quelque chose de personnalisé lorsque l’exception est levée.,
PS> $PSItem.Exception.MessageException calling "ReadAllText" with "1" argument(s): "The network path was not found."
C’est aussi le message retourné lors de l’appel de $PSItem.ToString()
si il n’y avait pas un jeu sur leErrorRecord
.
PS PSItem.Exception.InnerException
Les Exceptions peuvent contenir des exceptions internes. C’est souvent le cas lorsque le code que vous appelezcatches une exception et lève une exception différente. L’exception d’origine est placée à l’intérieurla nouvelle exception.
PS> $PSItem.Exception.InnerExceptionMessageThe network path was not found.
je reviendrai plus tard quand je parlerai de relancer les exceptions.
PS PSItem.Exception.,StackTrace
ceci est leStackTrace
pour l’exception. J’ai montré un ScriptStackTrace
ci-dessus, mais celui-ci est pour les appels au code géré.
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize, Boolean checkHost)at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)at CallSite.Target(Closure , CallSite , Type , String )
Vous n’obtenez cette trace de pile que lorsque l’événement est lancé à partir de code géré. Je suis d’appeler un .NETframework fonctionne directement, c’est tout ce que nous pouvons voir dans cet exemple. Généralement, lorsque vous recherchez une trace de pile, vous cherchez où votre code s’arrête et où les appels système commencent.
travailler avec des exceptions
Il y a plus aux exceptions que la syntaxe de base et les propriétés d’exception.,
capture des exceptions typées
Vous pouvez être sélectif avec les exceptions que vous attrapez. Les Exceptions ont un type et vous pouvez spécifierle type d’exception que vous souhaitez attraper.
le type d’exception est vérifié pour chaque bloc catch
jusqu’à ce qu’un soit trouvé qui correspond à votre exception.It il est important de réaliser que les exceptions peuvent hériter d’autres exceptions. Dans l’exemple ci-dessus,FileNotFoundException
hérite de IOException
. Donc, si le IOException
était le premier, alors il serait appelé à la place., Un seul bloc catch est invoqué même s’il y a plusieurs correspondances.
Si nous avions unSystem.IO.PathTooLongException
, leIOException
correspondrait mais si nous avions unInsufficientMemoryException
alors rien ne l’attraperait et il se propagerait dans la pile.
Attrape plusieurs types à la fois
Il est possible d’attraper plusieurs types d’exception avec la même instructioncatch
.
Merci/u/Sheppard_Ra
pour avoir suggéré cet ajout.
lancer des exceptions typées
Vous pouvez lancer des exceptions typées dans PowerShell., Au lieu d’appeler throw
avec une chaîne de caractères:
throw "Could not find: $path"
l’Utilisation d’une exception d’accélérateur comme ceci:
throw "Could not find: $path"
Mais vous devez spécifier un message lorsque vous le faire de cette façon.
Vous pouvez également créer une nouvelle instance de la levée d’une exception. Le message est facultatif lorsque vous le faites car le système a des messages par défaut pour toutes les exceptions intégrées.
throw ::new()throw ::new("Could not find path: $path")
Si vous n’êtes pas à l’aide de PowerShell 5.0 ou supérieur, vous devez utiliser l’ancienne New-Object
approche.,
en utilisant une exception typée, vous (ou d’Autres) pouvez attraper l’exception par le type comme mentionné dans la section précédente.
Write-Error -Exception
Nous pouvons ajouter ces tapé exceptions Write-Error
et nous pouvons encore catch
les erreurs par exceptiontype. Utilisez Write-Error
comme dans ces exemples:
alors nous pouvons l’attraper comme ceci:
catch { Write-Log $PSItem.ToString()}
la grande liste des exceptions .net
j’ai compilé une liste principale avec l’aide de la communauté Reddit/r / PowerShell qui contient des centaines de.,Exceptions nettes pour compléter ce poste.
- La grande liste des exceptions.net
je commence par rechercher dans cette liste des exceptions qui semblent convenir à la mysituation. Vous devriez essayer d’utiliser des exceptions dans l’espace de noms System
de base.
les Exceptions sont des objets
Si vous commencez à utiliser beaucoup d’tapé exceptions, n’oubliez pas qu’ils sont des objets. Différentes exceptionshave différents constructeurs et propriétés., Si nous regardons la FileNotFoundExceptiondocumentation pour System.IO.FileNotFoundException
, nous voyons que nous pouvons passer un message et un chemin de fichier.
::new("Could not find file", $path)
et il a une propriétéFileName
qui expose ce chemin de fichier.
catch { Write-Output $PSItem.Exception.FileName}
Vous devriez consulter la documentation.Net pour d’autres constructeurs et propriétés d’objet.
relancer une exception
Si tout ce que vous allez faire dans votre bloccatch
estthrow
la même exception, alors ne le faites pascatch
., Vous devez uniquementcatch
une exception que vous prévoyez de gérer ou d’effectuer une action lorsque ithappens.
Il y a des moments où vous voulez effectuer une action sur une exception mais relancer l’exception sosomething en aval peut y faire face. Nous pourrions écrire un message ou enregistrer le problème près de l’endroit où nous le découvrons, mais gérer le problème plus haut dans la pile.
catch{ Write-Log $PSItem.ToString() throw $PSItem}
assez intéressant, nous pouvons appelerthrow
à partir ducatch
et il relance l’exception currentexception.,
catch{ Write-Log $PSItem.ToString() throw}
nous voulons relancer l’exception pour préserver les informations d’exécution d’origine comme le script source et le numéro de ligne. Si nous lançons une nouvelle exception à ce stade, elle cache l’endroit où l’exception a commencé.
relancer une nouvelle exception
Si vous attrapez une exception mais que vous souhaitez en lancer une autre, vous devez imbriquer l’exception originalexception dans la nouvelle. Cela permet à quelqu’un en bas de la pile d’y accéder en tant que$PSItem.Exception.InnerException
.
catch{ throw ::new('Could not access field',$PSItem.Exception)}
let PSCmdlet.,ThrowTerminatingError ()
la seule chose que je n’aime pas à propos de l’utilisation de throw
pour les exceptions brutes est que le message d’erreur pointe à l’instruction throw
et indique que line est l’endroit où se trouve le problème.
avoir le message d’erreur me dire que mon script est cassé parce que j’ai appeléthrow
sur la ligne 31 est un message abad pour les utilisateurs de votre script à voir. Il ne dit rien d’utile.
Dexter Dhami souligné que je peux utiliser ThrowTerminatingError()
pour corriger cela.,
Si nous supposons que ThrowTerminatingError()
a été appelée à l’intérieur d’une fonction appelée Get-Resource
, puiscette est l’erreur que nous voudrions voir.
voyez-vous comment elle pointe vers la fonctionGet-Resource
comme source du problème? Cela dit à l’utilisateur quelque chose d’utile.
Car $PSItem
est une ErrorRecord
, nous pouvons aussi utiliser des ThrowTerminatingError
cette façon de re-jeter.,
catch{ $PSCmdlet.ThrowTerminatingError($PSItem)}
cela change la source de l’erreur en applet de commande et masque les éléments internes de votre fonction aux utilisateurs de votre applet de commande.
Try peut créer des erreurs de terminaison
Kirk Munro souligne que certaines exceptions ne sont terminatrices que lorsqu’elles sont exécutées dans un bloctry/catch
. Voici l’exemple qu’il m’a donné qui génère une division par zéro exception d’exécution.
function Start-Something { 1/(1-1) }
Puis l’appeler comme ça pour voir générer l’erreur et encore sortie le message.,
&{ Start-Something; Write-Output "We did it. Send Email" }
mais en plaçant ce même code dans un try/catch
, nous voyons quelque chose d’autre se produire.
try{ &{ Start-Something; Write-Output "We did it. Send Email" }}catch{ Write-Output "Notify Admin to fix error and send email"}
Nous voyons l’erreur devenir une erreur de terminaison et ne pas sortir le premier message. Ce que je n’aime pas à propos de celui-ci, c’est que vous pouvez avoir ce code dans une fonction et qu’il agit différemment si quelqu’un utilise un try/catch
.
je n’ai pas rencontré de problèmes avec cela moi-même, mais c’est un cas de coin dont il faut être conscient.
let PSCmdlet.,ThrowTerminatingError () dans try/catch
Une nuance de $PSCmdlet.ThrowTerminatingError()
est qu’il crée une erreur de terminaison dans yourCmdlet mais il se transforme en une erreur de non-terminaison après avoir quitté votre Cmdlet. Cela laisse le fardeau à l’appelant de votre fonction de décider comment gérer l’erreur. Ils peuvent le transformer en erreur aterminating en utilisant -ErrorAction Stop
ou en l’appelant depuis un try{...}catch{...}
.,
modèles de fonction publique
Un dernier moyen que j’ai eu avec ma conversation avec Kirk Munro était qu’il place untry{...}catch{...}
autour de chaquebegin
,process
Etend
bloquer dans toutes ses fonctions avancées. Dans ces blocs catch génériques, il a une seule ligne utilisant$PSCmdlet.ThrowTerminatingError($PSItem)
pour gérer toutes les exceptions quittant ses fonctions.
function Start-Something{ param() process { try { ... } catch { $PSCmdlet.ThrowTerminatingError($PSItem) } }}
parce que tout est dans une instructiontry
dans ses fonctions, tout agit de manière cohérente., Cela donne également des erreurs propres à l’utilisateur final qui masque le code interne de l’erreur générée.
Piège
je me suis concentré sur le try/catch
aspect des exceptions. Mais il y a une fonctionnalité héritée que je dois mentionner avant que nous terminions cela.
trap
est placé dans un script ou une fonction pour intercepter toutes les exceptions qui se produisent dans cette portée. Quandune exception se produit, le code dans le trap
est exécuté, puis le code normal continue. Ifmultiple exceptions se produisent, alors le piège est appelé encore et encore.,
personnellement, je n’ai jamais adopté cette approche, mais je peux voir la valeur dans les scripts admin ou controller qui enregistrent toutes les exceptions, puis continuent à s’exécuter.
remarques finales
L’ajout d’une gestion appropriée des exceptions à vos scripts les rend non seulement plus stables, mais vous permet également de résoudre plus facilement ces exceptions.
j’ai passé beaucoup de temps à parlerthrow
parce que c’est un concept de base quand on parle de exceptionhandling., PowerShell nous a également donné Write-Error
qui gère toutes les situations où vous utiliseriezthrow
. Donc, ne pensez pas que vous devez utiliser throw
après avoir lu ceci.
maintenant que j’ai pris le temps d’écrire sur la gestion des exceptions dans ce détail, je vais passer à l’utilisation deWrite-Error -Stop
pour générer des erreurs dans mon code. Je vais également prendre les conseils de takeKirk et faire de ThrowTerminatingError
mon gestionnaire d’exceptions goto pour chaque fonction.