functions/ShowTree.ps1

function Show-Tree {

    [CmdletBinding(DefaultParameterSetName = "Path")]
    [alias("pstree","shtree")]

    Param(
        [Parameter(Position = 0,
            ParameterSetName = "Path",
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
            )]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path = ".",

        [Alias("PSPath")]
        [Parameter(Position = 0,
            ParameterSetName = "LiteralPath",
            ValueFromPipelineByPropertyName
            )]
        [ValidateNotNullOrEmpty()]
        [string[]]$LiteralPath,

        [Parameter(Position = 1)]
        [ValidateRange(0, 2147483647)]
        [int]$Depth = [int]::MaxValue,

        [Parameter()]
        [ValidateRange(1, 100)]
        [int]$IndentSize = 3,

        [Parameter()]
        [alias("files")]
        [switch]$ShowItem,

        [Parameter(HelpMessage = "Display item properties. Use * to show all properties or specify a comma separated list.")]
        [alias("properties")]
        [string[]]$ShowProperty
    )
    DynamicParam {
        #define the InColor parameter if running PowerShell 7 and the path is a FileSystem path
        if ($PSBoundParameters.containsKey("Path")) {
            $here = $psboundParameters["Path"]
        }
        elseif ($PSBoundParameters.containsKey("LiteralPath")) {
            $here = $psboundParameters["LiteralPath"]
        }
        else {
            $here = (Get-Location).path
        }
        if (((Get-Item -path $here).PSprovider.Name -eq 'FileSystem' )-OR ((Get-Item -literalpath $here).PSprovider.Name -eq 'FileSystem')) {
         $IsFileSystem = $True
        }
        if ($PSVersiontable.psversion.Major -ge 7 -AND $IsFileSystem) {
            #define a parameter attribute object
            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.HelpMessage = "Show tree and item colorized."

            #add an alias
            $alias = [System.Management.Automation.AliasAttribute]::new("ansi")

            #define a collection for attributes
            $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributeCollection.Add($attributes)
            $attributeCollection.Add($alias)

            #define the dynamic param
            $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("InColor", [Switch], $attributeCollection)

            #create array of dynamic parameters
            $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add("InColor", $dynParam1)
            #use the array
            return $paramDictionary
        }
    }

    Begin {
        Write-Verbose "Starting $($myinvocation.MyCommand)"
        if (-Not $Path -and $psCmdlet.ParameterSetName -eq "Path") {
            $Path = Get-Location
        }

        if ($PSBoundParameters.containskey("InColor")) {
            $Colorize = $True
        }
        function GetIndentString {
            [CmdletBinding()]
            Param([bool[]]$IsLast)

            Write-Verbose "Starting $($myinvocation.MyCommand)"
            # $numPadChars = 1
            $str = ''
            for ($i = 0; $i -lt $IsLast.Count - 1; $i++) {
                $sepChar = if ($IsLast[$i]) {' '} else {'|'}
                $str += "$sepChar"
                $str += " " * ($IndentSize - 1)
            }

            #The \ indicates the item is the last in the container
            $teeChar = if ($IsLast[-1]) {'\'} else {'+'}
            $str += "$teeChar"
            $str += "-" * ($IndentSize - 1)
            $str

            Write-Verbose "Ending $($myinvocation.MyCommand)"
        }

        function ShowProperty() {
            [cmdletbinding()]
            Param(
                [string]$Name,
                [string]$Value,
                [bool[]]$IsLast
            )
            Write-Verbose "Starting $($myinvocation.MyCommand)"
            $indentStr = GetIndentString $IsLast
            $propStr = "${indentStr} $Name = "
            $availableWidth = $host.UI.RawUI.BufferSize.Width - $propStr.Length - 1
            if ($Value.Length -gt $availableWidth) {
                $ellipsis = '...'
                $val = $Value.Substring(0, $availableWidth - $ellipsis.Length) + $ellipsis
            }
            else {
                $val = $Value
            }
            $propStr += $val
            $propStr
            Write-Verbose "Ending $($myinvocation.MyCommand)"
        }
        function ShowItem {
            [CmdletBinding()]
            Param(
                [string]$Path,
                [string]$Name,
                [bool[]]$IsLast,
                [bool]$HasChildItems = $false,
                [switch]$Color,
                [ValidateSet("topcontainer","childcontainer","file")]
                [string]$ItemType
            )
            Write-Verbose "Starting $($myinvocation.MyCommand)"
            $PSBoundParameters | Out-String | Write-Verbose
            if ($IsLast.Count -eq 0) {
                if ($Color) {
                    Write-Output "`e[38;2;0;255;255m$("$(Resolve-Path $Path)")`e[0m"
                }
                else {
                    "$(Resolve-Path $Path)"
                }
            }
            else {
                $indentStr = GetIndentString $IsLast
                if ($Color) {
                    #ToDo - define a user configurable color map
                    Switch ($ItemType) {
                        "topcontainer" {
                            Write-Output "$indentStr`e[38;2;0;255;255m$("$Name")`e[0m"
                        }
                        "childcontainer" {
                            Write-Output "$indentStr`e[38;2;255;255;0m$("$Name")`e[0m"
                        }
                        "file" {
                            switch -regex ($name) {
                                "\.ps1$" {
                                     Write-Output "$indentStr`e[38;2;252;127;12m$("$Name")`e[0m"
                                }
                                "\.(jpg)|(png)|(gif)$" {
                                     Write-Output "$indentStr`e[38;2;255;0;255m$("$Name")`e[0m"
                                }
                                "\.(txt)|(json)|(md)|(xml)|(csv)" {
                                    Write-Output "$indentStr`e[38;2;58;120;255m$("$Name")`e[0m"
                                }
                                default {
                                    Write-Output "$indentStr`e[38;2;22;198;12m$("$Name")`e[0m"
                                }
                            }
                        }
                        Default {
                            Write-Output "$indentStr$Name"
                        }
                    } #switch
                } #if color
                else {
                    "$indentStr$Name"
                }
            }

            if ($ShowProperty) {
                $IsLast += @($false)

                $excludedProviderNoteProps = 'PSChildName', 'PSDrive', 'PSParentPath', 'PSPath', 'PSProvider'
                $props = @(Get-ItemProperty $Path -ea 0)
                if ($props[0] -is [pscustomobject]) {
                    if ($ShowProperty  -eq "*") {
                        $props = @($props[0].psobject.properties | Where-object {$excludedProviderNoteProps -notcontains $_.Name })
                    }
                    else {
                        $props = @($props[0].psobject.properties |
                        Where-object {$excludedProviderNoteProps -notcontains $_.Name -AND $showproperty -contains $_.name})
                    }
                }

                for ($i = 0; $i -lt $props.Count; $i++) {
                    $prop = $props[$i]
                    $IsLast[-1] = ($i -eq $props.count - 1) -and (-Not $HasChildItems)
                    $showParams = @{
                        Name = $prop.Name
                        Value = $prop.Value
                        IsLast = $IsLast
                    }
                    ShowProperty @showParams
                }
            }
            Write-Verbose "Ending $($myinvocation.MyCommand)"
        }

        function ShowContainer {
            [CmdletBinding()]
            Param (
                [string]$Path,
                [string]$Name = $(Split-Path $Path -Leaf),
                [bool[]]$IsLast = @(),
                [switch]$IsTop,
                [switch]$Color
            )

            Write-Verbose "Starting $($myinvocation.MyCommand) on $Path"
            $PSBoundParameters | Out-String | Write-Verbose
            if ($IsLast.Count -gt $Depth) { return }

            $childItems = @()
            if ($IsLast.Count -lt $Depth) {
                try {
                    $rpath = Resolve-Path $Path -ErrorAction stop
                }
                catch {
                    Throw "Failed to resolve $path. This PSProvider and path may be incompatible with this command."
                    #bail out
                    return
                }
                $childItems = @(Get-ChildItem $rpath -ErrorAction $ErrorActionPreference |
                        Where-object {$ShowItem -or $_.PSIsContainer})
            }
            $hasChildItems = $childItems.Count -gt 0

            # Show the current container
            $sParams = @{
                path          = $Path
                name          = $Name
                IsLast        = $IsLast
                hasChildItems = $hasChildItems
                Color         = $Color
                ItemType      =  If ($isTop) {"topcontainer"} else {"childcontainer"}
            }
            ShowItem @sParams

            # Process the children of this container
            $IsLast += @($false)
            for ($i = 0; $i -lt $childItems.count; $i++) {
                $childItem = $childItems[$i]
                $IsLast[-1] = ($i -eq $childItems.count - 1)
                if ($childItem.PSIsContainer) {
                    $iParams = @{
                        path   = $childItem.PSPath
                        name   = $childItem.PSChildName
                        isLast = $IsLast
                        Color  = $color
                    }
                    ShowContainer @iParams
                }
                elseif ($ShowItem) {
                    $unresolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($childItem.PSPath)
                    $name = Split-Path $unresolvedPath -Leaf
                    $iParams = @{
                        Path   = $childItem.PSPath
                        Name   = $name
                        IsLast = $IsLast
                        Color  = $Color
                       ItemType = "File"
                    }
                    ShowItem @iParams
                }
            }
            Write-Verbose "Ending $($myinvocation.MyCommand)"
        }
    } #begin

    Process {
        if ($psCmdlet.ParameterSetName -eq "Path") {
            # In the -Path (non-literal) resolve path in case it is wildcarded.
            $resolvedPaths = @($Path | Resolve-Path | Foreach-object { $_.Path})
        }
        else {
            # Must be -LiteralPath
            $resolvedPaths = @($LiteralPath)
        }
        Write-Verbose "Using these PSBoundParameters"
        $PSBoundParameters | Out-String | Write-Verbose

        foreach ($rpath in $resolvedPaths) {
            Write-Verbose "Processing $rpath"
            $showParams = @{
                Path  = $rpath
                Color = $colorize
                IsTop = $True
            }
            ShowContainer @showParams

        }
    } #process
    end {
        Write-Verbose "Ending $($myinvocation.MyCommand)"
    }
}