Comment utiliser Try, Catch et Finally avec PowerShell ?
Sommaire
I. Présentation
Dans ce tutoriel, nous allons voir comment utiliser les blocs d'instructions Try, Catch et Finally avec PowerShell. Ce bloc d'instruction est très pratique, car il permet d'exécuter le code contenu dans le bloc Try et en cas d'erreur seulement, on exécute le code contenu dans le bloc Catch. De son côté, le bloc Finally est optionnel et de toute façon, il est toujours exécuté. Grâce à un bloc d'instruction Try-Catch, vous allez pouvoir gérer les erreurs proprement dans vos scripts PowerShell.
II. PowerShell et la syntaxe de Try, Catch et Finally
Comme nous allons le voir au travers de différents exemples, le couple d'instructions Try-Catch est très pratique et personnellement je l'utilise énormément dans mes scripts. En revanche, le dernier bloc "Finally" n'est pas très souvent utilisé, car il manque d'intérêt (voir ci-dessous).
Dans la pratique, voici comment on écrit un bloc Try-Catch-Finally :
try { <code à exécuter, et qui potentiellement, peut générer une erreur> } catch { <code à exécuter lorsqu'une erreur se produit dans le Try> } finally { # Optionnel <code à exécuter dans tous les cas, peu importe le résultat de Try> }
Le bloc Finally est facultatif et le code contenu dans ce bloc sera toujours exécuté, quel que soit le résultat du bloc Try. Autrement dit, que le code soit dans l'instruction Finally ou à la suite du script après le Catch, cela ne change rien puisqu'il sera exécuté dans les deux cas. J'ai envie de dire qu'il est surtout présent pour produire un code propre et dans certains cas, par exemple pour fermer une connexion à la fin du traitement Try-Catch.
Sachez qu'il est possible d'accumuler plusieurs instructions Catch pour traiter les erreurs au cas par cas, comme nous le verrons dans la prochaine partie de cet article. Par contre, les blocs "Try" et "Finally" sont forcément uniques.
III. Exemples Try, Catch et Finally avec PowerShell
Au travers des exemples ci-dessous, vous allez apprendre à manipuler les blocs d'instructions Try-Catch-Finally en PowerShell. Ces différents exemples vont vous permettre de bien comprendre le fonctionnement de Try-Catch.
A. Try-Catch : un premier exemple basique
Nous allons chercher à récupérer le contenu du fichier "hosts" de notre machine Windows et à le stocker dans une variable. Pour rappel, voici le chemin complet vers ce fichier : "C:\Windows\System32\drivers\etc\hosts". En cas d'erreur, on affichera seulement un message pour dire que le fichier est introuvable.
- Script TryCatch1.ps1 :
$Fichier = "C:\Windows\System32\drivers\etc\hosts" try { $ContenuFichier = Get-Content $Fichier Write-Host -ForegroundColor Green "Contenu du fichier récupéré ($Fichier)" } catch { Write-Host "Attention, le fichier $Fichier est introuvable !" -ForegroundColor Red }
Si vous exécutez ce script, vous allez obtenir le résultat suivant :
C'est normal puisque le fichier existe, donc Get-Content ne retourne pas d'erreur. Le bloc Catch n'a pas besoin de s'exécuter puisque Try ne renvoie pas d'erreur.
Maintenant, faisons un essai en prenant un fichier qui n'existe pas. Ce qui donne un second script avec seulement le nom du fichier qui change.
- Script TryCatch2.ps1 :
$Fichier = "C:\Windows\System32\drivers\etc\monfichier" try { $ContenuFichier = Get-Content $Fichier Write-Host -ForegroundColor Green "Contenu du fichier récupéré ($Fichier)" } catch { Write-Host "Attention, le fichier $Fichier est introuvable !" -ForegroundColor Red }
Si vous exécutez ce script, vous allez obtenir un résultat qui n'est pas forcément celui que vous attendiez... L'erreur associée à Get-Content va s'afficher dans la console (l'erreur est normale puisque le fichier n'existe pas) et le message "Contenu du fichier récupéré...." s'affiche aussi.
Par contre, aucune trace de notre phrase "Attention, le fichier $Fichier est introuvable !" malgré la présence de l'erreur ! Le bloc Catch ne semble pas s'exécuter. Mais, pourquoi ?
Lorsqu'une commande ou un script PowerShell renvoie une erreur, il y a deux types d'erreurs : des erreurs qui stoppent l'exécution (terminating error) et les erreurs qui n'empêchent pas la suite du script de s'exécuter (non-terminating error).
Un bloc d'instruction Try-Catch agit seulement sur les erreurs de type "terminating error", et ce comportement dépend de deux choses :
- Le cmdlet utilisé dans le bloc Try et du type d'erreur qu'il génère
- La valeur de la variable $ErrorActionPreference ou du paramètre -ErrorAction au niveau du cmdlet
Pour capturer toutes les erreurs avec un bloc Try-Catch, il faut convertir les erreurs non-terminating (que l'on pourrait appeler erreur non fatale) en erreur terminating (fatale).
Pour cela, on peut agir sur la variable $ErrorActionPreference ou le paramètre -ErrorAction pris en charge au niveau de chaque cmdlet.
- $ErrorActionPreference
La variable $ErrorActionPreference est définie au sein de votre profil PowerShell et elle définit le comportement à adopter en cas d'erreur. Par défaut, sa valeur est "Continue" cela signifie qu'en cas d'erreur, on poursuit l'exécution du script. Pour convertir toutes les erreurs non fatales en erreurs fatales, il faudrait définir :
$ErrorActionPreference = "stop"
Cette valeur peut être modifiée dans le profil PowerShell pour être persistante, directement dans une console PowerShell pour s'appliquer uniquement le temps que la console est ouverte, ou dans un script.
- -ErrorAction
Chaque cmdlet dispose d'un paramètre natif nommé "-ErrorAction" et qui permet de préciser le comportement à adopter lorsqu'une erreur se produit. Par exemple, pour masquer le message d'erreur dans la console et continuer l'exécution du script, on indiquera :
-ErrorAction SilentlyContinue
Pour convertir les erreurs non fatales en erreur fatale, il faut utiliser la valeur "stop". Sur une commande complète, cela donne :
Get-Content $Fichier -ErrorAction Stop
De cette façon, le bloc Try-Catch va fonctionner sur Get-Content en cas d'erreur alors qu'il ne fonctionnait pas dans l'exemple précédent. Essayons avec ce troisième bout de code (toujours sur le fichier qui n'existe pas) :
- Script TryCatch3.ps1 :
$Fichier = "C:\Windows\System32\drivers\etc\monfichier" try { $ContenuFichier = Get-Content $Fichier -ErrorAction Stop Write-Host -ForegroundColor Green "Contenu du fichier récupéré ($Fichier)" } catch { Write-Host "Attention, le fichier $Fichier est introuvable !" -ForegroundColor Red }
Dans le bout de code ci-dessus, vous remarquerez la présence de "-ErrorAction Stop" à la fin de la commande Get-Content. Si j'exécute le script, j'obtiens ceci :
On peut voir que l'instruction contenue dans le bloc Catch s'est bien exécutée ! L'erreur a été capturée et on a pu obtenir le résultat attendu.
B. Try-Catch : afficher le message d'erreur
Dans l'exemple précédent, nous avons pris la décision d'afficher un message d'erreur personnalisé ("Attention, le fichier...."), mais il ne donne pas de détails précis sur l'erreur exacte.
Pour que ce soit plus pertinent, nous allons voir que l'on peut afficher le message correspondant à l'erreur générée. On sait que l'on peut récupérer le dernier message d'erreur en consultant la constante $Error comme ceci :
$Error[0].Exception.Message
L'index [0] est important puisque le message d'erreur le plus récent est toujours ajouté en premier, et donc il correspond à l'index 0. Si l'on ne précise pas ce numéro d'index, on va afficher tous les messages d'erreurs générés lors de la session en cours ou depuis la dernière remise à zéro de la constante $Error ("$Error.Clear()").
$Error.Exception.Message
Pour récupérer le message d'erreur dans un bloc d'instructions Try-Catch, on va pouvoir utiliser cette méthode en précisant l'index 0, mais on peut aussi utiliser une autre syntaxe grâce à l'objet en cours $_ :
$_.Exception.Message
Note : une autre alternative consiste à utiliser "$PSItem.Exception.Message".
Si l'on reprend le script précédent et que l'on modifie le bloc Catch, cela donne le TryCatch4.ps1 :
$Fichier = "C:\Windows\System32\drivers\etc\monfichier"
try
{
$ContenuFichier = Get-Content $Fichier -ErrorAction Stop
Write-Host -ForegroundColor Green "Contenu du fichier récupéré ($Fichier)"
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Red
}
Une fois que ce script est exécuté, toujours en ciblant un fichier qui n'existe pas, on obtient un retour court, mais efficace dans la console :
Cannot find path 'C:\Windows\System32\drivers\etc\monfichier' because it does not exist.
Si l'on compare le script TryCatch2 (erreur complète) avec le script TryCatch4 (uniquement le message de l'exception), on peut voir cela fait beaucoup plus propre :
C. Try-Catch avec plusieurs Catch pour gérer les différentes erreurs
Une commande PowerShell peut retourner plusieurs erreurs différentes, car la cause de l'erreur n'est pas toujours la même. Si l'on prend l'exemple de la commande New-Item qui permet de créer des éléments (fichiers, dossiers), une erreur peut être générée pour plusieurs raisons :
- Le lecteur cible où l'on souhaite créer le fichier (ou le dossier) n'existe pas
- Le dossier cible où l'on souhaite créer le fichier n'existe pas
- Le fichier que l'on essaie de créer existe déjà
- Etc.
Grâce à un bloc d'instructions Try-Catch, on va pouvoir gérer chaque erreur indépendamment grâce à plusieurs blocs Catch. Par exemple, si le dossier cible n'existe pas, on peut décider de le créer, tandis que si le fichier existe déjà on peut essayer de créer le fichier avec un nom alternatif.
Pour capturer une erreur spécifique dans un bloc Catch, il faut commencer par récupérer le nom précis de cette erreur. Pour cela, on va générer l'erreur. Prenons le cas où le lecteur dans lequel on veut créer le fichier n'existe pas. On exécute :
New-Item Z:\fichier.txt
Une erreur s'affiche... mais la console n'affiche pas son nom technique, disons. Pour cela, il faut regarder un peu plus en détail les propriétés disponibles pour cette dernière erreur, grâce à Get-Member. Ce qui donne :
$Error[0].Exception | Get-Member
Cela va lister toutes les propriétés et méthodes que l'on peut appliquer sur la commande passée en entrée. En l'occurrence, l'information qui nous intéresse est ailleurs : il s'agit de la valeur TypeName affichée au-dessus du tableau de valeurs. Dans cet exemple, l'erreur est :
System.Management.Automation.DriveNotFoundException
Une autre méthode consiste à récupérer le type via GetType() puis la propriété "FullName" associée afin de retourner dans la console uniquement le nom, comme ceci :
$Error[0].Exception.GetType().FullName
Il va falloir que notre bloc Catch capture l'erreur "System.Management.Automation.DriveNotFoundException" pour lui réserver un traitement particulier lorsque l'on tombe sur un cas où le lecteur n'existe pas. Dans le même esprit, si l'on veut gérer le cas où le dossier cible n'existe pas, il faut gérer l'erreur "System.IO.DirectoryNotFoundException".
Voici le code du script TryCatch5.ps1 avec plusieurs blocs catch :
$Fichier = "C:\TEMP\CATCH\fichier2.txt" try { New-Item -ItemType File -Path $Fichier -ErrorAction Stop Write-Host -ForegroundColor Green "Création du fichier : $Fichier" } catch [System.Management.Automation.DriveNotFoundException] { Write-Host "Le lecteur ciblé par la commande New-Item n'existe pas" -ForegroundColor Red } catch [System.IO.DirectoryNotFoundException] { Write-Host "Le dossier cible n'existe pas, on relance la création en forçant la création du dossier" -ForegroundColor DarkYellow New-Item -ItemType File -Path $Fichier -Force -ErrorAction Stop } catch { Write-Host $_.Exception.Message -ForegroundColor Red }
Dans le bout de code ci-dessus, on peut voir que si l'on veut déclarer plusieurs blocs catch, ce n'est pas plus compliqué qu'avec un seul. Néanmoins :
- Il ne faut pas essayer de gérer deux fois la même erreur
- Il faut ajouter un bloc catch générique à la fin pour gérer toutes les erreurs non traitées spécifiquement, mais ce n'est pas obligatoire si vous souhaitez vous intéresser seulement à une erreur spécifique
- L'espace entre "catch" et la déclaration de l'erreur "[...]" est indispensable d'un point de vue syntaxique
Pour tester ce bout de code, c'est assez simple, il suffit de jouer sur la variable $Fichier pour générer les différents types d'erreurs que l'on capture avec nos blocs catch spécifique (lecteur qui n'existe pas et dossier cible qui n'existe pas).
Voici ce que ça donne en image :
D. Exemple de Try-Catch-Finally
Nous n'avons pas encore utilisé le bloc facultatif Finally jusqu'ici, alors c'est ce que nous allons faire dans ce quatrième exemple. Pour rappel, le code au sein du bloc Finally sera exécuté dans tous les cas, peu importe si le bloc Try retourne une erreur ou non.
Nous allons récupérer l'état du service dans le bloc Try et en cas d'erreur, on l'affichera via le bloc Catch. Enfin, on utilisera le bloc Finally pour purger le contenu de la constate $Error. On aurait pu imaginer autre chose, comme par exemple démarrer le service.
À la suite de notre bloc d'instructions Try-Catch-Finally, on affichera le contenu de $Error. Normalement, la variable devrait être vide, car on va la purger au sein du bloc Finally. Ce sera l'occasion de confirmer que le code contenu dans le bloc Finally s'est bien exécuté.
Voici le contenu du script TryCatch6.ps1 :
$NomService = "serviceinexistant" try { $EtatService = Get-Service -Name $NomService -ErrorAction Stop Write-Host -ForegroundColor Green "Etat du service correctement récupéré !" } catch { Write-Host $_.Exception.Message -ForegroundColor Red } finally { $Error.Clear() } Write-Host "Contenu des erreurs : $Error"
Voici ce que l'on obtient :
On peut voir que le bloc Finally s'est bien exécuté puisque la variable $Erreur ne renvoie rien. Mais finalement, on aurait écrit la ligne "$Error.Clear()" à la suite du bloc d'instructions Try-Catch, sans utiliser Finally, le résultat aurait été le même.
IV. Conclusion
Dans ce tutoriel, nous avons pu voir comment utiliser un bloc d'instructions Try-Catch (ou Try-Catch-Finally) avec PowerShell pour gérer les erreurs proprement et agir en conséquence. Dans un script, la gestion des erreurs est importante pour anticiper les éventuels problèmes, et pourquoi pas, les solutionner à la volée.
Maintenant, à vous de jouer : appuyez-vous sur ce que vous venez d'apprendre avec cet article pour améliorer votre manière de coder en PowerShell.
Ressource complémentaire : Microsoft Learn - Try Catch Finally
Salut
le $Error.Clear() est une mauvaise pratique.Tu supprimes l’historique des erreurs qui ne sont pas bloquantes.
Salut,
Quelle serait la bonne façon de faire en remplacement de $Error.Clear() ?
Merci
Florian
Elément de réponse :
Si vous envisagez d’utiliser la variable $Error dans vos scripts, gardez à l’esprit qu’elle peut déjà contenir des informations sur les erreurs survenues dans la session PowerShell en cours avant même le démarrage de votre script. De plus, certaines personnes considèrent comme une mauvaise pratique d’effacer la variable $Error dans un script ; puisqu’il s’agit d’une variable globale pour la session PowerShell, la personne qui a appelé votre script souhaitera peut-être examiner le contenu de $Error une fois celui-ci terminé.
Voilà pourquoi c’est mieux de ne pas la vider.