Schluss mit Write-Host — PowerShells Ausgabeströme und Log-Design

· · PowerShell, Windows, Protokollierung, Betriebsoptimierung, Automatisierung, Skript, Design, Wartbarkeit

„Das Skript läuft, aber wenn es fehlschlägt, habe ich keine Ahnung, was passiert ist“ — das ist mit Abstand die häufigste Beschwerde, die wir über produktiv eingesetzte PowerShell-Skripte hören. Und verfolgt man die Ursache, landet man fast immer bei derselben Struktur: Der Zustand der Verarbeitung wird ausschließlich über Write-Host ausgedrückt, und bei einem nächtlichen Lauf, den niemand beobachtet hat, ist nichts übrig geblieben.

PowerShell besitzt sechs Ausgabeströme, und jeder unterscheidet sich darin, für wen die Information bestimmt ist. Ist es ein zurückzugebender Wert? Etwas, das ein Mensch lesen soll? Etwas, das nur während einer Untersuchung gebraucht wird? Schreibt man mit diesem Unterschied im Kopf, verhält sich dasselbe Skript bei interaktiver Ausführung hilfreich und bei unbeaufsichtigter Ausführung maschinenlesbar. Leitet man stattdessen alles in Write-Host, sammelt sich nur Information an, die weder als Wert nutzbar ist noch in einem Log erhalten bleibt.

Dieser Artikel behandelt die Rollen der sechs Ströme, wo Write-Host tatsächlich hingehört, den Mechanismus, durch den Rückgabewerte von Funktionen verunreinigt werden, wie man dem Aufrufer die Kontrolle über die Ausführlichkeit überlässt, sowie ein strukturiertes Log-Muster, das sich im Produktivbetrieb tatsächlich einsetzen lässt.

1. Das Wichtigste zuerst

  • PowerShell besitzt sechs Ausgabeströme. Erfolg (1), Fehler (2), Warnung (3), Ausführlich (4), Debug (5) und Information (6), jeweils per Nummer umleitbar. *> erfasst alle Ströme.1
  • Ab PowerShell 5.0 schreibt Write-Host in den Informationsstrom. Das bedeutet, es lässt sich mit 6> umleiten und mit -InformationVariable erfassen. Davor konnte es weder erfasst noch unterdrückt werden.2
  • Write-Host ist ausschließlich für „Anzeige für einen Menschen“ gedacht. Sie können es nicht verwenden, um Werte zurückzugeben. Werte, die durch die Pipeline weitergereicht werden, laufen über Write-Output (oder eine bloße Ausgabe).23
  • Eine Funktion gibt jedes innerhalb von ihr ausgegebene Objekt zurück. Ob ein return vorhanden ist, spielt keine Rolle. Der Standardweg ist, unerwünschte Ausgaben mit $null = ... zu verwerfen.4
  • Fortschritt gehört zu Write-Progress, der Verlauf der Verarbeitung zu Write-Verbose. Die Fortschrittsanzeige ist kein umleitbarer Datenstrom.5
  • Eine Funktion mit [CmdletBinding()] erhält automatisch die gemeinsamen Parameter wie -Verbose, -Debug und -InformationAction. Die Ausführlichkeit in die Hand des Aufrufers zu legen, ist die richtige Antwort.67
  • Die Standardwerte lohnt es sich zu merken. $VerbosePreference, $DebugPreference und $InformationPreference sind SilentlyContinue; $WarningPreference und $ErrorActionPreference sind Continue.8
  • Logs, die Sie später auswerten möchten, sollten strukturiert sein (ein JSON-Objekt pro Zeile). Möchten Sie das Bildschirmbild reproduzieren, lassen Sie Start-Transcript mitlaufen.9

2. Die sechs Ströme und ihre Adressaten

# Strom Schreibender Befehl Vorgesehener Leser Standardeinstellung
1 Erfolg Write-Output / bloße Ausgabe Nachgelagerte Verarbeitung (die Pipeline)
2 Fehler Write-Error / throw Menschen + Überwachung Continue
3 Warnung Write-Warning Menschen Continue
4 Ausführlich Write-Verbose Menschen bei der Untersuchung SilentlyContinue
5 Debug Write-Debug Entwickler SilentlyContinue
6 Information Write-Information / Write-Host Menschen + Aufzeichnung SilentlyContinue

