Comment utiliser Try, Catch et Finally avec PowerShell ?

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.

Ressource complémentaire : Microsoft Docs - Try Catch Finally

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 :

PowerShell Try Catch

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 :

PowerShell Try Catch

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 :

PowerShell : try-catch avec gestion spécifique des erreurs
PowerShell : try-catch avec gestion spécifique des erreurs

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 :

PowerShell Try Catch Finally

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.

Partagez cet article Partager sur Twitter Partager sur Facebook Partager sur Linkedin Partager sur Google+ Envoyer par mail

Florian Burnel

Ingénieur système et réseau, cofondateur d'IT-Connect et Microsoft MVP "Cloud and Datacenter Management". Je souhaite partager mon expérience et mes découvertes au travers de mes articles. Généraliste avec une attirance particulière pour les solutions Microsoft et le scripting. Bonne lecture.

florian has 3369 posts and counting.See all posts by florian

2 thoughts on “Comment utiliser Try, Catch et Finally avec PowerShell ?

  • Salut
    le $Error.Clear() est une mauvaise pratique.Tu supprimes l’historique des erreurs qui ne sont pas bloquantes.

    Répondre
    • Salut,
      Quelle serait la bonne façon de faire en remplacement de $Error.Clear() ?
      Merci
      Florian

      Répondre

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

 

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.