PowerShell Streams

Auf der Seite PowerShell Pipeline habe ich beschrieben, wie ein Skripte eine Information über die Pipeline an ein nachfolgendes Skript geben kann. Aber PowerShell hat nicht nur eine Pipeline, sondern mindestens 6 getrennte Pipelines, die als "Stream" bezeichnet werden und für unterschiedliche Aufgaben genutzt werden.

Sofern nicht anders beschrieben, wurden alle Tests mit der PowerShell 7.4.4 und den Standardeinstellungen erstellt.

Stream 1 bis 6

Die meisten Administratoren kennen sicherlich die "Pipeline", und über die "Verkettung" kann ein Skript entsprechend Daten über das "|"-Zeichen an ein anderes Skript übergeben. Zudem sollten alle Kommandozeilen-Nutzer auch die Umleitung mit ">" und ">>" in Dateien kennen. Vielleicht hat jemand unter DOS schon mal ein "2>" verwendet, um auch Errors in eine Datei zu schreiben. Und damit sind wir genau beim Thema. Das gibt es auch bei PowerShell aber an verschiedenen Stellen der Microsoft Dokumentation werden sechs Streams aufgeführt.

1 Success Stream
2 Error Stream
3 Warning Stream
4 Verbose Stream
5 Debug Stream
6 Information Stream

Die Streams können über verschiedene Befehle mit Daten gefüllt werden und auch die Ausgabe landet je nach Stream an unterschiedlichen Stellen.

Test mit PowerShell

Um die Funktionsweise zu testen, habe ich folgendes Skript verwendet.

Start-Transcript .\transcript.txt
Write-Host        "0WriteHost"
Write-Output      "1WriteOutput"
Write-Error       "2WriteError"
Write-Warning     "3WriteWarning"
Write-Verbose     "4WriteVerbose"
Write-Debug       "5WriteDebug"
Write-Information "Write6Information"
"7Direktausgabe"
[console]::Out.WriteLine("8ConsoleWrite")
[console]::Error.WriteLine("9ConsoleError")
Stop-Transcript

Die Ausgabe zeigt noch die erwartete Information, wenngleich etwas durchwachsen.

Die Ausgaben von "Write-Verbose", "Write-Debug" und "Write-Information" sind nicht sichtbar, das deren Funktion durch die Voreinstellungen der Preferences mit "SilentlyContinue" unterdrückt werden.

Im Transcript habe ich aber andere Ausgaben gefunden. Der Write-Host ist natürlich vorhanden aber Write-Error hat es zweimal ins Transcript geschafft. Die Direktausgabe ist hier auch im Transcript gelandet, da es keine Nachverarbeitung gegeben hat. Würde ich das Skript mit "| out-null" aufrufen, wäre der String "Direktausgabe" nicht hier sichtbar.

Die Ausgaben mit "[console]" sind generell nicht sichtbar.

**********************
PowerShell transcript start
Start time: 20240808184536
Username: NETATWORK\fcarius
RunAs User: NETATWORK\fcarius
Configuration Name: 
Machine: FC-T480S (Microsoft Windows NT 10.0.22631.0)
Host Application: C:\Program Files\PowerShell\7\pwsh.dll
Process ID: 94204
PSVersion: 7.4.4
PSEdition: Core
GitCommitId: 7.4.4
OS: Microsoft Windows 10.0.22631
Platform: Win32NT
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
WSManStackVersion: 3.0
**********************
Transcript started, output file is .\transcript.txt
0WriteHost
1WriteOutput
Write-Error: 2WriteError
Write-Error: 2WriteError
WARNING: 3WriteWarning
7Direktausgabe
**********************
PowerShell transcript end
End time: 20240808184537
**********************

Pipeline

