18

I am trying to count the files in all subfolders in a directory and display them in a list.

For instance the following dirtree:

TEST
    /VOL01
        file.txt
        file.pic
    /VOL02
        /VOL0201
            file.nu
            /VOL020101
                file.jpg
                file.erp
                file.gif
    /VOL03
        /VOL0301
            file.org

Should give as output:

PS> DirX C:\TEST

Directory              Count
----------------------------
VOL01                      2
VOL02                      0
VOL02/VOL0201              1
VOL02/VOL0201/VOL020101    3
VOL03                      0
VOL03/VOL0301              1

I started with the following:

Function DirX($directory)
{
    foreach ($file in Get-ChildItem $directory -Recurse)
    {
        Write-Host $file
    }
}

Now I have a question: why is my Function not recursing?

Pr0no
  • 3,910
  • 21
  • 74
  • 121
  • You code snippet does work as intended it shows me everyfile underneath $directory. The logic as it stands will not get your desired output. – Matt Aug 26 '14 at 13:41

6 Answers6

42

Something like this should work:

dir -recurse |  ?{ $_.PSIsContainer } | %{ Write-Host $_.FullName (dir $_.FullName | Measure-Object).Count }
  • dir -recurse lists all files under current directory and pipes (|) the result to
  • ?{ $_.PSIsContainer } which filters directories only then pipes again the resulting list to
  • %{ Write-Host $_.FullName (dir $_.FullName | Measure-Object).Count } which is a foreach loop that, for each member of the list ($_) displays the full name and the result of the following expression
  • (dir $_.FullName | Measure-Object).Count which provides a list of files under the $_.FullName path and counts members through Measure-Object

  • ?{ ... } is an alias for Where-Object

  • %{ ... } is an alias for foreach
David Brabant
  • 41,623
  • 16
  • 83
  • 111
  • This works; so awesome. But is it possible you could write this out a bit more into the function I started? This is great but like black magic to me at this point. I am trying to understand simple scripting first :-) – Pr0no Aug 26 '14 at 13:37
  • Thanks for the answer and the great explanation David. – BICube Jan 24 '20 at 15:17
  • @David I am trying to adapt your code to output to a file instead of to the console, but I can't figure it out. Will you please give me some guidance? – BoogieMan2718 Mar 05 '20 at 12:22
  • 1
    You can pipe to Out-File ... | Out-File -FilePath – David Brabant Mar 05 '20 at 13:09
  • 1
    If you want to filter for specific files (e.g. all files with the file ending ".txt"), you can modify this command as follows: `dir -recurse | ?{ $_.PSIsContainer } | %{ Write-Host $_.FullName (dir $_.FullName | ?{$_ -match '.txt$'} | Measure-Object).Count }` – eddex Jul 08 '20 at 08:30
9

Similar to David's solution this will work in Powershell v3.0 and does not uses aliases in case someone is not familiar with them

Get-ChildItem -Directory | ForEach-Object { Write-Host $_.FullName $(Get-ChildItem $_ | Measure-Object).Count}

Answer Supplement

Based on a comment about keeping with your function and loop structure i provide the following. Note: I do not condone this solution as it is ugly and the built in cmdlets handle this very well. However I like to help so here is an update of your script.

Function DirX($directory)
{
    $output = @{}

    foreach ($singleDirectory in (Get-ChildItem $directory -Recurse -Directory))
    {
        $count = 0 
        foreach($singleFile in Get-ChildItem $singleDirectory.FullName)
        {
            $count++
        }
        $output.Add($singleDirectory.FullName,$count)
    }

    $output | Out-String
}

For each $singleDirectory count all files using $count ( which gets reset before the next sub loop ) and output each finding to a hash table. At the end output the hashtable as a string. In your question you looked like you wanted an object output instead of straight text.

Matt
  • 45,022
  • 8
  • 78
  • 119
  • 1
    @Pr0no I added to my answer something that might be more aligned with you function. Understand though the other answers provided are more of a testament to the ease and power of powershell. – Matt Aug 26 '14 at 13:59
  • This method cuts off long file paths, however it seems to execute faster than my method. – Noah Sparks Aug 26 '14 at 14:14
  • A slight tweak as the expansion uses the wrong path when I run it: `Get-ChildItem -Directory | ForEach-Object { Write-Host $_.FullName $(Get-ChildItem $_.FullName | Measure-Object).Count}` – David Metcalfe Sep 21 '22 at 21:24
