PowerShell hat sich über die Jahre erheblich weiterentwickelt und leistungsstarke Sprachfeatures eingeführt, die das Skripting effizienter und lesbarer machen. Egal ob Sie Systemadministrator, DevOps-Ingenieur oder Entwickler sind - die Beherrschung dieser Sprachkonstrukte wird Ihre PowerShell-Produktivität dramatisch verbessern.


Variablen und Datentypen

Grundlegende Variablenzuweisung

# Einfache Zuweisung
$name = "Max Mustermann"
$alter = 30
$istAktiv = $true

# Typ-Einschränkungen
[string]$benutzername = "admin"
[int]$anzahl = 0
[datetime]$startZeit = Get-Date

Arrays und Sammlungen

# Arrays
$server = @("web01", "web02", "db01")
$zahlen = 1..10

# Hash-Tabellen
$konfiguration = @{
    Server = "localhost"
    Port = 8080
    SSL = $true
}

# Auf Elemente zugreifen
Write-Host $server[0]                    # web01
Write-Host $konfiguration.Server         # localhost
Write-Host $konfiguration["Port"]        # 8080

Erweiterte Sammlungen

# ArrayList für dynamische Arrays
$liste = [System.Collections.ArrayList]@()
$liste.Add("element1") | Out-Null
$liste.AddRange(@("element2", "element3"))

# Geordnete Hash-Tabelle
$geordneteKonfig = [ordered]@{
    Erster = "wert1"
    Zweiter = "wert2"
}

Operatoren

Vergleichsoperatoren

# Grundlegende Vergleiche
$a -eq $b       # Gleich
$a -ne $b       # Ungleich
$a -gt $b       # Größer als
$a -lt $b       # Kleiner als
$a -ge $b       # Größer oder gleich
$a -le $b       # Kleiner oder gleich

# String-Operationen
$text -like "*muster*"         # Platzhalter-Abgleich
$text -match "regex"           # Regulärer Ausdruck
$text -contains "teilstring"   # Enthält-Prüfung
$text -in @("a", "b", "c")     # Mitgliedschaftstest

Logische Operatoren

# Logische Operationen
$bedingung1 -and $bedingung2
$bedingung1 -or $bedingung2
-not $bedingung

# Praktisches Beispiel
if (($alter -gt 18) -and ($hatFuehrerschein -eq $true)) {
    Write-Host "Darf Auto fahren"
}

Moderne Operatoren (PowerShell 7+)

# Ternärer Operator
$status = ($punkte -gt 80) ? "Bestanden" : "Durchgefallen"

# Null-Koaleszierende Operatoren
$benutzername = $env:USER ?? $env:USERNAME ?? "Unbekannt"

# Null-Koaleszierende Zuweisung
$konfiguration.Timeout ??= 30

Bedingte Logik

If-Else-Anweisungen

# Grundlegendes if-else
if ($punkte -gt 90) {
    Write-Host "Ausgezeichnet!"
} elseif ($punkte -gt 70) {
    Write-Host "Gute Arbeit!"
} else {
    Write-Host "Weiter üben!"
}

# Einzeiler-Bedingungen
if ($debug) { Write-Host "Debug-Modus aktiviert" }

Switch-Anweisungen

# Grundlegender Switch
switch ($status) {
    "Running" { Write-Host "Dienst ist aktiv" }
    "Stopped" { Write-Host "Dienst ist inaktiv" }
    "Paused"  { Write-Host "Dienst ist pausiert" }
    default   { Write-Host "Unbekannter Status" }
}

# Erweiterter Switch mit Regex
switch -Regex ($logEintrag) {
    "ERROR"   { Write-Host "Fehler gefunden" -ForegroundColor Red }
    "WARNING" { Write-Host "Warnung gefunden" -ForegroundColor Yellow }
    "INFO"    { Write-Host "Information protokolliert" }
}

Schleifen und Iteration

ForEach-Schleifen

# Traditionelle foreach
foreach ($server in $server) {
    Test-Connection -ComputerName $server -Count 1
}

# Pipeline foreach
$server | ForEach-Object {
    Write-Host "Verarbeite $_"
    Test-Connection -ComputerName $_ -Count 1
}

# Parallele Verarbeitung (PowerShell 7+)
$server | ForEach-Object -Parallel {
    Test-Connection -ComputerName $_ -Count 1
} -ThrottleLimit 5

For- und While-Schleifen

# For-Schleife
for ($i = 0; $i -lt 10; $i++) {
    Write-Host "Iteration $i"
}

# While-Schleife
$zaehler = 0
while ($zaehler -lt 5) {
    Write-Host "Zähler: $zaehler"
    $zaehler++
}

