functions/Show-FolderUsage.ps1

Function Show-FolderUsage {
    [cmdletbinding(DefaultParameterSetName = '__AllParameterSets')]
    [alias('sfu')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            HelpMessage = 'Specify the path to the folder to analyze. Use a full file system path')]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(
            ValueFromPipelineByPropertyName,
            HelpMessage = 'Specify the minimum percentage to display. The default is 5%'
        )]
        [ValidateRange(0, 99)]
        [double]$Threshold = 5,

        [Parameter(HelpMessage = "Sort the graphical output by size or extension in ascending order")]
        [ValidateSet('Size', 'Name')]
        [string]$Sort = "Name",

        [switch]$Descending,

        [Parameter(HelpMessage = 'Display raw output without formatting.')]
        [switch]$Raw
    )
    DynamicParam {
        If ($isWindows -OR ($PSEdition -eq 'Desktop')) {
            $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary

            #Computername
            $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.ParameterSetName = 'Windows'
            $attributes.ValueFromPipelineByPropertyName = $True
            $attributes.ValueFromPipeline = $True
            $attributes.HelpMessage = 'Specify the name of a remote computer. You must have admin rights. The default is the localhost.'

            $v = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute
            $AttributeCollection.Add($v)
            $attributeCollection.Add($attributes)

            $dynAlias = New-Object System.Management.Automation.AliasAttribute -ArgumentList 'CN'
            $attributeCollection.Add($dynAlias)

            $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('ComputerName', [String[]], $attributeCollection)
            $dynParam1.Value = '$env:Computername'
            $paramDictionary.Add('ComputerName', $dynParam1)

            #credential
            $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.ParameterSetName = 'Windows'
            $attributes.ValueFromPipelineByPropertyName = $True
            $attributes.HelpMessage = 'specify an alternate credential'

            $v = New-Object System.Management.Automation.ValidateNotNullOrEmptyAttribute
            $AttributeCollection.Add($v)
            $attributeCollection.Add($attributes)

            $dynAlias = New-Object System.Management.Automation.AliasAttribute -ArgumentList 'RunAs'
            $attributeCollection.Add($dynAlias)

            $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter('Credential', [PSCredential], $attributeCollection)
            $paramDictionary.Add('Credential', $dynParam1)

            return $paramDictionary
        } # end if
    } #end DynamicParam

    Begin {
        $PSDefaultParameterValues['_verbose:Command'] = $MyInvocation.MyCommand
        $PSDefaultParameterValues['_verbose:block'] = 'Begin'
        _verbose -message $strings.Starting
        Write-Information $MyInvocation -Tags runtime
        if ($MyInvocation.CommandOrigin -eq 'Runspace') {
            $platformOS = If ($PSVersionTable.OS) {
                $PSVersionTable.OS
            }
            else {
                'Windows'
            }

            #Hide this metadata when the command is called from another command
            _verbose -message ($strings.PSVersion -f $PSVersionTable.PSVersion)
            _verbose -message ($strings.UsingHost -f $host.Name)
            _verbose -message ($strings.UsingOS -f $platformOS)
            _verbose -message ($strings.UsingModule -f $DiskReportingModule)
        }

        $sym = ' '

        #define the scriptblock to run remotely
        $sb = {
            param($path)
            Try {
                #validate the path
                $test = Get-Item -Path $path -ErrorAction Stop
                $files = Get-ChildItem -Path $path -File -Recurse -ErrorAction Stop
            }
            Catch {
                $_
            }
            If ($files) {
                $files | Group-Object -Property extension |
                Select-Object -Property Name, Count,
                @{Name = 'Size'; Expression = { ($_.Group | Measure-Object -Property length -Sum).sum } },
                @{Name = 'Computername'; Expression = { [System.Environment]::MachineName } }
            }
        }

        $icmSplat = @{
            ScriptBlock  = $sb
            ArgumentList = ''
            ErrorAction  = 'Stop'
        }

    } #begin
    Process {
        $PSDefaultParameterValues['_verbose:block'] = 'Process'
        $Path = Convert-Path $Path
        $icmSplat['ArgumentList'] = $Path
        _verbose ($strings.DetectedParameterSet -f $PSCmdlet.ParameterSetName)
        Write-Information $PSBoundParameters -Tags runtime
        if ($PSCmdlet.ParameterSetName -eq 'Windows') {
            $Credential = $PSBoundParameters['Credential']
            If ($Credential) {
                _verbose ($strings.RunAs -f $Credential.UserName)
                $icmSplat['Credential'] = $Credential
            }
            $ComputerName = $PSBoundParameters['ComputerName']
        } #Windows
        else {
            $Computername = [System.Environment]::MachineName
        }
        #Process computers individually
        foreach ($Computer in $ComputerName) {
            _verbose ($strings.FolderUsage -f $Path, ($Computer.ToUpper()))
            if ($computer -ne [System.Environment]::MachineName) {
                #don't include the local computer name for Invoke-Command
                $icmSplat['ComputerName'] = $Computer
                $icmSplat['HideComputerName'] = $True
            }
            else {
                #make sure Computername has been removed
                $icmSplat.Remove('ComputerName')
                $icmSplat.Remove('HideComputerName')
            }
            Write-Information $icmSplat -Tags runtime
            Try {
                $data = Invoke-Command @icmSplat
            }
            Catch {
                Write-Information $_ -Tags error
                $_
            }

            If ($data) {
                #get the total sum of all files
                $totalSum = ($data | Measure-Object -Property size -Sum).Sum
                Write-Information "Total size: $totalSum bytes" -Tags data
                $data | Add-Member -MemberType NoteProperty -Name 'Total' -Value $totalSum -Force
                $data | Add-Member -MemberType ScriptProperty -Name 'Pct' -Value { ($this.Size / $this.total) * 100 } -Force
                Write-Information $data -Tags data

                if ($raw) {
                    $data | Select-Object -Property *,
                    @{Name = 'Path'; Expression = { _toTitleCase $Path } }-ExcludeProperty RunspaceID
                }
                else {
                    #format the results
                    $header = "[$cnStyle{1}$reset] $pathStyle{0}$reset" -f (_toTitleCase $Path), $data[0].Computername
                    #this will be the output
                    $out = @"
 
$header
 
 
"@

                    #filter out extensions with less than the threshold percentage
                    $filtered = $data.Where({ $_.Pct -ge $Threshold }) | Sort-Object -Property $Sort -Descending:$Descending
                    #get longest extension name
                    $maxLength = ($filtered.name) | Select-Object -ExpandProperty length |
                    Sort-Object | Select-Object -Last 1
                    #get the longest file count
                    [string]$maxCount = ($filtered | Sort-Object Count | Select -Last 1 -ExpandProperty Count)

                    foreach ($item in $filtered) {
                        $pct = $item.Pct
                        [double]$scaled = $pct / 2
                        [int]$used = 50 - $scaled
                        $displayPct = [math]::Round($scaled * 2, 2)
                        $bar = ($sym * $scaled)

                        $remain = 50 - $scaled
                        #colors are defined as module-scoped variables
                        if ($pct -gt 50) {
                            $bgColor = $redBG
                            $fgColor = $red
                        }
                        elseif ($pct -gt 20) {
                            $bgColor = $yellowBG
                            $fgColor = $yellow
                        }
                        else {
                            $bgColor = $greenBG
                            $fgColor = $green
                        }

                        #19 June 2025 Include file count in the output
                        #format as Black
                        $fileCount = "$([char]27)[30m{0}" -f ([string]$item.count).PadRight($maxCount.Length)
                        $out += "{0} [{1} {7}{2}{3}{4}] {5}{6:P2}{3}`n" -f ($item.Name).PadRight($maxLength), $bgColor, $bar, $Reset, (' ' * $remain), $fgColor, ($pct / 100),$fileCount

                    } #foreach item

                    $out
                }
                Clear-Variable -Name data
            } #if data
        } #foreach computer
    } #process
    End {
        $PSDefaultParameterValues['_verbose:block'] = 'End'
        $PSDefaultParameterValues['_verbose:Command'] = $MyInvocation.MyCommand
        _verbose $strings.Ending
        Write-Information $strings.Ending -Tags runtime
    } #end
}