Die wichtigste Zeile dieser Tabelle ist die erste. Der Erfolgsstrom ist nicht der Ort, um Nachrichten für Menschen zu schreiben. Schreiben Sie dort eine für Menschen lesbare Zeichenkette hinein, fließt in dem Moment, in dem Sie diese Funktion mit | verketten, eine unerwartete Zeichenkette in die nachfolgende Verarbeitung.

function Get-KsTargetFile {
    Write-Output "Searching for targets..."   # [SCHLECHT] mischt sich in den Rückgabewert
    Get-ChildItem -Path $path -Filter '*.csv'
}

# Der Aufrufer erwartet ein Array von FileInfo, aber am Anfang steht eine Zeichenkette
$files = Get-KsTargetFile
$files[0].FullName    # -> leer (weil das erste Element eine Zeichenkette ist)

Richtig ist, Fortschrittsmeldungen an den ausführlichen oder den Informationsstrom zu senden.

function Get-KsTargetFile {
    [CmdletBinding()]
    param([string] $Path)

    Write-Verbose "Searching for targets: $Path"   # nur angezeigt, wenn -Verbose angegeben ist
    Get-ChildItem -Path $Path -Filter '*.csv'      # der Rückgabewert ist ausschließlich FileInfo
}

3. Ist Write-Host wirklich böse?

Die Behauptung „Write-Host niemals verwenden“ hat sich einst weit verbreitet, aber im modernen PowerShell hat sich die Lage geändert. Ab PowerShell 5.0 ist Write-Host als Schreibvorgang in den Informationsstrom (Nummer 6) implementiert, sodass Sie es mit 6> umleiten oder mit -InformationVariable erfassen können.2 Die damalige Kritik — „es kann nur auf den Bildschirm gehen und lässt sich nie wieder aufgreifen“ — trifft nicht mehr zu.

Dennoch sind die Einsatzmöglichkeiten begrenzt.

Wo Write-Host angemessen ist

  • In interaktiven Werkzeugen, wenn Sie farbige Überschriften oder Trennlinien wünschen (-ForegroundColor)
  • Wenn Sie dem Benutzer mitteilen, was das Skript als Nächstes tun wird
  • Wenn die dekorierte Anzeige selbst, nicht ein Verarbeitungswert, der eigentliche Zweck ist

Wo Write-Host nicht verwendet werden darf

  • Wenn Sie einen Wert als Rückgabewert der Funktion übergeben möchten (→ Write-Output)
  • Wenn Sie ein Betriebslog für die spätere Analyse hinterlassen möchten (→ strukturiertes Logging, oder Write-Information)
  • Wenn der Aufrufer die Anzeige ein- und ausschalten können soll (→ Write-Verbose)

In einem unbeaufsichtigten Skript gibt es überhaupt kein Anzeigeziel. Ein Skript, das seinen Zustand nur über Write-Host ausdrückt, wird in dem Moment, in dem es von der Aufgabenplanung ausgeführt wird, zu „einem Skript, das nichts verrät“. Genau darauf zielt der Titel dieses Artikels ab.

4. Dem Aufrufer die Kontrolle überlassen — [CmdletBinding()] und die gemeinsamen Parameter

Der eigentliche Wert von Write-Verbose liegt darin, dass der Aufrufer entscheidet, ob es angezeigt wird. Fügt man einer Funktion einfach [CmdletBinding()] hinzu, stehen automatisch gemeinsame Parameter wie -Verbose, -Debug, -WarningAction, -InformationAction und -ErrorAction zur Verfügung.67

function Invoke-KsImport {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $CsvPath
    )

    Write-Verbose "Import started: $CsvPath"          # standardmäßig nicht angezeigt
    Write-Information "Importing: $CsvPath" -Tags 'KsImport'   # standardmäßig nicht angezeigt (aber erfassbar)

    $rows = Import-Csv -Path $CsvPath
    if ($rows.Count -eq 0) {
        Write-Warning "$CsvPath contains nothing to import"   # standardmäßig angezeigt
        return
    }

    Write-Verbose "Processing $($rows.Count) rows"
    $rows | ForEach-Object { ConvertTo-KsRecord $_ }         # allein dies ist der Rückgabewert
}

