我正在编写一个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
,它反射(reflect)了您在(本地计算机)中执行的代码。因此,当您使用throw "error"
时,您可以访问该代码的invocationinfo和异常。
当您使用Invoke-Command
时,您不再执行throw "error"
,而远程计算机则在执行。您(本地计算机)正在执行Invoke-Command ....
,这就是为什么您获得的ErrorRecord
反射(reflect)了这一点(而不是您想要的真正异常)。这是必须的方式,因为异常可能来自远程计算机执行的脚本块,但是它也可能是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
,它们包含来自远程 session 中引发的异常的信息。使用这些,您可以收到“内部”异常。
样本:#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
关于powershell - 如何将ScriptStackTrace保留在远程计算机上的Invoke-Command中引发的异常中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35199777/