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?

2
  • 1
    $myList = Recurse($myPath) Shouldn't that be Recurse $myPath (without parenthesis)? Commented Jul 19, 2017 at 7:47
  • I dont know :) Im new to powershell and I guess Im used to calling function like that Commented Jul 19, 2017 at 8:08

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

3 Comments

calling Recurse inside of Recurse is a key point in actually (recursively) getting all files in all subdirectories though. Will this still work ?
should work actually. lets see if you are facing any issue.
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 :)
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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.