如何在远程计算机上的 Invoke-Command 内抛出的异常中保留 ScriptStackTrace? [英] How do I retain ScriptStackTrace in an exception thrown from within an Invoke-Command on a remote computer?

查看:49
本文介绍了如何在远程计算机上的 Invoke-Command 内抛出的异常中保留 ScriptStackTrace?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个 Powershell 脚本,它执行我的构建/部署过程中的一个步骤,它需要在远程机器上运行一些操作.该脚本相对复杂,因此如果在该远程活动期间发生错误,我需要详细的堆栈跟踪,以了解错误发生在脚本中的哪个位置(超出已生成的日志记录).

I'm writing a Powershell script which executes one of the steps in my build/deploy process, and it needs to run some actions on a remote machine. The script is relatively complex, so if an error occurs during that remote activity I want a detailed stack trace of where in the script the error occurred (over and above the logging that is already produced).

问题在于 Invoke-Command 在从远程机器中继终止异常时丢失堆栈跟踪信息.如果在本地机器上调用了一个脚本块:

The problem arises in that Invoke-Command loses stack trace information when relaying terminating exceptions from a remote machine. If a script block is invoked on the local machine:

Invoke-Command -ScriptBlock {
    throw "Test Error";
}

返回所需的异常详细信息:

The required exception detail is returned:

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 块:

The exception stack trace points to the whole Invoke-Command block:

Test Error
At C:\ScriptTest\Test2.ps1:3 char:1
+ Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Test Error:String) [], RuntimeException
    + FullyQualifiedErrorId : Test Error

我可以手动将异常传输回本地机器:

I can transport the exception back to the local machine manually:

$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
    try
    {
        throw "Test Error";
    }
    catch
    {
        return $_;
    }
}

throw $exception;

但是重新抛出它会丢失堆栈跟踪:

But re-throwing it loses the stack trace:

Test Error
At C:\ScriptTest\Test2.ps1:14 char:1
+ throw $exception;
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Test Error:PSObject) [], RuntimeException
    + FullyQualifiedErrorId : Test Error

如果我将异常写入输出:

If I write the exception to Output:

$exception = Invoke-Command -ComputerName $remoteComputerName -ScriptBlock {
    try
    {
        throw "Test Error";
    }
    catch
    {
        return $_;
    }
}

Write-Output $exception;

我得到了正确的堆栈跟踪信息:

I get the correct stack trace information:

Test Error
At line:4 char:3
+         throw "Test Error";
+         ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Test Error:String) [], RuntimeException
    + FullyQualifiedErrorId : Test Error

但由于它不在错误流中,因此我的构建工具无法正确拾取它.如果我尝试 Write-Error,我会遇到类似的问题,即重新抛出异常并且堆栈跟踪指向脚本的错误部分.

But as it's not on the Error stream it isn't picked up correctly by my build tools. If I try Write-Error, I have a similar problem to re-throwing the exception and the stack trace points to the wrong part of the script.

所以我的问题是 - 如何让 Powershell 从远程机器报告异常,就像它是在本地引发的一样,具有相同的堆栈跟踪信息和错误流?

So my question is - how do I get Powershell to report the exception from a remote machine as if it had been raised locally, with the same stack trace information and on the Error stream?

推荐答案

当您运行某些代码但它失败时,您会收到一个 ErrorRecord 反映代码 (本地计算机)执行.因此,当您使用 throw "error" 时,您可以访问该代码的调用信息和异常.

When you run some code and it fails, you receive an ErrorRecord that reflects the code you (local computer) executed. So when you use throw "error" you can access the invocationinfo and exception for that code.

当您使用 Invoke-Command 时,您不再执行 throw "error",远程计算机是.您(本地计算机)正在执行 Invoke-Command ....,这就是为什么您得到的 ErrorRecord 反映了这一点(而不是您想要的真正异常).这是必须的方式,因为异常可能来自远程计算机执行的脚本块,但它也可能是 Invoke-Command 本身的异常,因为它无法连接到远程计算机或类似设备.

When you use Invoke-Command, you are not executing throw "error" anymore, the remote computer is. You (local computer) are executing Invoke-Command ...., which is why the ErrorRecord you get reflects that (and not the real exception like you wanted). This is the way it has to be since an exception may be coming from the scriptblock the remote comptuer executed, but it could just as well be an exception from Invoke-Command itself because it couldn't connect to the remote computer or something similar.

当异常最初是在远程计算机上抛出时,Invoke-Command/PowerShell 会抛出一个 RemoteException 在本地计算机上.

When the exception is originally thrown on the remote computer, Invoke-Command/PowerShell throws a RemoteException on the local computer.

#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

此异常类型有一些额外的属性,包括 SerializedRemoteExceptionSerializedRemoteInvocationInfo,其中包含来自远程会话中抛出的异常的信息.使用这些,您可以收到内部"异常.

This exception-type has a few extra properties, including SerializedRemoteException and SerializedRemoteInvocationInfo which contains the information from the exception that was thrown in the remote session. Using these, you can receive the "internal" exception.

  • SerializedRemoteException: Gets the original exception that was thrown by the remote instance of Windows PowerShell.
  • SerializedRemoteInvocationInfo: Gets the invocation information for the remote instance of Windows PowerShell.

示例:

#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"
+  ~~~~~~~~~~~~~

然后您可以编写一个简单的函数来动态提取信息,例如:

You can then write a simple function to extract the information dynamically, ex:

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 中提取它.

Be aware that the SerializedRemoteExpcetion is shown as PSObject because of the serialization/deserialization during network transfer, so if you're going to check the exception-type you need to extract it from 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

这篇关于如何在远程计算机上的 Invoke-Command 内抛出的异常中保留 ScriptStackTrace?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