# Normaler Lauf: nur Warnungen werden angezeigt, und der Rückgabewert sind die Datensätze
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv'

# Während der Untersuchung: wir wollen auch den Fortschritt sehen
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -Verbose

# Nur den Informationsstrom in einer Variablen erfassen, dann in eine Log-Datei schreiben
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -InformationVariable info
$info | ForEach-Object { $_.MessageData } | Add-Content -Path $logPath

Man sieht häufig Implementierungen, die eine $LogLevel-Variable erfinden und mit if-Anweisungen verzweigen, aber sich auf den Standardmechanismus zu stützen ist kürzer und vermittelt anderen die eigene Absicht. Dass das Hinzufügen von -Verbose Details liefert, ist gemeinsames Wissen aller PowerShell-Nutzer.

Beachten Sie, dass Präferenzvariablen wie $VerbosePreference für den aktuellen Gültigkeitsbereich und dessen untergeordnete Bereiche gelten.8 Ruft man eine Funktion mit -Verbose auf, beginnen auch die von innen aufgerufenen Cmdlets, ausführliche Ausgaben zu erzeugen, sodass mehr Ausgabe entstehen kann als erwartet.

5. Verunreinigte Rückgabewerte — eine PowerShell-spezifische Falle

PowerShell-Funktionen geben jedes innerhalb von ihnen ausgegebene Objekt zurück, auch ohne explizites return.4 Das ist ein mächtiges Designmerkmal, zugleich aber auch die Quelle der meisten Unfälle.

function New-KsWorkFolder {
    param([string] $Path)

    New-Item -Path $Path -ItemType Directory   # [FALLE] DirectoryInfo mischt sich in den Rückgabewert

    $list = [System.Collections.Generic.List[string]]::new()
    $list.Add('log')                            # [FALLE 2] .Add() ist void, daher kein Schaden
    $sb = [System.Text.StringBuilder]::new()
    $sb.Append('x')                             # [FALLE 3] der StringBuilder selbst wird zurückgegeben

    return $Path
}

$p = New-KsWorkFolder -Path 'D:\work'   # $p wird zu einem dreielementigen Array (DirectoryInfo, StringBuilder, string)

Die Lösung besteht darin, „die unerwünschte Ausgabe wegzuwerfen“. Es gibt drei Schreibweisen, aber $null = ... ist die leichteste.

$null = New-Item -Path $Path -ItemType Directory   # empfohlen
New-Item -Path $Path -ItemType Directory | Out-Null # langsamer um die Kosten der Pipe
[void] $sb.Append('x')                             # häufig für .NET-Methoden verwendet

Schreiben Sie einen Test, und Sie bemerken dieses Verhalten sofort. Wie wichtig Tests sind, die „die Form des Rückgabewerts festlegen“, wird in „PowerShell mit Pester testen — Ein praktischer Ansatz, um Betriebsskripte widerstandsfähiger zu machen“ behandelt.

6. Umleitung und Erfassung

Ströme lassen sich einzeln per Nummer umleiten.1

.\Invoke-NightlyExport.ps1 3> warnings.log            # nur Warnungen in eine separate Datei
.\Invoke-NightlyExport.ps1 4>&1 | Tee-Object -FilePath run.log   # ausführliche Ausgaben in den Erfolgsstrom einfließen lassen
.\Invoke-NightlyExport.ps1 *> all.log                 # alle Ströme in eine Datei
.\Invoke-NightlyExport.ps1 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }

> überschreibt und >> hängt an. Beachten Sie jedoch, dass das Zusammenführen per Umleitung Typen vermischt. Führen Sie wie im obigen Beispiel den Fehlerstrom eines PowerShell-Skripts oder einer Funktion zusammen, bleiben die Elemente ErrorRecord-Objekte, sodass Sie sie wie oben nach Typ sortieren können.

Andererseits verhält sich 2>&1 bei einem externen Programm (einem nativen Befehl) anders. Ab PowerShell 7.4 wird die umgeleitete Ausgabe als Bytestrom behandelt und wird nach dem Zusammenführen zu Zeichenketten, sodass eine Sortierung nach ErrorRecord nicht mehr funktioniert. Wenn Sie stdout und stderr eines externen Befehls unterscheiden müssen, empfangen Sie sie getrennt, statt sie zusammenzuführen (siehe „Externe Programme korrekt aus PowerShell aufrufen“).

