Powershell pour les débutants (4ème partie)
Sommaire
I. Présentation
Pour faire suite et achever cette introduction à Powershell, je vous propose de découvrir la gestion des erreurs sous Powershell. Un vaste programme, mais là encore, je souhaite vous amener à l'essentiel. Et pour conclure, histoire de se détendre un peu, je vous parlerais de la gestion des couleurs sous Powershell.
Pour ceux qui souhaitent consulter les autres parties :
- Powershell pour les débutants (1ère partie)
- Powershell pour les débutants (2ème partie)
- Powershell pour les débutants (3ème partie)
II. Précision sur les canaux ou flux (stream) d'une commande
Bien que cette précision soit quelque peu "annexe" au traitement des erreurs, il me semble que sa compréhension est utile ne serait-ce que pour intercepter les bonnes informations.
Les flux d'une commande peuvent être redirigés ou fusionnés à votre convenance. Pour cela, il suffit d'utiliser le caractère ">" pour la redirection avec ou sans l'esperluette "&" pour une fusion :
> Redirection vers un fichier (création/Ecrasement)
>> Redirection vers un fichier (mode ajout)
&> Fusion de flux
*> Redirige tous les flux (vers un fichier par exemple)
- Exemples :
3> Redirige les avertissements vers un fichier
3>&1 Fusionne les avertissements avec la sortie standard
Powershell propose plusieurs applets de commande, utilisant le verbe "write-*" destinées à l'écriture de données sur les différents flux.
Exemple de fonction de test d'envoi d'un message vers les différents canaux :
Function Write-Messages { [CmdletBinding()] param($Message = "Message de test") Write-Host "Canal [Host] $message" Write-Output "Canal 1 [Output] $Message" Write-Error " Canal 2 [Error] $Message" Write-Verbose "Canal 4 [Verbose] $Message" Write-Warning "Canal 3 [Warning] $Message" Write-Debug "Canal 5 [Debug] $Message" } Write-Messages -Verbose -Debug
Bien sûr, si vous interrogez les applets de commande utilisant le verbe "write", ("gcm -verb write"), vous obtiendrez un périmètre sensiblement annexe aux flux, tel que "write-host", "write-eventlog", ou "write-progress".
Vers un flux donné | Préfixe de message | Couleurs par défaut (Fore / Back) |
Write-Host | DarkYellow / DarkMagenta | |
Write-Output | ||
Write-Error | Red / Black | |
Write-Warning | AVERTISSEMENT : | Yellow / Black |
Write-Verbose | COMMENTAIRES : | Yellow / Black |
Write-Debug | DÉBOGUER : | Yellow / Black |
Note : Reportez-vous à la partie sur la gestion des couleurs de cet article si vous souhaitez modifier les valeurs par défaut lors de l'affichage de ces messages particuliers.
Exemples de redirection :
- Par commande :
Get-Process none 2> Errors.txt
- Pour un script :
.\MyScript.ps1 2>&1 | Tee-Object -filePath c:\results.txt
III. La gestion (basique) des erreurs
A. Les variables
Avant de parler des instructions spécifiques aux traitements des erreurs, il convient de présenter les variables dédiés à ces cas particuliers.
1 - "$ErrorActionPreference"
La première variable à connaitre est "$ErrorActionPreference" qui définit le comportement global de Powershell lorsqu'il rencontre une erreur. Par défaut, la valeur est positionnée sur "Continue" - Autrement dit, en cas d'erreur, Powershell renvoie l'information sur le canal d'erreur (et dans un objet dédié que j'évoque juste après) et poursuit le reste du code. (Ce qui correspond à la directive "on error resume next" en vbs.).
Les valeurs possibles pour "$ErrorActionPreference" sont :
• 'Continue' (default)
• 'stop'
• 'Inquire'
• 'SilentlyContinue'
Si vous intégrez un traitement d'erreur dans votre code, (genre, trap, ou try/catch), je vous conseille de positionner la valeur sur 'stop' afin que l'erreur devienne "bloquante". Il est en effet fort désagréable de laisser passer une erreur non bloquante qui pourrait avoir des conséquences ultérieures.
2 - "$?"
Cette variable "$?" d'état binaire (booléenne) prend la valeur $false si la dernière commande a retourné une erreur, $true s'il n'y a pas eu d'erreur.
3 - "$Error"
Cet objet dédié, nommé "$Error", recense toutes les erreurs qui sont survenues précédemment (Vous vous souvenez, le fameux canal 2). Attention, c'est ce que j'appellerais une sorte de pile FIFO, (First In First Out). C’est-à-dire que l'élément [0] correspond à la dernière erreur retournée.
Vous pouvez l'afficher ainsi :
$Error[0]
Pour vider toutes les erreurs :
$Error.clear()
Mais cet objet est plus complexe et riche qu'il n'y parait. Pour faire court, vous pouvez afficher uniquement le message de l'erreur (à l'instar de "err.description" en vbs) :
$Error.Exception.Message
Information provenant du type [System.Management.Automation.ErrorRecord] mais vous pouvez obtenir bien d'autres éléments sur son origine, comme par exemple :
$Error.CategoryInfo.Reason
Dans les grandes lignes, vous trouverez des informations (à consigner dans un journal par exemple) sous :
ErrorRecord | Informations |
$error[0].Exception | Contient l'instance de l'erreur |
$error[0].Exception |gm | Détails sur les membres disponibles d'une erreur |
$error[0].InvocationInfo | Détails sur le "contexte" de l'erreur (dépend du type d'erreur et n'est pas toujours disponible) |
4 - "$LASTEXITCODE"
Cette variable correspond approximativement au célèbre "ErrorLevel" utilisé en mode batch, et qui retourne la valeur "0" en l'absence d'erreur. - Faites toutefois attention à la portée et au caractère local de cette variable (scope)
En effet, si vous utilisez cette variable au sein d'une fonction, il est souhaitable de modifier la portée afin d'utiliser la valeur plus tard, comme par exemple :
function DemoExitCode { Param ($computer) $global:LASTEXITCODE = $null ping $computer > $null Write-Host "Ping sur $computer" # Write-Host "La valeur de LastExitCode est : $LASTEXITCODE" } DemoExitCode -computer localhost Write-Host "La valeur de LastExitCode est : $LASTEXITCODE" DemoExitCode -computer bidon Write-Host "La valeur de LastExitCode est : $LASTEXITCODE"
5 - Options au niveau des applets de commandes
Le dernier élément qui me semble pertinent à préciser, est l'usage des variables ponctuelles qui peuvent être mentionnées lors de l'appel d'une applet de commande.
En effet, chacune d'entre elles contient les 2 options suivantes :
Option | Alias d'option | Description |
-ErrorAction | -EA | Outrepasse ponctuellement le comportement indiqué dans la variable globale "$ErrorActionPreference" |
-ErrorVariable | -EV | Redirige l'erreur vers un objet dédié (pas $Error) - Indiquez un nom quelconque de variable sans le préfixe "$" |
B. Les instructions "Trap" et "Throw"
Historiquement, la première mouture de Powershell (v1) proposait une gestion d'erreur plutôt triviale avec les instructions "trap" et "throw" (un peu comme "raise" en vbs).
Grossièrement, le trap consiste à "préparer" le code à un traitement d'erreur, afin d'intercepter celle-ci puis effectuer une action en conséquence.
Exemple de fonction trap :
function Demo-Trap { trap {"Erreur détectée"; return;} 1/$null # on génère une erreur, on exécute le "trap" et on quitte la fonction via "return" ou le script via un "break" Write-Host "Fonction terminée." # Ce message ne s'affichera pas car une erreur sera détectée } Demo-Trap # on appelle la fonction Write-Host "Code Demo-trap terminé"
Exemple de fonction throw :
function Demo-Throw { param ($file) if (-not (Test-Path $file)) { Throw [System.IO.FileNotFoundException] "Le fichier $file est introuvable!." } } Demo-Throw -file c:\Foo.txt
Vous n'êtes pas obligé d'indiquer le type d'erreur entre crochet, dans le cas ou seul le message vous intéresse, mais l'erreur sera moins précise.(cf $error.CategoryInfo)
Note : Alternativement à l'instruction "Throw", il peut être préférable d'utiliser les applets de commande "write-error" ou "write-warning" avec éventuellement l'option "-ErrorAction". En effet, cette technique permet de gérer des erreurs que vous considérez "non critiques", alors que throw est plutôt réservé au traitement des erreurs "bloquantes". (à moins que vous indiquiez "write-error -ErrorAction stop" ce qui reviendrait approximativement au même résultat)
C. Les instructions "Try" ,"Catch" (et "Finally")
Depuis Powershell v2, l'instruction trap est de plus en plus rarement utilisée au profit des blocs de code "Try" et "Catch". Cette fois ci, on essaye un bloc de code via "Try", et en cas d'erreur on exécute les instructions contenues dans le(s) bloc(s) "Catch" suivant(s).
Le bloc "Finally" est optionnel et sera toujours exécuté.
Par exemple :
$array = @(3,0,1,2) $nb = 10 foreach ($item in $array) { try { $nb/$item | Out-Null Write-Host -Fore 'green' "Le chiffre $nb est divisible par $item" } catch { Write-Host -Fore 'red' "Erreur! Impossible de diviser $nb par $item !" } finally { Write-Host -Fore 'green' "Information - Le chiffre traité est $nb" } }
Voici un second exemple, exploitant certains concepts évoqués précédemment :
try { # on génère une erreur 1 + "ab" } catch { # on affiche uniquement le message d'erreur Write-Warning -Message "$($_.Exception.Message)" }
Vous aurez probablement remarqué que nous utilisons l'objet du pipeline "$_" correspondant de ce cas au canal d'erreur du bloc "try".
Remarque : Dans cet exemple, le bloc catch ci-dessus traite et s'applique à toutes les erreurs. Vous pouvez traiter uniquement certaines erreurs en indiquant le type .NET [System.Management.Automation....] en modifiant le code comme suit :
try { # génère une erreur gérée # Remove-Item "C:\fauxfichier.txt" -ErrorAction Stop # génère une erreur non gérée 1 + "ab" } catch [System.Management.Automation.ItemNotFoundException] { # Exemple de capture d'erreurs spécifiques # afin de personnaliser les actions en conséquence write-host "Une erreur de type [objet non trouvé] a été détectée" -ForegroundColor Magenta } Catch { # Traitement des autres erreurs write-host "Une erreur non gérée est survenue :`n $($Error[0])" -ForegroundColor Magenta }
Exécutez ce code en l'état puis décommentez (enlevez le "#") sur la 3ème ligne et exécutez-le de nouveau. Vous constaterez alors plusieurs choses : Seul le dernier bloc catch correspondant à l'erreur non gérée est traité. Au 2ème lancement, l'exception est traitée par le premier bloc catch correspondant au type d'erreur alors que le second catch est ignoré. Enfin, dès qu'une erreur survient, l’interpréteur "quitte" le bloc try sans exécuter les instructions suivantes.
Nous sommes malheureusement loin d'avoir exploré toutes les possibilités et subtilités en matière de gestion des erreurs sous Powershell, mais je pense qu'il y a déjà bien assez de matière pour un début.
IV. La gestion des couleurs
Après la lecture d'un sujet aussi sensible que la gestion des erreurs, je pense que nous méritons bien, un passage moins éprouvant pour l'esprit. J'ai donc pensé à vous présenter les moyens d'agrémenter l'affichage en vous présentant la gestion des couleurs dans Powershell.
A. Les constantes prédéfinies
Vous probablement déjà remarqué qu'il était très facile d'afficher un résultat en couleur avec l'applet de commande "Write-Host" suivie des commutateurs " -ForegroundColor" et/ou " -BackgroundColor", avec respectivement un nom de couleur en anglais. Ce que vous connaissez probablement moins, c'est d'où sortent ces constantes désignant ces couleurs, ainsi que les différentes valeurs supportées.
Pour afficher les constantes prédéfinies (utilisables dans la console) vous pouvez utiliser le code suivant :
[system.Consolecolor] | get-member -Static -MemberType Properties | select name
Ou plus simplement :
[system.consolecolor]::GetNames("consolecolor")
Ou bien encore :
[Enum]::GetValues([System.ConsoleColor])
Et pour le fun, voici une jolie fonction d'affichage des couleurs de base :
function Show-Colors( ) { $colors = [Enum]::GetValues( [ConsoleColor] ) $max = ($colors | foreach { "$_ ".Length } | Measure-Object -Maximum).Maximum foreach( $color in $colors ) { Write-Host (" {0,2} {1,$max} " -f [int]$color,$color) -NoNewline Write-Host "$color" -Foreground $color } } Show-Colors
B. Les couleurs par défaut
Vous avez la possibilité de modifier les couleurs par défaut affichées en fonction des différents types de message de console.
Pour cela, vous pouvez modifier les valeurs des variables suivantes :
Variable | Valeur par défaut |
$Host.UI.RawUI.ForegroundColor | = 'DarkYellow' |
$Host.UI.RawUI.BackgroundColor | = 'DarkMagenta' |
$Host.PrivateData.ErrorForegroundColor | = 'Red' |
$Host.PrivateData.ErrorBackgroundColor | = 'Black' |
$Host.PrivateData.WarningForegroundColor | = 'Yellow' |
$Host.PrivateData.WarningBackgroundColor | = 'Black' |
$Host.PrivateData.DebugForegroundColor | = 'Yellow' |
$Host.PrivateData.DebugBackgroundColor | = 'Black' |
$Host.PrivateData.VerboseForegroundColor | = 'Yellow' |
$Host.PrivateData.VerboseBackgroundColor | = 'Black' |
$Host.PrivateData.ProgressForegroundColor | = 'Yellow' |
$Host.PrivateData.ProgressBackgroundColor | = 'DarkCyan' |
Vous pouvez éventuellement renseigner ces valeurs dans le profil afin de conserver vos préférences pour chaque session Powershell.
C. Les couleurs avancées
Pour terminer cette petite présentation, sachez qu'il est possible de renseigner de nombreuses autres variantes de couleurs, basées sur des noms prédéfinis et/ou des codes ARGB. Cette fonctionnalité est essentiellement destinée aux formulaires et autres représentations graphiques. (PresentationFramework / Windows.Forms).
Voici une fonction permettant d'afficher le nom des couleurs prédéfinies et leur code ARGB en hexa.
# Merci à Learn-PowerShell
Function Get-ARGBColor { Param ([string[]]$Color) Begin { Add-Type –assemblyName PresentationFramework } Process { If (-Not $PSBoundParameters['Color']) { $Color = [windows.media.colors] | Get-Member -static -Type Property | Select -Expand Name } ForEach ($c in $color) { Try { $ARGB = [windows.media.color]$c New-Object PSObject -Property @{ ARGB = "$([windows.media.colors]::$c)" Color = $c } } Catch { Write-Warning "[$c] n'est pas un nom de couleur valide " } } } }
Pour utiliser cette fonction, il suffit de l'invoquer en indiquant le nom d'une couleur en paramètre :
Get-ARGBColor -Color magenta
Utilisée sans paramètre, la fonction vous renverra tous les noms de couleurs disponibles (+140 !)
Amusez-vous bien.
Powershellement votre.
Super Tuto, complet tout en étant clair et allant à l’essentiel, avec des exemples bien utiles.
Un grand MERCI!