3

Well, the way you are doing it the entire Get-ChildItem cmdlet needs to complete before the foreach loop can begin iterating. Are you sure you're waiting long enough? If you run that against very large directories (like C:) it is going to take a pretty long time.

Edit: saw you asked earlier for a way to make your function do what you are asking, here you go.

Function DirX($directory)
{
    foreach ($file in Get-ChildItem $directory -Recurse -Directory )
    {
        [pscustomobject] @{
        'Directory' = $File.FullName
        'Count' = (GCI $File.FullName -Recurse).Count
        }
    }
}
DirX D:\

The foreach loop only get's directories since that is all we care about, then inside of the loop a custom object is created for each iteration with the full path of the folder and the count of the items inside of the folder.

Also, please note that this will only work in PowerShell 3.0 or newer, since the -directory parameter did not exist in 2.0

Matt
  • 45,022
  • 8
  • 78
  • 119
Noah Sparks
  • 1,710
  • 13
  • 14
  • I am actually running it over the test-tree given in the OP. Currently, the function only displays the direct children: `VOL01`, `VOL02`, `VOL03` – Pr0no Aug 26 '14 at 13:16
  • 1
    I tested it in both 2.0 and 4.0 to make sure it was not a version issue and it gave me full results in both. – Noah Sparks Aug 26 '14 at 13:24
  • Sorry, My mistake. I'm new to Powershell (working in the Powershell ISE) and had to refresh my script to see results change. I thought only saving the script would be enough. Now I get a full list of both subfolders and files..how can I list the number of files per folder? – Pr0no Aug 26 '14 at 13:35
  • No problem, glad you could identify the issue – Noah Sparks Aug 26 '14 at 13:57
  • @NoahSparks consider changing `$file` to something else. It represents a directory and not a file. Not that it affects the functionality of the script. +1 as you handle the object creation better than i did. – Matt Aug 26 '14 at 14:12
  • Thanks, good point. Was just building off the OP's thing and didn't pay much attention. – Noah Sparks Aug 26 '14 at 14:16
1
Get-ChildItem $rootFolder `
    -Recurse -Directory | 
        Select-Object `
            FullName, `
            @{Name="FileCount";Expression={(Get-ChildItem $_ -File | 
        Measure-Object).Count }} 
ChiliYago
  • 11,341
  • 23
  • 79
  • 126
  • The best one! I'd suggest getting rid of the backtick and format with natural line continuators - see https://get-powershellblog.blogspot.com/2017/07/bye-bye-backtick-natural-line.html – pavol.kutaj Oct 12 '21 at 08:45
0

My version - slightly cleaner and dumps content to a file

Original - Recursively count files in subfolders

Second Component - Count items in a folder with PowerShell

$FOLDER_ROOT = "F:\"
$OUTPUT_LOCATION = "F:DLS\OUT.txt"
Function DirX($directory)
{
    Remove-Item $OUTPUT_LOCATION

    foreach ($singleDirectory in (Get-ChildItem $directory -Recurse -Directory))
    {
        $count = Get-ChildItem $singleDirectory.FullName -File | Measure-Object | %{$_.Count}
        $summary = $singleDirectory.FullName+"    "+$count+"    "+$singleDirectory.LastAccessTime
        Add-Content $OUTPUT_LOCATION $summary
    }
}
DirX($FOLDER_ROOT)
Community
  • 1
  • 1
0

I modified David Brabant's solution just a bit so I could evaluate the result:

$FileCounter=gci "$BaseDir" -recurse |  ?{ $_.PSIsContainer } | %{ (gci "$($_.FullName)" | Measure-Object).Count }
Write-Host "File Count=$FileCounter"
If($FileCounter -gt 0) {
  ... take some action...
}
Antebios
  • 1,681
  • 1
  • 13
  • 22