Alles, was Sie schon immer über die switch-Anweisung wissen wollten

Wie viele andere Sprachen bietet PowerShell Befehle zur Ablaufsteuerung der Ausführung innerhalb Ihrer Skripts. Eine dieser Anweisungen ist die switch-Anweisung in PowerShell mit Features, die es in anderen Sprachen nicht gibt. Heute beschäftigen wir uns eingehend mit dem Arbeiten mit der PowerShell-Anweisung switch.

Hinweis

Die Originalversion dieses Artikels erschien ursprünglich in einem Blog von @KevinMarquette. Das PowerShell-Team dankt Kevin Marquette, dass er diesen Inhalt mit uns teilt. Weitere Informationen finden Sie in seinem Blog auf PowerShellExplained.com.

Die Anweisung if

Eine der ersten Anweisungen, die Sie kennenlernen, ist die if-Anweisung. Sie können einen Skriptblock ausführen, wenn eine Anweisung $true ist.

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

Eine viel kompliziertere Logik ist möglich, wenn Sie die Anweisungen elseif und else nutzen. Hier ist ein Beispiel, bei dem ich einen numerischen Wert für einen Wochentag habe und ich den Namen als Zeichenfolge abrufen möchte.

$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

Es stellt sich heraus, dass dies ein weit verbreitetes Muster ist und dass es viele Möglichkeiten gibt, damit umzugehen. Eine davon ist mit einer switch-Anweisung.

switch-Anweisung

Mit der switch-Anweisung können Sie eine Variable und eine Liste möglicher Werte angeben. Wenn der Wert mit der Variablen übereinstimmt, wird der zugehörige Skriptblock ausgeführt.

$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'

In diesem Beispiel entspricht der Wert von $day einem der numerischen Werte. Dann wird $result der richtige Name zugewiesen. Wir weisen in diesem Beispiel nur eine Variable zu, aber jeder beliebige PowerShell-Befehl kann in diesen Skriptblöcken ausgeführt werden.

Zuweisen zu einer Variablen

Dieses letzte Beispiel kann auf andere Weise geschrieben werden.

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

Wir legen den Wert in der PowerShell-Pipeline ab und weisen ihn $result zu. Dasselbe lässt sich mit den Anweisungen if und foreach realisieren.

Standard

Wir können das Schlüsselwort default nutzen, um zu bestimmen, was geschehen soll, wenn es keine Übereinstimmung gibt.

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

Hier geben wir im Standardfall den Wert Unknown zurück.

Zeichenfolgen

In den letzten Beispielen habe ich Zahlen auf Übereinstimmung abgeglichen, aber Sie können auch Zeichenfolgen abgleichen.

$item = 'Role'

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

Ich habe beschlossen, die Abgleiche auf Component, Role und Location hier nicht in Anführungszeichen zu setzen, um hervorzuheben, dass sie optional sind. switch behandelt diese in den meisten Fällen als Zeichenfolge.

Arrays

Eines der coolen Features der PowerShell-Anweisung switch ist die Handhabung von Arrays. Wenn Sie an switch ein Array übergeben, wird jedes Element in dieser Sammlung verarbeitet.

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

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

Wenn Ihr Array sich wiederholende Elemente enthält, werden diese im entsprechenden Abschnitt mehrfach abgeglichen.

PSItem

Sie können $PSItem oder $_ nutzen, um auf das aktuelle Element zu verweisen, das verarbeitet wurde. Wenn wir einen einfachen Abgleich vornehmen, ist $PSItem der Wert, den wir auf Übereinstimmung abgleichen. Im nächsten Abschnitt möchte ich einige komplexere Abgleiche durchführen, bei denen diese Variable zum Einsatz kommt.

Parameter

Ein besonderes Merkmal der PowerShell-Anweisung switch ist, dass sie über eine Reihe von switch-Parameter verfügt, die ihre Funktionsweise beeinflussen.

-CaseSensitive

Standardmäßig wird beim Abgleich Groß-/Kleinschreibung nicht beachtet. Wenn Sie Groß-/Kleinschreibung beachten müssen, können Sie -CaseSensitiveverwenden. Dies kann in Kombination mit den anderen Schalterparametern genutzt werden.

-Wildcard

Mit dem Schalter -wildcard können wir Platzhalterunterstützung aktivieren. Dabei wird für jeden Abgleich auf Übereinstimmung die gleiche Platzhalterlogik wie beim Operator -like genutzt.

$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

Hier verarbeiten wir eine Nachricht und geben sie dann je nach Inhalt in verschiedenen Datenströmen aus.

-Regex

Die switch-Anweisung unterstützt Übereinstimmungen mit regulären Ausdrücken (RegEx) ebenso wie Platzhalter.

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

Weitere Beispiele für die Nutzung regulärer Ausdrücke finden Sie in einem anderen Artikel von mir: The many ways to use regex (Die vielfältigen Verwendungsmöglichkeiten von regulären Ausdrücken)

