When a function calls itself, this is called “recursion”. You can see this technique often when a script wants to traverse part of the filesystem: a function processes folder content, and when it encounters a subfolder, it calls itself.

Recursion can be powerful but it’s very hard to debug and potentially dangerous because when you do a mistake, you end up in endless loops. Also, there is always the risk of a stack overflow when recursion depth is too high.

Many tasks that typically require recursion can also be designed by using a “queue”: when your code encounters a new task, instead of calling itself again, the new task is placed on the queue, and once the initial task is done, anything left on the queue is tackled.

Thanks to Lee Holmes, here is a simple sample that traverses the entire drive C:\ but does not use recursion. Instead, you can see the queue in action:

# get a new queue
[System.Collections.Queue]$queue = [System.Collections.Queue]::new()
# place the initial search path(s) into the queue
$queue.Enqueue('c:\')
# add as many more search paths as you need
# they will eventually all be traversed
#$queue.Enqueue('D:\')

# while there are still elements in the queue...
    while ($queue.Count -gt 0)
    {
        # get one item off the queue
        $currentDirectory = $queue.Dequeue()
        try
        {
            # find all subfolders and add them to the queue
            # a classic recurse approach would have called itself right here
            # this approach instead pushes the future tasks just onto
            # the queue for later use
            [IO.Directory]::GetDirectories($currentDirectory) | ForEach-Object {
                $queue.Enqueue($_)
            }
        }
        catch {}
    
        try
        {
            # find all files in this folder with the given extensions
            [IO.Directory]::GetFiles($currentDirectory, '*.psm1')
            [IO.Directory]::GetFiles($currentDirectory, '*.ps1')
        }
        catch{}
    }  




Twitter This Tip! ReTweet this Tip!

Anonymous
  • Interesting, for sure, and thanks for posting it.

    I have some comments:

    • You might consider whether a specific implementation of this concept would benefit from specifying the Queue size and growth factor.
    • By enqueueing everything, you don't get the benefit of pipelining.
    • [IO.Directory]::GetDirectories() fails when it hits an error, and you get nothing back. A better, PowerShell, solution would be to use Get-ChildDirectories, which will return what it can, and continue.

    I know this tip was written generically as an alternative to recursion, but my comments still stand as things to watch out for, for any PowerShell recursion alternative.

    For this specific solution, however, I think a more "PowerShell" solution would be to do something as simple as the code below, adding in additional logic for whatever work you need to do within each directory or with each file:

    Get-ChildDirectories -Recurse | ForEach-Object { Get-ChildItem $_.FullName -File | Where-Object { $_.Extension -in (".psm1", ".ps1")} }

    If you don't need to do anything with the folders, then it's simpler still:

    Get-ChildItem $_.FullName -File -Recurse | Where-Object { $_.Extension -in (".psm1", ".ps1")}


    I don't mean to nit pick at the posted tip/concept. I simply mean to focus attention to PowerShell, given this is a PowerShell tips site. Many problems that recursive solutions in other languages simply don't, with PowerShell. If you're someone who is experienced enough to consider rewriting a recursive routine because you're hitting a Stack Overflow issue, you probably don't need my comments anyway.