[PowerShell] Out-Error für Try-Catch-Statements für detaillierte Analysen von Scripts

DPXone

Lieutenant
Registriert
Mai 2009
Beiträge
554
Hi Forum,

wollte mal eine Möglichkeit in PowerShell vorstellen, mit welcher man leicht sehr detaillierte Fehlermeldungen bei Script-Fehlern ausgeben kann.
Das Ganze kann man dann z. B. per Send-MailMessage ganz leicht an sein Postfach senden lassen und/oder in das Windows-Eventlog (Write-EventLog) schreiben kann (zur Nachverfolgung).
Bei Verbesserungsvorschlägen, Fragen oder Anregungen einfach hier schreiben.

Anmerkung:
Ihr solltet bei allen Cmdlets (in C# progammierte Funktionen für PowerShell als DLL; = alle von Microsoft bereitgestellten Funktionen in Modulen) immer die Error-Action auf Stop setzen ( -ErrorAction Stop .
Manche haben per Default nämlich Continue als ErrorAction, im Sinne von: Gib einen Error in der Konsole aus, verursache aber keinen Error an sich (Throw "Error") , was die Try-Catch-Methode dann nicht abgreift und somit die Funktion von mir nicht greift.

Bei eigenen Funktionen bitte [CmdletBinding()] einbauen (siehe Beispiel unten; Zeile 47), sonst gibt es diesen Parameter nicht.
Code:
Function Out-Error { 
	Param (
		[Parameter(Mandatory = $true , ValueFromPipeline = $true)] 
		[System.Management.Automation.ErrorRecord[]] $ErrorObjects # Input-Object  
	) 
	
	Begin { 
		[string[]] $Message = @() # Deklariere ein String-Array für die ErrorMessage (erlaubt Addition) 
		$CrLf = "`r`n" # = Carriage-Return (Cr) + Line-Feed (Lf) als Variable (=Neue Zeile) 
		$Tab = "`t" # = Tabstop 
	} 
	
	Process { 
		Foreach ($ErrorObject In $ErrorObjects) { # Falls es mehrere Objecte in der Pipeline gibt; Bei $ErrorObject aber selten/garnicht der Fall
			$Message+= '-' * 200 + $CrLf   # Header - # Füge ein '-' als Erkennungs- und Trennzeichenmerkmal an
			$Message+= $ErrorObject.Exception.Message + ($CrLf * 2)  # Header - Kurzer Error-Text
			$Message+= ( # Body
				(
					(
						(
							$ErrorObject | Select @{ n = 'CategoryInfo' ; e = {($_.CategoryInfo | fl * -Force | Out-String).trim() } } , # Format-List -Force  -> um mehr Informationen zu erhalten (PowerShell gibt sonst nicht alle Infos aus)                  
							@{ n = 'ErrorDetails' ; e = {($_.ErrorDetails | fl * | Out-String).trim() } } , 
							@{ n = 'Exception' ; e = {($_.Exception | fl * -Force | Out-String).trim() } } , # Format-List -Force  -> um mehr Informationen zu erhalten (PowerShell gibt sonst nicht alle Infos aus)                   
							@{ n = 'FullyQualifiedErrorId' ; e = {($_.FullyQualifiedErrorId | fl * | Out-String).trim() } } , 
							@{ n = 'InvocationInfo' ; e = {($_.InvocationInfo | fl * -Force | Out-String).trim() } } , # Format-List -Force  -> um mehr Informationen zu erhalten (PowerShell gibt sonst nicht alle Infos aus)                
							@{ n = 'PipelineIterationInfo' ; e = {($_.PipelineIterationInfo | fl * | Out-String).trim() } } , 
							@{ n = 'PSMessageDetails' ; e = {($_.PSMessageDetails | fl * | Out-String).trim() } } , 
							@{ n = 'ScriptStackTrace' ; e = {($_.ScriptStackTrace | fl * | Out-String).trim() } } , 
							@{ n = 'TargetObject' ; e = {($_.TargetObject | fl * | Out-String).trim() } } , * -ErrorAction Ignore 
						) | format-list * | out-string  # Formatiere als Liste und erstelle ein String aus dem Object
					).trim() # Entferne führende (leading) und endende (trailing) Leerzeichen/Leerzeilen (das Object gibt beim Umwandeln zum String immer beides aus)
				) -split "`r`n" | % { "$($Tab*2)$_" }  # Splitte den erstellten (Komplett-)String in separate Strings, um pro Zeile ein Tabstop hinzufügen zu können
			) -join $CrLf # Füge die Strings wieder zusammen
			$Message+= $CrLf # Füge eine neue Zeile an
			$Message+= '-' * 200  # Füge ein '-' als Erkennungs- und Trennzeichenmerkmal an
		} 
	} 
	
	End { 
		$Message # String Ausgabe
	} 
} 

########################################################################################           
#Test Function           
Function Get-MyItem { 
	[CmdletBinding()] # Wichtig, damit die -ErrorAction verfügbar werden in eigenen Funktionen; Bei Built-Ins bereits vorhanden
	Param (
		[string] $Path 
	) 
	Process { 
		Get-Item $Path 
	} 
} 

#Test Execution             
Try { 
	Get-MyItem 'C:\System Volume Information' -ErrorAction Stop # Ohne "-ErrorAction Stop" wird der detaillierte Error nicht reportet  
	# Anderes Beispiel: 
	# Get-CimInstance Win342_tegsgf -ErrorAction stop 
} Catch { 
	write-host(Out-Error $_) -ForegroundColor Red 
}


Error-Output:
Man beachte insbesondere den ScriptStackTrace, welche auf die Zeile des Funktions-Aufrufes im Script (58), als auch auf die verursachende Zeile in der Funktion (52) aufmerksam macht!
Code:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Could not find item C:\System Volume Information.

 		CategoryInfo          : Category   : ObjectNotFound
		                        Activity   : Get-Item
		                        Reason     : IOException
		                        TargetName : C:\System Volume Information
		                        TargetType : String
		ErrorDetails          : 
		Exception             : Message        : Could not find item C:\System Volume Information.
		                        Data           : {}
		                        InnerException : 
		                        TargetSite     : 
		                        StackTrace     : 
		                        HelpLink       : 
		                        Source         : 
		                        HResult        : -2146232800
		FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetItemCommand
		InvocationInfo        : MyCommand             : Get-Item
		                        BoundParameters       : {}
		                        UnboundArguments      : {}
		                        ScriptLineNumber      : 52
		                        OffsetInLine          : 3
		                        HistoryId             : 34
		                        ScriptName            : 
		                        Line                  : 		Get-Item $Path 
		                                                
		                        PositionMessage       : At line:52 char:3
		                                                +         Get-Item $Path
		                                                +         ~~~~~~~~~~~~~~
		                        PSScriptRoot          : 
		                        PSCommandPath         : 
		                        InvocationName        : Get-Item
		                        PipelineLength        : 0
		                        PipelinePosition      : 0
		                        ExpectingInput        : False
		                        CommandOrigin         : Internal
		                        DisplayScriptPosition :
		PipelineIterationInfo : 
		PSMessageDetails      : 
		ScriptStackTrace      : at Get-MyItem<Process>, <No file>: line 52
		                        at <ScriptBlock>, <No file>: line 58
		TargetObject          : C:\System Volume Information 
 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Ergänzung ()


Anders Error-Output Beispiel mit WinSCP .NET Assembly
:

Code:
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Exception calling "Open" with "1" argument(s): "Host "ftp.xxxxx.com" does not exist."

 		CategoryInfo          : Category   : NotSpecified
		                        Activity   : 
		                        Reason     : MethodInvocationException
		                        TargetName : 
		                        TargetType :
		ErrorDetails          : 
		Exception             : ErrorRecord                 : Exception calling "Open" with "1" argument(s): "Host "ftp.xxxxx.com" does not exist."
		                        WasThrownFromThrowStatement : False
		                        Message                     : Exception calling "Open" with "1" argument(s): "Host "ftp.xxxxx.com" does not exist."
		                        Data                        : {System.Management.Automation.Interpreter.InterpretedFrameInfo}
		                        InnerException              : WinSCP.SessionRemoteException: Host "ftp.xxxxx.com" does not exist.
		                                                         at WinSCP.SessionLogReader.Read(LogReadFlags flags)
		                                                         at WinSCP.ElementLogReader.Read(LogReadFlags flags)
		                                                         at WinSCP.SessionElementLogReader.Read(LogReadFlags flags)
		                                                         at WinSCP.CustomLogReader.TryWaitForNonEmptyElement(String localName, LogReadFlags flags)
		                                                         at WinSCP.CustomLogReader.WaitForNonEmptyElement(String localName, LogReadFlags flags)
		                                                         at WinSCP.Session.Open(SessionOptions sessionOptions)
		                                                         at CallSite.Target(Closure , CallSite , Object , Object )
		                        TargetSite                  : Void CheckActionPreference(System.Management.Automation.Language.FunctionContext, System.Exception)
		                        StackTrace                  :    at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
		                                                         at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
		                                                         at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
		                                                         at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
		                        HelpLink                    : 
		                        Source                      : System.Management.Automation
		                        HResult                     : -2146233087
		FullyQualifiedErrorId : SessionRemoteException
		InvocationInfo        : MyCommand             : 
		                        BoundParameters       : {}
		                        UnboundArguments      : {}
		                        ScriptLineNumber      : 76
		                        OffsetInLine          : 2
		                        HistoryId             : -1
		                        ScriptName            : D:\XXXXX\WinSCP-5.11.2-Automation\Test.ps1
		                        Line                  : 	$session.Open($sessionOptions) 
		                                                
		                        PositionMessage       : At D:\XXXXX\WinSCP-5.11.2-Automation\Test.ps1:76 char:2
		                                                +     $session.Open($sessionOptions)
		                                                +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		                        PSScriptRoot          : D:\XXXXX\WinSCP-5.11.2-Automation
		                        PSCommandPath         : D:\XXXXX\WinSCP-5.11.2-Automation\Test.ps1
		                        InvocationName        : 
		                        PipelineLength        : 0
		                        PipelinePosition      : 0
		                        ExpectingInput        : False
		                        CommandOrigin         : Internal
		                        DisplayScriptPosition :
		PipelineIterationInfo : 
		PSMessageDetails      : 
		ScriptStackTrace      : at <ScriptBlock>, D:\XXXXX\WinSCP-5.11.2-Automation\Test.ps11: line 76
		TargetObject          : 
 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
Zuletzt bearbeitet:
Zurück
Oben