Wenn ich das gleiche Skript mit "| out-file pipe.txt" aufrufe, dann ändert sich die Ausgabe. Schon die Rückmeldung, dass das Transcript gestartet wurde, ist nicht mehr auf der Konsole sichtbar aber sehr wohl im Transcript selbst. Aber auch andere Meldungen fehlen auf der Konsole. So wird die Ausgabe von "Write-Host" und die Direktausgabe natürlich abgefangen.

Diese landen beide in der PIPE1.TXT-Datei.

Das war so zu erwarten.

Ausgabeumleitung

Eine Ausgabe in einen Stream macht natürlich nur Sinn, wenn die Ausgaben ankommen und auch weiter verarbeitet werden können. dazu kennt PowerShell die Pipeline für die Standardausgabe aber auch Wege die anderen Streams umzuleiten.

Also habe ich das gleiche Skript noch mal mit folgenden Parametern gestartet.

PS C:\> .\testwriteoutput.ps1 1>stream1.txt 2>stream2.txt 3>stream3.txt 4>stream4.txt 5>stream5.txt 6>stream6.txt | out-file .\pipe.txt

Die Ausgabe war dann weiter reduziert:

Nur die Ausgaben direkt auf die Konsole wurden ausgegeben. Alle anderen Ausgaben wurden in die entsprechenden Dateien umgeleitet:

Der Inhalt der Dateien war:

Dateiname Inhalt

pipe.txt

Diese Datei war leer, obwohl ich doch mit "| out-file" die Pipeline umleiten wollte..

stream1.txt

Transcript started, output file is .\transcript.txt
1WriteOutput
7Direktausgabe
Transcript stopped, output file is C:\test\transcript.txt

stream2.txt

Write-Error: 2WriteError

stream3.txt

3WriteWarning

stream4.txt

<leer>

stream5.txt

<leer>

stream6.txt

0WriteHost
Write6Information

Transcript.txt

**********************
PowerShell transcript start
Start time: 20240808185242
Username: NETATWORK\fcarius
RunAs User: NETATWORK\fcarius
Configuration Name: 
Machine: FC-T480S (Microsoft Windows NT 10.0.22631.0)
Host Application: C:\Program Files\PowerShell\7\pwsh.dll
Process ID: 94204
PSVersion: 7.4.4
PSEdition: Core
GitCommitId: 7.4.4
OS: Microsoft Windows 10.0.22631
Platform: Win32NT
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
WSManStackVersion: 3.0
**********************
0WriteHost
Write-Error: 2WriteError
**********************
PowerShell transcript end
End time: 20240808185243
**********************

Ich finde hier folgende Dinge interessant:

  • Stream-Umleitung vor Pipeline
    Wenn ich beim Aufruf mit "1>stream1.txt den Stream in eine Datei umleite, dann wird er nicht mehr in die klassische Pipeline ausgegeben. Das muss man wissen, aber kann es auch strategisch einsetzen, um z.B. bei einer Verkettung zum Test die Daten einem nachfolgenden Prozess zu entziehen.
  • Stream4 (Verbose) und Stream5 (Debug) sind noch leer
    Das ist verständlich, weil die Preference bei beiden auf "SilentlyContinue" steht und damit schon der Write-Verbose und Write-Debug keine Ausgabe schreiben
  • Stream 6 ist mit Write-Information gefüllt
    Dass die normale Ausgabe von "Write-Host" auch im Stream6 (Information) landet, ist bemerkenswert. Noch mehr finde ich interessant, dass auch der Write-Information eine Ausgabe in den Stream schreibt, obwohl auch hier die "InformationPreference=SilentlyContinue" ist.
  • Write-Host und Write-Information unterscheiden sich im Transcript
    Im Transcript landen mit den aktuellen Preference-Einstellungen nur die Ausgaben von Write-Host und Write-Error aber nicht Write-Information.
  • Keine Direktausgaben im Transcript
    Direkte Ausgaben auf die Konsole landen nicht im Transcript

Insofern gibt es zumindest bei der PS 7.4.4 einen Unterschied zwischen Write-Host und Write-Informationen hinsichtlich des Stream 6 und dem Transcript.

