17/11/2024

PowerShell

Powershell pour les débutants (4ème partie)

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 :

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.

ps41

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é.

ps42

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.

author avatar
Christophe Mandin Consultant - Formateur indépendant
Consultant/Formateur indépendant en quête de solutions et de moyens alliant efficacement la théorie et la pratique. Fort d’une expérience de plusieurs dizaines années dans l’informatique, j’ai pu apprécier de nombreuses problématiques, développer des qualités rédactionnelles et un esprit de synthèse, tout en me forgeant de solides fondamentaux théoriques, indispensables à toute analyse et mise en œuvre fonctionnelle. Malgré toutes ces années, je ne me lasse pas du plaisir de transmettre mes connaissances en misant sur 3 critères que sont les fondamentaux, la simplicité et le pragmatisme. Bien à vous. Retrouvez-moi sur LinkedIn : Christophe Mandin
Partagez cet article Partager sur Twitter Partager sur Facebook Partager sur Linkedin Envoyer par mail

1 commentaire sur “Powershell pour les débutants (4ème partie)

  • Super Tuto, complet tout en étant clair et allant à l’essentiel, avec des exemples bien utiles.
    Un grand MERCI!

    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.