使用WinRM for ScriptMethods增加堆栈大小

时间:2017-01-24 07:48:08

标签: powershell winrm

我们目前正在重构我们的管理脚本。 刚刚出现的WinRM,错误处理和ScriptMethod的组合大大降低了可用的递归深度。

请参阅以下示例:

Invoke-Command -ComputerName . -ScriptBlock {
    $object = New-Object psobject
    $object | Add-Member ScriptMethod foo {
        param($depth)
        if ($depth -eq 0) {
            throw "error"
        }
        else {
            $this.foo($depth - 1)
        }
    }

    try {
        $object.foo(5) # Works fine, the error gets caught
    } catch {
        Write-Host $_.Exception
    }

    try {
        $object.foo(6) # Failure due to call stack overflow
    } catch {
        Write-Host $_.Exception
    }
}

只有六次嵌套调用足以溢出调用堆栈! 实际上,超过200个本地嵌套调用工作正常,没有try-catch,可用深度加倍。常规函数也不限于递归。

注意:我只使用递归来重现问题,真正的代码在不同模块中的不同对象上包含许多不同的函数。所以琐碎的优化就像使用函数而不是ScriptMethod"需要架构更改

有没有办法增加可用的堆栈大小? (我有一个管理帐户。)

2 个答案:

答案 0 :(得分:0)

你有两个问题合谋使这个困难。如果可能的话,那么通过增加堆栈大小来最有效地解决这个问题(我不知道是不是这样)。

首先,正如您所经历的那样,远程处理会增加减少可用堆栈的调用的开销。我不知道为什么,但很容易证明它确实如此。这可能是由于配置了运行空间的方式,或者如何调用解释器,或者由于增加了簿记 - 我不知道最终的原因。

第二个也是更诅咒,你的方法会产生一堆嵌套的异常,而不仅仅是一个。发生这种情况是因为脚本方法实际上是一个包含在另一个异常处理程序中的脚本块,该异常处理程序将异常重新抛出为MethodInvocationException。因此,当您调用foo(N)时,会设置一个嵌套异常处理程序块(释义,实际上并不是PowerShell代码):

try {
    try {
         ...
         try {
             throw "error"
         } catch {
             throw [System.Management.Automation.MethodInvocationException]::new(
                 "Exception calling ""foo"" with ""1"" argument(s): ""$($_.Exception.Message)""", 
                 $_.Exception
             )
         }
         ...
     } catch {
         throw [System.Management.Automation.MethodInvocationException]::new(
             "Exception calling ""foo"" with ""1"" argument(s): ""$($_.Exception.Message)""", 
             $_.Exception
         )
     }
 } catch {
     throw [System.Management.Automation.MethodInvocationException]::new(
         "Exception calling ""foo"" with ""1"" argument(s): ""$($_.Exception.Message)""", 
         $_.Exception
     )
 }

这会产生大量的堆栈跟踪,最终溢出所有合理的边界。当您使用远程处理时,问题因为即使脚本执行并产生这个巨大的异常,它(以及函数产生的任何结果)也无法成功远程 - 在我的机器上使用PowerShell 5,当我调用foo(10)时,我没有收到堆栈溢出错误,但发生了远程错误。

这里的解决方案是避免递归脚本方法和异常的这种特殊致命组合。假设你不想摆脱递归或异常,通过包装常规函数最容易做到这一点:

$object = New-Object PSObject
$object | Add-Member ScriptMethod foo {
    param($depth)

    function foo($depth) {
        if ($depth -eq 0) {
            throw "error"
        }
        else {
            foo ($depth - 1)
        }
    }
    foo $depth
}

虽然这会产生更多令人愉快的异常,但是当您进行远程处理时,即使这样也很快就会耗尽堆栈。在我的机器上,这可以达到foo(200);除此之外,我得到了一个呼叫深度溢出。在本地,限制要高得多,尽管PowerShell通过大量参数得到了不合理的缓慢。

作为一种脚本语言,PowerShell并非完全旨在有效地处理递归。如果您需要超过foo(200),我的建议是咬紧牙关并重写函数,这样它就不会递归。像Stack<T>这样的课程可以在这里提供帮助:

$object = New-Object PSObject
$object | Add-Member ScriptMethod foo {
    param($depth)

    $stack = New-Object System.Collections.Generic.Stack[int]
    $stack.Push($depth)

    while ($stack.Count -gt 0) {
        $item = $stack.Pop()
        if ($item -eq 0) {
            throw "error"
        } else {
            $stack.Push($item - 1)
        }
    }
}

显然foo是一个简单的尾递归,这是过度的,但它说明了这个想法。迭代可能会在堆栈中推送多个项目。

这不仅消除了堆栈深度有限的任何问题,而且速度也快得多。

答案 1 :(得分:0)

如果您超出远程会话中的可用内存,可能值得检查一下:Running Java remotely using PowerShell

我知道它是用于运行Java应用程序,但该解决方案会更新远程WinRM会话可用的最大内存。