# Do-While-Schleife
do {
    $eingabe = Read-Host "Geben Sie 'beenden' ein zum Verlassen"
} while ($eingabe -ne "beenden")

Funktionen und Parameter

Grundlegende Funktionen

# Einfache Funktion
function Get-SystemInfo {
    $os = Get-CimInstance -ClassName Win32_OperatingSystem
    return @{
        Computername = $env:COMPUTERNAME
        Betriebssystem = $os.Caption
        FreierSpeicher = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
    }
}

# Funktion aufrufen
$info = Get-SystemInfo
Write-Host "Computer: $($info.Computername)"

Erweiterte Funktionen mit Parametern

function Test-DienstStatus {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]]$DienstName,
        
        [ValidateSet("Running", "Stopped", "All")]
        [string]$Status = "All",
        
        [switch]$Detailliert
    )
    
    process {
        foreach ($dienst in $DienstName) {
            $svc = Get-Service -Name $dienst -ErrorAction SilentlyContinue
            
            if ($svc) {
                if ($Status -eq "All" -or $svc.Status -eq $Status) {
                    if ($Detailliert) {
                        $svc | Select-Object Name, Status, StartType, DisplayName
                    } else {
                        "$($svc.Name): $($svc.Status)"
                    }
                }
            } else {
                Write-Warning "Dienst '$dienst' nicht gefunden"
            }
        }
    }
}

# Verwendungsbeispiele
Test-DienstStatus -DienstName "Spooler", "BITS"
"Spooler", "BITS" | Test-DienstStatus -Status Running -Detailliert

Fehlerbehandlung

Try-Catch-Finally

try {
    $inhalt = Get-Content -Path "C:\nichtvorhanden.txt" -ErrorAction Stop
    Write-Host "Datei erfolgreich gelesen"
} catch [System.IO.FileNotFoundException] {
    Write-Host "Datei nicht gefunden - erstelle neue Datei"
    New-Item -Path "C:\nichtvorhanden.txt" -ItemType File
} catch {
    Write-Host "Ein unerwarteter Fehler ist aufgetreten: $($_.Exception.Message)"
} finally {
    Write-Host "Aufräumarbeiten abgeschlossen"
}

Fehleraktions-Einstellungen

# Globale Fehlereinstellung setzen
$ErrorActionPreference = "Stop"

# Pro-Befehl Fehlerbehandlung
Get-Process -Name "Nichtvorhanden" -ErrorAction SilentlyContinue
Get-Service -Name "Ungueltig" -ErrorAction Ignore

# Fehler erfassen
Get-Process -Name "Nichtvorhanden" -ErrorAction SilentlyContinue -ErrorVariable meineFehler
if ($meineFehler) {
    Write-Host "Fehler aufgetreten: $($meineFehler.Count)"
}

Objektmanipulation

Benutzerdefinierte Objekte erstellen

# PSCustomObject (empfohlen)
$server = [PSCustomObject]@{
    Name = "WEB01"
    IP = "192.168.1.10"
    Rolle = "Webserver"
    LetzerNeustart = (Get-Date).AddDays(-7)
}

# Methoden zu Objekten hinzufügen
$server | Add-Member -MemberType ScriptMethod -Name "GetBetriebszeit" -Value {
    (Get-Date) - $this.LetzerNeustart
}

# Methode verwenden
$betriebszeit = $server.GetBetriebszeit()
Write-Host "Server-Betriebszeit: $($betriebszeit.Days) Tage"

Objektfilterung und -auswahl

# Laufende Dienste mit spezifischen Eigenschaften abrufen
$dienste = Get-Service | Where-Object {
    $_.Status -eq "Running" -and $_.Name -like "Win*"
} | Select-Object Name, Status, StartType

# Objekte gruppieren und sortieren
$prozesse = Get-Process | Group-Object ProcessName | 
    Sort-Object Count -Descending | Select-Object -First 5

# Objekte messen
$speicherVerbrauch = Get-Process | Measure-Object WorkingSet -Sum -Average
Write-Host "Gesamtspeicher: $([math]::Round($speicherVerbrauch.Sum / 1MB, 2)) MB"

Pipeline-Operationen

Pipeline-Grundlagen

# Befehle mit Pipeline verketten
Get-Service | 
    Where-Object Status -eq "Running" |
    Sort-Object Name |
    Select-Object Name, Status |
    Format-Table -AutoSize

# Pipeline-Variable $_
Get-ChildItem -Path C:\Logs -Filter "*.log" |
    ForEach-Object {
        $groesse = [math]::Round($_.Length / 1KB, 2)
        Write-Host "$($_.Name): ${groesse} KB"
    }

