1

I've been trying to write a script which will do the following:

  • a.) Show the path of several folders and all of their subfolders
  • b.) Show the number of files in all of these folders and subfolders
  • c.) Show the size of the contents of these folders and each of their subfolders

So far, a.) and b.) have been simple, with something like the following:

$folders = @('C:\Directory1','C:\Directory2','C:\Directory3')
$output = foreach ($folder in $folders) {
    Get-ChildItem $folder -Recurse -Directory | ForEach-Object{
        [pscustomobject]@{
            Folder = $_.FullName
            Count = @(Get-ChildItem -Path $_.Fullname -File).Count
        }
    } | Select-Object Folder,Count
}
$output | export-csv C:\Temp\Folderinfo.csv

This worked great, but I haven't been able to get Powershell to output the folder sizes alongside the paths and numbers of files. I tried to use the Get-DirectorySize function from this StackOverflow thread, and could only get it to output the size of the top-level folder, and never the subfolders. I have also tried passing Get-ChildItem to Measure-Object -Property Length -sum but ran into similar problems, where it would only show the size of the top-level folder.

Does anyone know the correct way to incorporate Measure-Object or Get-DirectorySize into this script, or one like it, so that it works with the needed recursion, and outputs the folder size of each path?

Thanks!

1 Answers1

0

I will go ahead and assume you're looking to show the size of the folders recursively, similar to how explorer does it. In which case you would need to gather the sum of the files size in a folder (which you already have) but also add to that sum, the sum of each child folder in that parent folder. For this you can use an OrderedDictionary which will serve as an indexer, a structure that will hold information of each processed folder and allow for fast lookups of processed folders to add to the stored size.

Function Definition

function Get-DirectoryInfo {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
        [alias('FullName')]
        [ValidateScript({
            if(Test-Path $_ -PathType Container) {
                return $true
            }
            throw 'Directories only!'
        })]
        [string[]] $LiteralPath,

        [Parameter()]
        [switch] $Force
    )

    begin {
        class Tree {
            [string] $FullName
            [Int64] $Count
            [Int64] $Size
            [string] $FriendlySize

            [void] AddSize([Int64] $Size) {
                $this.Size += $Size
            }

            [void] SetFriendlySize() {
                # Inspired from https://stackoverflow.com/a/40887001/15339544

                $suffix = "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"
                $index  = 0
                $length = $this.Size

                while ($Length -ge 1kb) {
                    $Length /= 1kb
                    $index++
                }

                $this.FriendlySize = [string]::Format(
                    '{0} {1}',
                    [math]::Round($Length, 2), $suffix[$index]
                )
            }
        }

        $indexer = [ordered]@{}
        $stack   = [Collections.Generic.Stack[IO.DirectoryInfo]]::new()
        $PSBoundParameters['Directory'] = $true
    }
    process {
        foreach($folder in $LiteralPath | Get-Item) {
            $stack.Push($folder)
        }

        while($stack.Count) {
            $PSBoundParameters['LiteralPath'] = $stack.Pop().FullName
            foreach($folder in Get-ChildItem @PSBoundParameters) {
                $stack.Push($folder)
                $count = 0
                $size  = 0

                foreach($file in $folder.EnumerateFiles()) {
                    $count++
                    $size += $file.Length
                }

                $indexer[$folder.FullName] = [Tree]@{
                    FullName = $folder.FullName
                    Count    = $count
                    Size     = $size
                }

                $parent = $folder.Parent

                while($parent -and $indexer.Contains($parent.FullName)) {
                    $indexer[$parent.FullName].AddSize($size)
                    $parent = $parent.Parent
                }
            }
        }

        $output = $indexer.PSBase.Values
        if($output.Count) {
            $output.SetFriendlySize()
            $output
        }
        $indexer.Clear()
    }
}

Usage

Get-ChildItem 'C:\Directory1', 'C:\Directory2', 'C:\Directory3' | 
    Get-DirectoryInfo

The logic used in this function is more or less similar to the one used in this module to display File / Folder hierarchy (similar to the tree command).

Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • Hello, @Santiago Squarzon. Thank you for your reply! Taking a look at this script, it seemed like it would be doing exactly what I was hoping for, but it seems as if it is not seeing certain inputs as valid directories. The strange thing is that it _is_ working for some directories, but not others. For instance, I was using the following at the end of the script: `Get-ChildItem 'C:\Users\myself\Documents\Scripts', 'C:\Temp' | Get-DirectoryInfo | export-csv "C:\Users\Myself\Documents\Scripts\folderinfo.csv"` – closedcasketfuneral Oct 25 '22 at 14:57
  • @closedcasketfuneral my honest advise would be to try out the module I linked, it should do what you're looking for and better than this function – Santiago Squarzon Oct 25 '22 at 14:59
  • For many of the directories, it throws this error: `Cannot validate argument on parameter 'LiteralPath'. Directories only!` but on many of the other directories, it outputs to the CSV as expected, like this: `"C:\temp\Username\Profile Info","2","4644","4.54 KB"` – closedcasketfuneral Oct 25 '22 at 15:02
  • I will definitely take a look at the module that you linked. I'm aware that this is testing the limits of what Powershell is useful for, so I appreciate your patience and interest in helping out. – closedcasketfuneral Oct 25 '22 at 15:03