Eine Umleitung von "Wirte-Host" mit z.B. "6> $null" beschleunigt den Ablauf des PowerShell Scripts nur minimal.

Preference Continue

Ich habe das Skript dann noch mal mit "VerbosePreference="Continue" und "DebugPreference="Continue" gestartet. Beim Setzen der Einstellungen ist mir etwas aufgefallen. Der Standard steht auf "SilentlyContinue" und ich habe dann einfach den Wert auf Continue gesetzt. Es kam keine Fehlermeldung aber der Wert wurde auch nicht übernommen. Erst wenn ich "Continue" in Anführungszeichen als String geschrieben habe, wurde er übernommen. Habe ich aber eine Abkürzung verwendet, dann kam ein Fehler. Die Abkürzung konnte ich aber problemlos beim Write-Information mit dem Parameter "-InformationAction" nutzen.

Das Verhalten gibt übrigens  generell für alle Preference-Variablen. Achten Sie als darauf, wenn Sie die Preference-Variablen setzen, das Sie diese auch wirklich korrekt setzen.

Dies gilt nicht, wenn Sie im Skript das Schlüsselwort "[CMDLetBinding[]]" verwenden und dann über die Parameter nur für das Skript die Preferences ändern.

Die folgenden Test wurden mit folgender Einstellung durchgeführt:

Zuerst wieder der Aufruf ganz ohne Parameter.

Diesmal sehen wie alle Ausgaben von 1 - 9. Im Transcript landen aber nur die Zeilen 1-7

0WriteHost
1WriteOutput
Write-Error: 2WriteError
Write-Error: 2WriteError
WARNING: 3WriteWarning
VERBOSE: 4WriteVerbose
DEBUG: 5WriteDebug
Write6Information
7Direktausgabe

Nun starte ich das Skript noch einmal mit der Umleitung.

Mit aktivierter Umleitung für alle Streams landen nur die Ausgaben auf der Console auch auf dem Bildschirm. Dafür hat nun jeder Stream-Datei ihren Teil abbekommen.

Allerdings haben sich nun die Inhalte etwas verändert.

Dateiname Inhalt

pipe.txt

Diese Datei war leer, obwohl ich doch mit "| out-file" die Pipeline umleiten wollte..

stream1.txt

Transcript started, output file is .\transcript.txt
1WriteOutput
7Direktausgabe
Transcript stopped, output file is C:\test\transcript.txt

stream2.txt

Write-Error: 2WriteError

stream3.txt

3WriteWarning

stream4.txt

4WriteVerbose

stream5.txt

5WriteDebug

stream6.txt

0WriteHost
Write6Information

Transcript.txt

**********************
PowerShell transcript start
Start time: 20240808185242
Username: NETATWORK\fcarius
RunAs User: NETATWORK\fcarius
Configuration Name: 
Machine: FC-T480S (Microsoft Windows NT 10.0.22631.0)
Host Application: C:\Program Files\PowerShell\7\pwsh.dll
Process ID: 94204
PSVersion: 7.4.4
PSEdition: Core
GitCommitId: 7.4.4
OS: Microsoft Windows 10.0.22631
Platform: Win32NT
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
WSManStackVersion: 3.0
**********************
0WriteHost
Write-Error: 2WriteError
Write6Information
**********************
PowerShell transcript end
End time: 20240808185243
**********************

Auch hier sehen Sie aber, dass bei Stream2 noch ein "Write-Error" davor steht, die "pipe.txt" wieder leer ist weil der Inhalte in stream1.txt gelandet ist und auch im Transcript finde ich die Ausgabe von Write-Information, während die Ausgaben von Write-Verbose, Write-Warning und Write-Debug nicht protokolliert werden.

