powershell - 如何将ScriptStackTrace保留在远程计算机上的Invoke-Command中引发的异常中?

标签 powershell invoke-command

我正在编写一个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

此异常类型具有一些额外的属性,包括SerializedRemoteExceptionSerializedRemoteInvocationInfo,它们包含来自远程 session 中引发的异常的信息。使用这些,您可以收到“内部”异常。
  • SerializedRemoteException:获取Windows PowerShell的远程实例引发的原始异常。
  • SerializedRemoteInvocationInfo:获取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"
    +  ~~~~~~~~~~~~~
    

    然后,您可以编写一个简单的函数来动态提取信息,例如:
    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/

    相关文章:

    powershell - Powershell在ScriptBlock中传递参数

    json - 从Invoke-Command返回的无关数据

    Powershell 错误/异常处理

    SCOM 上的 PowerShell 无法导入模块

    html - Powershell 解析 Html 系列以将其保存在 csv 中

    windows - 如何从 Powershell 设置 Windows 服务权限?

    powershell - 在powershell中格式化每行命令输出

    PowerShell 中的正则表达式从 Active Directory 中的 Managedby 属性获取城市名称

    Powershell 捕获调用命令输出

    powershell - 使用通用参数在本地或远程执行Powershell脚本