-File

Ein wenig bekanntes Merkmal der switch-Anweisung ist, dass sie eine Datei mit dem Parameter -File verarbeiten kann. Sie verwenden -file mit einem Pfad zu einer Datei, anstatt dafür einen variablen Ausdruck anzugeben.

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

Dies funktioniert wie die Verarbeitung eines Arrays. In diesem Beispiel kombiniere ich dies mit dem Platzhalterabgleich und nutze $PSItem. Damit ließe sich eine Protokolldatei verarbeiten und in Abhängigkeit von Übereinstimmungen mit regulären Ausdrücken in Warn- und Fehlermeldungen umwandeln.

Details zur erweiterten Verarbeitung

Jetzt, da Ihnen all diese dokumentierten Merkmale bekannt sind, können wir sie im Rahmen einer komplexeren Verarbeitung nutzen.

Ausdrücke

switch kann für einen Ausdruck statt für eine Variable gelten.

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

Das Ergebnis des Ausdrucks ist der für den Abgleich auf Übereinstimmung genutzte Wert.

Mehrere Übereinstimmungen

Möglicherweise haben Sie dies bereits bemerkt, aber eine switch-Anweisung kann mit mehreren Bedingungen übereinstimmen. Dies gilt insbesondere, wenn der Abgleich mit -wildcard oder -regex erfolgt. Sie können dieselbe Bedingung mehrmals hinzufügen, und alle werden ausgelöst.

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

Alle drei dieser Anweisungen werden ausgelöst. Dies zeigt, dass jede Bedingung (der Reihe nach) geprüft wird. Dies gilt für die Verarbeitung von Arrays, bei der jedes Element jede Bedingung prüft.

Continue

Normalerweise würde ich an dieser Stelle die Anweisung break einführen, aber es ist besser, wenn wir zunächst erfahren, wie continue verwendet wird. Ebenso wie bei einer foreach-Schleife fährt continue mit dem nächsten Element in der Sammlung fort oder verlässt die switch-Anweisung, wenn es keine weiteren Elemente mehr gibt. Wir können das letzte Beispiel mit continue-Anweisungen so umschreiben, dass nur eine Anweisung ausgeführt wird.

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

Anstatt alle drei Elemente abzugleichen, wird das erste abgeglichen, und die switch-Anweisung fährt mit dem nächsten Wert fort. Da es keine weiteren Werte zu verarbeiten gibt, endet die switch-Anweisung. Dieses nächste Beispiel zeigt, wie ein Platzhalter mit mehreren Elementen übereinstimmen kann.

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

Da eine Zeile in der Eingabedatei sowohl das Wort Error als auch das Wort Warning enthalten könnte, möchten wir nur, dass das erste ausgeführt wird, und dann mit der Verarbeitung der Datei fortfahren.

Break

Eine break-Anweisung beendet die switch-Anweisung. Dies ist das gleiche Verhalten wie bei continue für einzelne Werte. Der Unterschied zeigt sich bei der Verarbeitung eines Arrays. break beendet die gesamte Verarbeitung in der switch-Anweisung, während continue zum nächsten Element übergeht.

$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

Wenn wir in diesem Fall auf Zeilen stoßen, die mit Error beginnen, erhalten wir einen Fehler, woraufhin die switch-Anweisung endet. Diese Aufgabe erledigt diese break-Anweisung für uns. Wenn wir Error innerhalb der Zeichenfolge und nicht nur am Anfang finden, schreiben wir dies als Warnung. Das Gleiche machen wir für Warning. Es ist möglich, dass eine Zeile die Wörter Error und Warning enthalten kann, aber wir müssen nur eines davon verarbeiten. Diese Aufgabe erledigt die continue-Anweisung für uns.

break-Bezeichnungen

Die switch-Anweisung unterstützt break/continue-Bezeichnungen wie 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
        }
    }
}

Mir persönlich gefallen break-Bezeichnungen nicht, aber ich möchte auf sie hinweisen, weil sie verwirrend sind, wenn Sie sie noch nie zuvor gesehen haben. Wenn Sie mehrere geschachtelte switch- oder foreach-Anweisungen haben, möchten Sie möglicherweise bei mehr als nur dem innersten Element unterbrechen. Sie können einer switch-Anweisung eine Bezeichnung zuweisen, die das Ziel Ihrer break-Anweisung sein kann.

Enum

Seit PowerShell 5.0 stehen uns Enumerationen zur Verfügung, die wir in einer switch-Anweisung nutzen können.

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

Wenn Sie alles als stark typisierte Enumerationen beibehalten möchten, können Sie sie in Klammern setzen.

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

Die Klammern werden hier benötigt, damit die switch-Anweisung den Wert [Context]::Location nicht als Literalzeichenfolge behandelt.

ScriptBlock

