PowerShell Transcript

PowerShell-Skripte sind meist "Skripte" die in einer PowerShell-Shell ablaufen und keine GUI haben. Der klassische Weg den Fortschritt anzuzeigen ist ein "Write-Host" und manchmal auch ein "Write-Verbose" oder "Write-Error". Diese Ausgaben sind normalerweise aber sehr schnell aus dem Blickfeld gescrollt und wenn der Bildschirmpuffer klein ist oder das Fenster geschlossen wird, sind die Informationen weg

Start-Transcript

Aber dem kann man als Administrator entgegen wirken, indem am Anfang das "Mitschreiben" mit dem Befehl "Start-Transcript" aktiviert wird. Dann werden alle Ausgaben auf den Bildschirm 1:1 in die angegeben Textdatei geschrieben.

Das "Mitschreiben" bleibt sogar nach dem Ende des Skripts für die Session aktiv, Wer also in einer PowerShell dann auch auf der Konsole weitere Befehle eingibt, findet diese Ebenso wieder. Erst mit einem "Stop-Transcript" wird die Protokollierung wieder beendet und die angelegte Datei zum Löschen frei gegeben.

Es kann übrigens nur ein Transcript in einer Session aktiv sind. Ein zweiter Versuch wird mit einer Fehlermeldung quittiert.

Das kann gut oder schlecht sein. Wenn Sie mehrere Skripte miteinander verketten und jedes macht einen "Transcript", dann kommt es beim zweiten Aufruf zu einem Fehler und je nach Einstellung bricht das Skript dann ab. Es kann aber auch gut sein, wenn sie mehrere Skripte verbinden und diese dank dieser globalen Einstellung alle in das gleiche Logfile protokollieren.

Wenn Sie sicher sind, dass ihr Skript das einzige ist, was diesen Host mit einen neuen Transcript starten soll, dann können Sie mit einer Try/Catch-Anweisung ein eventuell laufendes Transcript ohne sichtbare Fehlermeldung stoppen

try {stop-transcript} catch {$error.clear()}

Tipp:
Wenn Sie mit "Write-Host" Ausgaben erzeugen, dann sollten Sie immer auch den Skriptnamen addieren.

Start-Transcript mit Datum

Ich habe mir angewöhnt, in jedem Script ein "Mitschreiben" zu aktivieren, weil es die einfachste Möglichkeit ist, Fehler und Warnungen zu erkennen, ohne gesonderten Code für Eventlog, DebugLog o.ä. zu schreiben. Ein einfacher "Start-Transcript" legt aber ein Logfile im "Documents"-Ordner des Benutzers ab:

PS C:\temp> Start-Transcript
Die Aufzeichnung wurde gestartet. Die Ausgabedatei ist "C:\\Documents\PowerShell_transcript.20130930131504.txt".

Das ist natürlich gar nicht geeignet. Ich nutze daher die Option einen Dateinamen und Pfad mitzugeben, aber muss dann natürlich mich selbst um einen passenden Namen kümmern. Die folgenden Zeilen nutze ich dazu:

Kleine Info am Rande
Ich bim mittlerweile dazu übergegangen, Parameter für Skripte nicht mehr im Skripte selbst statisch zu hinterlegen und nur selten als "Param-Block" am Anfang, sondern diese als XML-Datei abzulegen. So kann ich einfach eine "neue" Version des Skripts beim Kunden ersetzen ohne viel editieren zu müssen oder umgekehrt schnell zwischen "Test" und "Produktion" umschalten.

Hier mal exemplarisch ein Auszug aus solche einer XML-Datei:

Dann können Sie die folgenden Zeilen einfacher verstehen.

$config = ([xml](get-content -path $configxml)).reportweb
$transcriptfile = $config.main.logpath+"ReportWeb-worker."+$reportname+"."+(get-date -format yyyyMMddhhmmss)+".log"
write-host "Transcript to:$transcriptfile"
start-transcript -append -path $transcriptfile

Vergessen Sie am Ende des Skripts natürlich nicht das Logging wieder auszuschalten. Das Logging wird aber automatisch beendet, wenn Sie die PowerShell-Session schließen.

Cleanup!

Wenn Sie glauben, dass Textdateien einen Server heute nicht "voll" schreiben können, dann unterschätzen Sie die Realität. Gerade "virtuelle Server" haben meist eher kleine Festplatten, da man sie ja dynamisch erweitern kann. Es wäre nicht das erste mal, dass ein Server wenig Platz hat, weil umfangreiche PowerShell-Skripte und damit verbundene Logs die Festplatten vollgeschrieben haben.

