Tout ce que vous avez toujours voulu savoir sur l’instruction switch

À l’instar de nombreux autres langages, PowerShell a des commandes pour contrôler le flux d’exécution dans vos scripts. switch est une de ces instructions et, dans PowerShell, elle offre des fonctionnalités qui n’existent pas dans d’autres langages. Aujourd’hui, nous nous intéressons à l’utilisation de l’instruction switch de PowerShell.

Notes

La version originale de cet article est parue sur le blog écrit par @KevinMarquette. L’équipe PowerShell remercie Kevin d’avoir partagé ce contenu. Consultez son blog à l’adresse PowerShellExplained.com.

Instruction if

Une des premières instructions que vous apprenez est l’instruction if. Elle vous permet d’exécuter un bloc de script si une affirmation est $true.

if ( Test-Path $Path )
{
    Remove-Item $Path
}

Vous pouvez avoir une logique bien plus compliquée en utilisant les instructions elseif et else. Voici un exemple où j’ai une valeur numérique pour le jour de la semaine et où je veux obtenir le nom sous la forme d’une chaîne.

$day = 3

if ( $day -eq 0 ) { $result = 'Sunday'        }
elseif ( $day -eq 1 ) { $result = 'Monday'    }
elseif ( $day -eq 2 ) { $result = 'Tuesday'   }
elseif ( $day -eq 3 ) { $result = 'Wednesday' }
elseif ( $day -eq 4 ) { $result = 'Thursday'  }
elseif ( $day -eq 5 ) { $result = 'Friday'    }
elseif ( $day -eq 6 ) { $result = 'Saturday'  }

$result
Wednesday

Il s’avère qu’il s’agit d’un cas de figure courant et qu’il existe de nombreuses façons de le traiter. Une de ces façons est via une instruction switch.

Instruction switch

L’instruction switch vous permet de fournir une variable et une liste de valeurs possibles. Si la valeur correspond à la variable, son scriptblock est exécuté.

$day = 3

switch ( $day )
{
    0 { $result = 'Sunday'    }
    1 { $result = 'Monday'    }
    2 { $result = 'Tuesday'   }
    3 { $result = 'Wednesday' }
    4 { $result = 'Thursday'  }
    5 { $result = 'Friday'    }
    6 { $result = 'Saturday'  }
}

$result
'Wednesday'

Pour cet exemple, la valeur de $day correspond à une des valeurs numériques ; le nom correct est alors affecté à $result. Nous n’effectuons qu’une affectation de variable dans cet exemple, mais vous pouvez exécuter n’importe quelle instruction PowerShell dans ces blocs de script.

Affecter à une variable

Nous pouvons écrire ce dernier exemple d’une autre façon.

$result = switch ( $day )
{
    0 { 'Sunday'    }
    1 { 'Monday'    }
    2 { 'Tuesday'   }
    3 { 'Wednesday' }
    4 { 'Thursday'  }
    5 { 'Friday'    }
    6 { 'Saturday'  }
}

Nous plaçons la valeur sur le pipeline PowerShell et nous l’affectons à $result. Vous pouvez effectuer la même chose avec les instructions if et foreach.

Default

Nous pouvons utiliser le mot clé default pour identifier ce qui doit se produire s’il n’y a pas de correspondance.

$result = switch ( $day )
{
    0 { 'Sunday' }
    # ...
    6 { 'Saturday' }
    default { 'Unknown' }
}

Ici, nous retournons la valeur Unknown dans le cas « default ».

Chaînes

Dans ces derniers exemples, j’ai mis en correspondance des nombres, mais vous pouvez également faire correspondre des chaînes.

$item = 'Role'

switch ( $item )
{
    Component
    {
        'is a component'
    }
    Role
    {
        'is a role'
    }
    Location
    {
        'is a location'
    }
}
is a role

J’ai décidé de ne pas placer ici de guillemets autour des correspondances de Component,Role et Location, pour mettre en évidence le fait qu’ils sont facultatifs. switch les traite comme une chaîne dans la plupart des cas.

