Gérer les erreurs : try, catch, finally
Objectifs de la séance
- Distinguer une erreur bloquante d'une erreur non bloquante
- Utiliser
try/catch/finallypour réagir proprement à un échec - Lire les informations d'une exception avec
$_ - Journaliser les erreurs plutôt que de les laisser s'afficher en silence
Notions théoriques
Deux familles d'erreurs
PowerShell distingue deux types d'erreurs, une notion absente en Bash et centrale ici :
- les erreurs non bloquantes (non-terminating) : la commande affiche une erreur en rouge, mais le script continue son exécution. C'est le comportement par défaut de la plupart des cmdlets —
Get-Service NomInexistanten est un exemple. - les erreurs bloquantes (terminating) : l'exécution du script s'arrête immédiatement à cet endroit, sauf si elle est interceptée par un
try/catch.
-ErrorAction
Le paramètre commun -ErrorAction contrôle le comportement d'une commande face à une erreur non bloquante :
| Valeur | Effet |
|---|---|
Continue | comportement par défaut : affiche l'erreur, poursuit l'exécution |
SilentlyContinue | masque l'erreur, poursuit l'exécution |
Stop | transforme l'erreur en erreur bloquante |
Inquire | demande confirmation à l'utilisateur |
-ErrorAction Stop est la clé de voûte de la gestion d'erreurs en PowerShell : sans lui, un try/catch autour d'une cmdlet ne se déclenche pas, car l'erreur reste non bloquante et le script continue simplement après la ligne fautive.
try {
Get-Service -Name "NomInexistant" -ErrorAction Stop
}
catch {
Write-Host "Le service n'existe pas."
}
$ErrorActionPreference
Plutôt que de répéter -ErrorAction Stop sur chaque ligne, une variable de préférence globale peut s'appliquer à tout le script :
$ErrorActionPreference = 'Stop'
C'est l'équivalent PowerShell du set -e de Bash niveau 1 : toute erreur, même non bloquante à l'origine, devient attrapable par un try/catch.
try / catch / finally
try {
# code susceptible d'échouer
}
catch {
# exécuté seulement si une erreur bloquante survient dans le try
}
finally {
# exécuté systématiquement, erreur ou pas
}
Le bloc finally sert typiquement à libérer une ressource (fermer une session distante, un fichier), qu'une erreur soit survenue ou non.
L'objet erreur : $_
À l'intérieur d'un catch, la variable automatique $_ (ou son alias $PSItem) contient l'exception qui vient d'être interceptée :
catch {
Write-Host "Erreur : $($_.Exception.Message)"
Write-Host "Type : $($_.Exception.GetType().FullName)"
Write-Host "Ligne : $($_.InvocationInfo.ScriptLineNumber)"
}
$_.Exception.Message est la propriété la plus utile au quotidien : le texte lisible de l'erreur.
Attraper un type précis
Plusieurs blocs catch peuvent se succéder, du plus précis au plus général — chaque erreur en PowerShell est un objet .NET typé, ce qui permet de réagir différemment selon le type rencontré :
try {
Restart-Service -Name "Spooler" -ErrorAction Stop
}
catch [System.UnauthorizedAccessException] {
Write-Host "Droits insuffisants."
}
catch [Microsoft.PowerShell.Commands.ServiceCommandException] {
Write-Host "Le service n'existe pas sur cette machine."
}
catch {
Write-Host "Erreur inattendue : $($_.Exception.Message)"
}
Cette notion de hiérarchie de types d'exceptions rejoint directement ce que vous avez vu avec les classes et l'héritage en programmation orientée objet : une exception .NET est un objet, comme un autre, avec son propre type.
throw
throw permet de lever volontairement sa propre erreur bloquante, l'équivalent d'un exit 1 en Bash mais qui peut être intercepté par un catch appelant :
if (-not (Test-Path $chemin)) {
throw "Le chemin $chemin n'existe pas."
}
À distinguer de Write-Error, qui produit une erreur non bloquante par défaut.
$Error
PowerShell conserve un historique des erreurs récentes dans le tableau automatique $Error, le plus récent en premier :
$Error[0]
$Error.Clear()
Le paramètre -ErrorVariable maVariable permet aussi de capturer l'erreur d'une commande précise dans une variable dédiée.
Journaliser
Une fonction de journalisation simple, réutilisée dans tout ce cours :
function Write-Journal {
param(
[string]$Message,
[string]$Niveau = "INFO"
)
$ligne = "[{0}] {1} : {2}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Niveau, $Message
Add-Content -Path $journal -Value $ligne
}
Cette fonction reprend l'idée de log_info déjà écrite en Bash niveau 1, transposée en PowerShell.
Exemple pratique
# Script : Restart-ServiceSurParc.ps1
# Role : redemarrer un service sur plusieurs machines, sans s'arreter a la premiere panne
$journal = "C:\Scripts\journal.log"
$machines = @("PC01", "PC02", "SRV-FIC")
function Write-Journal {
param(
[string]$Message,
[string]$Niveau = "INFO"
)
$ligne = "[{0}] {1} : {2}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Niveau, $Message
Add-Content -Path $journal -Value $ligne
}
foreach ($machine in $machines) {
try {
Restart-Service -Name "Spooler" -ErrorAction Stop
Write-Journal "Service Spooler redemarre sur $machine"
}
catch [System.UnauthorizedAccessException] {
Write-Journal "Droits insuffisants sur $machine" -Niveau "ERREUR"
}
catch {
Write-Journal "Echec sur $machine : $($_.Exception.Message)" -Niveau "ERREUR"
}
finally {
Write-Journal "Fin du traitement de $machine"
}
}
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Ce TP s'exécute sans droits particuliers sur n'importe quel poste Windows : une erreur de droits ou un service absent déclenchera simplement le bloc catch, ce qui fait partie du résultat attendu.
Nous fiabilisons le script de la séance précédente.
Étape 1 — Forcer l'erreur bloquante
Sans -ErrorAction Stop, le bloc catch n'est jamais atteint : l'erreur reste non bloquante et le script continue silencieusement après la ligne fautive.
Étape 2 — Journaliser le message d'erreur
Un bloc catch qui ne fait rien masque le problème et rend le débogage impossible — le même principe déjà vu avec les exceptions PHP.
Étape 3 — Ajouter un catch typé avant le catch générique
Placez toujours vos blocs catch typés avant le catch générique : PowerShell évalue les blocs dans l'ordre et s'arrête au premier qui correspond.
Étape 4 — Toujours journaliser la fin du traitement
finally garantit qu'une ligne de journal, une fermeture de session ou de fichier s'exécute toujours, même en cas d'échec — indispensable pour ne jamais laisser une ressource ouverte.