Bei Bedarf können wir einen Skriptblock verwenden, um die Auswertung für eine Übereinstimmung vorzunehmen.

$age = 37

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

Dies erhöht die Komplexität und kann Ihre switch-Anweisung schwer lesbar machen. In den meisten Fällen, in denen Sie so etwas verwenden würden, wäre es besser, mit den Anweisungen if und elseif zu arbeiten. Ich würde dies erwägen, wenn ich bereits eine große switch-Anweisung hätte und ich zwei Elemente bräuchte, um auf denselben Auswertungsblock zu treffen.

Ein Punkt, der meiner Meinung nach zur Lesbarkeit beiträgt, ist das Setzen des Skriptblocks in Klammern.

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

Er wird weiterhin auf die gleiche Weise ausgeführt und bietet beim schnellen Betrachten eine bessere visuelle Abgrenzung.

$matches für reguläre Ausdrücke

Wir müssen reguläre Ausdrücke wieder aufgreifen, um etwas anzusprechen, das nicht sofort offensichtlich ist. Bei Verwendung regulärer Ausdrücke wird die Variable $matches aufgefüllt. Ich gehe mehr auf die Verwendung von $matches ein, wenn ich auf The many ways to use regex eingehe. Hier ist ein kurzes Beispiel, um das Ganze mit benannten Übereinstimmungen in Aktion zu zeigen.

$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

Sie können einen $null-Wert abgleichen, der nicht der Standardwert sein muss.

$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

Wenn Sie auf eine leere Zeichenfolge in einer switch-Anweisung prüfen, ist es wichtig, die Vergleichsanweisung wie in diesem Beispiel zu verwenden, anstelle des unformatierten Werts ''. In einer switch-Anweisung entspricht der unformatierte ''-Wert auch $null. Beispiel:

$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

Seien Sie außerdem vorsichtig mit leeren Rückgaben von Cmdlets. Cmdlets oder Pipelines, die keine Ausgabe aufweisen, werden als leeres Array behandelt, das keine Übereinstimmung hat, einschließlich des default-Falls.

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

Konstanter Ausdruck

Lee Dailey hat darauf hingewiesen, dass wir einen Ausdruck, der konstant $true ist, nutzen können, um [bool]-Elemente auszuwerten. Stellen Sie sich vor, wir müssen mehrere Prüfungen boolescher Werte durchführen.

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

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

Dies ist eine einfache Möglichkeit, den Status mehrerer boolescher Felder auszuwerten und entsprechende Maßnahmen zu ergreifen. Das Besondere daran ist, dass Sie mit einer Übereinstimmung den Status eines noch nicht ausgewerteten Werts umdrehen können.

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

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

Das Festlegen von $isEnabled auf $true in diesem Beispiel stellt sicher, dass $isVisible auch auf $true festgelegt wird. Wenn $isVisible dann ausgewertet wird, wird sein Skriptblock aufgerufen. Das ist ein wenig widersinnig, aber eine geschickte Nutzung vorhandener Mechanismen.

$switch (automatische Variable)

Wenn switch seine Werte verarbeitet, wird ein Enumerator erstellt, der $switch genannt wird. Dies ist eine von PowerShell automatisch erstellte Variable, die Sie direkt verändern können.

$a = 1, 2, 3, 4

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

Dadurch erhalten Sie die folgenden Ergebnisse:

2
4

Indem Sie den Enumerator vorwärts bewegen, wird das nächste Element nicht durch switch verarbeitet, aber Sie können direkt auf diesen Wert zugreifen. Das würde ich Aberwitz nennen.

Andere Muster

Hashtabellen

Einer meiner beliebtesten Beiträge ist der zu Hashtabellen. Einer der Anwendungsfälle für eine hashtable soll eine Nachschlagetabelle sein. Das ist eine alternative Annäherung an ein gängiges Muster, mit dem sich eine switch-Anweisung oft befasst.

$day = 3

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

$lookup[$day]
Wednesday

Wenn ich eine switch-Anweisung nur zum Nachschlagen verwende, nutze ich stattdessen oft eine hashtable.

Enum

Seit PowerShell 5.0 gibt es Enum, was in diesem Fall auch eine Option ist.

$day = 3

enum DayOfTheWeek {
    Sunday
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
}

[DayOfTheWeek]$day
Wednesday

Wir könnten den ganzen Tag damit verbringen, verschiedene Wege zur Lösung dieses Problems zu finden. Ich wollte nur sichergehen, dass Sie wissen, welche Möglichkeiten Sie haben.

Schlusswort

Die switch-Anweisung ist oberflächlich betrachtet einfach, aber sie bietet einige erweiterte Merkmale, von denen die meisten nicht wissen, dass sie zur Verfügung stehen. Durch die Kombination dieser Merkmale steht Ihnen ein leistungsstarkes Instrument zur Verfügung. Ich hoffe, Sie haben etwas gelernt, was Sie vorher noch nicht wussten.