Tableaux

Une des fonctionnalités intéressantes de l'instruction switch de PowerShell est la façon dont elle traite les tableaux. Si vous donnez un tableau à switch, elle traite chaque élément de cette collection.

$roles = @('WEB','Database')

switch ( $roles ) {
    'Database'   { 'Configure SQL' }
    'WEB'        { 'Configure IIS' }
    'FileServer' { 'Configure Share' }
}
Configure IIS
Configure SQL

Si vous avez des éléments qui se répètent dans votre tableau, ils sont mis en correspondance plusieurs fois par la section appropriée.

PSItem

Vous pouvez utiliser $PSItem ou $_ pour référencer l’élément actif qui a été traité. Quand nous faisons une correspondance simple, $PSItem est la valeur que nous mettons en correspondance. Je ferai des correspondances avancées dans la section suivante où cette variable est utilisée.

Paramètres

Une fonctionnalité unique de l'instruction switch de PowerShell est qu’elle a plusieurs paramètres de commutateur qui modifient son fonctionnement.

-CaseSensitive

Par défaut, les correspondances ne sont pas sensibles à la casse. Si vous devez respecter la casse, vous pouvez utiliser -CaseSensitive. Ceci peut être utilisé en combinaison avec les autres paramètres de commutateur.

-Wildcard

Nous pouvons activer la prise en charge des caractères génériques avec le commutateur -wildcard. Ceci utilise la même logique de caractères génériques que l’opérateur -like pour établir chaque correspondance.

$Message = 'Warning, out of disk space'

switch -Wildcard ( $message )
{
    'Error*'
    {
        Write-Error -Message $Message
    }
    'Warning*'
    {
        Write-Warning -Message $Message
    }
    default
    {
        Write-Information $message
    }
}
WARNING: Warning, out of disk space

Ici, nous traitons un message, puis nous le plaçons sur des flux différents en fonction du contenu.

-Regex

L’instruction switch prend en charge les correspondances d’expressions régulières exactement comme les caractères génériques.

switch -Regex ( $message )
{
    '^Error'
    {
        Write-Error -Message $Message
    }
    '^Warning'
    {
        Write-Warning -Message $Message
    }
    default
    {
        Write-Information $message
    }
}

Il y a plus d’exemples d’utilisation d’expressions régulières dans un autre article que j’ai écrit : Les nombreuses façons d’utiliser les expressions régulières.

-File

Une fonctionnalité peu connue de l’instruction switch est qu’elle peut traiter un fichier avec le paramètre -File. Vous utilisez -file avec un chemin vers un fichier au lieu de lui donner une expression de variable.

switch -Wildcard -File $path
{
    'Error*'
    {
        Write-Error -Message $PSItem
    }
    'Warning*'
    {
        Write-Warning -Message $PSItem
    }
    default
    {
        Write-Output $PSItem
    }
}

Elle fonctionne de la même façon qu’elle traite un tableau. Dans cet exemple, je la combine avec la correspondance de caractères génériques et j’utilise $PSItem. Ceci va traiter un fichier journal et le convertir en messages d’avertissement et d’erreur en fonction des correspondances des expressions régulières.

Détails avancés

Maintenant que vous avez pris connaissance de toutes ces fonctionnalités documentées, nous pouvons les utiliser dans le contexte d’un traitement plus avancé.

Expressions

switch peut porter sur une expression au lieu d’une variable.

switch ( ( Get-Service | Where status -eq 'running' ).name ) {...}

Le résultat de l’évaluation de l’expression est la valeur utilisée pour la correspondance.

Correspondances multiples

Vous avez peut-être déjà rencontré cela, mais switch peut établir une correspondance avec plusieurs conditions. C’est particulièrement vrai lors de l’utilisation de correspondances -wildcard ou -regex. Vous pouvez ajouter la même condition plusieurs fois : dans ce cas, toutes sont déclenchées.

