Recursive Directory Traversal to a Specific Depth in PowerShell

Get-ChildItemToDepth

The Get-ChildItemToDepth function acts as a wrapper around Get-ChildItem facilitating recursive traversal of a path to a specific depth. Whilst what’s covered should be provider agnostic, I’ll be focusing specifically on file system directories.

Although the function serves a single purpose, behind the scenes the implementation differs depending on whether the PowerShell version is pre-5.0 or 5.x, read on for details.

PowerShell Prior to 5.0

Prior to PowerShell 5.0, Get-ChildItem had no built-in method to limit recursive traversal of a path to a specific depth. The Get-ChildItemToDepth function achieves this through the use of a recursive function (a function that calls itself). The recursion logic is a derivative work based on this code snippet written by Chris Dent.

As at the time of writing this post (April 2020), all supported versions of Windows are capable of running PowerShell 5.x. Consequently I don’t tend to encounter anything older than PowerShell 4.0 (the stock version of PowerShell on Windows Server 2012 R2), so that’s all I’ve had the opportunity to test this function against. However, I see no reason for it not to work on even older versions of PowerShell.

PowerShell 5.x

In PowerShell 5.0, the Get-ChildItem cmdlet gained a -Depth parameter, enabling a path to be recursively traversed to a specific depth. Unfortunately in PowerShell 5.x the -Depth parameter has a peculiar quirk that’s exhibited when used against a legitimate path containing one or more wildcard characters. It results in recursive traversal of the entire directory tree.

NB: This behaviour has since been rectified in PowerShell 6.0.

Problem

To illustrate the problem, I recursively traversed "C:\Program File?" (the question mark is a wildcard denoting a single character) with no depth limit (-Recurse) and with a depth limit of two (-Depth 2), using PowerShell 5.1 and 6.0.1 on the same machine. See how in PowerShell 5.1 count returns the same value in both cases.

PS C:\> $PSVersionTable.PSVersion.ToString()
5.1.18362.752

# Recursion with no depth limit.
PS C:\> (Get-ChildItem -Path "C:\Program File?" -Recurse).Count
28296

# Recursion with a depth limit of 2.
PS C:\> (Get-ChildItem -Path "C:\Program File?" -Depth 2).Count
28296
PS C:\> $PSVersionTable.PSVersion.ToString()
6.0.1

# Recursion with no depth limit.
PS C:\> (Get-ChildItem -Path "C:\Program File?" -Recurse).Count
28296

# Recursion with a depth limit of 2.
PS C:\> (Get-ChildItem -Path "C:\Program File?" -Depth 2).Count
2532

Workaround

To overcome this issue, Get-ChildItemToDepth offloads responsibility for expanding a path containing wildcards from Get-ChildItem to Resolve-Path and passes the result(s) to Get-ChildItem -LiteralPath <path> -Depth <n>. The -LiteralPath parameter is used to avoid the expansion of literal wildcard characters in file and directory names.

See example below, it recursively traverses "C:\Test\Sub*" to a depth of two, returning files with the extension XLSX. I’ve deliberately included a subdirectory with square brackets in the name (SubB[1]) just to highlight why Get-ChildItemToDepth uses -LiteralPath behind the scenes.

C:\Test
+---SubA1
|   |   Spreadsheet.xlsx
|   |   TextFile.txt
|   |
|   \---SubA2
|       |   Spreadsheet.xlsx
|       |   TextFile.txt
|       |
|       \---SubA3
|           |   Spreadsheet.xlsx
|           |   TextFile.txt
|           |
|           \---SubA4
|                   Spreadsheet.xlsx
|                   TextFile.txt
|
\---SubB[1]
    |   Spreadsheet.xlsx
    |   TextFile.txt
    |
    \---SubB2
        |   Spreadsheet.xlsx
        |   TextFile.txt
        |
        \---SubB3
            |   Spreadsheet.xlsx
            |   TextFile.txt
            |
            \---SubB4
                    Spreadsheet.xlsx
                    TextFile.txt
Get-ChildItemToDepth -Path "C:\Test\Sub*\" -Filter "*.xlsx" -Depth 2 -File | Select-Object FullName

FullName
--------
C:\Test\SubA1\Spreadsheet.xlsx
C:\Test\SubA1\SubA2\Spreadsheet.xlsx
C:\Test\SubA1\SubA2\SubA3\Spreadsheet.xlsx
C:\Test\SubB[1]\Spreadsheet.xlsx
C:\Test\SubB[1]\SubB2\Spreadsheet.xlsx
C:\Test\SubB[1]\SubB2\SubB3\Spreadsheet.xlsx

Comments

Leaving comments has been disabled for this post.

Copyright © 2018 - 2022 thecliguy.co.uk
For details, see Licences and Copyright