在工作流旁边使用递归函数

Using a recursive function beside a workflow

我有一个 PowerShell 脚本,其目的是获取文件列表,然后对每个文件执行一些操作。

文件列表是由这样的递归函数生成的:

function Recurse($path)
{
    .. create $folder

    foreach ($i in $folder.files) { 
        $i
    }
    foreach ($i in $folder.subfolders) {
        Recurse($i.path)
    }
}

与此功能分开,我执行一个工作流,该工作流获取文件列表并对每个文件执行(并行)工作。代码看起来像这样:

workflow Do-Work {
    param(
        [parameter(mandatory)][object[]]$list
    )
    foreach -parallel ($f in $list) {
        inlinescript {
            .. do work on $Using:f
        }
    }
}

这两部分再结合以下逻辑:

$myList = Recurse($myPath)
Do-Work -list $myList

问题是这会产生错误:

A workflow cannot use recursion.
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : RecursiveWorkflowNotSupported

当递归函数和工作流分开时,为什么会发生这种情况? 有什么办法可以解决这个问题吗?

工作流中不允许递归调用。

直接给出路径:

而不是这个:

Recurse($myPath)

这样做:

Recurse $myPath

可以参考这篇文章:

Adding Nested functions and Nested Workflows

我最终(当然是在发布问题后几分钟)通过将函数提取到它自己的模块中解决了这个问题:

get-files.psm1:

function Recurse()
{
    params(
        [parameter(mandatory)][string]$path
    )
    .. create $folder

    foreach ($i in $folder.files) { 
        $i
    }
    foreach ($i in $folder.subfolders) {
        Recurse($i.path)
    }
}

Export-ModuleMember -Function Recurse

get-files.psd1:

@{
...
FunctionsToExport = @(Recurse)
...
}

脚本.ps1:

workflow do-work {
    params(
        [parameter(mandatory)][object[]]$files
    )
    ...
}
Import-Module -Name "path\to\module\get-files.psm1"
$files = Recurse -path $myPath

do-work -files $files

这似乎让主脚本错过了 Recurse 使用递归并且它有效。

对于那些希望在模块中使用“并行”工作流(无递归)的人,解决方案类似但略有不同。

例如,此工作流可用于start/stop并行服务

Workflow Invoke-ServiceInParallelWF
{
    <#
    .SYNOPSIS
    Workflow to stop/start services in parallel on a server.
    .DESCRIPTION
    Utilizes a workflow to start/stop services running on a server in parallel to shorten the start/stop duration.
    #>
    Param(
        [Parameter(Mandatory=$true)]
        [string[]]$Name,
        [Parameter(Mandatory=$true)]
        [ValidateSet("Start","Stop")]
        [string]$Action
    )

    if (!($Name.Count -gt 0))
    {
        throw "No services provided!"
    }
    
    # run parrallel on services argument
    foreach -parallel ($svc in $Name){
    
        InlineScript{
        
            #build object first for consistency
            $props=[ordered]@{
                Service=$using:svc;
                Action=$using:action
                Result=$null
                Error=$null
            }
            
            # Wrap in exception handler
            Try{
                #Perform the desired service action
                if ($using:action -eq 'stop') {
                    Stop-Service -name $using:svc -ErrorAction stop
                } elseif ($using:action -eq 'start') {
                    Start-Service -name $using:svc -ErrorAction stop
                } else {
                    $Action='unknown'
                }
                $props.Result='Success'
            }
            Catch{
                $props.Result='Fail'
                $props.Error="$_"
            }
            
            # generate object back to workflow
            New-Object -TypeName PSCustomObject -Property $props
        }
        
    }
}

如果将其放入 psm1 文件并尝试导入它,它将失败并出现以下错误:

At C:\Source\Powershell\Common\Modules\MyModule\MyModule.psm1:1 char:1
+ #
+ ~
A workflow cannot use recursion.
    + CategoryInfo          : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : RecursiveWorkflowNotSupported

要将其嵌入到模块中,请勿将其放入 .psm1 文件中,创建一个单独的 ps1 文件并将其放入模块文件夹中。 例如调用 ServiceInParallelWF.ps1

然后在您的清单 (psd1) 文件中,修改 ScriptsToProcess 以包含 ps1 文件。

@{

  # Script module or binary module file associated with this manifest.
  RootModule = 'MyModule.psm1'

  # Version number of this module.
  ModuleVersion = '1.47.1'

  # ID used to uniquely identify this module
  GUID = 'bd4390dc-a8ad-4bce-8d69-f53ccf8e4163'

  # Author of this module
  Author = 'Justin Marshall'

  # Script files (.ps1) that are run in the caller's environment prior to importing this module.
  ScriptsToProcess = @('Invoke-ServiceInParallelWF.ps1')
}

最后,导入您的模块并测试功能:

PS C:\source\powershell> Import-Module MyModule -force

PS C:\source\powershell> Invoke-ServiceInParallelWF -Action Start -Name w3svc

Service               : w3svc
Action                : Start
Result                : Success
Error                 :
PSComputerName        : localhost
PSSourceJobInstanceId : 1a564d5d-f363-44b7-a27e-88670764de2d