首页 > 解决方案 > 在 ForEach-Object -Parallel 块内从自身递归调用函数 - 在并行块内无法识别函数

问题描述

第一次在这里提问。请善待:)

我试图以并行方式递归获取所有目录,以期减少遍历驱动器所需的时间。下面是我尝试过的代码。基本上我要做的是输入一个文件夹并对其子文件夹及其子文件夹等并行执行相同的操作,但是在并行块内无法识别该功能

function New-RecursiveDirectoryList {
    [CmdletBinding()]
    param (
        # Specifies a path to one or more locations.
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Path to one or more locations.')]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Path
    )
    process {
        foreach ($aPath in $Path) {
            Get-Item $aPath

            Get-ChildItem -Path $aPath -Directory |
                # Recursively call itself in Parallel block not working
                # Getting error "The term 'New-RecursiveDirectoryList' is not recognized as a name of a cmdlet"
                # Without -Parallel switch this works as expected
                ForEach-Object -Parallel {
                    $_ | New-RecursiveDirectoryList
                }
        }
    }
}

错误:

New-RecursiveDirectoryList: 
Line |
   2 |                      $_ | New-RecursiveDirectoryList
     |                           ~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The term 'New-RecursiveDirectoryList' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

我也尝试在这里使用 mklement0 提供的解决方案,但没有运气。以下是我对此的尝试:

    function CustomFunction {
    [CmdletBinding()]
    param (
        # Specifies a path to one or more locations.
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Path to one or more locations.')]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Path
    )

    begin {
        # Get the function's definition *as a string*
        $funcDef = $function:CustomFunction.ToString()
    }

    process {
        foreach ($aPath in $Path) {
            Get-Item $aPath

            Get-ChildItem -Path $aPath -Directory |
                # Recursively call itself in Parallel block not working
                # Getting error "The term 'New-RecursiveDirectoryList' is not recognized as a name of a cmdlet"
                # Without -Parallel switch this works as expected
                ForEach-Object -Parallel {
                    $function:CustomFunction = $using:funcDef
                    $_ | CustomFuction
                }
        }
    }
}

错误

CustomFuction: 
Line |
   3 |                      $_ | CustomFuction
     |                           ~~~~~~~~~~~~~
     | The term 'CustomFuction' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

有谁知道这可能是如何实现的或以不同的方式做到这一点?

标签: powershell

解决方案


所以,这对我有用,它显然看起来不漂亮。需要注意的一件事是,foreach ($aPath in $Path) {...}脚本上的 是不必要的,process {...}当您传递多个路径时,该块将为您处理。

代码:

function Test {
    [CmdletBinding()]
    param (
        # Specifies a path to one or more locations.
        [Parameter(Mandatory)]
        [Alias('PSPath')]
        [string[]]$Path
    )

    begin {
        $scriptblock = $MyInvocation.MyCommand.ScriptBlock.ToString()
    }

    process {
        # Get-Item $Path <= This will slow down the script
        Get-ChildItem -Path $Path -Directory | ForEach-Object -Parallel {
            $_ # You can do this instead
            $i = $using:scriptblock
            $thisIsNotPretty = [scriptblock]::Create($i)
            & $thisIsNotPretty -Path $_
        }
    }
}

您还可以执行类似这样的操作来显示文件夹层次结构非常酷(我在为AD Groups制作的功能上使用了非常相似的功能):

function Test {
    [CmdletBinding()]
    param (
        # Specifies a path to one or more locations.
        [Parameter(Mandatory)]
        [Alias('PSPath')]
        [string[]]$Path,
        [int]$Nesting = 0
    )

    begin {
        $scriptblock = $MyInvocation.MyCommand.ScriptBlock.ToString()
    }

    process {

        Get-ChildItem -Path $Path -Directory | ForEach-Object -Parallel {

            function Indent{
                param(
                    [String]$String,
                    [Int]$Indent
                )
                
                $x='_';$y='|';$z='    '
                
                switch($Indent)
                {
                    {$_ -eq 0}{return $String}
                    {$_ -gt 0}{return "$($z*$_)$y$x $string"}    
                }
            }

            $z = $using:Nesting

            [pscustomobject]@{
                Nesting = $z
                Path = Indent -String $_.FullName -Indent $z
            }

            $z++
            $i = $using:scriptblock
            $thisIsNotPretty = [scriptblock]::Create($i)
            & $thisIsNotPretty -Path $_ -Nesting $z
        }
    }
}

[System.Collections.ArrayList]$result = Test -Path /home/user/Documents

function Draw-Hierarchy{
    param(
        [System.Collections.ArrayList]$Array
    )
    
    $Array.Reverse()
    
    for($i=0;$i -lt $Array.Count;$i++){
    
        if(
            $Array[$i+1] -and 
            $Array[$i].Path.IndexOf('|_') -lt $Array[$i+1].Path.IndexOf('|_')
        ){
        $z=$i+1
        $ind=$Array[$i].Path.IndexOf('|_')
            while($Array[$z].Path[$ind] -ne '|'){
                $string=($Array[$z].Path).ToCharArray()
                $string[$ind]='|'
                $string=$string -join ''
                $Array[$z].Path=$string
                $z++
                if($Array[$z].Path[$ind] -eq '|'){break}
                }
            }
        }
    
    $Array.Reverse()
    return $Array
    
}

Draw-Hierarchy $result

结果看起来像这样:

Nesting Path
------- ----
      0 /home/user/Documents/...
      1     |_ /home/user/Documents/...
      1     |_ /home/user/Documents/...
      1     |_ /home/user/Documents/...
      2     |   |_ /home/user/Documents/...
      1     |_ /home/user/Documents/...
      1     |_ /home/user/Documents/...
      2     |   |_ /home/user/Documents/...
      1     |_ /home/user/Documents/...
      1     |_ /home/user/Documents/...
      3     |       |_ /home/user/Documents/...
      1     |_ /home/user/Documents/...
      3             |_ /home/user/Documents/...
      3             |_ /home/user/Documents/...
      4                 |_ /home/user/Documents/...
      5                     |_ /home/user/Documents/...

推荐阅读