Kurz: Ein typisches Admin-Skript: Logzeilen einlesen, nach Stufe und Tag auswerten und einen Bericht ausgeben.
Teil des Kurses PowerShell
Kapitel 7 von 8 im Kurs PowerShell (Abschnitt „Projekte“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.
Projekt 1: Logdatei-Report
Ein typisches Admin-Skript: Logzeilen einlesen, nach Stufe und Tag auswerten und einen Bericht ausgeben.
$log = @"
2025-03-01 10:00:01 INFO Dienst gestartet
2025-03-01 10:05:12 ERROR Datenbank nicht erreichbar
2025-03-01 10:06:40 WARN Wiederholungsversuch 1
2025-03-02 08:15:00 INFO Nutzer mia angemeldet
2025-03-02 09:30:21 ERROR Zeitüberschreitung bei /api/daten
2025-03-02 09:31:05 ERROR Zeitüberschreitung bei /api/daten
2025-03-03 12:00:00 INFO Nutzer tom angemeldet
"@ -split "`r?`n" | Where-Object { $_ }
$eintraege = foreach ($zeile in $log) {
if ($zeile -match '^(?<datum>\S+) (?<zeit>\S+) (?<stufe>\w+)\s+(?<text>.*)$') {
[pscustomobject]@{
Datum = [datetime]$Matches.datum
Stufe = $Matches.stufe
Text = $Matches.text
}
}
}
"Einträge gesamt: $($eintraege.Count)"
"--- pro Stufe"
$eintraege | Group-Object Stufe | Sort-Object Count -Descending | ForEach-Object { "{0,-6} {1}" -f $_.Name, $_.Count }
"--- Fehler pro Tag"
$eintraege | Where-Object Stufe -eq "ERROR" | Group-Object { $_.Datum.ToString("yyyy-MM-dd") } | ForEach-Object { "$($_.Name): $($_.Count)" }
"--- häufigste Fehlermeldung"
$eintraege | Where-Object Stufe -eq "ERROR" | Group-Object Text | Sort-Object Count -Descending | Select-Object -First 1 | ForEach-Object { "$($_.Name) ($($_.Count)x)" }
"--- Nutzer"
($eintraege | Where-Object Text -like "Nutzer*" | ForEach-Object { ($_.Text -split " ")[1] } | Sort-Object) -join ", "Ausgabe:
Einträge gesamt: 7
--- pro Stufe
ERROR 3
INFO 3
WARN 1
--- Fehler pro Tag
2025-03-01: 1
2025-03-02: 2
--- häufigste Fehlermeldung
Zeitüberschreitung bei /api/daten (2x)
--- Nutzer
mia, tomProjekt 2: Bestandsverwaltung mit Hashtable und CSV
$lager = @{}
$bestellungen = @(
@{ Artikel = "Schrauben"; Menge = 500; Preis = 0.12 }
@{ Artikel = "Muttern"; Menge = 300; Preis = 0.08 }
@{ Artikel = "Schrauben"; Menge = 250; Preis = 0.12 }
@{ Artikel = "Bohrer"; Menge = 12; Preis = 14.9 }
)
foreach ($b in $bestellungen) {
if (-not $lager.ContainsKey($b.Artikel)) { $lager[$b.Artikel] = [pscustomobject]@{ Artikel = $b.Artikel; Menge = 0; Wert = 0.0 } }
$lager[$b.Artikel].Menge += $b.Menge
$lager[$b.Artikel].Wert += $b.Menge * $b.Preis
}
$bericht = $lager.Values | Sort-Object Wert -Descending
$bericht | ForEach-Object { "{0,-10} {1,5} Stück {2,9:N2} EUR" -f $_.Artikel, $_.Menge, $_.Wert }
"Gesamtwert: {0:N2} EUR" -f ($bericht | Measure-Object Wert -Sum).Sum
$csv = $bericht | ConvertTo-Csv -NoTypeInformation
$csv.Count
($csv | ConvertFrom-Csv | Where-Object { [double]$_.Wert -gt 50 }).Artikel -join ", "Ausgabe:
Bohrer 12 Stück 178.80 EUR
Schrauben 750 Stück 90.00 EUR
Muttern 300 Stück 24.00 EUR
Gesamtwert: 292.80 EUR
4
Bohrer, SchraubenProjekt 3: Backup-Skript mit Rotation
function Invoke-Backup {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)][string]$Quelle,
[Parameter(Mandatory)][string]$Ziel,
[int]$Behalten = 3,
[string]$Stempel = (Get-Date -Format "yyyyMMdd-HHmmss")
)
if (-not (Test-Path $Quelle)) { throw "Quelle fehlt: $Quelle" }
New-Item -ItemType Directory -Path $Ziel -Force | Out-Null
$name = "backup-$Stempel"
$zielDatei = Join-Path $Ziel "$name.zip"
if ($PSCmdlet.ShouldProcess($zielDatei, "Sicherung erstellen")) {
Compress-Archive -Path (Join-Path $Quelle "*") -DestinationPath $zielDatei
}
$alte = Get-ChildItem $Ziel -Filter "backup-*.zip" | Sort-Object Name -Descending | Select-Object -Skip $Behalten
$alte | Remove-Item
"erstellt: $name.zip, gelöscht: $($alte.Count)"
}
$basis = Join-Path ([IO.Path]::GetTempPath()) "psbackup_$(Get-Random)"
$quelle = Join-Path $basis "daten"
$ziel = Join-Path $basis "sicherungen"
New-Item -ItemType Directory $quelle -Force | Out-Null
"wichtig" | Set-Content (Join-Path $quelle "a.txt")
1..5 | ForEach-Object { Invoke-Backup -Quelle $quelle -Ziel $ziel -Behalten 3 -Stempel "2025030$_" }
(Get-ChildItem $ziel | Sort-Object Name).Name
(Get-ChildItem $ziel).Count
try { Invoke-Backup -Quelle "/gibt/es/nicht" -Ziel $ziel } catch { "Fehler: $($_.Exception.Message)" }
Remove-Item $basis -Recurse -ForceAusgabe:
erstellt: backup-20250301.zip, gelöscht: 0
erstellt: backup-20250302.zip, gelöscht: 0
erstellt: backup-20250303.zip, gelöscht: 0
erstellt: backup-20250304.zip, gelöscht: 1
erstellt: backup-20250305.zip, gelöscht: 1
backup-20250303.zip
backup-20250304.zip
backup-20250305.zip
3
Fehler: Quelle fehlt: /gibt/es/nichtMit -WhatIf (z. B. Invoke-Backup -Quelle ... -Ziel ... -WhatIf) zeigt PowerShell nur an, was passieren würde. ShouldProcess und -WhatIf / -Confirm gehören zu jedem Skript, das etwas verändert: Nutzer können gefahrlos testen.
Projekt 4: Pester-Test für eine Funktion
Das Test-Framework Pester (in PowerShell enthalten oder per Install-Module Pester) prüft Funktionen:
BeforeAll {
function Get-Rabatt([double]$Preis, [int]$Prozent) { [math]::Round($Preis * (1 - $Prozent / 100), 2) }
}
Describe "Get-Rabatt" {
It "berechnet 10 Prozent Rabatt" {
Get-Rabatt -Preis 50 -Prozent 10 | Should -Be 45
}
It "gibt bei 0 Prozent den Originalpreis" {
Get-Rabatt 19.99 0 | Should -Be 19.99
}
It "wirft bei Text einen Fehler" {
{ Get-Rabatt "abc" 10 } | Should -Throw
}
}Ausführen mit Invoke-Pester. Schreibe Tests, bevor Skripte produktiv laufen.
Merke
- Regex mit benannten Gruppen (
$Matches.datum) macht Log-Auswertung einfach [pscustomobject]baut eigene Datensätze;Group-Object,Measure-Objectfassen zusammen[CmdletBinding(SupportsShouldProcess)]mit-WhatIffür sichere Skripte- Pester testet Funktionen
- Skripte lassen sich plattformübergreifend mit
pwshausführen
Übungsaufgabe
Schreibe ein Skript, das alle .log-Dateien eines Ordners nach Fehlerzeilen durchsucht und eine Zusammenfassung ausgibt.
Quiz zur Selbstkontrolle
Wofür ist -WhatIf?
- Trockenlauf: zeigt, was passieren würde (richtig)
- Löscht Dateien
- Macht das Skript schneller
- Zeigt Hilfe
Wofür ist Pester?
- Test-Framework für PowerShell (richtig)
- Paketmanager
- Editor
- Webserver
Wie baut man eigene Datensätze?
- [pscustomobject]@{ … } (richtig)
- new Record
- struct
- class nur
Weiter im Kurs
Zurück: Dateien, Text, Regex, Module und Remoting
Weiter: Referenz und Spickzettel
Alle Kapitel: PowerShell im Überblick