<#
Function Show-FolderUsage {
    [cmdletbinding(DefaultParameterSetName = '__AllParameterSets')]
    [alias('sfu')]
    [OutputType('PSObject',ParameterSetName='raw')]
    [OutputType('System.String',ParameterSetName='__AllParameterSets')]
    Param(
        [Parameter(
            Position = 0,
            Mandatory = $true,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            HelpMessage = 'Specify the path to the folder to analyze. Use a full file system path')]
        [ValidateNotNullOrEmpty()]
        [string]$Path,
 
        [Parameter(
            Position = 1,
            ValueFromPipelineByPropertyName,
            HelpMessage = 'Specify the name of a remote computer. You must have admin rights. The default is the localhost.'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias("CN")]
        [string[]]$ComputerName = [System.Environment]::MachineName,
 
        [Parameter(
            ValueFromPipelineByPropertyName,
            HelpMessage = 'Specify an alternate credential for the remote computer.'
        )]
        [ValidateNotNullOrEmpty()]
        [PSCredential]$Credential,
 
        [Parameter(
            ValueFromPipelineByPropertyName,
            HelpMessage = 'Specify the minimum percentage to display. The default is 5%'
        )]
        [ValidateRange(1, 99)]
        [int]$Threshold = 5,
 
        [Parameter(ParameterSetName = 'raw', HelpMessage = 'Display raw output without formatting.')]
        [switch]$Raw
    )
 
    Begin {
        $PSDefaultParameterValues['_verbose:Command'] = $MyInvocation.MyCommand
        $PSDefaultParameterValues['_verbose:block'] = 'Begin'
        _verbose -message $strings.Starting
        if ($MyInvocation.CommandOrigin -eq 'Runspace') {
            #Hide this metadata when the command is called from another command
            _verbose -message ($strings.PSVersion -f $PSVersionTable.PSVersion)
            _verbose -message ($strings.UsingHost -f $host.Name)
            _verbose -message ($strings.UsingModule -f $DiskReportingModule)
        }
 
        $sym = ' '
 
        #define the scriptblock to run remotely
        $sb = {
            param($path)
            Try {
                #validate the path
                $test = Get-Item -Path $path -ErrorAction Stop
                $files = Get-ChildItem -Path $path -File -Recurse -ErrorAction Stop
            }
            Catch {
                $_
            }
            If ($files) {
                $files | Group-Object -Property extension |
                Select-Object -Property Name, Count,
                @{Name = 'Size'; Expression = { ($_.Group | Measure-Object -Property length -Sum).sum } }
            }
        }
 
        $icmSplat = @{
            ScriptBlock = $sb
            ArgumentList = ''
            HideComputerName = $True
            ErrorAction = 'Stop'
        }
 
    } #begin
    Process {
        $PSDefaultParameterValues['_verbose:block'] = 'Process'
        Write-Information $PSBoundParameters -Tags runtime
        If ($Credential) {
            _verbose ($strings.RunAs -f $Credential.UserName)
            $icmSplat['Credential'] = $Credential
        }
        #Process computers individually
        foreach ($Computer in $ComputerName) {
            _verbose ($strings.FolderUsage -f $Path, ($Computer.ToUpper()))
            $icmSplat['ArgumentList'] = $Path
            $icmSplat['ComputerName'] = $Computer
            Write-Information $icmSplat -Tags runtime
            Try {
                $data = Invoke-Command @icmSplat
            }
            Catch {
                $_
            }
 
            If ($data) {
                #get the total sum of all files
                $totalSum = ($data | Measure-Object -Property size -Sum).Sum
                Write-Information "Total size: $totalSum bytes" -Tags data
                $data | Add-Member -MemberType NoteProperty -Name 'Total' -Value $totalSum -Force
                $data | Add-Member -MemberType ScriptProperty -Name 'Pct' -Value { ($this.Size / $this.total) * 100 } -Force
                Write-Information $data -Tags data
 
                if ($raw) {
                    $data | Select-Object -Property *,
                    @{Name="Path";Expression={_toTitleCase $Path}},
                    @{Name="ComputerName" ;Expression = {$_.PSComputername.ToUpper()}} -ExcludeProperty RunspaceID
                }
                else {
                    #format the results
                    $header = "[$cnStyle{1}$reset] $pathStyle{0}$reset" -f (_toTitleCase $Path), $data[0].PSComputername.ToUpper()
                    #this will be the output
                    $out = @"
 
$header
 
 
"@
                    #filter out extensions with less than the threshold percentage
                    $filtered = $data.Where({ $_.Pct -ge $Threshold })
                    #get longest extension name
                    $maxLength = ($filtered.name) | Select-Object -ExpandProperty length | Sort-Object | Select-Object -Last 1
 
                    foreach ($item in $filtered) {
                        $pct = $item.Pct
                        [double]$scaled = $pct / 2
                        [int]$used = 50 - $scaled
                        $displayPct = [math]::Round($scaled * 2, 2)
                        $bar = ($sym * $scaled)
                        $remain = 50 - $scaled
                        if ($pct -gt 50) {
                            $bgColor = $redBG
                            $fgColor = $red
                        }
                        elseif ($pct -gt 20) {
                            $bgColor = $yellowBG
                            $fgColor = $yellow
                        }
                        else {
                            $bgColor = $greenBG
                            $fgColor = $green
                        }
                        $out += "{0} [{1}{2}{3}{4}] {5}{6:P2}{3}`n" -f ($item.Name).PadRight($maxLength), $bgColor, $bar, $Reset, (' ' * $remain), $fgColor, ($pct / 100)
 
                    } #foreach item
 
                    $out
                }
            } #if data
 
 
        } #foreach computer
    } #process
    End {
        $PSDefaultParameterValues['_verbose:block'] = 'End'
        $PSDefaultParameterValues['_verbose:Command'] = $MyInvocation.MyCommand
        _verbose $strings.Ending
    } #end
}
#>