Insofern stimmt es nicht, dass "Write-Host" nur ein Alias für "[console].out.write" ist und auch wenn Wirte-Host sich auf "Write-Information" stützen mag, sind es dennoch unterschiedliche Streams.

Zudem sollten Sie die Prefixes "Warning, Verbose, Debug" beachten.

Übersicht

All die Erkenntnisse habe ich hier in eine Tabelle gebracht. Sie zeigt, welches Ausgaben in welchem Stream landen und ob diese per Default im Transcript landen und bei der Umleitung der Streams

  • Transcript Default
    Landen die Ausgaben im Transcript mit der Standardeinstellung
  • Transcript mit Umleitung
    Landen die Ausgaben im Transcript, obwohl die Streams mit ">" in eigene Dateien umgeleitet wurden?
  • Transcript mit Preference
    Nun sind alle Ausgaben mit "Continue" aktiviert, also auch Information, Debug, Warning. Welche landen nun im Transcript und welche im Stream
Write Cmdlet Stream # Beschreibung Seit PowerShell Transcript
Default
Transcript
mit Umleitung
Transcript
mit Preference
Transcript
mit Preference und Umleitung

Write-Output

1

Success Stream

2.0

Ja

Nein

Nein

Nein

Write-Error

2

Error Stream

2.0

Ja

Ja

Ja

Ja

Write-Warning

3

Warning Stream

3.0

Ja

Nein

Ja

Nein

Write-Verbose

4

Verbose Stream

3.0

Nein

Nein

Ja

Nein

Write-Debug

5

Debug Stream

3.0

Nein

Nein

Ja

Nein

Write-Information

6

Information Stream

5.0

Nein

Nein

Ja

Nein

Write-Host

6

Information Stream

5.0

Ja

Ja

Ja

Ja

>*

All Streams

 

3.0

-

-

-

-

[console]::Out.WriteLine

-

Direkte Ausgabe

1.0

 

[console]::Error.WriteLine

-

Direkte Ausgabe

1.0

 

 

 

 

Write-Progress

Kein

Kein Streamsupport

 

Nein

Nein

Nein

Nein

Die Ausgaben ins Transcript orientieren sich an den Default-Einstellungen. Natürlich können Sie auch die Verbose, Debug und Information-Streams von "SilentlyContinue" auf "Continue" umstellen.

Sonderfall Write-Host"

  • Auch wenn viele Artikel früher von Write-Host abgeraten haben, weil es die reine Lehre der PowerShell mit der Pipeline als einzige Ausgabe quasi verrät, war Write-Host immer noch erste Wahl zur Ausgabe von Zwischeninformationen auf die Konsole. Insbesondere bei Ausgaben mit Farben, die mit Write-Verbose (gelb) oder Write-Information (keine Farbe) nicht möglich sind, ist Write-Host immer noch unschlagbar. Seit PowerShell 5 und neuer nutzt Write-Host angeblich im Unterbau "Write-Information". Das stimmt aber nicht ganz, denn Write-Informationen schreibt nur etwas auf die Konsole, wenn $InformationPreference passend gesetzt ist.

Damit ist Write-Host immer noch mein bevorzugtes Werkzeug, um einfache Skripte mit einer Verarbeitungsanzeige zu versehen. Die Daten kann ich ja problemlos in die Pipeline senden.

Streams mit System.Management.Automation

Solange wir uns in einer einzelnen PowerShell bewegen, ist die Zuordnung der Streams noch einfach. Allerdings kennt die PowerShell ja auch eine "Parallelität" und Hintergrundprozesse. Mit Powershell Job, Start-ThreadJob oder PS Runspace. Ich kann z.B. folgenden Code "in den Hintergrund" senden.

