Tiny PowerShell Project 2 - Recursion and Call Depth Overflow

Tiny PowerShell Project 2 - Recursion and Call Depth Overflow

Recursion in PowerShell (and in programming in general) refers to a technique where a function or method calls itself repeatedly until a certain condition is met. It is a powerful programming concept used for solving problems that can be broken down into smaller, similar sub-problems.

Recursion is a powerful technique, but it's essential to handle it with care, as it can lead to stack overflow errors if not managed properly. The following code is extracted from a Fivver project in which I was asked to list out and extract a sharedmailbox subfolders.

Add-Type -Assembly "Microsoft.Office.Interop.Outlook"

$Outlook = New-Object -ComObject Outlook.Application
$Namespace = $Outlook.GetNamespace("MAPI")
$Recipient = "sharedmailbox@xx.com"
$RecipientAddressEntry = $Namespace.CreateRecipient($Recipient)
$RecipientAddressEntry.Resolve()

$SharedMailbox = $Namespace.GetSharedDefaultFolder($RecipientAddressEntry, [Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
$Subfolders = $SharedMailbox.Folders

# Create an array to store folder names
$FolderNames = @()

# Loop through the subfolders recursively
function Get-NestedSubfolders($ParentFolder) {
    foreach ($Folder in $ParentFolder.Folders) {
        # Add folder name to the array
        $FolderNames += $Folder.Name

        # Recursive call to retrieve nested subfolders
        Get-NestedSubfolders -ParentFolder $Folder
    }
}

# Start retrieving subfolders
Get-NestedSubfolders -ParentFolder $Subfolders

# Export the folder names array as CSV
$FolderNames | Export-Csv -Path "folder_names.csv" -NoTypeInformation

So let's examine the above code together, the first 8 lines are necessary for getting us access to the sharedmailbox and reading the main folders. $FolderNames variable is an array that we're intending to use for storing the subfolder names. The function Get-NestedSubfolders is a recursive function that loops over the main folders' folders, adds the subfolders of each parent folders in the loop to our array, and calls itself again with that subfolder to see whether there are more nested subfolders within the children of a parent folder.

If you haven't met this error before, the above code could potentially introduce you to the recursion call depth overflow. The error message you could encounter is due to excessive recursion in the code. To avoid the call depth overflow issue, we can modify the script to use a stack-based approach instead of recursion.

Before I show you the refactored code, let's first learn about a structure that can help us. A stack is a collection data structure that follows the Last-In-First-Out (LIFO) principle, meaning the last element added to the stack will be the first one to be removed. It can be visualized as a stack of plates or books, where you add or remove plates from the top.

In PowerShell, System.Collection.Stack provides a way to work with stacks, and it supports various operations like adding items to the stack, removing items from the stack, and checking if the stack is empty.

Here's an example of how to perform CRUD (Create, Read, Update, Delete) operations with a stack using PowerShell:

# Create a new stack
$stack = New-Object System.Collections.Stack

# CRUD operations
# Create (Push): Add elements to the stack
$stack.Push("Apple")
$stack.Push("Banana")
$stack.Push("Orange")

# Read (Peek): View the top element without removing it
$topElement = $stack.Peek()
Write-Output "Top element of the stack: $topElement"

# Read (Iterate): View all elements in the stack (from top to bottom)
Write-Output "Elements in the stack (from top to bottom):"
$stack | ForEach-Object { Write-Output $_ }

# Update (Replace): Pop the top element and push a new element
$replacedElement = $stack.Pop()
$stack.Push("Cherry")

# Read (Iterate): View all elements in the stack after the update
Write-Output "Elements in the stack after the update (from top to bottom):"
$stack | ForEach-Object { Write-Output $_ }

# Delete (Pop): Remove elements from the stack
$removedElement = $stack.Pop()
$removedElement2 = $stack.Pop()

# Read (Iterate): View all elements in the stack after the deletion
Write-Output "Elements in the stack after the deletions (from top to bottom):"
$stack | ForEach-Object { Write-Output $_ }

In this example, we create a new stack using $stack = New-Object System.Collections.Stack. Then we perform CRUD operations on the stack:

  • Create (Push): We add elements "Apple," "Banana," and "Orange" to the stack using $stack.Push().

  • Read (Peek): We view the top element (last added element) without removing it using $stack.Peek().

  • Read (Iterate): We iterate through the elements in the stack using ForEach-Object and view them from top to bottom.

  • Update (Replace): We replace the top element by popping it using $stack.Pop() and then push a new element "Cherry" using $stack.Push().

  • Delete (Pop): We remove elements from the stack using $stack.Pop().

Please note that the stack operations "Peek" and "Pop" will throw an error if the stack is empty. Therefore, it's essential to check if the stack is not empty before performing these operations using $stack.Count -gt 0.

Add-Type -Assembly "Microsoft.Office.Interop.Outlook"

$Outlook = New-Object -ComObject Outlook.Application
$Namespace = $Outlook.GetNamespace("MAPI")
$Recipient = "sharedmailbox@xx.com"
$RecipientAddressEntry = $Namespace.CreateRecipient($Recipient)
$RecipientAddressEntry.Resolve()

$SharedMailbox = $Namespace.GetSharedDefaultFolder($RecipientAddressEntry, [Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)

# Create a stack to store folder objects
$FolderStack = New-Object System.Collections.Stack

# Create an array to store custom folder objects
$FolderObjects = @()

# Push the root folder to the stack
$FolderStack.Push($SharedMailbox)

# Process folders in a loop until the stack is empty
while ($FolderStack.Count -gt 0) {
    $CurrentFolder = $FolderStack.Pop()

    # Create a custom folder object and add it to the array
    $FolderObject = [PSCustomObject]@{
        Name = $CurrentFolder.Name
    }
    $FolderObjects += $FolderObject

    # Add nested subfolders to the stack
    $Subfolders = $CurrentFolder.Folders
    foreach ($Folder in $Subfolders) {
        $FolderStack.Push($Folder)
    }
}

# Export the folder objects array as CSV
$FolderObjects | Export-Csv -Path "folder_names.csv" -NoTypeInformation

In the main stack, we start with our main folders, adding them to the stack. We enter a while loop, which is checking on the existence of items within the stack(i.e. whether elements exist within the stack). Entering into the while loop, we take the last element of the stack as our current folder and turn it into a pscustom object, capturing its name. Then we add that object into the array that we created earlier. As we know arrays are immutable so this action of our create a new array and discard the existing one. Then we check to see whether the current folder has any subfolders and if they do we loop over these sub folders adding them to the stack. Finally, once we meet the exist criteria of the stack(when there is no element left) we're ready to export the folder objects array into csv.

As you can see this is a very strong construct and for problems where recursion is not necessary, using stacks or iterative approaches might be more efficient.

Did you find this article valuable?

Support Application Support by becoming a sponsor. Any amount is appreciated!