Wenn Sie einen vollständigen Nachweis eines Laufs behalten möchten, ist Start-Transcript die einfache Option. Es zeichnet die Befehle und Ausgaben der Sitzung als Text auf, sodass Sie später rekonstruieren können, „was zu diesem Zeitpunkt auf dem Bildschirm stand“.9

Start-Transcript -Path "C:\Logs\export_$(Get-Date -f yyyyMMdd_HHmmss).log" -Append
try   { Invoke-KsExport }
finally { Stop-Transcript }

7. Logs später analysierbar machen — strukturiertes Logging

Logs, die Menschen lesen, und Logs, die Maschinen aggregieren, sind unterschiedliche Dinge. Wenn Sie wissen möchten, „wie oft ist dieser Fehler letzten Monat aufgetreten?“, wird frei formulierter Text zu einem Ausdauerkampf mit grep. Schreiben Sie ein JSON-Objekt pro Zeile (JSON Lines), und die Auswertung lässt sich vollständig mit PowerShell erledigen.

function Write-KsLog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [ValidateSet('INFO','WARN','ERROR')] [string] $Level,
        [Parameter(Mandatory)] [string] $Message,
        [hashtable] $Data,
        [string] $Path = $script:KsLogPath
    )

    $entry = [ordered]@{
        ts      = (Get-Date).ToString('o')    # ISO 8601: leicht zu sortieren und zu korrelieren
        level   = $Level
        message = $Message
        script  = $MyInvocation.ScriptName
        host    = $env:COMPUTERNAME
        user    = $env:USERNAME
    }
    # Zusätzliche Informationen unter data ablegen. Mischt man sie auf die oberste Ebene, kann
    # der Aufrufer mit Schlüsseln wie level oder user die Basisfelder überschreiben
    if ($Data) { $entry['data'] = $Data }

    # -Compress bringt es auf eine Zeile. Anhängen mit Add-Content (UTF-8)
    $entry | ConvertTo-Json -Compress -Depth 5 | Add-Content -Path $Path -Encoding utf8

    # Die für Menschen bestimmte Anzeige stützt sich auf den Standardmechanismus (wird auf dem Bildschirm angezeigt, aber kein Wert wird zurückgegeben)
    switch ($Level) {
        # -ErrorAction nicht angeben. Legte man es hier fest, könnte der Aufrufer
        # daraus mit -ErrorAction Stop keinen abbrechenden Fehler mehr machen
        'ERROR' { Write-Error   $Message }
        'WARN'  { Write-Warning $Message }
        default { Write-Verbose $Message }
    }
}

# Anwendungsbeispiel
Write-KsLog -Level INFO -Message 'Import complete' -Data @{ rows = 1250; file = 'orders.csv'; ms = 4210 }
# -> {"ts":"...","level":"INFO","message":"Import complete","script":"...","host":"...","user":"...",
#    "data":{"rows":1250,"file":"orders.csv","ms":4210}}

Die Auswertung sieht dann so aus.

Get-Content 'C:\Logs\ks.log' |
    ForEach-Object { $_ | ConvertFrom-Json } |
    Where-Object { $_.level -eq 'ERROR' -and [datetime]$_.ts -ge (Get-Date).AddDays(-30) } |
    Group-Object message | Sort-Object Count -Descending | Select-Object Count, Name

Ihre Logs auf Windows’ Standard-Protokollierungsinfrastruktur (Ereignisprotokoll und ETW) zu legen, ist eine weitere Option. Wenn Sie über eine Anbindung an Überwachungswerkzeuge oder die Sammlung von mehreren Rechnern nachdenken, hat dieser Weg Vorteile. Einen Designvergleich finden Sie in „Einführung in Windows-Ereignisprotokoll und ETW — Die Logs Ihrer Geschäftsanwendung auf die Standardmechanismen des Betriebssystems legen“, und die Rotation von Log-Dateien wird in „PowerShell-Skripte in der Praxis — Protokollauswertung, Archivierung und Reporting sicher automatisieren“ behandelt.

