3

I have a PowerShell script whose purpose is to get a list of files then do some work on each file.

The list of files is generated by a recursive function like this:

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

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

Separate from this function i do a workflow that takes a list of files and do the work (in parallel) on each file. The code looks something like this:

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

These two parts are then combined with the following logic:

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

The problem is that this generates an error:

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

Why is this happening when the recursive function and the workflow is separate? Is there any way to work around this issue?

Artog
  • 1,132
  • 1
  • 13
  • 25

3 Answers3

1

Recursive calling is not permitted in workflows.

Give your path directly:

Instead of this:

Recurse($myPath)

do this:

Recurse $myPath

You can refer to this article:

Adding Nested functions and Nested Workflows

Ranadip Dutta
  • 8,857
  • 3
  • 29
  • 45
  • calling `Recurse` inside of `Recurse` is a key point in actually (recursively) getting all files in all subdirectories though. Will this still work ? – Artog Jul 19 '17 at 08:06
  • should work actually. lets see if you are facing any issue. – Ranadip Dutta Jul 19 '17 at 08:07
  • It seems like it's the very function definition that is causing the error. I did solve it in another way though, but moving the function to its own module and then importing the function. I did give up the `Recurse($myPath)` syntax for `Recurse $myPath` though :) – Artog Jul 19 '17 at 08:12
0

I eventually (of course just a few minutes after posting the question) solved this by just extracting the function into its own module:

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)
...
}

script.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

This seem to have made the main script to miss that Recurse uses recursion and it works.

Artog
  • 1,132
  • 1
  • 13
  • 25
0

For those looking to use a "parallel" workflow (no recursion) in a module, the solution is similar but a little different.

for example, this workflow can be used to start/stop service in parallel

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
        }
        
    }
}

if you put this in your psm1 file and try to import it, it will fail with this error:

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

To embed this in the module, do NOT put it in the .psm1 file, create a separate ps1 file and place it in the module folder. e.g. Invoke-ServiceInParallelWF.ps1

Then in your manifest (psd1) file, modify the ScriptsToProcess to include the ps1 file.

@{

  # 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')
}

finally, import your module and test the function:

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
Justin
  • 1,303
  • 15
  • 30