我正在编写一个Powershell脚本,该脚本执行构建/部署过程中的步骤之一,并且需要在远程计算机上运行一些操作。该脚本是相对复杂的,因此,如果在该远程活动期间发生错误,我想详细了解该错误在脚本中何处发生的堆栈跟踪(已经生成的日志之上)。
出现的问题是,当从远程计算机中继终止异常时,Invoke-Command会丢失堆栈跟踪信息。如果在本地计算机上调用了脚本块:
Invoke-Command -ScriptBlock {
throw "Test Error";
}
返回所需的异常详细信息:
Test Error
At C:\ScriptTest\Test2.ps1:4 char:2
+ throw "Test Error";
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
但是,如果远程运行:
Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
throw "Test Error";
}
异常堆栈跟踪指向整个Invoke-Command块:
Test Error
At C:\ScriptTest\Test2.ps1:3 char:1
+ Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
我可以手动将异常传输回本地计算机:
$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
try
{
throw "Test Error";
}
catch
{
return $_;
}
}
throw $exception;
但是重新抛出它会丢失堆栈跟踪:
Test Error
At C:\ScriptTest\Test2.ps1:14 char:1
+ throw $exception;
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:PSObject) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
如果我将异常写入输出:
$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
try
{
throw "Test Error";
}
catch
{
return $_;
}
}
Write-Output $exception;
我得到正确的堆栈跟踪信息:
Test Error
At line:4 char:3
+ throw "Test Error";
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Test Error:String) [], RuntimeException
+ FullyQualifiedErrorId : Test Error
但是由于它不在错误流中,因此我的构建工具没有正确地将其拾取。如果尝试Write-Error,我将遇到与重新引发异常类似的问题,并且堆栈跟踪指向脚本的错误部分。
所以我的问题是-如何使Powershell从远程计算机报告异常,就好像它是在本地引发的一样,具有相同的堆栈跟踪信息并且在Error流上?
当您运行某些代码而失败时,您会收到一个ErrorRecord
,它反映了您(本地计算机)执行的代码。因此,在使用时,throw "error"
您可以访问该代码的invocationinfo和异常。
使用时Invoke-Command
,您将不再执行throw "error"
远程计算机。您(本地计算机)正在执行Invoke-Command ....
,这就是为什么ErrorRecord
您得到反映的原因(而不是您想要的真正异常)。这是必须的方式,因为异常可能来自远程计算机执行的脚本块,但是它也可能是Invoke-Command
自身的异常,因为它无法连接到远程计算机或类似的东西。
最初在远程计算机上引发异常时,Invoke-Command
/ PowerShell在本地计算机上引发RemoteException。
#Generate errors
try { Invoke-Command -ComputerName localhost -ScriptBlock { throw "error" } }
catch { $remoteexception = $_ }
try { throw "error" }
catch { $localexception = $_ }
#Get exeception-types
$localexception.Exception.GetType().Name
RuntimeException
$remoteexception.Exception.GetType().Name
RemoteException
此异常类型具有一些额外的属性,包括SerializedRemoteException
并且SerializedRemoteInvocationInfo
包含来自远程会话中引发的异常的信息。使用这些,您可以收到“内部”异常。
样本:
#Show command that threw error
$localexception.InvocationInfo.PositionMessage
At line:4 char:7
+ try { throw "error" }
+ ~~~~~~~~~~~~~
$remoteexception.Exception.SerializedRemoteInvocationInfo.PositionMessage
At line:1 char:2
+ throw "error"
+ ~~~~~~~~~~~~~
然后,您可以编写一个简单的函数来动态提取信息,例如:
function getExceptionInvocationInfo ($ex) {
if($ex.Exception -is [System.Management.Automation.RemoteException]) {
$ex.Exception.SerializedRemoteInvocationInfo.PositionMessage
} else {
$ex.InvocationInfo.PositionMessage
}
}
function getException ($ex) {
if($ex.Exception -is [System.Management.Automation.RemoteException]) {
$ex.Exception.SerializedRemoteException
} else {
$ex.Exception
}
}
getExceptionInvocationInfo $localexception
At line:4 char:7
+ try { throw "error" }
+ ~~~~~~~~~~~~~
getExceptionInvocationInfo $remoteexception
At line:1 char:2
+ throw "error"
+ ~~~~~~~~~~~~~
请注意,SerializedRemoteExpcetion
显示的PSObject
原因是由于网络传输期间发生了序列化/反序列化,因此,如果要检查异常类型,则需要从中提取它psobject.TypeNames
。
$localexception.Exception.GetType().FullName
System.Management.Automation.ItemNotFoundException
$remoteexception.Exception.SerializedRemoteException.GetType().FullName
System.Management.Automation.PSObject
#Get TypeName from psobject
$remoteexception.Exception.SerializedRemoteException.psobject.TypeNames[0]
Deserialized.System.Management.Automation.ItemNotFoundException
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句