Erweiterte Pipeline-Techniken

# Tee-Object für Pipeline-Verzweigung
Get-Process |
    Tee-Object -FilePath "prozesse.txt" |
    Where-Object CPU -gt 100 |
    Sort-Object CPU -Descending

# Out-GridView für interaktive Filterung
Get-EventLog -LogName System -Newest 100 |
    Out-GridView -Title "Systemereignisse" -PassThru |
    Export-Csv -Path "ausgewaehlte_ereignisse.csv"

Klassen und Methoden

Klassen definieren

class Server {
    [string]$Name
    [string]$IP
    [string]$Rolle
    [datetime]$LetzerNeustart
    
    # Konstruktor
    Server([string]$name, [string]$ip, [string]$rolle) {
        $this.Name = $name
        $this.IP = $ip
        $this.Rolle = $rolle
        $this.LetzerNeustart = Get-Date
    }
    
    # Methoden
    [timespan]GetBetriebszeit() {
        return (Get-Date) - $this.LetzerNeustart
    }
    
    [void]Neustart() {
        Write-Host "Starte Server $($this.Name) neu..."
        $this.LetzerNeustart = Get-Date
    }
    
    [string]ToString() {
        return "$($this.Name) ($($this.IP)) - $($this.Rolle)"
    }
}

# Klasse verwenden
$webServer = [Server]::new("WEB01", "192.168.1.10", "Webserver")
Write-Host $webServer.ToString()
Write-Host "Betriebszeit: $($webServer.GetBetriebszeit().TotalHours) Stunden"

Vererbung

class DatenbankServer : Server {
    [string]$DatenbankEngine
    [int]$Port
    
    DatenbankServer([string]$name, [string]$ip, [string]$engine, [int]$port) : base($name, $ip, "Datenbank") {
        $this.DatenbankEngine = $engine
        $this.Port = $port
    }
    
    [void]DatenbankSichern() {
        Write-Host "Sichere $($this.DatenbankEngine) Datenbank auf $($this.Name)"
    }
}

$dbServer = [DatenbankServer]::new("DB01", "192.168.1.20", "SQL Server", 1433)
$dbServer.DatenbankSichern()

Moderne Sprachfeatures

Splatting

# Parameter-Splatting
$parameter = @{
    Path = "C:\Logs"
    Filter = "*.log"
    Recurse = $true
}
Get-ChildItem @parameter

# Array-Splatting
$argumente = @("notepad.exe", "C:\temp\datei.txt")
Start-Process @argumente

String-Interpolation und Here-Strings

# String-Interpolation
$name = "Hans"
$alter = 30
Write-Host "Hallo, $name! Du bist $alter Jahre alt."

# Unterausdrucks-Operator
Write-Host "Aktuelle Zeit: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"

# Here-Strings für mehrzeiligen Text
$skript = @"
Get-Service | Where-Object {
    $_.Status -eq "Running"
} | Select-Object Name, Status
"@

# Here-String als Skriptblock ausführen
Invoke-Expression $skript

Reguläre Ausdrücke

# Grundlegender Regex-Abgleich
$text = "Fehler: Verbindung fehlgeschlagen um 2024-01-15 10:30:25"
if ($text -match "(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})") {
    Write-Host "Zeitstempel gefunden: $($Matches[1])"
}

# Ersetzen mit Regex
$logEintrag = "FEHLER: Datenbankverbindung fehlgeschlagen"
$bereinigterEintrag = $logEintrag -replace "FEHLER:", "Warnung:"

# Aufteilen mit Regex
$daten = "apfel,banane;orange:traube"
$fruechte = $daten -split "[,;:]"

Pipeline-Ketten-Operatoren (PowerShell 7+)

# Erfolgskette (&&)
Get-Service -Name "Spooler" && Write-Host "Spooler-Dienst existiert"

# Fehlerkette (||)
Get-Service -Name "Nichtvorhanden" 2>$null || Write-Host "Dienst nicht gefunden"

# Ketten kombinieren
Test-Path "C:\temp" && Write-Host "Temp existiert" || Write-Host "Temp fehlt"

Praktische Beispiele

System-Überwachungsskript