switch ( 'Word' )
{
    'word' { 'lower case word match' }
    'Word' { 'mixed case word match' }
    'WORD' { 'upper case word match' }
}
lower case word match
mixed case word match
upper case word match

Ces trois instructions sont déclenchées. Ceci montre que chaque condition est vérifiée (dans l’ordre). Ceci vaut aussi pour le traitement des tableaux où chaque élément vérifie chaque condition.

Continue

Normalement, ce serait le moment d’introduire l’instruction break, mais il est préférable de d’abord apprendre à utiliser continue. À l’instar d’une boucle foreach, continue poursuit sur l’élément suivant de la collection ou quitte l’instruction switch s’il n’y a plus d’éléments. Nous pouvons réécrire ce dernier exemple avec des instructions continue de façon à ce qu’une seule instruction s’exécute.

switch ( 'Word' )
{
    'word'
    {
        'lower case word match'
        continue
    }
    'Word'
    {
        'mixed case word match'
        continue
    }
    'WORD'
    {
        'upper case word match'
        continue
    }
}
lower case word match

Au lieu de mettre en correspondance les trois éléments, le premier est mis en correspondance et l’instruction switch continue à la valeur suivante. Comme il n’y a plus de valeurs à traiter, l’instruction switch se termine. L’exemple suivant montre comment un caractère générique peut correspondre à plusieurs éléments.

switch -Wildcard -File $path
{
    '*Error*'
    {
        Write-Error -Message $PSItem
        continue
    }
    '*Warning*'
    {
        Write-Warning -Message $PSItem
        continue
    }
    default
    {
        Write-Output $PSItem
    }
}

Comme une ligne du fichier d’entrée peut contenir à la fois le mot Error et le mot Warning, nous voulons seulement que le premier s’exécute, puis nous poursuivons le traitement du fichier.

Arrêter

Une instruction break quitte l’instruction switch. Il s’agit du même comportement que celui présenté par continue pour les valeurs uniques. La différence apparaît lors du traitement d’un tableau. break arrête tout traitement dans l’instruction switch et continue passe à l’élément suivant.

$Messages = @(
    'Downloading update'
    'Ran into errors downloading file'
    'Error: out of disk space'
    'Sending email'
    '...'
)

switch -Wildcard ($Messages)
{
    'Error*'
    {
        Write-Error -Message $PSItem
        break
    }
    '*Error*'
    {
        Write-Warning -Message $PSItem
        continue
    }
    '*Warning*'
    {
        Write-Warning -Message $PSItem
        continue
    }
    default
    {
        Write-Output $PSItem
    }
}
Downloading update
WARNING: Ran into errors downloading file
write-error -message $PSItem : Error: out of disk space
+ CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException

Dans le cas présent, si nous atteignons des lignes commençant par Error, nous obtenons une erreur et l’instruction switch s’arrête. C’est ce que l’instruction break fait pour nous. Si nous trouvons Error dans la chaîne et pas seulement au début, nous l’écrivons en tant qu’avertissement. Nous faisons la même chose pour Warning. Il est possible qu’une ligne ait à la fois le mot Error et le mot Warning, mais nous ne devons traiter qu’un seul de ces mots. C’est ce que fait l’instruction continue pour nous.

Étiquette de break

L’instruction switch prend en charge les étiquettes break/continue, tout comme foreach.

:filelist foreach($path in $logs)
{
    :logFile switch -Wildcard -File $path
    {
        'Error*'
        {
            Write-Error -Message $PSItem
            break filelist
        }
        'Warning*'
        {
            Write-Error -Message $PSItem
            break logFile
        }
        default
        {
            Write-Output $PSItem
        }
    }
}

Personnellement, je n’aime pas utiliser des étiquettes de break, mais je voulais en parler car elles induisent de la confusion si vous ne les avez jamais vues auparavant. Quand vous avez plusieurs instructions switch ou foreach imbriquées, vous pouvez souhaiter quitter plus que l’élément le plus intérieur. Vous pouvez placer une étiquette sur une instruction switch qui peut être la cible de votre break.

Enum