8. Der Umgang mit der Fortschrittsanzeige

Write-Progress nutzt die Fortschrittsanzeigefunktion des Hosts, und es ist kein umleitbarer Datenstrom.5 Mit anderen Worten, Sie können es nicht in einem Log festhalten. Bei unbeaufsichtigter Ausführung gibt es keinen Anzeigeort, und in manchen Umgebungen ist der Aktualisierungsaufwand der Fortschrittsanzeige alles andere als vernachlässigbar.

# Die Fortschrittsanzeige am Anfang eines unbeaufsichtigten Skripts abschalten
$ProgressPreference = 'SilentlyContinue'

Wenn Sie Fortschritt in Ihrem Betriebslog haben möchten, ist es praktisch, nur die Meilensteine in den ausführlichen Strom zu schreiben.

$i = 0
foreach ($row in $rows) {
    $i++
    if ($i % 100 -eq 0) { Write-Verbose "$i / $($rows.Count) complete" }
    ...
}

9. Bewährte Praxis in der Praxis (Entscheidungstabelle)

Auszugebende Information Was zu verwenden ist Warum
Werte für nachgelagerte Verarbeitung Write-Output / bloße Ausgabe Der Erfolgsstrom ist ausschließlich für Daten3
Fortschritt (nur bei Untersuchung gewünscht) Write-Verbose Vom Aufrufer mit -Verbose gesteuert7
Ereignisse, die betrieblich festgehalten werden sollen Write-Information + strukturiertes Logging Mit -InformationVariable erfassbar2
Dekorierte Anzeige in einem interaktiven Werkzeug Write-Host Läuft über den Informationsstrom, daher ebenfalls erfassbar2
Erwartete, aber beachtenswerte Zustände Write-Warning Standardmäßig angezeigt, mit -WarningVariable erfassbar8
Fehlschläge Write-Error / throw Siehe den eigenen Artikel zur Fehlerbehandlung
Interner Zustand während der Entwicklung Write-Debug Nur wenn -Debug angegeben ist7
Fortschritt Write-Progress (nur interaktiv) Erscheint nie in Logs. Bei unbeaufsichtigter Ausführung abschalten5
Ein vollständiger Nachweis des Laufs Start-Transcript Als Absicherung neben dem eigenen Logging mitlaufen lassen9

10. Zusammenfassung

  • Die Ausgabe von PowerShell teilt sich in sechs Ströme auf. Der Erfolgsstrom ist ausschließlich für Daten, und das Vermischen mit für Menschen bestimmten Nachrichten zerstört den Rückgabewert.
  • Ab PowerShell 5.0 schreibt Write-Host in den Informationsstrom und ist daher erfassbar, kann aber weiterhin nicht dazu verwendet werden, Werte zurückzugeben oder Betriebslogs zu erzeugen.
  • Eine Funktion gibt alles zurück, was innerhalb von ihr ausgegeben wird. Der Standardweg ist, unerwünschte Ausgaben mit $null = ... zu verwerfen.
  • Fügen Sie [CmdletBinding()] hinzu und verwenden Sie Write-Verbose / Write-Information, um die Kontrolle über die Ausführlichkeit dem Aufrufer zu überlassen. Das ist kürzer als eine eigene Log-Level-Variable und vermittelt die Absicht.
  • Logs, die Sie später aggregieren möchten, sollten strukturiert sein, ein JSON-Objekt pro Zeile. Geht es darum, den Bildschirm zu reproduzieren, lassen Sie Start-Transcript mitlaufen.
  • Die Fortschrittsanzeige ist kein Datenstrom, daher landet sie nie in einem Log. Bei unbeaufsichtigter Ausführung ist die praktische Antwort, sie mit $ProgressPreference = 'SilentlyContinue' abzuschalten.

Beispielcode zum Download

Der in diesem Artikel behandelte Code ist in direkt ausführbarer Form gepackt. Er enthält das strukturierte Log mit einem JSON pro Zeile sowie die Art und Weise, Funktionen zu schreiben, die ihren Rückgabewert nicht verunreinigen.

Beispielcode herunterladen (zip)

