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, tom
Projekt 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, Schrauben
Projekt 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/nicht
Mit -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
Aufgabe
Schreibe ein Skript, das alle .log-Dateien eines Ordners nach Fehlerzeilen durchsucht und eine Zusammenfassung ausgibt.