Daher addiere ich in der Regel direkt nach dem Start-Transcript auch den Code, um alte Logfiles wieder zu entfernen.

write-host (" Purging Logfiles older than "+$config.main.logpurgeage+" days  from "+$config.main.logpath)
foreach ($file in (get-childitem $config.main.logpath | where {$_.lastwritetime -lt ( (get-date).adddays(-$config.main.logpurgeage))} )) {
	write-host ("Purging old log("+$config.main.logpurgeage+"):" + $config.main.logpath +$file )
	remove-item -path ($config.main.logpath +$file)
}

Diese Zeilen löschen alle Dateien im Logpfad, die älter als das konfigurierte Alter sind.

Sie sollten natürlich darauf achten, dass in dem Log-Verzeichnis auch wirklich nur "Logdateien" liegen.

Transcript schon aktiv?

Wenn ein PowerShell-Skript abbricht oder mit CTRL-C beendet wird, dann ist die "Transcription" in der Regel immer noch aktiv. Das führt natürlich zu unschönen Fehlermeldungen, wenn das nächste Script schon wieder ein "Start-Transcript" machen will. Da gibt es mehrere Wege die zu verhindern.

  • Parameter ErrorAction
    Wie die meisten anderen PowerShell Commandlets haben auch Start-Transcript und Stop-Transcript einen "Erroraction" und "ErrorVariable"-Parameter. Allerdings habe ich selbst mit PowerShell 4 auch mit einem "Erroraction Silentlycontinue" immer eine Fehlermeldung bekommen

    Damit kommen wir hier zumindest nicht weiter
  • Try-Catch
    Die Alternative ist natürlich den Aufruf in eine Try-Catch-Zeile einzufangen und die Errors löschen

    Hier kommt zumindest keine Fehlermeldung, wenn das Transcript bereits aktiv ist
  • Abfrage des aktuellen Status
    Leider gibt es kein "Get-Transcript" und es ist auch keine direktes Property des "$HOST"-Objekts. Dennoch hat jemand rausgefunden, wie per PowerShell der aktuelle Status ermittelt werden kann. Auf der Seite http://poshcode.org/1500 ist der Code publiziert, den ich hier nur als Kopie wiedergeben. Nutzen werde ich ihn wohl nicht.
# Source http://poshcode.org/1500 
#requires -version 2.0

function Test-Transcribing {
   $externalHost = $host.gettype().getproperty("ExternalHost",
      [reflection.bindingflags]"NonPublic,Instance").getvalue($host, @())

   try {
      $externalHost.gettype().getproperty("IsTranscribing",
         [reflection.bindingflags]"NonPublic,Instance").getvalue($externalHost, @())
   } catch {
      write-warning "This host does not support transcription."
   }
}

Da ist der Einsatz der "Try/Catch"-Version viel einfacher zu verstehen.

Beispielcode für Skripte

Für den unbewachten Betrieb von Skripten ist ein Logging unerlässlich, um auch im Nachhinein Fehler zu ermitteln. Aber natürlich besteht die Gefahr, dass die Logdateien die Festplatte füllen. Also brauche ich immer ein Stück Code um wechselnd Protokolldateien anzulegen und auch wieder aufzuräumen.

if (!(test-path ".\logs")) {mkdir ".\logs"}
[string]$transcriptfile = (".\logs\WFMReporting."+(get-date -format "yyyy-MM-dd-HH-mm-ss-fff")+".log")
write-output "Start Transcript to " $transcriptfile 
Start-Transcript -path $transcriptfile

[string[]]$oldlogfilelist=""
get-item -path ".\logs\transcript.*.log" `
   | where {$_.lastwritetime -lt ((get-date).adddays(-$transcriptpurgedays))} `
   | %{$oldlogfilelist+=$_.fullname}
foreach ($oldlogfile in $oldlogfilelist) {
   if ($oldlogfile) { # only if oldlogpath is not null or empty
      if (test-path $oldlogfile) {
         write-output "  Removing Old LogFile:" $oldlogfile
         remove-item -path $oldlogfile
      }
      else {
         write-output "  Skip not existing  Old LogFile:" $oldlogfile
      }
   }
   else {
      write-output "  Skip empty name of Old LogFile:" $oldlogfile
   }
}

Mitten im Skript kann ich natürlich mit Write-Output Ausgaben auf den Bildschirm ausgeben.

Und am Ende des Script steht natürlich:

stop-transcript

Weitere Links