PowerShell 5.0 nous a apporté les énumérations et nous pouvons les utiliser dans une instruction switch.

enum Context {
    Component
    Role
    Location
}

$item = [Context]::Role

switch ( $item )
{
    Component
    {
        'is a component'
    }
    Role
    {
        'is a role'
    }
    Location
    {
        'is a location'
    }
}
is a role

Si vous voulez conserver tout sous forme d’énumérations fortement typées, vous pouvez les placer entre parenthèses.

switch ($item )
{
    ([Context]::Component)
    {
        'is a component'
    }
    ([Context]::Role)
    {
        'is a role'
    }
    ([Context]::Location)
    {
        'is a location'
    }
}

Les parenthèses sont nécessaires ici afin que l’instruction switch ne traite pas la valeur [Context]::Location comme chaîne littérale.

ScriptBlock

Nous pouvons utiliser un scriptblock pour effectuer si nécessaire l’évaluation d’une correspondance.

$age = 37

switch ( $age )
{
    {$PSItem -le 18}
    {
        'child'
    }
    {$PSItem -gt 18}
    {
        'adult'
    }
}
'adult'

Ceci ajoute de la complexité et peut rendre votre instruction switch difficile à lire. Dans la plupart des cas où vous utiliseriez quelque chose comme ceci, il serait préférable d’utiliser des instructions if et elseif. J’envisagerais de l’utiliser si j’avais déjà une grande instruction switch en place et que j’avais besoin de deux éléments pour atteindre le même bloc d’évaluation.

Une chose dont je pense qu’elle augmente la lisibilité est de placer le scriptblock entre parenthèses.

switch ( $age )
{
    ({$PSItem -le 18})
    {
        'child'
    }
    ({$PSItem -gt 18})
    {
        'adult'
    }
}

Il s’exécute pareillement et offre une meilleure décomposition visuelle quand vous le regardez rapidement.

$matches pour les expressions régulières

Nous devons revisiter regex pour expliquer quelque chose qui n’est pas immédiatement évident. L’utilisation de regex remplit la variable $matches. J’explique de façon plus détaillée l’utilisation de $matches quand je parle des Les nombreuses façons d’utiliser les expressions régulières. Voici un exemple rapide qui le montre en action avec des correspondances nommées.

$message = 'my ssn is 123-23-3456 and credit card: 1234-5678-1234-5678'

switch -regex ($message)
{
    '(?<SSN>\d\d\d-\d\d-\d\d\d\d)'
    {
        Write-Warning "message contains a SSN: $($matches.SSN)"
    }
    '(?<CC>\d\d\d\d-\d\d\d\d-\d\d\d\d-\d\d\d\d)'
    {
        Write-Warning "message contains a credit card number: $($matches.CC)"
    }
    '(?<Phone>\d\d\d-\d\d\d-\d\d\d\d)'
    {
        Write-Warning "message contains a phone number: $($matches.Phone)"
    }
}
WARNING: message may contain a SSN: 123-23-3456
WARNING: message may contain a credit card number: 1234-5678-1234-5678

$null

Vous pouvez mettre en correspondance une valeur de $null qui ne doit pas nécessairement être la valeur par défaut.

$values = '', 5, $null
switch ( $values )
{
    $null          { "Value '$_' is `$null" }
    { '' -eq $_ }  { "Value '$_' is an empty string" }
    default        { "Value [$_] isn't an empty string or `$null" }
}
Value '' is an empty string
Value [5] isn't an empty string or $null
Value '' is $null

Lors du test d’une chaîne vide dans une instruction switch, il est important d’utiliser l’instruction de comparaison comme indiqué dans cet exemple au lieu de la valeur brute ''. Dans une instruction switch, la valeur brute '' correspond également à $null. Par exemple :

$values = '', 5, $null
switch ( $values )
{
    $null          { "Value '$_' is `$null" }
    ''             { "Value '$_' is an empty string" }
    default        { "Value [$_] isn't an empty string or `$null" }
}
Value '' is an empty string
Value [5] isn't an empty string or $null
Value '' is $null
Value '' is an empty string