Write-Host "Main1"
$PSHost1 = [powershell]::Create()
[void]$PSHost1.AddScript({
   Start-Transcript .\transcript.txt
   Write-Host        "0WriteHost"
   Write-Output      "1WriteOutput"
   Write-Error       "2WriteError"
   Write-Warning     "3WriteWarning"
   Write-Verbose     "4WriteVerbose"
   Write-Debug       "5WriteDebug"
   Write-Information "Write6Information"
   "7Direktausgabe"
   [console]::Out.WriteLine("8ConsoleWrite")
   [console]::Error.WriteLine("9ConsoleError")
   Stop-Transcript
   start-sleep -seconds 5
})
Write-Host "Main2"
$PSHost1.Invoke()
Write-Host "Main3

Die Ausgabe sieht wie folgt aus. Zuerst sehen Sie, dass die Ausgaben von MAIN1, MAIN2, MAIN3 sequentiell erfolgen. Das Skript wurde also nicht im Hintergrund gestartet und einige Ausgaben wurden direkt auf der Konsole mit ausgegeben. Aber einige Ausgaben sind in der Variable RESULT gelandet.

Der Inhalte von "$result" ist das "PSHOST"-Objekt, welches über den Stream1 die "Write-Output" und "7Direktausgabe" liefert. Die anderen Streams sind aber im Property "Streams" abrufbar. Hier erscheint interessanterweise auch das Property "Progress" welches meines Wissens nach gar kein Stream ist.

Sie sehen aber auch, dass die Streams für Warning, Debug, Verbose und Progress in dem Beispiel leer sind, weil die "Preferences" nicht entsprechend gesetzt wurde. Nur der "Information"-Stream wird nicht nur durch Write-Host sondern auch durch Write-Information gefüllt, obwohl $InformationPreference = 'SilentlyContinue' eingestellt war.

Streams mit InvokeAsync()

Natürlich habe ich das soeben genutzte Script auch noch mal am Ende mit "$pshost.invokeasync()" im Hintergrund gestartet. Ich wollte ja wissen, was dann passiert. Die Ausgabe sah etwas anders aus, da $Result ein Array war, und das erste Element $result[0] ein System.Threading.Task-Objekt war. welches den Status des Hintergrundjobs ausgegeben hat. Ich habe natürlich bis zum Ende des Threads gewartet. Sie sehen am Anfang aber direkt die Rückgabe von "Main1", "Main2" und "Main3" und das Skript "testwriteoutputpshostasync.ps1" ist dann auch geendet.

In die dann weiter aktive Konsole zur Eingabe hat dann aber der Hintergrundthread mit dem "[console]::Out.WriteLine" und "[console]::Error.WriteLine" direkt geschrieben.

Die Ausgaben des Hintergrundthreads auf den Stream 1 (STDOUT) landeten dann aber nicht direkt in $Result sondern muss ich aus dem Property "Result" des Task-Objekts anrufen. Alle anderen Streams finde ich im Property "Streams" des Task-Objekts.

Lernkurve

Schon zu MSDOS-Zeiten gab es die Pipeline und auch Streams. Umleitungen wie 2>& etc. waren aber für die meisten Administratoren eher befremdlich. Die bis zu sechs Streams der PowerShell machen es nicht einfacher und viele Administratoren werden zurecht fragen, wozu das alles denn gut ein soll, wenn man mit "Pipe" immer nur STDOUT/STREAM1 an den nächsten Prozess übergeben kann.

Aus meiner Sicht sind die zusätzlichen Streams durchaus interessant, um unterschiedliche Informationen über getrennte Kanäle an ein anderes Programm zu übergeben. Der Standardstream 1 (STDOUT/STDIN) kann dann zur Übermittlung der reinen Daten dienen, während Diagnoseinformationen, Statusanzeigen oder Verbose-Details über die separaten Streams erfolgen kann. Denken Sie dabei aber daran, dass die Streams für Warning, Debug, Verbose und Progress nur gefüllt werden, wenn Sie die Preferences entsprechend angepasst haben. Nur der "Information"-Stream wird mit Write-Information auch gefüllt, wenn $InformationPreference = 'SilentlyContinue' ist.

Weitere Links