Die Beispiele dieses Artikels wurden tatsächlich unter PowerShell 7.6 ausgeführt und verifiziert (14 Pester-Tests). Führen Sie das im zip enthaltene Invoke-SampleTests.ps1 aus, um dieselbe Verifikation auf Ihrer eigenen Maschine zu reproduzieren.

# Syntaxprüfung + statische Analyse + Pester-Tests
./Invoke-SampleTests.ps1

Die Konfigurationswerte (Pfade, Servernamen, Mandanten-IDs usw.) sind Beispiele. Führen Sie sie nicht unverändert in einer Produktivumgebung aus — passen Sie sie an Ihre eigene Umgebung an.

Verwandte Artikel

Verwandte Beratungsbereiche

Die KomuraSoft LLC übernimmt Reviews des Log-Designs in Betriebsskripten, die Auflösung der Situation „wir wissen nicht, warum es fehlgeschlagen ist“ sowie den Aufbau von Log-Grundlagen, die in Überwachung und Aggregation einfließen.

  1. Microsoft Learn, about_Redirection. Dazu, dass PowerShell Erfolgs-, Fehler-, Warn-, Ausführlich-, Debug- und Informationsströme besitzt, die jeweils durch eine Nummer gekennzeichnet sind; zur Umleitung in eine Datei mit > und >>; zum Zusammenführen mit einem anderen Strom über n>&1; und zur Umleitung aller Ströme mit *> 2

  2. Microsoft Learn, Write-Host. Dazu, dass Write-Host ab PowerShell 5.0 zu einem Wrapper um Write-Information wird, der in den Informationsstrom schreibt und dadurch eine Umleitung mit 6> ermöglicht; zur Dekoration über -ForegroundColor / -BackgroundColor; und dazu, dass seine Ausgabe nicht durch die Pipeline weitergereicht wird. Damit verbunden behandelt Write-Information das explizite Schreiben in den Informationsstrom und die Klassifizierung mit -Tags.  2 3 4 5

  3. Microsoft Learn, Write-Output. Zum Senden von Objekten in den Erfolgsstrom (die Pipeline) und dazu, dass das Ergebnis eines Ausdrucks auf dieselbe Weise ausgegeben wird, auch ohne expliziten Aufruf.  2

  4. Microsoft Learn, about_Return. Dazu, dass PowerShell-Funktionen jedes innerhalb der Funktion ausgegebene Objekt an den Aufrufer zurückgeben, unabhängig davon, ob return vorhanden ist, und dazu, dass return die Syntax ist, um einen Wert zurückzugeben und dabei den aktuellen Gültigkeitsbereich zu verlassen.  2

  5. Microsoft Learn, Write-Progress. Zur Ausgabe des Fortschritts eines Befehls als Fortschrittsanzeige des Hosts, zur Steuerung dieser Anzeige mit $ProgressPreference, und dazu, dass sie ab PowerShell 7.4 auch über den gemeinsamen Parameter -ProgressAction gesteuert werden kann.  2 3

  6. Microsoft Learn, about_Functions_CmdletBindingAttribute. Dazu, dass erweiterte Funktionen mit dem Attribut [CmdletBinding()] sich wie kompilierte Cmdlets verhalten und die gemeinsamen Parameter automatisch verfügbar werden.  2

  7. Microsoft Learn, about_CommonParameters. Zum Verhalten von -Verbose / -Debug / -WarningAction / -InformationAction / -ErrorAction und den entsprechenden -*Variable-Parametern sowie zu ihrer Beziehung zu den Präferenzvariablen.  2 3 4

  8. Microsoft Learn, about_Preference_Variables. Dazu, dass $VerbosePreference, $DebugPreference und $InformationPreference standardmäßig SilentlyContinue sind, während $WarningPreference und $ErrorActionPreference standardmäßig Continue sind; zur Steuerung der Fortschrittsanzeige mit $ProgressPreference; und dazu, dass diese für den aktuellen Gültigkeitsbereich und dessen untergeordnete Bereiche gelten.  2 3

  9. Microsoft Learn, Start-Transcript. Zur Aufzeichnung der Befehle und Konsolenausgaben einer Sitzung in einer Textdatei, zum Anhängen mit -Append, und zum Beenden mit Stop-Transcript.  2 3

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Darf ich Write-Host niemals verwenden?
Es ist nicht verboten — treffender ist, dass die Einsatzmöglichkeiten begrenzt sind. Ab PowerShell 5.0 schreibt Write-Host in den Informationsstrom (Nummer 6), sodass es sich mit 6> umleiten und mit -InformationVariable erfassen lässt. Dennoch handelt es sich um einen Befehl, der davon ausgeht, dass seine Ausgabe standardmäßig immer auf dem Bildschirm erscheint, und Sie können ihn nicht verwenden, um einen Wert in die Pipeline zu leiten. Die Aufteilung lautet: Write-Host für dekorierte Ausgaben, die für Menschen in einem interaktiven Werkzeug gedacht sind; Write-Verbose oder Write-Information für Fortschritt und ergänzende Informationen; und Write-Output (oder eine bloße Ausgabe) für Werte, die nachgelagerte Schritte verarbeiten müssen.
In den Rückgabewert meiner Funktion mischen sich unbeabsichtigte Werte.
Das liegt daran, dass eine PowerShell-Funktion jedes innerhalb von ihr ausgegebene Objekt zurückgibt, unabhängig davon, ob ein explizites return vorhanden ist. Wenn Sie einen Befehl oder eine Methode aufrufen, die einen Wert zurückgibt — etwa New-Item oder .Append() eines StringBuilders —, ohne mit dem Ergebnis etwas zu tun, fließt dieser Rückgabewert in den Erfolgsstrom und erreicht den Aufrufer. Umgekehrt geben Methoden mit void-Rückgabe, wie .Add() von List[T], nichts aus, sodass keine Unterdrückung nötig ist. Die Lösung besteht darin, die unerwünschte Ausgabe mit $null = ... zu verwerfen, | Out-Null anzuhängen oder mit [void] zu casten. Leistungsmäßig ist $null = ... von den dreien die leichteste Variante.
Ich möchte die ausführliche Protokollierung eines Skripts zur Laufzeit ein- und ausschalten können.
Schreiben Sie den zwischenzeitlichen Fortschritt mit Write-Verbose und fügen Sie der Funktion [CmdletBinding()] hinzu. Allein dadurch erscheint die Ausgabe nur, wenn der Aufrufer -Verbose angibt. Möchten Sie sie dauerhaft aktivieren, setzen Sie $VerbosePreference = 'Continue' am Anfang des Skripts. Ebenso lässt sich Write-Debug vom Aufrufer mit -Debug und Write-Warning mit -WarningAction steuern. Statt eine eigene Log-Level-Variable zu erfinden, vermittelt es die eigene Absicht anderen Lesern deutlich besser, wenn Sie sich auf die Standardmechanismen von PowerShell stützen.
Wie kann ich alles — ausführliche Ausgaben, Warnungen eingeschlossen — in einer Datei festhalten?
Es gibt drei Ansätze, je nach Bedarf. Um einfach alle Ströme in eine Datei zu schreiben, leiten Sie mit *> um. Um einen vollständigen Nachweis dessen zu behalten, was auf dem Bildschirm erschien, ist Start-Transcript die einfache Option. Wollen Sie die Ausgabe später programmatisch analysieren, ist ein strukturiertes Log mit einem JSON-Objekt pro Zeile aus einer eigenen Log-Funktion die zuverlässige Wahl — und selbst dann lohnt es sich, zusätzlich als Absicherung ein Transkript mitlaufen zu lassen.
Kann die Ausgabe von Write-Progress in einer Log-Datei festgehalten werden?
Nein. Die Fortschrittsanzeige ist eine Funktion der Hostanzeige und wird getrennt von den umleitbaren Datenströmen behandelt. Bei unbeaufsichtigter Ausführung gibt es keinen Ort zur Anzeige, behandeln Sie Fortschritt daher als etwas Eigenständiges gegenüber den Informationen, die Sie in Logs festhalten. Bei unbeaufsichtigter Ausführung kann das Setzen von $ProgressPreference = 'SilentlyContinue', um die Fortschrittsanzeige ganz zu stoppen, je nach Umgebung spürbar schneller machen. Möchten Sie den Fortschritt dennoch im Log haben, ist es praktischer, nur die Meilensteine — „50 von 100 abgeschlossen“ — mit Write-Verbose zu schreiben.

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.

Zurück zum Blog