Powershell 自定义递归和 BeginInvoke()

Powershell Custom Recursion and BeginInvoke()

我需要一些有关异步 Powershell 调用的帮助,该调用因 (1) 超出最大递归深度或 (2) 无效输入参数而失败。

基本设置如下...

异步调用是否有什么东西扰乱了我的代码,以至于 $NestLevel 和 $MaxDepth 之间的比较无效或者 $MaxDepth 设置为 0?

我已经检查了一百次,但我没有看到 $MaxDepth 被设置为低于 1 的任何值。"if ( $NestLevel -gt $ MaxDepth ) { return }" 应该终止递归但看起来不像是这样。

我是不是漏掉了什么?

代码示例...

function Test-Recursion {
            Param (
                [Parameter(Mandatory=$false)]
                $InputObject,
        
                [Parameter(Mandatory=$false)]
                [ValidateRange(1,100)]
                [Int16] $MaxDepth   = 2
            )
        
            [Int16] $_NestLevel_ += 1
            if ( $_NestLevel_ -gt $MaxDepth ) { return }
        
            "<item><nest_level>$($_NestLevel_)</nest_level><max_depth>$($MaxDepth)</max_depth></item>"
            & $MyInvocation.MyCommand.ScriptBlock -InputObject $InputObject -MaxDepth $MaxDepth
        }
        
function Get-Info {
                Param (
                    [Parameter(Mandatory=$true)]
                    [String] $HostName,
        
                    [Parameter(Mandatory=$true)]
                    [ScriptBlock] $ConvertTo
                )
                
                $some_collection | `
                ForEach-Object {
                    @(
                        $_as  | ForEach-Object {
                            & $ConvertTo -InputObject $_ -MaxDepth 3 
                        }
                    ) | Sort-Object
                }
        }
        
        # this code fails with either...
        #   - excessive recursion depth
        #   - invalid $MaxDepth value of 0
        try {
            $_ps    = [powershell]::Create()
            $null   = $_ps.AddScript(${function:Get-Info})
            $null   = $_ps.AddParameter('HostName', 'some_server_name')
            $null   = $_ps.AddParameter('ConvertTo', ${function:Test-Recursion}) 
        
            $_handle    = $_ps.BeginInvoke()
            while ( -Not $_handle.IsCompleted ) {
                Start-Sleep -Seconds 5
            }
        
            $_result    = $_ps.EndInvoke($_handle)
            $_result
        } catch {
            $_
        } finally {
            $_ps.Dispose()
        }
        
        # this code works as expected
        $items = Get-Info -HostName 'some_server_name' -ConvertTo ${function:Test-Recursion}
        $items.table_data

我不会详细说明您希望使用这些功能完成什么,只会指出问题所在,您的 powershell instance 是 运行由于 无限循环 导致的堆栈内存由您的 Test-Recursion 函数导致 不知道何时停止 ,因此添加了 新参数 ([Int16] $NestingLevel) 将作为参数传递给 每次递归调用:

function Test-Recursion {
    Param (
        [Parameter(Mandatory = $false)]
        $InputObject,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1,100)]
        [Int16] $MaxDepth = 2,

        [Parameter(DontShow)]
        [Int16] $NestingLevel
    )

    [Int16] $NestingLevel += 1
    if ( $NestingLevel -gt $MaxDepth ) {
        return
    }

    "<item><nest_level>$NestingLevel</nest_level><max_depth>$MaxDepth</max_depth></item>"
    Test-Recursion -InputObject $InputObject -MaxDepth $MaxDepth -NestingLevel $NestingLevel
}

Test-Recursion -InputObject hello -MaxDepth 3

还值得注意的是,您的递归函数可以使用 while 循环进行简化:

function Test-Recursion {
    Param (
        [Parameter(Mandatory = $false)]
        $InputObject,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1,100)]
        [Int16] $MaxDepth = 2,

        [Parameter(DontShow)]
        [Int16] $NestingLevel
    )

    while($NestingLevel++ -lt $MaxDepth) {
        "<item><nest_level>$NestingLevel</nest_level><max_depth>$MaxDepth</max_depth></item>"
    }
}

关于您的最新评论:

The root call initializes the variable to 1. Recursive calls would increment the variable and effectively provide a way to check the current nesting level.

不,变量在函数的每次调用中都存在,然后增量值在递归调用后丢失。但是,您可以在 $script: scope 中初始化变量,然后它将起作用:

[Int16] $script:_NestLevel_ += 1

就个人而言,我不喜欢在我的函数中使用 $script: 作用域变量,我认为有更好的方法来做到这一点。

一个简单的演示方法是修改函数以在每次调用时输出 $_NestLevel_,as-is,您会看到控制台显示无限量的 1 .

function Test-Recursion {
    Param (
        [Parameter(Mandatory = $false)]
        $InputObject,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1,100)]
        [Int16] $MaxDepth = 2
    )

    [Int16] $_NestLevel_ += 1
    if ( $_NestLevel_ -gt $MaxDepth ) { return }
    $_NestLevel_
    & $MyInvocation.MyCommand.ScriptBlock -InputObject $InputObject -MaxDepth $MaxDepth
}