Faites également attention aux retours vides des applets de commande. Les applets de commande ou les pipelines qui n’ont pas de sortie sont traités comme des tableaux vides qui ne correspondent à rien, cas default compris.

$file = Get-ChildItem NonExistantFile*
switch ( $file )
{
    $null   { '$file is $null' }
    default { "`$file is type $($file.GetType().Name)" }
}
# No matches

Expression de constante

Lee Dailey a fait remarquer que nous pouvions utiliser une expression $true de constante pour évaluer des éléments [bool]. Imaginez si nous avons plusieurs vérifications booléennes qui doivent se produire.

$isVisible = $false
$isEnabled = $true
$isSecure = $true

switch ( $true )
{
    $isEnabled
    {
        'Do-Action'
    }
    $isVisible
    {
        'Show-Animation'
    }
    $isSecure
    {
        'Enable-AdminMenu'
    }
}
Do-Action
Enabled-AdminMenu

Il s’agit d’une méthode propre pour évaluer et entreprendre des actions selon l’état de plusieurs champs booléens. L’avantage est que vous pouvez faire en sorte qu’une correspondance inverse l’état d’une valeur qui n’a pas encore été évaluée.

$isVisible = $false
$isEnabled = $true
$isAdmin = $false

switch ( $true )
{
    $isEnabled
    {
        'Do-Action'
        $isVisible = $true
    }
    $isVisible
    {
        'Show-Animation'
    }
    $isAdmin
    {
        'Enable-AdminMenu'
    }
}
Do-Action
Show-Animation

Définir $isEnabled sur $true dans cet exemple permet de garantir que $isVisible est également défini sur $true. Ensuite, quand $isVisible est évalué, son scriptblock est appelé. C’est un peu contre-intuitif, mais c’est une utilisation intelligente de la mécanique.

Variable automatique $switch

Quand l’instruction switch traite ses valeurs, elle crée un énumérateur et l’appelle $switch. Il s’agit d’une variable automatique créée par PowerShell et vous pouvez la manipuler directement.

$a = 1, 2, 3, 4

switch($a) {
    1 { [void]$switch.MoveNext(); $switch.Current }
    3 { [void]$switch.MoveNext(); $switch.Current }
}

Vous obtenez les résultats suivants :

2
4

En déplaçant l’énumérateur vers l’avant, l’élément suivant n’est pas traité par l’instruction switch, mais vous pouvez accéder à cette valeur directement. Je pense que ce ne serait pas raisonnable.

Autres modèles

Tables de hachage

Une de mes publications les plus consultées est celle qui est consacrée aux tables de hachage. Un des cas d’usage d’une hashtable est d’en faire une table de recherche. Il s’agit d’une approche alternative à un modèle courant où une instruction switch est souvent utilisée.

$day = 3

$lookup = @{
    0 = 'Sunday'
    1 = 'Monday'
    2 = 'Tuesday'
    3 = 'Wednesday'
    4 = 'Thursday'
    5 = 'Friday'
    6 = 'Saturday'
}

$lookup[$day]
Wednesday

Si je constate que j’utilise une instruction switch seulement comme une structure pour la recherche, j’utilise alors souvent une hashtable à la place.

Enum

PowerShell 5.0 a introduit Enum et c’est également une option dans ce cas.

$day = 3

enum DayOfTheWeek {
    Sunday
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
}

[DayOfTheWeek]$day
Wednesday

Nous aurions pu rechercher pendant toute la journée différentes façons de résoudre ce problème. Je voulais simplement être sûr que vous sachiez qu’il y avait différentes options.

Le mot de la fin

L’instruction switch est simple en apparence, mais elle offre des fonctionnalités avancées dont la plupart des gens ne savent pas qu’elles sont disponibles. Combiner ensemble ces fonctionnalités transforme le tout en une puissante fonctionnalité. J’espère que vous avez appris des choses que vous ne connaissiez pas encore.