Public/Get-FolderProperties.ps1

# Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)

using namespace System.IO

function Get-FolderProperties {
    [CmdletBinding()]
    [OutputType([object])]

    ## PARAMETERS #############################################################
    param (
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName,
            ValueFromPipeline,
            ParameterSetName = "Path"
        )]
        [ValidateScript({
                if (Test-Path -Path $_ -PathType Container) {
                    return $true
                }
                throw "The argument specified must resolve to a valid folder path."
            })]
        [string[]]
        $Path,

        [Parameter(
            Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName = "LiteralPath"
        )]
        [Alias("PSPath")]
        [ValidateScript({
                if (Test-Path -LiteralPath $_ -PathType Container) {
                    return $true
                }
                throw "The argument specified must resolve to a valid folder path."
            })]
        [string[]]
        $LiteralPath,

        [Parameter()]
        [ValidateSet(
            "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", # Decimal Metric (Base 10)
            "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" # Binary IEC (Base 2)
        )]
        [string]
        $Unit = "MiB"
    )

    ## BEGIN ##################################################################
    begin {
        $Prefix = @{
            [char] "K" = 1 # kilo
            [char] "M" = 2 # mega
            [char] "G" = 3 # giga
            [char] "T" = 4 # tera
            [char] "P" = 5 # peta
            [char] "E" = 6 # exa
            [char] "Z" = 7 # zetta
            [char] "Y" = 8 # yotta
        }

        $Base = $Unit.Contains("i") | ?: { 1024 } { 1000 }
        $Divisor = [Math]::Pow($Base, $Prefix[$Unit[0]])
    }

    ## PROCESS ################################################################
    process {
        $Process = ($PSCmdlet.ParameterSetName -cmatch "^LiteralPath") | ?: { Resolve-PoshPath -LiteralPath $LiteralPath } { Resolve-PoshPath -Path $Path }

        foreach ($Object in $Process) {
            try {
                if ($Object.Provider.Name -ne "FileSystem") {
                    New-ArgumentException "The argument specified must resolve to a valid path on the FileSystem provider." -Throw
                }

                $Folder = [DirectoryInfo] $Object.ProviderPath

                Write-Verbose ("GET {0}" -f $Folder)
                $Dirs = $Files = $Bytes = 0
                # https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy
                $Result = robocopy $Folder.FullName.TrimEnd("\") \\null /l /e /np /xj /r:0 /w:0 /bytes /nfl /ndl

                if (($LASTEXITCODE -eq 16) -and ($Result[-2] -eq "Access is denied.")) {
                    New-UnauthorizedAccessException -Message ("Access to the path '{0}' is denied." -f $Folder) -Throw
                } elseif ($LASTEXITCODE -eq 16) {
                    New-ArgumentException -Message ("The specified path '{0}' is invalid." -f $Folder) -Throw
                }

                switch -Regex ($Result) {
                    "Dirs :\s+(\d+)" { $Dirs = [double] $Matches[1] - 1 }
                    "Files :\s+(\d+)" { $Files = [double] $Matches[1] }
                    "Bytes :\s+(\d+)" { $Bytes = [double] $Matches[1] }
                }

                Write-Output ([pscustomobject] @{
                        FullName = $Folder.FullName
                        Length   = $Bytes
                        Size     = "{0:n2} {1}" -f ($Bytes / $Divisor), $Unit
                        Contains = "{0} Files, {1} Folders" -f $Files, $Dirs
                        Created  = "{0:F}" -f $Folder.CreationTime
                    })
                ## EXCEPTIONS #################################################
            } catch {
                $PSCmdlet.WriteError($_)
            }
        }
    }

    ## END ####################################################################
    end {
    }
}