function Get-SystemGesundheit {
    [CmdletBinding()]
    param(
        [string[]]$ComputerName = $env:COMPUTERNAME,
        [int]$CPUSchwellwert = 80,
        [int]$SpeicherSchwellwert = 90
    )
    
    $ergebnisse = foreach ($computer in $ComputerName) {
        try {
            # Systeminformationen abrufen
            $os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $computer
            $cpu = Get-CimInstance -ClassName Win32_Processor -ComputerName $computer
            
            # Metriken berechnen
            $speicherVerwendet = [math]::Round((($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize) * 100, 2)
            $cpuAuslastung = Get-Counter "\Processor(_Total)\% Processor Time" -ComputerName $computer -SampleInterval 1 -MaxSamples 1 |
                Select-Object -ExpandProperty CounterSamples |
                Select-Object -ExpandProperty CookedValue
            
            # Ergebnisobjekt erstellen
            [PSCustomObject]@{
                Computername = $computer
                CPUAuslastung = [math]::Round($cpuAuslastung, 2)
                SpeicherVerwendung = $speicherVerwendet
                Status = if ($cpuAuslastung -gt $CPUSchwellwert -or $speicherVerwendet -gt $SpeicherSchwellwert) { "Warnung" } else { "OK" }
                Zeitstempel = Get-Date
            }
        } catch {
            [PSCustomObject]@{
                Computername = $computer
                CPUAuslastung = "Fehler"
                SpeicherVerwendung = "Fehler"
                Status = "Nicht erreichbar"
                Zeitstempel = Get-Date
            }
        }
    }
    
    return $ergebnisse
}

# Verwendung
$gesundheitsPruefung = Get-SystemGesundheit -ComputerName @("localhost", "server01") -CPUSchwellwert 70
$gesundheitsPruefung | Format-Table -AutoSize

Log-Analyseskript

function Analyze-Logs {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$LogPfad,
        
        [string]$Muster = "FEHLER|WARNUNG|KRITISCH",
        
        [int]$LetzteStunden = 24
    )
    
    $stichzeit = (Get-Date).AddHours(-$LetzteStunden)
    
    Get-ChildItem -Path $LogPfad -Filter "*.log" |
        ForEach-Object {
            $inhalt = Get-Content -Path $_.FullName
            $treffer = $inhalt | Where-Object { $_ -match $Muster }
            
            if ($treffer) {
                [PSCustomObject]@{
                    LogDatei = $_.Name
                    FehlerAnzahl = ($treffer | Measure-Object).Count
                    Fehler = $treffer
                    LetztGeaendert = $_.LastWriteTime
                }
            }
        } |
        Where-Object { $_.LetztGeaendert -gt $stichzeit } |
        Sort-Object FehlerAnzahl -Descending
}

# Verwendung
$logAnalyse = Analyze-Logs -LogPfad "C:\Logs" -LetzteStunden 8
$logAnalyse | Select-Object LogDatei, FehlerAnzahl, LetztGeaendert | Format-Table

Best Practices und Tipps

Performance-Tipps

# ArrayList statt Array für häufige Hinzufügungen verwenden
$liste = [System.Collections.ArrayList]@()
# Anstatt: $array += $element (langsam)
$liste.Add($element) | Out-Null  # Viel schneller

# -contains statt -in für Einzelelement-Prüfungen verwenden
if ($server -contains $zielServer) { }  # Schneller
# Anstatt: if ($zielServer -in $server) { }

# Arrays vorab zuweisen wenn Größe bekannt ist
$ergebnisse = New-Object System.Object[] 1000

Code-Organisation

# Regionen für große Skripte verwenden
#region Hilfsfunktionen
function Get-Konfiguration {
    # Implementierung
}
#endregion

#region Hauptlogik
# Hauptskript-Logik hier
#endregion

Best Practices für Fehlerbehandlung

# Immer spezifische Fehlertypen verwenden wenn möglich
try {
    # Riskante Operation
} catch [System.UnauthorizedAccessException] {
    Write-Warning "Zugriff verweigert - Berechtigungen prüfen"
} catch [System.IO.FileNotFoundException] {
    Write-Error "Erforderliche Datei nicht gefunden"
} catch {
    Write-Error "Unerwarteter Fehler: $($_.Exception.Message)"
    throw  # Weiterwerfen wenn nicht behandelbar
}

Fazit

PowerShells Sprachfeatures bieten mächtige Werkzeuge für Automatisierung, Systemadministration und Entwicklungsaufgaben. Die modernen Features wie ternäre Operatoren, Null-Koaleszierende Operatoren und parallele Verarbeitung machen PowerShell konkurrenzfähig zu anderen Skriptsprachen, während die objektorientierte Natur und Pipeline-Philosophie erhalten bleiben.


Dieser Spickzettel deckt PowerShell 5.1 bis 7.5 Features ab. Einige erweiterte Features erfordern PowerShell 7+ und sind möglicherweise nicht in Windows PowerShell 5.1 verfügbar.