PowerShell-Skripte in der Praxis ── Protokollauswertung, Archivierung und Reporting sicher automatisieren
· Go Komura · PowerShell, Windows, Automatisierung, Protokollauswertung, Betriebsoptimierung, Nutzung vorhandener Ressourcen
1. Was zuerst wichtig ist
Im letzten Artikel haben wir die grundlegenden PowerShell-Befehle, die Pipeline, CSV-Ausgabe, JSON, .ps1-Skripte und die Sicherheitsprüfung mit -WhatIf behandelt. Diesmal bauen wir darauf aufbauend ein etwas größeres Skript, das sich gut im Praxisalltag einsetzen lässt. Als Thema dient folgende Betriebsaufgabe:
- Protokolle auswerten
- Fehlerzeilen in einer CSV zusammenfassen
- Alte Protokolle als Archivierungskandidaten auflisten
- Alte Protokolle bei Bedarf verschieben
- Das Ergebnis des Laufs als Nachweis aufbewahren
Bei der praktischen Anwendung von PowerShell kommt es nicht darauf an, Befehle länger zu machen, sondern den folgenden Ablauf zu etablieren:
- Konfiguration trennen
- Lesende Verarbeitung aufbauen
- Ausgaben aufbewahren
- Verändernde Verarbeitung in Funktionen auslagern
- Mit
-WhatIfeinen Probelauf durchführen - Zuletzt eine automatische Ausführung in Erwägung ziehen
Automatisierung bedeutet nicht, „die menschliche Prüfung wegzulassen“. Es bedeutet, festzulegen, an welcher Stelle geprüft wird, und dafür zu sorgen, dass bei jedem Lauf derselbe Nachweis entsteht.
Der in diesem Artikel gezeigte Code steht als direkt ausführbares Beispielpaket auf GitHub bereit (das fertige Skript, die Konfigurationsdatei, ein Skript zum Erzeugen einer Dummy-Protokollumgebung sowie Pester-Tests, die Auswertung, Probelauf und Verschieben prüfen).
powershell-script-log-maintenance-automation - komurasoft-blog-samples (GitHub)
2. Was wir diesmal bauen
Diesmal erstellen wir ein Skript namens Invoke-LogMaintenance.ps1.
Die wichtigsten Funktionen sind:
| Funktion | Inhalt |
|---|---|
| Protokollsuche | Sucht .log-Dateien unterhalb des angegebenen Ordners |
| Zeitraum | Untersucht nur Protokolle, die innerhalb der letzten N Tage aktualisiert wurden |
| Fehlerextraktion | Extrahiert Zeilen mit ERROR, WARN, FATAL und Ähnlichem |
| CSV-Ausgabe | Speichert die Suchergebnisse in log-hits.csv |
| Liste alter Protokolle | Speichert Protokolle, die älter als N Tage sind, in archive-targets.csv |
| Archivierung | Verschiebt alte Protokolle in einen separaten Ordner |
| Probelauf | Bei Angabe von -Preview wird nicht verschoben, sondern nur der geplante Vorgang angezeigt |
| Laufprotokoll | Speichert Transkript, zusammenfassendes JSON und Ergebnis-CSV |
Gelöscht wird dabei nichts. In diesem ersten vertiefenden Teil beschränken wir uns auf das sicherere „Verschieben“ statt auf ein Löschen.
3. Ordnerstruktur
Als Beispiel verwenden wir folgende Struktur.
C:\Ops
Invoke-LogMaintenance.ps1
log-maintenance.json
C:\App\Logs
app.log
batch.log
old
app-202401.log
C:\App\Reports
20260602-030000
log-hits.csv
archive-targets.csv
archive-result.csv
summary.json
transcript.txt
C:\App\Archive
20260602-030000
old
app-202401.log
Wenn Sie das eigentliche Skript und die Konfigurationsdatei getrennt halten, lässt sich der Austausch je Umgebung leicht bewerkstelligen. So können Sie etwa in der Entwicklungsumgebung C:\Test\Logs und in der Produktion D:\App\Logs verwenden, indem Sie nur den Pfad ändern.
4. Die Konfigurationsdatei anlegen
Zunächst erstellen wir log-maintenance.json.
{
"LogPath": "C:\\App\\Logs",
"OutputPath": "C:\\App\\Reports",
"Days": 7,
"Patterns": [
"ERROR",
"WARN",
"FATAL"
],
"ArchiveDays": 90,
"ArchivePath": "C:\\App\\Archive"
}
Die Bedeutung der Felder ist wie folgt.
| Feld | Bedeutung |
|---|---|
LogPath |
Der zu untersuchende Protokollordner |
OutputPath |
Ziel für die Reports |
Days |
Wie viele der letzten Tage an Protokollen untersucht werden |
Patterns |
Die zu suchenden Zeichenketten/Muster |
ArchiveDays |
Ab welchem Alter ein Protokoll als Archivierungskandidat gilt |
ArchivePath |
Der Zielordner für die Archivierung |
Durch die Auslagerung nach JSON lassen sich die Bedingungen ändern, ohne das eigentliche Skript zu bearbeiten.
In PowerShell können Sie JSON mit ConvertFrom-Json als Objekt behandeln. Umgekehrt verwenden Sie ConvertTo-Json, um Verarbeitungsergebnisse als JSON abzulegen. ConvertTo-Json ist das Cmdlet, das Objekte in JSON-Zeichenketten umwandelt; bei tief verschachtelten Strukturen ist die Angabe von -Depth wichtig.
5. Zunächst nur den lesenden Teil bauen
Schreiben Sie nicht sofort die Verschiebeverarbeitung, sondern beginnen Sie damit, Protokolle zu finden und in eine CSV zu überführen.
$config = Get-Content .\log-maintenance.json -Raw -Encoding UTF8 | ConvertFrom-Json
$since = (Get-Date).AddDays(-[int]$config.Days)
$files = Get-ChildItem -LiteralPath $config.LogPath -Filter *.log -File -Recurse |
Where-Object { $_.LastWriteTime -ge $since }
$files |
Select-Object FullName, Length, LastWriteTime
Als Nächstes durchsuchen wir den Inhalt der Protokolle.
$patterns = [string[]]$config.Patterns
Select-String -LiteralPath ($files | Select-Object -ExpandProperty FullName) -Pattern $patterns |
Select-Object Path, LineNumber, Pattern, Line |
Export-Csv .\log-hits.csv -NoTypeInformation -Encoding UTF8
Bis hierhin handelt es sich ausschließlich um lesende Vorgänge. Auch wenn Sie dies im Produktivordner ausprobieren, halten Sie zunächst an dieser Stelle an.
6. Alte Protokolle auflisten
Als Nächstes listen wir die Archivierungskandidaten auf.
$limit = (Get-Date).AddDays(-[int]$config.ArchiveDays)
$targets = Get-ChildItem -LiteralPath $config.LogPath -Filter *.log -File -Recurse |
Where-Object { $_.LastWriteTime -lt $limit } |
Sort-Object LastWriteTime
$targets |
Select-Object FullName, Length, LastWriteTime |
Export-Csv .\archive-targets.csv -NoTypeInformation -Encoding UTF8
Auch in diesem Schritt wird noch nichts verschoben. Prüfen Sie anhand von archive-targets.csv, ob die Kandidaten nicht zu zahlreich sind und der Ordner stimmt.
7. Verändernde Verarbeitung in Funktionen auslagern
Trennen Sie verändernde Vorgänge wie das Verschieben von der lesenden Verarbeitung.
In PowerShell können Sie eine Funktion mit -WhatIf und -Confirm ausstatten, indem Sie ihr SupportsShouldProcess hinzufügen. -WhatIf zeigt, ohne auszuführen, an, „was geändert würde“, und -Confirm ist der Mechanismus, der vor der Ausführung eine Bestätigung einholt. Details dazu finden Sie in about_Functions_CmdletBindingAttribute auf Microsoft Learn.
function Move-OldLogFile {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
param(
[Parameter(Mandatory)]
[System.IO.FileInfo[]]$File,
[Parameter(Mandatory)]
[string]$SourceRoot,
[Parameter(Mandatory)]
[string]$ArchiveRoot
)
foreach ($item in $File) {
$relativePath = [System.IO.Path]::GetRelativePath($SourceRoot, $item.FullName)
$destination = Join-Path $ArchiveRoot $relativePath
$destinationDirectory = Split-Path -Path $destination -Parent
if ($PSCmdlet.ShouldProcess($item.FullName, "Move to $destination")) {
if (-not [System.IO.Directory]::Exists($destinationDirectory)) {
[System.IO.Directory]::CreateDirectory($destinationDirectory) | Out-Null
}
Move-Item -LiteralPath $item.FullName -Destination $destination -ErrorAction Stop
[pscustomobject]@{
Source = $item.FullName
Destination = $destination
Status = "Moved"
Message = ""
}
}
else {
[pscustomobject]@{
Source = $item.FullName
Destination = $destination
Status = "Preview"
Message = ""
}
}
}
}
Entscheidend ist, dass $PSCmdlet.ShouldProcess() unmittelbar vor Move-Item steht. Die Entscheidung, ob geändert wird, wird nicht außerhalb der Funktion getroffen, sondern unmittelbar vor der Änderung selbst.
8. Die fertige Version des Skripts
Hier ist die fertige Version, die alles bisher Gezeigte zusammenführt. Der Dateiname lautet Invoke-LogMaintenance.ps1.
# Invoke-LogMaintenance.ps1
#Requires -Version 7.0
[CmdletBinding()]
param(
[ValidateNotNullOrEmpty()]
[string]$ConfigPath = ".\log-maintenance.json",
[switch]$Preview,
[switch]$SkipArchive,
[switch]$SkipTranscript
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Ensure-Directory {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path
)
if (-not [System.IO.Directory]::Exists($Path)) {
[System.IO.Directory]::CreateDirectory($Path) | Out-Null
}
}
function Import-LogMaintenanceConfig {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path
)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Config file not found: $Path"
}
$config = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json
foreach ($name in @("LogPath", "OutputPath", "Days", "Patterns", "ArchiveDays", "ArchivePath")) {
if (-not ($config.PSObject.Properties.Name -contains $name)) {
throw "Config value missing: $name"
}
}
if ([string]::IsNullOrWhiteSpace([string]$config.LogPath)) {
throw "LogPath is empty."
}
if (-not (Test-Path -LiteralPath $config.LogPath)) {
throw "LogPath not found: $($config.LogPath)"
}
if ([string]::IsNullOrWhiteSpace([string]$config.OutputPath)) {
throw "OutputPath is empty."
}
if ([string]::IsNullOrWhiteSpace([string]$config.ArchivePath)) {
throw "ArchivePath is empty."
}
if (@($config.Patterns).Count -eq 0) {
throw "Patterns is empty."
}
if ([int]$config.Days -lt 1) {
throw "Days must be 1 or greater."
}
if ([int]$config.ArchiveDays -lt 1) {
throw "ArchiveDays must be 1 or greater."
}
return $config
}
function Export-CsvWithHeader {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[object[]]$InputObject,
[Parameter(Mandatory)]
[string]$Path,
[Parameter(Mandatory)]
[string[]]$Header
)
if ($InputObject.Count -gt 0) {
$InputObject |
Export-Csv -LiteralPath $Path -NoTypeInformation -Encoding UTF8
}
else {
($Header -join ",") |
Set-Content -LiteralPath $Path -Encoding UTF8
}
}
function Get-LogHit {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$LogPath,
[Parameter(Mandatory)]
[ValidateRange(1, 3650)]
[int]$Days,
[Parameter(Mandatory)]
[string[]]$Pattern
)
$since = (Get-Date).AddDays(-$Days)
$files = @(
Get-ChildItem -LiteralPath $LogPath -Filter *.log -File -Recurse -ErrorAction Stop |
Where-Object { $_.LastWriteTime -ge $since }
)
Write-Verbose "Recent log files: $($files.Count)"
if ($files.Count -eq 0) {
return @()
}
$paths = $files | Select-Object -ExpandProperty FullName
Select-String -LiteralPath $paths -Pattern $Pattern -ErrorAction Stop |
ForEach-Object {
[pscustomobject]@{
Path = $_.Path
LineNumber = $_.LineNumber
Pattern = $_.Pattern
Line = $_.Line.Trim()
}
}
}
function Get-OldLogFile {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$LogPath,
[Parameter(Mandatory)]
[ValidateRange(1, 3650)]
[int]$ArchiveDays
)
$limit = (Get-Date).AddDays(-$ArchiveDays)
Get-ChildItem -LiteralPath $LogPath -Filter *.log -File -Recurse -ErrorAction Stop |
Where-Object { $_.LastWriteTime -lt $limit } |
Sort-Object LastWriteTime
}
function Move-OldLogFile {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "Medium")]
param(
[Parameter(Mandatory)]
[System.IO.FileInfo[]]$File,
[Parameter(Mandatory)]
[string]$SourceRoot,
[Parameter(Mandatory)]
[string]$ArchiveRoot
)
foreach ($item in $File) {
$relativePath = [System.IO.Path]::GetRelativePath($SourceRoot, $item.FullName)
$destination = Join-Path $ArchiveRoot $relativePath
$destinationDirectory = Split-Path -Path $destination -Parent
if (Test-Path -LiteralPath $destination) {
$name = [System.IO.Path]::GetFileNameWithoutExtension($item.Name)
$ext = $item.Extension
$destination = Join-Path $destinationDirectory ("{0}_{1:yyyyMMddHHmmss}{2}" -f $name, $item.LastWriteTime, $ext)
}
if ($PSCmdlet.ShouldProcess($item.FullName, "Move to $destination")) {
Ensure-Directory -Path $destinationDirectory
Move-Item -LiteralPath $item.FullName -Destination $destination -ErrorAction Stop
[pscustomobject]@{
Source = $item.FullName
Destination = $destination
Status = "Moved"
Message = ""
}
}
else {
[pscustomobject]@{
Source = $item.FullName
Destination = $destination
Status = "Preview"
Message = ""
}
}
}
}
$config = Import-LogMaintenanceConfig -Path $ConfigPath
$runStamp = Get-Date -Format "yyyyMMdd-HHmmss"
$reportDir = Join-Path ([string]$config.OutputPath) $runStamp
Ensure-Directory -Path $reportDir
$transcriptStarted = $false
$transcriptPath = Join-Path $reportDir "transcript.txt"
try {
if (-not $SkipTranscript) {
Start-Transcript -Path $transcriptPath -Force | Out-Null
$transcriptStarted = $true
}
Write-Host "Report directory: $reportDir"
$hits = @(
Get-LogHit `
-LogPath ([string]$config.LogPath) `
-Days ([int]$config.Days) `
-Pattern ([string[]]$config.Patterns)
)
$hitCsv = Join-Path $reportDir "log-hits.csv"
Export-CsvWithHeader `
-InputObject $hits `
-Path $hitCsv `
-Header @("Path", "LineNumber", "Pattern", "Line")
$oldFiles = @(
Get-OldLogFile `
-LogPath ([string]$config.LogPath) `
-ArchiveDays ([int]$config.ArchiveDays)
)
$archiveTargets = @(
$oldFiles |
Select-Object FullName, Length, LastWriteTime
)
$archiveTargetCsv = Join-Path $reportDir "archive-targets.csv"
Export-CsvWithHeader `
-InputObject $archiveTargets `
-Path $archiveTargetCsv `
-Header @("FullName", "Length", "LastWriteTime")
$moveResults = @()
if ($SkipArchive) {
Write-Host "Archive skipped."
}
elseif ($oldFiles.Count -eq 0) {
Write-Host "No archive targets."
}
else {
$archiveRunRoot = Join-Path ([string]$config.ArchivePath) $runStamp
$moveResults = @(
Move-OldLogFile `
-File $oldFiles `
-SourceRoot ([string]$config.LogPath) `
-ArchiveRoot $archiveRunRoot `
-WhatIf:$Preview
)
}
$archiveResultCsv = Join-Path $reportDir "archive-result.csv"
Export-CsvWithHeader `
-InputObject $moveResults `
-Path $archiveResultCsv `
-Header @("Source", "Destination", "Status", "Message")
$summary = [pscustomobject]@{
CheckedAt = (Get-Date).ToString("s")
ComputerName = $env:COMPUTERNAME
LogPath = [string]$config.LogPath
ReportDirectory = $reportDir
HitCount = $hits.Count
ArchiveTargetCount = $oldFiles.Count
ArchiveResultCount = $moveResults.Count
Preview = [bool]$Preview
SkipArchive = [bool]$SkipArchive
}
$summaryPath = Join-Path $reportDir "summary.json"
$summary |
ConvertTo-Json -Depth 5 |
Set-Content -LiteralPath $summaryPath -Encoding UTF8
Write-Host "Finished."
Write-Host "Hits: $($hits.Count)"
Write-Host "Archive targets: $($oldFiles.Count)"
}
catch {
$errorPath = Join-Path $reportDir "error.txt"
$_ | Out-String | Set-Content -LiteralPath $errorPath -Encoding UTF8
Write-Error "Failed: $($_.Exception.Message)"
exit 1
}
finally {
if ($transcriptStarted) {
Stop-Transcript | Out-Null
}
}
9. Beispielaufrufe
Zunächst führen wir nur die Protokollauswertung aus, ohne die Archivierung durchzuführen.
.\Invoke-LogMaintenance.ps1 -ConfigPath .\log-maintenance.json -SkipArchive
Als Nächstes prüfen wir die geplante Archivierung.
.\Invoke-LogMaintenance.ps1 -ConfigPath .\log-maintenance.json -Preview
Mit -Preview wird die Verschiebeverarbeitung für alte Protokolle als -WhatIf behandelt.
An dieser Stelle sind drei Dateien zu prüfen:
log-hits.csvarchive-targets.csvarchive-result.csv
Wenn alles unauffällig ist, führen Sie das Skript ohne -Preview aus.
.\Invoke-LogMaintenance.ps1 -ConfigPath .\log-maintenance.json
Für ausführlichere Ausgaben fügen Sie -Verbose hinzu.
.\Invoke-LogMaintenance.ps1 -ConfigPath .\log-maintenance.json -Preview -Verbose
10. Die erzeugten Ausgabedateien
Beim Ausführen wird unterhalb von OutputPath ein zeitgestempelter Ordner angelegt, etwa C:\App\Reports\20260602-030000.
Darin werden folgende Dateien erzeugt.
| Datei | Inhalt |
|---|---|
log-hits.csv |
Als Fehler oder Warnung erkannte Zeilen |
archive-targets.csv |
Alte Protokolle, die zu Archivierungskandidaten wurden |
archive-result.csv |
Ergebnis der Verschiebung bzw. das Preview-Ergebnis |
summary.json |
Zusammenfassung von Anzahlen und Ausführungsbedingungen |
transcript.txt |
Aufzeichnung der PowerShell-Sitzung |
error.txt |
Details im Fehlerfall |
Start-Transcript ist das Cmdlet, mit dem Befehle und Konsolenausgaben einer PowerShell-Sitzung in einer Textdatei aufgezeichnet werden. Bei Betriebsskripten erleichtert dies im Nachhinein die Feststellung, „wann unter welchen Bedingungen was ausgegeben wurde“.
11. Regelmäßige Ausführung über die Aufgabenplanung
Wenn die manuelle Ausführung unauffällig verläuft, lässt sich das Skript über die Aufgabenplanung regelmäßig ausführen.
Hier ein Beispiel, das zunächst täglich um 3 Uhr nachts läuft.
$scriptPath = "C:\Ops\Invoke-LogMaintenance.ps1"
$configPath = "C:\Ops\log-maintenance.json"
$action = New-ScheduledTaskAction `
-Execute "pwsh.exe" `
-Argument "-NoProfile -File `"$scriptPath`" -ConfigPath `"$configPath`"" `
-WorkingDirectory "C:\Ops"
$trigger = New-ScheduledTaskTrigger -Daily -At 3:00
Register-ScheduledTask `
-TaskName "AppLogMaintenance" `
-Action $action `
-Trigger $trigger `
-Description "Collect app log errors and archive old logs"
New-ScheduledTaskAction erzeugt ein Objekt, das den von der Aufgabe auszuführenden Befehl darstellt, und New-ScheduledTaskTrigger legt die Startbedingungen fest – täglich, wöchentlich, bei Anmeldung und Ähnliches. Zuletzt registriert Register-ScheduledTask die Aufgabe auf dem lokalen Computer.
Im Produktivbetrieb sollten Sie zusätzlich Folgendes prüfen:
- Der Ausführungsbenutzer hat Leserechte auf den Protokollordner
- Es besteht Schreibzugriff auf das Archivierungsziel
- Der Pfad zu
pwsh.exeist auflösbar - Die Ausführungsrichtlinie und die Signaturregeln des Skripts werden eingehalten
- Manuelle Ausführung und Ausführung über die Aufgabe liefern dasselbe Ergebnis
- Im Fehlerfall lassen sich
error.txtund der Aufgabenverlauf einsehen
12. Häufige Stolperfallen
| Symptom | Ursache | Abhilfe |
|---|---|---|
| Protokolle werden nicht gefunden | LogPath ist falsch |
Mit Test-Path und Get-ChildItem prüfen |
| CSV bleibt leer | Im gewählten Zeitraum liegen keine passenden Protokolle vor | Days vergrößern und erneut prüfen |
| Zeichen werden falsch dargestellt | Die Zeichenkodierung des Protokolls weicht von der Annahme ab | Ein- und Ausgabekodierung überprüfen |
| Läuft nicht als Aufgabe | Abweichender Ausführungsbenutzer oder Arbeitsordner | WorkingDirectory und Berechtigungen prüfen |
| Zu viele Archivierungskandidaten | ArchiveDays ist zu kurz gewählt |
archive-targets.csv ansehen und anpassen |
| Zielort weicht von der Erwartung ab | Die Regel zur Beibehaltung relativer Pfade ist nicht bekannt | Destination mit -Preview prüfen |
| Fehler nur in der Produktion | Unterschiede bei Berechtigungen, Richtlinien oder gesperrten Dateien | error.txt und transcript.txt prüfen |
Insbesondere beim Ausführen über die Aufgabenplanung kann sich „der Benutzer, unter dem Sie selbst ausführen“ vom „Ausführungsbenutzer der Aufgabe“ unterscheiden. Läuft das Skript manuell, schlägt aber als Aufgabe fehl, sollten Sie zuerst Berechtigungen und den Arbeitsordner in Verdacht haben.
13. Überlegungen für Anpassungen
Dieses Skript ist eher als Ausgangspunkt gedacht, den Sie Schritt für Schritt an Ihre Umgebung anpassen, statt es unverändert einzusetzen. Hier einige typische Anpassungen.
| Ziel | Anpassungsstelle |
|---|---|
Auch .txt-Dateien einbeziehen |
Get-ChildItem -Filter *.log ändern |
Auch einige Zeilen um ERROR herum sehen |
Anhand des Select-String-Ergebnisses mit Get-Content die umliegenden Zeilen abrufen |
| Vor der Archivierung komprimieren | Compress-Archive vor Move-OldLogFile einfügen |
| Per E-Mail benachrichtigen | Anhand von summary.json eine Benachrichtigungsverarbeitung ergänzen |
| Konfiguration je Anwendung trennen | Mehrere JSON-Dateien anlegen und die Aufgaben entsprechend aufteilen |
| Auch das Löschen automatisieren | Zunächst über einen gewissen Zeitraum mit dem Verschiebe-Workflow arbeiten und erst danach entscheiden |
Es ist jedoch sicherer, nicht von Anfang an alles einzubauen. Bei Betriebsskripten zählt weniger die Anzahl der Funktionen als vielmehr, im Fehlerfall nachvollziehen zu können, was passiert ist.
14. Betriebs-Checkliste für den Praxiseinsatz
Prüfen Sie vor der regelmäßigen Ausführung eines PowerShell-Skripts Folgendes:
- Zunächst mit
-SkipArchivenur die lesende Verarbeitung ausgeführt - Anschließend mit
-Previewdie geplante Verschiebung geprüft - Die Kandidaten in
archive-targets.csvwaren plausibel - Die
Destination-Werte inarchive-result.csventsprachen den Erwartungen - Im Ausgabeordner blieb ein zeitgestempelter Nachweis erhalten
- Bestätigt, dass im Fehlerfall
error.txterzeugt wird - Die Berechtigungen des Ausführungsbenutzers der Aufgabe geprüft
- Ausführungsrichtlinie, Signatur und interne Vorgaben geprüft
- Nicht sofort löschen, sondern zunächst mit Verschieben arbeiten
- Festgelegt, wohin im Wiederherstellungsfall zurückgespielt wird
15. Zusammenfassung
Praktisches PowerShell bedeutet nicht, viele schwierige Syntaxkonstrukte zu verwenden. Im Berufsalltag zahlt sich aus, dieses Muster zu etablieren:
- Konfiguration in JSON auslagern
- Lesende Verarbeitung zuerst aufbauen
- Nachweise in CSV und JSON festhalten
- Verändernde Verarbeitung in Funktionen auslagern
- Ein
-WhatIf-ähnliches Probelauf-Verfahren bereitstellen - Über Transkript und
error.txtNachvollziehbarkeit sicherstellen - Erst nach manueller Prüfung die regelmäßige Ausführung aktivieren
Dieses Skript behandelt Protokollauswertung und Archivierung als Thema, doch die zugrunde liegende Denkweise lässt sich auch auf andere Aufgaben übertragen:
- Dateiorganisation
- Erstellung von Berichten
- CSV-Auswertungen
- Ablösung von Batch-Verarbeitung
- Bestandsaufnahme alter Ressourcen
- Tägliche und monatliche Betriebsprüfungen
PowerShell ist auch als Einzeiler praktisch, doch für den geschäftlichen Einsatz ist es sicherer, folgende Reihenfolge einzuhalten:
Ansehen → Aufzeichnen → Probelauf → Ausführen → Nachweis aufbewahren
Wenn Sie sich an diesem Muster orientieren, wird PowerShell nicht nur zu einem Werkzeug zur Zeitersparnis, sondern zu einer kleinen Fachanwendung, die den Betrieb stabilisiert.
Referenzlinks
- Das vollständige Beispielcode-Paket zu diesem Artikel (fertiges Skript, Konfigurationsdatei, Pester-Tests) - komurasoft-blog-samples (GitHub)
- PowerShell Documentation - Microsoft Learn
- ConvertTo-Json - Microsoft Learn
- about_Functions_CmdletBindingAttribute - Microsoft Learn
- Start-Transcript - Microsoft Learn
- New-ScheduledTaskAction - Microsoft Learn
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Windows-App-Outsourcing und Auftragsentwicklung: Was Sie vor der Beauftragung klären sollten
Bevor Sie die Entwicklung einer Windows-App outsourcen oder in Auftrag geben, sollten Sie folgende Punkte klären: Überarbeitung bestehend...
Die seltsame Liebe eines Entwicklers, oder: Wie ich lernte, mir keine Sorgen mehr zu machen, und Windows lieben lernte
Windows ist umständlich. Aber diese Umständlichkeit ist auch die Umständlichkeit eines Betriebssystems, das jahrzehntelang reale Geschäft...
Process Monitor (ProcMon) Praxisleitfaden — „Konfiguration wird nicht gelesen“ und ACCESS DENIED in 10 Minuten identifizieren
„Ich habe die Konfigurationsdatei korrigiert, aber die Änderung wird nicht übernommen“ – „Bis gestern lief es noch, aber heute startet es...
Wenn man eine Windows-App besser nicht ins Web migriert: Entscheidungstabelle und die pragmatische Lösung der Aufteilung
Der Wunsch, interne Windows-Anwendungen ins Web zu migrieren, nimmt stetig zu. Doch bei Apps mit Gerätekopplung, lokaler Dateiverarbeitun...
Mehrsprachigkeit für WinForms/WPF-Anwendungen ── resx, Satelliten-Assemblies und Kulturumschaltung in der Praxis
Dieser Artikel behandelt die Mehrsprachigkeit von Windows-Desktopanwendungen aus Praxissicht: den Unterschied zwischen CurrentCulture und...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Womit sollte ich beginnen, wenn ich die Protokollauswertung mit PowerShell automatisiere?
- Schreiben Sie nicht sofort verändernde Vorgänge wie Verschieben oder Löschen, sondern bauen Sie zunächst nur die lesenden Verarbeitungsschritte auf. Suchen Sie mit Get-ChildItem die betreffenden Protokolle, extrahieren Sie mit Select-String die Fehlerzeilen und geben Sie sie zunächst vollständig als CSV aus. Auch wenn Sie das Skript im Produktivordner testen, halten Sie an dieser lesenden Stufe zunächst an und prüfen Sie das Ergebnis. Erst danach gehen Sie schrittweise zur Auflistung der Archivierungskandidaten und zur Auslagerung der Verschiebeverarbeitung in eine Funktion über – das ist der sicherere Weg.
- Wie baue ich -WhatIf in ein PowerShell-Skript ein?
- Wenn Sie das CmdletBinding-Attribut einer Funktion um SupportsShouldProcess ergänzen, kann diese Funktion -WhatIf und -Confirm verwenden. Entscheidend ist, $PSCmdlet.ShouldProcess() unmittelbar vor dem tatsächlich verändernden Vorgang (etwa Move-Item) zu platzieren, sodass die Entscheidung, ob geändert wird, direkt vor der Änderung selbst fällt. Im Skript dieses Artikels wird der Schalter -Preview als -WhatIf:$Preview weitergereicht, sodass sich nur die geplanten Vorgänge anzeigen lassen, ohne tatsächlich etwas zu verschieben.
- Warum funktioniert mein PowerShell-Skript manuell, schlägt aber in der Aufgabenplanung fehl?
- Ein typischer Grund ist, dass der Benutzer, unter dem die Aufgabe ausgeführt wird, sich von dem unterscheidet, mit dem Sie das Skript selbst getestet haben. Prüfen Sie, ob dieser Ausführungsbenutzer Leserechte auf den Protokollordner und Schreibrechte auf das Archivierungsziel besitzt, ob der Arbeitsordner (WorkingDirectory) korrekt gesetzt ist, ob der Pfad zu pwsh.exe aufgelöst werden kann und ob die Ausführungsrichtlinie sowie die Signaturregeln eingehalten werden. Wichtig ist außerdem, dass sich Fehlschläge anhand von error.txt, transcript.txt und dem Aufgabenverlauf nachvollziehen lassen.
- Sollte ich das Löschen alter Protokolle ebenfalls automatisieren?
- Es wird nicht empfohlen, das Löschen von Anfang an zu automatisieren. Auch das Skript in diesem Artikel beschränkt sich auf das sicherere „Verschieben“ statt auf ein Löschen. Betreiben Sie zunächst über einen bestimmten Zeitraum den Verschiebe-Workflow, prüfen Sie anhand der Nachweise wie archive-targets.csv und archive-result.csv, dass keine Probleme auftreten, und ziehen Sie erst danach eine Automatisierung des Löschens in Betracht. Ebenso wichtig ist es, vorab festzulegen, wohin Dateien im Wiederherstellungsfall zurückgespielt werden.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.
Öffentliche Links