Home > Enterprise >  How to recurse through folders and display relative folder path?
How to recurse through folders and display relative folder path?

Time:06-16

Here is a simple script:

$srcpth = "C:\Users\Mark\Desktop\dummy\"
$files = Get-ChildItem -Path $srcpth -File -Recurse 

foreach ($f in $files) {
    $filen = $f.Name
    $filesize = $f.Length
    Write-Output "$filen $filesize"
    }

This will correctly loop through all subfolders in C:\Users\Mark\Desktop\dummy and output file name with file size, but it will not show relative path. How do I resolve the relative path? Thanks.

EDIT: added below for clarification of desired output:

For example, under C:\Users\Mark\Desktop\dummy are subfolders with files

C:\Users\Mark\Desktop\dummy\file00.txt
C:\Users\Mark\Desktop\dummy\folder01\file01_01.txt
C:\Users\Mark\Desktop\dummy\folder01\file01_02.txt
C:\Users\Mark\Desktop\dummy\folder01\file01_03.txt

C:\Users\Mark\Desktop\dummy\folder02\file02_01.txt
C:\Users\Mark\Desktop\dummy\folder02\file02_01.txt

C:\Users\Mark\Desktop\dummy\folder03\file03_01.txt
C:\Users\Mark\Desktop\dummy\folder03\file03_02.txt
C:\Users\Mark\Desktop\dummy\folder03\file03_03.txt
C:\Users\Mark\Desktop\dummy\folder03\file03_04.txt

Output with above code produces:

file00.txt 9
file01_01.txt 10
file01_02.txt 12
file01_03.txt 12
file02_01.txt 15
file02_01.txt 14
file03_01.txt 11
file03_02.txt 15
file03_03.txt 13
file03_04.txt 12

But what I want is:

file00.txt 9
\folder01\file01_01.txt 10
\folder01\file01_02.txt 12
\folder01\file01_03.txt 12
\folder02\file02_01.txt 15
\folder02\file02_01.txt 14
\folder03\file03_01.txt 11
\folder03\file03_02.txt 15
\folder03\file03_03.txt 13
\folder03\file03_04.txt 12

preceeding \, no slash, or .\ are fine.

CodePudding user response:

Here you go:

$srcpth = "C:\Users\Mark\Desktop\dummy\"
$files = Get-ChildItem -Path $srcpth -File -Recurse 

foreach ($f in $files) {
    $filen = $f.Name
    $filesize = $f.Length
    $relativePath = $f.fullname.remove(0,($srcpth.length))
    Write-Output "$filen $filesize $relativePath"
    }

There aren't any object properties with the value you're looking for. But you can calculate it like above. It's always useful to look at the members of an object when you're trying to figure something like this out:

$files[0] | get-member

This will give you a better idea of what you can work with, what properties you can use, and what methods are available.

CodePudding user response:

I would recommend you to output objects instead of strings as you're doing right now, in any case, you can get the relative paths either using .SubString(..):

foreach ($f in Get-ChildItem -Path $srcpth -File -Recurse) {
    [pscustomobject]@{
        FileName     = $f.Name
        FileSize     = $f.Length
        RelativePath = $f.FullName.Substring($srcpth.Length   1)
    }
}

Or if you're using PowerShell Core, you can access the .NET API Path.GetRelativePath(String, String):

foreach ($f in Get-ChildItem -Path $srcpth -File -Recurse) {
    [pscustomobject]@{
        FileName     = $f.Name
        FileSize     = $f.Length
        RelativePath = [IO.Path]::GetRelativePath($srcpth, $f.FullName)
    }
}

There is also PathIntrinsics.NormalizeRelativePath(String, String) Method available to both, Windows PowerShell and PowerShell Core, though this seems an overkill:

$ExecutionContext.SessionState.Path.NormalizeRelativePath($f.FullName, $srcpth)

CodePudding user response:

While the String.Substring() / .Remove() and [IO.Path]::GetRelativePath() solutions are sufficient when working with only absolute native paths, they fail when the -Path argument for Get-ChildItem is a relative path or a PowerShell-only path (see examples at the end of this answer for how they can fail).

For a solution that additionally supports PowerShell paths and relative paths, I recommend to use Resolve-Path -Relative:

# For this demo, create a Powershell-only path.
$null = New-PSDrive -Name TempDrv -Root ([IO.Path]::GetTempPath()) -PSProvider FileSystem 
$srcpth = 'TempDrv:\RelativePathTest'
$null = New-Item "$srcpth\subdir\test.txt" -Force

# Set base path for Get-ChildItem and Resolve-Path. This is necessary because
# Resolve-Path -Relative resolves paths relative to the current directory.
Push-Location $srcpth

try {
    foreach ($f in Get-ChildItem -File -Recurse) {
        [pscustomobject]@{
            FileName     = $f.Name
            FileSize     = $f.Length
            RelativePath = Resolve-Path $f.FullName -Relative
            # Alternative to remove ".\" or "./" prefix from the path:
            # RelativePath = (Resolve-Path $f.FullName -Relative) -replace '^\.[\\/]'
        }
    }
}
finally {
    # Restore current directory even in case of script-terminating error
    Pop-Location
}

Output:

FileName FileSize RelativePath     
-------- -------- ------------     
test.txt        0 .\subdir\test.txt

Modes of failure:

This is how the String.Substring() method fails for the PowerShell path of the sample above, on my system (you may see a different outcome depending on the location of your temp directory):

FileName FileSize RelativePath  
-------- -------- ------------  
test.txt        0 ubdir\test.txt

And this is how [IO.Path]::GetRelativePath() fails:

FileName FileSize RelativePath  
-------- -------- ------------  
test.txt        0 ..\..\..\..\temp\RelativePathTest\subdir\test.txt
  • Related