MartinsProfile.psm1

function AddDevShell {
    <#
        .Synopsis
            Add Visual Studio Developer Powershell Prompt functionality to the
            current session.
    #>

    [CmdletBinding()]
    param ()

    $vsWhere = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"

    if (!(Test-Path $vsWhere)) {
        throw "Visual Studio Installer not found. Cannot determine VS location."
    }

    $installPath = &$vsWhere -version 16.0 -property installationpath
    Import-Module (Join-Path $installPath "Common7\Tools\Microsoft.VisualStudio.DevShell.dll")
    Enter-VsDevShell -VsInstallPath $installPath -SkipAutomaticLocation
}


function GetParentProcess {
        <#
        .Synopsis
            Get the Process of the current process' parent.
    #>

    [CmdletBinding()]
    param ()
    Get-Process -Id (GetParentProcessId)
}


function GetParentProcessId {
    <#
        .Synopsis
            Get the Process Id of the current process' parent.
    #>

    [CmdletBinding()]
    param ()

    if ($PSVersionTable.PSVersion.Major -lt 6)
    {
        # PS 5 isn't as smart...
        ((Get-WmiObject win32_process | Where-Object processid -eq  $pid).parentprocessid)
    } else {
        (Get-Process -Id $pid).Parent.Id
    }
}

function GetPhysicalPsDrive {
    [CmdletBinding()]
    param ()

    process {
        Get-PSDrive |
            Where-Object Name -in @(
                Get-Partition |
                    Where-Object Type -eq Basic |
                    Where-Object { -not [string]::IsNullOrWhitespace($_) } |
                    Select-Object -ExpandProperty DriveLetter)
    }
}

function ImportHumanizer {
    <#
    .SYNOPSIS
        Import Humanizer formats and type updates.
    .EXAMPLE
        PS C:\> Import-Humanizer
        Import Humanizer formats and type updates.
    .NOTES
        Does not include localization libraries, so only Invariant Culture.
    #>

    [CmdletBinding()]
    param ()

    $path = "$PSScriptRoot/Dlls/Humanizer.dll"
    WriteDebug "Importing Humanizer: $path"

    Add-Type -Path $path -Verbose:([boolean]$env:MartinsProfileDebugMode) -Debug:([boolean]$env:MartinsProfileDebugMode)
}

function InstallEverythingCli {
    [CmdletBinding()]
    param ()

    WriteDebug "Checking on Windows"
    if (-not $IsWindows) {
        return
    }

    # Test for cli
    WriteDebug "Checking for existing es.exe"
    if (Test-Path "$env:LOCALAPPDATA\Everything\es.exe") {
        $p = @{
            SearchPath = @("$env:LOCALAPPDATA\Everything")
            ToolName = 'es.exe'
            AliasName = 'es'
        }
        WriteDebug "Creating Alias 'es'"
        NewToolAlias @p
        return
    }

    WriteDebug "Checking for winget"
    if (-not (Test-Command -Name winget)) {
        Write-Warning "Winget not available, reduced functionality likely"
    }

    WriteDebug "Checking for voidtools.everything"
    $response = winget list voidtools.everything
    if ($response -match "No installed package found matching input criteria") {
        WriteDebug "Installing voidtools.everything"
        Write-Warning "Everything is not installed, installing"
        winget install voidtools.everything -r
    }

    WriteDebug "Checking for AppData\Everything"
    if (-not (Test-Path "$env:LOCALAPPDATA\Everything")) {
        New-Item -Path $env:LOCALAPPDATA\Everything -ItemType Directory
    }

    WriteDebug "Checking for es.exe"
    if (-not (Test-Path "$env:LOCALAPPDATA\Everything\es.exe")) {
        Write-Warning "Everything CLI is not installed, installing"
        $esZipPath = "$env:LOCALAPPDATA\Everything\ES-1.1.0.23.zip"
        Invoke-WebRequest -Uri https://www.voidtools.com/ES-1.1.0.23.zip -OutFile $esZipPath
        Unblock-File $esZipPath
        Expand-Archive -Path $esZipPath -DestinationPath "$env:LOCALAPPDATA\Everything"
    }

    WriteDebug "Creating Alias 'es'"
    $p = @{
        SearchPath = @("$env:LOCALAPPDATA\Everything")
        ToolName = 'es.exe'
        AliasName = 'es'
    }
    NewToolAlias @p
}


function NewToolAlias {
    <#
    .SYNOPSIS
        Add an alias for a CLI tool
    #>

    param(
        # Paths to search for the tool (if not in env:PATH)
        # Do not include tool/exe name
        # e.g. c:\windows\system32
        [String[]]$SearchPath,
        # Tool/Exe name e.g. notepad.exe
        [String]$ToolName,
        # Alias for tool/exe
        [String]$AliasName
    )

    $s = $PSStyle.Foreground.Yellow
    $f = $PSStyle.Reset

    if (-not [String]::IsNullOrWhiteSpace((Get-DropboxFolder -ErrorAction SilentlyContinue))) {
        # First portable/dropbox
        $dropboxPortable = Join-Path -Path (Get-DropboxFolder) 'Software' -AdditionalChildPath 'portable'
        $portable = Get-ChildItem -Path $dropboxPortable -Recurse -Filter $ToolName | Select-Object -First 1
        if (-not [String]::IsNullOrWhiteSpace($portable)) {
            WriteDebug "Set Tool Alias ${s}${AliasName}${f} -> ${s}${portable}${f}"
            New-Alias -Name $AliasName -Value $portable -Scope Global
            return
        }
    }
    else {
        WriteDebug "Dropbox not found/installed"
    }

    # Then Check in path
    if (Test-Command -Name $ToolName) {
        WriteDebug "Set Tool Alias From env:PATH: ${s}${AliasName}${f} -> ${s}${ToolName}${f}"
        return
    }

    # Then check search paths
    $found = $SearchPath | Where-Object { Test-Path (Join-Path $_ $ToolName) } | Select-Object -First 1
    if ([String]::IsNullOrWhiteSpace($found)) {
        WriteDebug "Tool ${s}$ToolName${f} not found"
        return
    }

    $path = (Join-Path $found $ToolName)
    WriteDebug "Set Tool Alias ${s}${AliasName}${f} -> ${s}${path}${f}"
    New-Alias -Name $AliasName -Value $path -Scope Global
}

# Copied from Environment Module ((c) 2016,2018 Joel Bennett. All rights reserved. MIT License)

function SelectPrfUniquePath {
    [CmdletBinding()]
    param(
        # If non-full, split path by the delimiter. Defaults to '[IO.Path]::PathSeparator' so you can use this on $Env:Path
        [Parameter(Mandatory=$False)]
        [AllowNull()]
        [string]$Delimiter = [IO.Path]::PathSeparator,

        # Paths to folders
        [Parameter(Position=1,Mandatory=$true,ValueFromRemainingArguments=$true)]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]$Path
    )
    begin {
        Write-Information "Select-PrfUniquePath $Delimiter $Path" -Tags "Trace", "Enter"
        [string[]]$Output = @()
    }
    process {
        $Output += $(
            # Split and trim trailing slashes to normalize
            $oldPaths = $Path -split $Delimiter -replace '[\\\/]$' -gt ""
            # Injecting wildcards causes Windows to figure out the actual case of the path
            $folders = $oldPaths -replace '(?<!(?::|\\\\))(\\|/)', '*$1' -replace '$', '*'
            $newPaths = Get-Item $folders -Force | Convert-Path
            # Make sure we didn't add anything that wasn't already there
            $newPaths | Where-Object { $_ -iin $oldPaths }
        )
    }
    end {
        if($Delimiter) {
            [System.Linq.Enumerable]::Distinct($Output) -join $Delimiter
        } else {
            [System.Linq.Enumerable]::Distinct($Output)
        }
        Write-Information "Select-PrfUniquePath $Delimiter $Path" -Tags "Trace", "Exit"
    }
}


function UpdatePSReadLine {
    <#
        .Synopsis
            Preferred PSReadline Settings.
    #>


}

function UpdateToolPath {
    <#
        .Synopsis
            Add useful tool aliases
 
    #>

    param()

    VerboseBlock "docker" {
    $p = @{
        SearchPath = @()
        ToolName = 'docker.exe'
        AliasName = 'd'
    }
    NewToolAlias @p
}

    VerboseBlock "docker-compose" {
        $p = @{
            SearchPath = @()
            ToolName = 'docker-compose.exe'
            AliasName = 'dc'
        }
        NewToolAlias @p
    }

    VerboseBlock "Sublime Merge" {
        $p = @{
            SearchPath = @("$env:ProgramFiles\Sublime Merge")
            ToolName = 'smerge.exe'
            AliasName = 'sm'
        }
        NewToolAlias @p
    }

    VerboseBlock "Notepad++" {
        $p = @{
            SearchPath = @("$env:ProgramFiles\Notepad++")
            ToolName = 'notepad++.exe'
            AliasName = 'npp'
        }
        NewToolAlias @p
    }

    VerboseBlock "FAR filemanager" {
        $p = @{
            SearchPath = @()
            ToolName = 'Far.exe'
            AliasName = 'far'
        }
        NewToolAlias @p
    }

    VerboseBlock "RegexBuddy" {
        $p = @{
            SearchPath = @()
            ToolName = 'RegexBuddy4.exe'
            AliasName = 'regbuddy'
        }
        NewToolAlias @p
    }
}

$VerboseDepth = 0
$VerboseBlockName = [System.Collections.Stack]::new()
Function VerboseBlock {
    param(
        [Parameter(Mandatory = $true)]
        $Name,
        [Parameter(Mandatory = $true)]
        [scriptblock]$Scriptblock
    )

    try {
        if ($ProfileDebugMode) {
            WriteDebug "🔽 '$($PSStyle.Foreground.BrightYellow)$Name$($PSStyle.Reset)'"
        }

        $sw = [System.Diagnostics.Stopwatch]::StartNew()
        $script:VerboseDepth++
        $script:VerboseBlockName.Push($Name)

        $Scriptblock.Invoke()

        $sw.Stop
        $nameArray = $script:VerboseBlockName.ToArray()
        [array]::Reverse($nameArray)
        $fullName = [string]::Join( '  ', $nameArray)
        $script:timers += [PSCustomObject]@{ Name = $fullName; Timer = $sw.ElapsedMilliseconds }
        $null = $script:VerboseBlockName.Pop()
        $script:VerboseDepth--

        if ($ProfileDebugMode) {
            WriteDebug "🔼 '$($PSStyle.Foreground.BrightYellow)$Name$($PSStyle.Reset)' $($sw.ElapsedMilliseconds)ms"
        }
    } catch {
        WriteError "Unhandled Error in VerboseBlock '$Name': $_"
        throw
    }
}

function WriteDebug() {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline=$true)]
        [string]$Message
    )

    if ($ProfileDebugMode) {
        $space = " " * $VerboseDepth
        Write-Information "${messagePrefix}$($PSStyle.Foreground.Cyan)DEBUG$($PSStyle.Reset): ${space}${Message}" -Tags @('MartinsProfile', 'Debug') -InformationAction "Continue"
    }
}

function WriteError() {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline=$true)]
        [string]$Message
    )
    $space = " " * $script:VerboseDepth
    Write-Information "${messagePrefix}$($PSStyle.Foreground.Red)ERROR$($PSStyle.Reset): ${space}${Message}" -Tags @('MyPro', 'Error') -InformationAction "Continue"
}

function Add-ExtensionMethod {
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Path to assembly to process.')]
        [Alias('PSPath')]
        [Alias('Path')]
        [ValidateNotNullOrEmpty()]
        [string]
        $DllPath
    )

    Process {
        $assembly = [System.Reflection.Assembly]::LoadFrom($DllPath)
        $types = $assembly.GetExportedTypes()

        $extensionMethods = @()

        foreach($type in $types)
        {
            if($type.IsSealed -and $type.IsAbstract)
            {
                $methods = $type.GetMethods() | Where-Object { $_.IsStatic } | Where-Object { -not $_.IsGenericMethod }
                foreach($method in $methods)
                {
                    if([System.Runtime.CompilerServices.ExtensionAttribute]::IsDefined($method, [System.Runtime.CompilerServices.ExtensionAttribute]))
                    {
                        $parameters = $method.GetParameters()
                        # simple extension methods first
                        if ($parameters.Count -eq 1) {
                            $extensionMethods += $method
                        }
                    }
                }
            }
        }

        $extensionMethods | ForEach-Object {
            $targetType = $_.GetParameters()[0].ParameterType
            $methodName = $_.Name

            Write-Debug "Found: TypeName: $targetType, MemberName: $methodName"

            $command = @"
[OutputType([$($_.ReturnType)])]
param()
[$($_.DeclaringType)]::$methodName(`$this)
"@


            $scriptblock = [Scriptblock]::Create($command)

            Write-Debug "--- Generated Command ---"
            Write-Debug $command
            Write-Debug "-------------------------"

            Update-TypeData -TypeName $targetType -MemberType ScriptProperty -MemberName $methodName -Value $scriptblock
        }
    }
}

function Add-TerminalConfig {
    [CmdletBinding()]
    param ()

    $InformationPreference = 'Continue'

    if (-not $IsWindows) {
        Write-Error 'Only supported on Windows'
        return
    }

    $file = 'default.json'
    $dir = Join-Path $env:LOCALAPPDATA 'Microsoft\Windows Terminal\Fragments\MartinsProfile'
    $path = Join-Path $dir $file

    if (-not (Test-Path $dir)) {
        Write-Information "Creating Fragments Directory '$dir'"
        $null = New-Item -ItemType Directory -Path $dir -Force
    }

    if (Test-Path $path) {
        Write-Information "File '$path' already exists, removing"
        $null = Remove-Item -Path $path -Force
    }

    @{
        profiles = @(
            @{
                name        = "Martin's Profile"
                commandline = 'pwsh -NoLogo -NoProfile -NoExit -Command "Import-Module MartinsProfile"'
                icon        = 'ms-appx:///ProfileIcons/pwsh.png'
            },
            @{
                name        = 'pwsh (NoProfile)'
                commandline = 'pwsh -NoLogo -NoProfile'
                icon        = 'ms-appx:///ProfileIcons/pwsh.png'
            }
        )
        schemes  = @(
            @{
                name = "Tokyo Night"
                black = "#15161e"
                red = "#f7768e"
                green = "#9ece6a"
                yellow = "#e0af68"
                blue = "#7aa2f7"
                purple = "#bb9af7"
                cyan = "#7dcfff"
                white = "#a9b1d6"
                brightBlack = "#414868"
                brightRed = "#f7768e"
                brightGreen = "#9ece6a"
                brightYellow = "#e0af68"
                brightBlue = "#7aa2f7"
                brightPurple = "#bb9af7"
                brightCyan = "#7dcfff"
                brightWhite = "#c0caf5"
                background = "#1a1b26"
                foreground = "#c0caf5"
                selectionBackground = "#33467c"
                cursorColor = "#c0caf5"
            },
            @{
                name                = 'Dracula'
                black               = '#000000'
                red                 = '#ff5555'
                green               = '#50fa7b'
                yellow              = '#f1fa8c'
                blue                = '#bd93f9'
                purple              = '#ff79c6'
                cyan                = '#8be9fd'
                white               = '#bbbbbb'
                brightBlack         = '#555555'
                brightRed           = '#ff5555'
                brightGreen         = '#50fa7b'
                brightYellow        = '#f1fa8c'
                brightBlue          = '#bd93f9'
                brightPurple        = '#ff79c6'
                brightCyan          = '#8be9fd'
                brightWhite         = '#ffffff'
                background          = '#1e1f29'
                foreground          = '#f8f8f2'
                selectionBackground = '#44475a'
                cursorColor         = '#bbbbbb'
            },
            @{
                name                = 'GitHub Dark'
                black               = '#000000'
                red                 = '#f78166'
                green               = '#56d364'
                yellow              = '#e3b341'
                blue                = '#6ca4f8'
                purple              = '#db61a2'
                cyan                = '#2b7489'
                white               = '#ffffff'
                brightBlack         = '#4d4d4d'
                brightRed           = '#f78166'
                brightGreen         = '#56d364'
                brightYellow        = '#e3b341'
                brightBlue          = '#6ca4f8'
                brightPurple        = '#db61a2'
                brightCyan          = '#2b7489'
                brightWhite         = '#ffffff'
                background          = '#101216'
                foreground          = '#8b949e'
                selectionBackground = '#3b5070'
                cursorColor         = '#c9d1d9'
            }
        )
    } | ConvertTo-Json | Set-Content -Path $path -Encoding UTF8

    Write-Information "Saved '$path'"
    Write-Warning "You need to restart Windows Terminal to take effect."
}

function Clear-Space {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(ParameterSetName = 'All')]
        [switch]$All,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$Folders,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$Chocolatey,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$Scoop,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$NuGet,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$Info,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$RecycleBin,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$Docker,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$DockerVhdx,

        [Parameter(ParameterSetName = 'Individual')]
        [switch]$Yarn,

        [Parameter(ParameterSetName = 'Help')]
        [switch]$Help
    )

    if (-not $IsWindows) {
        Write-Error 'Only supported on Windows OS'
        return
    }

    if ($Help) {
        Get-Help Clear-Space
        return
    }

    if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) {
        Write-Error 'Must Run as Administrator.'
        return
    }

    class SpaceInfo {
        $Before
        $After
        $Info
        $Command
    }

    function DirectorySize() {
        param (
            $Path
        )

        (du -c -nobanner -l 1000 $Path | ConvertFrom-Csv | Measure-Object -AllStats -Property DirectorySize).Sum.bytes
    }

    $cHeader = '{0}{1}' -f $PSStyle.Foreground.Black, $PSStyle.Background.LightYellow
    $cInfoStart = $PSStyle.Foreground.LightYellow
    $cText = $PSStyle.Foreground.Yellow
    $cName = $PSStyle.Foreground.Cyan
    $cFolder = $PSStyle.Foreground.Magenta
    $cReset = $PSStyle.Reset
    $cSuccess = $PSStyle.Foreground.LightGreen

    $drives = Get-PhysicalPsDrive | ForEach-Object { @{ Name = $_.Name ; StartFree = $_.Free } }

    $results = @()
    $startFree = (Get-PSDrive c).Free

    Write-Host
    Write-Host "$cHeader ---- Clean ---- $cReset"
    Write-Host

    if ($All -or $Folders) {
        @(
            @{ Path = $env:TEMP; Name = '$env:TEMP' },
            @{ Path = $env:TMP; Name = '$env:TMP' },
            @{ Path = 'c:\temp'; Name = 'c:\temp' }) | ForEach-Object {
            Write-Host "$cText $cName$($_.Name) $cText($cFolder$($_.Path)$cText) folder$cReset"

            $sizeBefore = DirectorySize -Path $_.Path
            if ($PSCmdlet.ShouldProcess($_, 'Clean')) {
                Get-ChildItem $_.Path | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue

            }

            $sizeAfter = DirectorySize -Path $_.Path
            $results += [SpaceInfo]@{ Command = 'Folders'; Info = "Directory $($_.Name)"; Before = $sizeBefore; After = $sizeAfter }

            Write-Host
        }
    }

    if ($All -or $Docker) {
        if ($null -eq (Get-Command -ErrorAction SilentlyContinue docker)) {
            Write-Warning 'Docker not in PATH. Skipping.'
        } else {

            Write-Host "$cText Docker" -ForegroundColor Cyan
            if ($PSCmdlet.ShouldProcess('Docker', 'Prune')) {
                docker system prune --force
            }
            if ($All -or $DockerVhdx) {
                if ($null -eq (Get-Command -ErrorAction SilentlyContinue wsl)) {
                    Write-Warning 'WSL not in PATH. Skipping.'
                } else {
                    Write-Host "$cText  Docker VHXD" -ForegroundColor Cyan
                    if ($PSCmdlet.ShouldProcess('Docker', 'Shrink VXHD')) {
                        wsl --shutdown
                        Optimize-VHD -Path $env:LOCALAPPDATA\Docker\wsl\Data\ext4.vhdx, $env:LOCALAPPDATA\Packages\CanonicalGroupLimited.*\LocalState\ext4.vhdx  -Mode full
                        Write-Warning 'You Will Need to Restart Docker Desktop.'
                    }
                }
            }
            Write-Host
        }
    }

    if ($All -or $Chocolatey) {
        if ($null -eq (Get-Command -ErrorAction SilentlyContinue choco)) {
            Write-Warning 'Choco not in PATH. Skipping.'
        } else {
            Write-Host "$cText🍫 Chocolatey$cReset"
            if ($PSCmdlet.ShouldProcess('Chocolatey', 'Cleaner')) {
                cinst choco-cleaner -y
                choco-cleaner
            }
            Write-Host
        }
    }

    if ($All -or $Scoop) {
        if ($null -eq (Get-Command -ErrorAction SilentlyContinue scoop)) {
            Write-Warning 'Scoop not in PATH. Skipping.'
        } else {

            Write-Host "$cText🍧 Scoop$cReset"
            if ($PSCmdlet.ShouldProcess('Scoop', 'Cleanup, Empty Cache')) {
                scoop cleanup *
                scoop cache rm *
            }
            Write-Host
        }
    }

    if ($All -or $RecycleBin) {
        Write-Host "$cText Recycle Bin$cReset"
        if ($PSCmdlet.ShouldProcess('Recycle Bin', 'Clear')) {
            Clear-RecycleBin -Force
        }
        Write-Host
    }

    if ($All -or $NuGet) {
        if ($null -eq (Get-Command -ErrorAction SilentlyContinue dotnet)) {
            Write-Warning 'dotnet cli not in PATH. Skipping.'
        } else {

            Write-Host "$cText NuGet$cReset"
            if ($PSCmdlet.ShouldProcess('NuGet', 'Clear Caches')) {
                dotnet nuget locals all --clear
            }
            Write-Host
        }
    }

    if ($All -or $Yarn) {
        if ($null -eq (Get-Command -ErrorAction SilentlyContinue yarn)) {
            Write-Warning 'yarn not in path. Skipping.'
        } else {
            if ($null -eq (Get-Command -ErrorAction SilentlyContinue node)) {
                Write-Warning 'node not in path. Skipping.'
            } else {
                Write-Host "$cText Yarn$cReset"
                if ($PSCmdlet.ShouldProcess('Yarn', 'Clear Caches')) {
                    yarn cache clean --emoji true
                }
                Write-Host
            }
        }
    }

    $endFree = (Get-PSDrive c).Free
    $spaceRecovered = ($endFree - $startFree).bytes

    function InfoTable() {
        param (
            $DataTable,
            $Name

        )

        $tableFormat = @(@{
                Expression = { $v = ([int]$_.Size).bytes ; "$cName{0:n1} {1}$cReset" -f $v.LargestWholeNumberValue, $v.LargestWholeNumberSymbol }
                Label      = 'Size'
            }
            , 'Filename')

        Write-Host
        Write-Host "$cInfoStart === ${cText}$Name$cReset"
        $DataTable | Select-Object -First 10 | Format-Table -Property $tableFormat

        $stats = $DataTable | Measure-Object -Sum -Property Size
        Write-Host "Total Items: $($stats.Count)"
        Write-Host ("Total Size: $cName{0:n1} {1}$cReset" -f $stats.Sum.bytes.LargestWholeNumberValue, $stats.Sum.bytes.LargestWholeNumberSymbol)
    }

    if ($All -or $Info) {
        if ($null -eq (Get-Command -ErrorAction SilentlyContinue es)) {
            Write-Warning 'es [1] not in PATH. Skipping. [1] voidtools everything command line'
        } else {
            Write-Host "$cHeader ---- Info ---- $cReset"
            InfoTable -DataTable (es -csv -sort-size -size wholefilename:cache attrib:D | ConvertFrom-Csv) -Name 'Caches'
            InfoTable -DataTable (es -csv -sort-size -size wholefilename:temp attrib:D | ConvertFrom-Csv) -Name 'Temps'
            InfoTable -DataTable (es -csv -sort-size -size ext:log attrib:F | ConvertFrom-Csv) -Name 'Logs'
            InfoTable -DataTable (es -csv -sort-size -size (Resolve-Path ~\downloads) | ConvertFrom-Csv) -Name 'Downloads'
        }
    }

    $drives = $drives | ForEach-Object { $_.EndFree = (Get-PSDrive -Name $_.Name).Free ; $_.Recovered = $_.EndFree - $_.StartFree; $_ } | ForEach-Object { [PSCustomObject]$_ }

    Write-Host
    $drives | Format-Table -Property    @{ Label = "💾 ${cText}Name$cReset"; Expression = { "💾 ${cText}$($_.Name)$cReset" } },
    @{ Label = "▶ ${cName}Start$cReset"; Expression = { "▶ ${cName}{0,6:n1} {1,-2:s}$cReset" -f $_.StartFree.bytes.LargestWholeNumberValue, $_.StartFree.bytes.LargestWholeNumberSymbol } },
    @{ Label = "🏁 ${cFolder}End$cReset"; Expression = { "🏁 ${cFolder}{0,6:n1} {1,-2:s}$cReset" -f $_.EndFree.bytes.LargestWholeNumberValue, $_.EndFree.bytes.LargestWholeNumberSymbol } },
    @{ Label = "✔ ${cSuccess}Freed$cReset"; Expression = { "✔ ${cSuccess}{0,6:n1} {1,-2:s}$cReset" -f $_.Recovered.bytes.LargestWholeNumberValue, $_.Recovered.bytes.LargestWholeNumberSymbol } }
    Write-Host
    Write-Host "${cSuccess}Done. Bye. 🙋‍$cReset"
}

function ConvertTo-EncodedCommand {
    <#
        .Description
            Encode a command, as required by pwsh -EncodedCommand
    #>

    [CmdletBinding()]

    param (
        [Parameter(Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        # Command (string) to encode.
        $Command
    )
    process {
        $bytes = [System.Text.Encoding]::Unicode.GetBytes($Command)
        $encodedCommand = [Convert]::ToBase64String($bytes)
        $encodedCommand
    }
}


function script:Find-VisualStudio {
    [CmdletBinding()]
    param ()

    if (-not $IsWindows) {
        Write-Error 'Only supported on Windows.'
        return
    }

    $vsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
    if (-not (Test-Path $vsWhere)) {
        Write-Error "'vswhere.exe' not found in default location: '$vsWhere'. Is Visual Studio installed?"
    }

    &$vsWhere -format json | ConvertFrom-Json | ForEach-Object { $_.PSObject.TypeNames.Insert(0, 'VsWhereOutput'); $_ }
}


function Get-DropboxFolder {
    <#
        .SYNOPSIS
            Find the location where Dropbox stores files
            for the current user.
    #>

    [CmdletBinding()]
    param ()

    $info = $null

    $testPaths = @()
    if ($IsWindows) {
        $testPaths = @(
            (Join-Path "$env:APPDATA" 'dropbox\info.json')
            (Join-Path "$env:LOCALAPPDATA" 'dropbox\info.json')
        )
    } else {
        WriteDebug "Dropbox not supported on $env:OS"
        return $null
    }

    if ($testPaths.Count -le 0) {
        WriteDebug 'Dropbox not installed'
        return $null
    }

    $path = $testPaths | Where-Object { Test-Path $_ } | Select-Object -First 1

    if ($null -eq $path) {
        WriteDebug 'Dropbox is not found'
        return $null
    }
    $info = Get-Content $path | ConvertFrom-Json

    if ($null -eq $info) {
        Write-Warning 'Dropbox info.json invalid.'
        return $null
    }

    $path = $info.personal.path
    Write-Verbose "Found Path: $path"
    $path
}


function Get-OneDriveFolder() {
    [CmdletBinding()]
    param(
        [switch]$FullInfo
    )
    if (-not $IsWindows) {
        Write-Error 'Only supported on Windows'
        return
    }

    $folders = Get-ChildItem HKCU:\Software\Microsoft\OneDrive\Accounts\ |
        ForEach-Object { [pscustomobject]@{
                Name      = Split-Path $_.Name -Leaf
                Folder    = $_.GetValue('UserFolder')
                UserEmail = $_.GetValue('UserEmail')
            } }

    if ($FullInfo) {
        $folders
    } else {
        $folders.Folder | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
    }
}

<#
.SYNOPSIS
    Get the location of various profile related files.

.DESCRIPTION
    Get the location of various profile related files.

.EXAMPLE
    Get-ProfileLocation

    Get the location of various profile related files.

.OUTPUTS
    [PSCustomObject]

    A custom object with the properties:
        - Name: The name of the profile related file.
        - Location: The location of the profile related file.
        - Exists: A string indicating whether the file exists or not, colored green for "YES" and red for "NO".

.PARAMETER
    None

.NOTES
    This function is only available in PowerShell v3 and later, due to the use of the $PSStyle automatic variable.

.LINK
    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/about/about_automatic_variables?view=powershell-7#psstyle

#>


function Get-ProfileLocation {
    [CmdletBinding()]
    param()

    $stYes = $PSStyle.Background.Green + $PSStyle.Foreground.BrightWhite
    $stNo = $PSStyle.Background.Red + $PSStyle.Foreground.BrightWhite
    $r = $PSStyle.Reset
    $Profile | Get-Member -MemberType NoteProperty | ForEach-Object {
        [PSCustomObject]@{
            Name = $_.Name
            Exists = (Test-Path $Profile.$($_.Name)) ? "$stYes YES $r" : "$stNo NO $r"
            Location = $Profile.$($_.Name)
        }
    }
}



function Get-Quote {
    [CmdletBinding()][Alias('gq')]
    param(
        [Parameter(ParameterSetName = 'File', Mandatory = $true)]
        [string]$Path,

        [Parameter(ParameterSetName = 'Known', Mandatory = $true)]
        [ValidateSet('ChuckNorris', 'Quotes')]
        [string]$Source,

        [int]$Count = 1,

        [switch]$Pretty
    )

    if ($PSCmdlet.ParameterSetName -eq 'Path') {
        if (!(Test-Path $Path) ) {
            throw "File not found: '$Path'"
        }
    }

    if ($PSCmdLet.ParameterSetName -eq 'Known') {
        switch ($Source) {
            'Quotes' {
                $Path = (Resolve-Path "$PSScriptRoot/config/Quotes/attributed-quotes.txt")
            }
            'ChuckNorris' {
                $Path = (Resolve-Path "$PSScriptRoot/config/Quotes/chuck-norris.txt")
            }
            Default {
                throw "Unknown Source '$Source'"
            }
        }
    }

    $quotes = Get-Content $Path | Where-Object { $_ } | Get-Random -Count $Count
    if ($Pretty) {
        $quotes | Show-MyProQuote
    } else {
        $quotes
    }
}

function Get-StringHash {
    <#
        .Synopsis
            Get a hash/checksum for a string.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        # String to encode
        [string] $String,

        [ValidateSet('SHA1', 'SHA256', 'SHA384', 'SHA512', 'MD5')]
        # Hashing algorithm to use. Default is SHA1
        [string]$Algorithm = 'SHA1'
    )
    $stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($String))
    Get-FileHash -InputStream $stream -Algorithm $Algorithm
}



enum Terminals {
    Unknown
    VsCode
    JetBrainsJediTerm
    WindowsTerminal
    WindowsConsole
    Console2Z
    ConEmu
    FluentTerminal
    AzureCloudShell
}

function Get-Terminal {
    [OutputType([Terminals])]
    [CmdletBinding()]
    param (
        [switch]$ListKnown
    )

    if ($ListKnown) {
        return [Terminals].GetEnumNames() | Select-Object -Skip 1
    }

    # VS Code
    if ($env:TERM_PROGRAM -eq 'vscode' ) {
        return [Terminals]::VsCode
    }

    # IntelliJ
    if ($env:TERMINAL_EMULATOR -eq 'JetBrains-JediTerm') {
        return [Terminals]::JetBrainsJediTerm
    }

    # AzureCloudShell
    if ($null -ne $env:ACC_CLOUD) {
        return [Terminals]::AzureCloudShell
    }

    # Resort to Process Name
    $processName = (Get-Process -Id $PID).Parent.Name
    switch ($processName) {
        'code' {
            [Terminals]::VsCode
        }
        'idea64' {
            [Terminals]::JetBrainsJediTerm
        }
        'rider64' {
            [Terminals]::JetBrainsJediTerm
        }
        'WindowsTerminal' {
            [Terminals]::WindowsTerminal
        }
        { $PSItem -in 'explorer', 'conhost' } {
            [Terminals]::WindowsConsole
        }
        'Console' {
            [Terminals]::Console2Z
        }
        'ConEmuC64' {
            [Terminals]::ConEmu
        }
        'FluentTerminal.SystemTray' {
            [Terminals]::FluentTerminal
        }
        Default {
            [Terminals]::Unknown
        }
    }
}

Function New-DynamicParam {
    <#
        .SYNOPSIS
            Helper function to simplify creating dynamic parameters
 
        .DESCRIPTION
            Helper function to simplify creating dynamic parameters
            Example use cases:
                Include parameters only if your environment dictates it
                Include parameters depending on the value of a user-specified parameter
                Provide tab completion and intellisense for parameters, depending on the environment
            Please keep in mind that all dynamic parameters you create will not have corresponding variables created.
               One of the examples illustrates a generic method for populating appropriate variables from dynamic parameters
               Alternatively, manually reference $PSBoundParameters for the dynamic parameter value
        .NOTES
            Source: https://github.com/RamblingCookieMonster/PowerShell/blob/master/New-MyProDynamicParam.ps1
            Credit to http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/
                Added logic to make option set optional
                Added logic to add RuntimeDefinedParameter to existing DPDictionary
                Added a little comment based help
            Credit to BM for alias and type parameters and their handling
        .PARAMETER Name
            Name of the dynamic parameter
        .PARAMETER Type
            Type for the dynamic parameter. Default is string
        .PARAMETER Alias
            If specified, one or more aliases to assign to the dynamic parameter
        .PARAMETER ValidateSet
            If specified, set the ValidateSet attribute of this dynamic parameter
        .PARAMETER Mandatory
            If specified, set the Mandatory attribute for this dynamic parameter
        .PARAMETER ParameterSetName
            If specified, set the ParameterSet attribute for this dynamic parameter
        .PARAMETER Position
            If specified, set the Position attribute for this dynamic parameter
        .PARAMETER ValueFromPipelineByPropertyName
            If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter
        .PARAMETER HelpMessage
            If specified, set the HelpMessage for this dynamic parameter
 
        .PARAMETER DPDictionary
            If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary (appropriate for multiple dynamic parameters)
            If not specified, create and return a RuntimeDefinedParameterDictionary (appropriate for a single dynamic parameter)
            See final example for illustration
        .EXAMPLE
 
            function Show-Free
            {
                [CmdletBinding()]
                Param()
                DynamicParam {
                    $options = @( gwmi win32_volume | %{$_.driveletter} | sort )
                    New-MyProDynamicParam -Name Drive -ValidateSet $options -Position 0 -Mandatory
                }
                begin{
                    #have to manually populate
                    $drive = $PSBoundParameters.drive
                }
                process{
                    $vol = gwmi win32_volume -Filter "driveletter='$drive'"
                    "{0:N2}% free on {1}" -f ($vol.Capacity / $vol.FreeSpace),$drive
                }
            } #Show-Free
            Show-Free -Drive <tab>
        # This example illustrates the use of New-MyProDynamicParam to create a single dynamic parameter
        # The Drive parameter ValidateSet populates with all available volumes on the computer for handy tab completion / intellisense
        .EXAMPLE
        # I found many cases where I needed to add more than one dynamic parameter
        # The DPDictionary parameter lets you specify an existing dictionary
        # The block of code in the Begin block loops through bound parameters and defines variables if they don't exist
            Function Test-DynPar{
                [cmdletbinding()]
                param(
                    [string[]]$x = $Null
                )
                DynamicParam
                {
                    #Create the RuntimeDefinedParameterDictionary
                    $Dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
 
                    New-MyProDynamicParam -Name AlwaysParam -ValidateSet @( gwmi win32_volume | %{$_.driveletter} | sort ) -DPDictionary $Dictionary
                    #Add dynamic parameters to $dictionary
                    if($x -eq 1)
                    {
                        New-MyProDynamicParam -Name X1Param1 -ValidateSet 1,2 -mandatory -DPDictionary $Dictionary
                        New-MyProDynamicParam -Name X1Param2 -DPDictionary $Dictionary
                        New-MyProDynamicParam -Name X3Param3 -DPDictionary $Dictionary -Type DateTime
                    }
                    else
                    {
                        New-MyProDynamicParam -Name OtherParam1 -Mandatory -DPDictionary $Dictionary
                        New-MyProDynamicParam -Name OtherParam2 -DPDictionary $Dictionary
                        New-MyProDynamicParam -Name OtherParam3 -DPDictionary $Dictionary -Type DateTime
                    }
 
                    #return RuntimeDefinedParameterDictionary
                    $Dictionary
                }
                Begin
                {
                    #This standard block of code loops through bound parameters...
                    #If no corresponding variable exists, one is created
                        #Get common parameters, pick out bound parameters not in that set
                        Function _temp { [cmdletbinding()] param() }
                        $BoundKeys = $PSBoundParameters.keys | Where-Object { (get-command _temp | select -ExpandProperty parameters).Keys -notcontains $_}
                        foreach($param in $BoundKeys)
                        {
                            if (-not ( Get-Variable -name $param -scope 0 -ErrorAction SilentlyContinue ) )
                            {
                                New-Variable -Name $Param -Value $PSBoundParameters.$param
                                Write-Verbose "Adding variable for dynamic parameter '$param' with value '$($PSBoundParameters.$param)'"
                            }
                        }
                    #Appropriate variables should now be defined and accessible
                        Get-Variable -scope 0
                }
            }
        # This example illustrates the creation of many dynamic parameters using New-MyProDynamicParam
            # You must create a RuntimeDefinedParameterDictionary object ($dictionary here)
            # To each New-MyProDynamicParam call, add the -DPDictionary parameter pointing to this RuntimeDefinedParameterDictionary
            # At the end of the DynamicParam block, return the RuntimeDefinedParameterDictionary
            # Initialize all bound parameters using the provided block or similar code
        .FUNCTIONALITY
            PowerShell Language
    #>

    param(

        [string]
        $Name,

        [System.Type]
        $Type = [string],

        [string[]]
        $Alias = @(),

        [string[]]
        $ValidateSet,

        [switch]
        $Mandatory,

        [string]
        $ParameterSetName = '__AllParameterSets',

        [int]
        $Position,

        [switch]
        $ValueFromPipelineByPropertyName,

        [string]
        $HelpMessage,

        [validatescript({
                if (-not ( $_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary] -or -not $_) ) {
                    Throw 'DPDictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object, or not exist'
                }
                $True
            })]
        $DPDictionary = $false

    )
    #Create attribute object, add attributes, add to collection
    $ParamAttr = New-Object System.Management.Automation.ParameterAttribute
    $ParamAttr.ParameterSetName = $ParameterSetName
    if ($mandatory) {
        $ParamAttr.Mandatory = $True
    }
    if ($Position -ne $null) {
        $ParamAttr.Position = $Position
    }
    if ($ValueFromPipelineByPropertyName) {
        $ParamAttr.ValueFromPipelineByPropertyName = $True
    }
    if ($HelpMessage) {
        $ParamAttr.HelpMessage = $HelpMessage
    }

    $AttributeCollection = New-Object 'Collections.ObjectModel.Collection[System.Attribute]'
    $AttributeCollection.Add($ParamAttr)

    #param validation set if specified
    if ($ValidateSet) {
        $ParamOptions = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $ValidateSet
        $AttributeCollection.Add($ParamOptions)
    }

    #Aliases if specified
    if ($Alias.count -gt 0) {
        $ParamAlias = New-Object System.Management.Automation.AliasAttribute -ArgumentList $Alias
        $AttributeCollection.Add($ParamAlias)
    }


    #Create the dynamic parameter
    $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)

    #Add the dynamic parameter to an existing dynamic parameter dictionary, or create the dictionary and add it
    if ($DPDictionary) {
        $DPDictionary.Add($Name, $Parameter)
    } else {
        $Dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $Dictionary.Add($Name, $Parameter)
        $Dictionary
    }
}



function New-TemporaryFolder {
    $tempPath = [System.IO.Path]::GetTempPath() + [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName())
    New-Item -ItemType Directory -Path $tempPath
}

function Resolve-Command {
    <#
    .SYNOPSIS
        Equivalent to unix "which" command.
    .DESCRIPTION
        Equivalent to unix "which" command.
 
        Gets the first available command, ie. the command that will
        will be used if you invoke it from the CLI.
 
        NOTE: for Functions et.al. it will return the DLL name, or
        module name.
    .EXAMPLE
        PS C:\> Resolve-Command pwsh.exe
        Test if pwsh.exe exists in available in path.
    .EXAMPLE
        PS C:\> Resolve-Command Get-ChildItem
    #>

    [CmdletBinding()]
    param (
        [OutputType([string])]
        [Parameter(Mandatory = $true)]
        [string] $Name
    )
    process {
        if ($IsWindows) {
            $path = where.exe $Name 2>&1
            if ($?) {
                return $path | Select-Object -First 1
            }
        } else {
            $path = which $Name 2>&1
            if ($?) {
                return $path
            }
        }

        $alias = Get-Alias -Name $Name -ErrorAction SilentlyContinue
        if ($?) {
            return $alias;
        }

        Get-Command $Name -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source
    }
}

function Show-AzBuild() {
    [CmdletBinding()]
    param ()

    $raw = az pipelines build list | ConvertFrom-Json
    $raw |
        Sort-Object -Property startTime -Descending |
        Format-Table -Property @(
            @{Label = 'R'; Expression = {
                    switch ($_.result) {
                        'succeeded' {
                            '💚'
                        }
                        'failed' {
                            '🔴'
                        }
                        'canceled' {
                            '🚫'
                        }
                        Default {
                            $_.result
                        }
                    } }
            }
            @{Label = 'DefName'; Expression = { $_.definition.name } }
            @{Label = 'DefId'; Expression = { $_.definition.id } }
            @{Label = 'BuildId' ; Expression = { $_.id } }
            @{Label = 'BuildNumber' ; Expression = { $PSStyle.FormatHyperlink($_.buildNumber, $_.url) } }
            @{Label = 'StartTime' ; Expression = { $_.startTime } }
            @{Label = 'Status' ; Expression = { $_.status } }
        )
}


function Show-CustomViews() {

    $formats = @(
        @{ File = (Get-Item "$PSScriptRoot/../Formats/FileSystemTypes.formats.ps1xml" ); Example = { Get-ChildItem "$PSScriptRoot/.." | Format-Table } }
        @{ File = (Get-Item "$PSScriptRoot/../Formats/CommandInfo.formats.ps1xml" ); Example = { Get-Command -Name gi, sm } }
        @{ File = (Get-Item "$PSScriptRoot/../Formats/HistoryInfo.formats.ps1xml" ); Example = { Get-History -Count 2 | Format-Table } }
        @{ File = (Get-Item "$PSScriptRoot/../Formats/Runtime.formats.ps1xml" ); Example = { ''.GetType() | Format-Table } }
        @{ File = (Get-Item "$PSScriptRoot/../Formats/TimeSpan.formats.ps1xml" ); Example = { New-TimeSpan -Seconds 4 -Minutes 2 -Hours 3 } }
    )

    $types = @(
        @{ File = (Get-Item "$PSScriptRoot/../Types/CommandInfo.formats.ps1xml" ); Example = { Get-Command -Name gi, sm } }
    )

    foreach ($item in @($formats + $types)) {
        $header = $item.File.BaseName
        Write-Host "$($PSStyle.Foreground.BrightBlue)====== $($PSStyle.Foreground.White)${header} $($PSStyle.Foreground.BrightBlue)======"

        $item.Example.Invoke()

        Write-Host ''
        Write-Host "$($PSStyle.Foreground.BrightBlue)======"
        Write-Host ''
    }
}

function Show-Quote {
    <#
        .Synopsis
            Pretty print a quote.
        .Notes
            The quote must be a single-line string with the quote attribution
            after the last '--'.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        # The text to format.
        $Text,
        [Int]
        # Width of the displayed qoute.
        $Width = 50,
        [string]$BackgroundColor = $PSStyle.Background.Black,
        [string]$FrameColor = $PSStyle.Foreground.BrightWhite,
        [string]$QuoteColor = $PSStyle.Foreground.BrightYellow,
        [string]$AttribColor = $PSStyle.Foreground.BrightCyan
    )

    $quoteMatch = ([Regex]'^(?<text>.+)\s*--(?!.*--)\s*(?<attrib>.+)$').Match($Text.Trim())

    if ($quoteMatch.Success) {
        $quote = $quoteMatch.Groups['text'].Value
        $attrib = $quoteMatch.Groups['attrib'].Value
    } else {
        $quote = $Text.Trim()
        $attrib = $null
    }

    $myStack = New-Object System.Collections.Queue
    $quote -split ' ' | ForEach-Object { $myStack.Enqueue($_) }

    $leftIndent = ' '
    $rightOutdent = " $($PSStyle.Reset)"
    $tl = "${FrameColor}┌"
    $tr = "${FrameColor}┐"
    $bl = "${FrameColor}└"
    $br = "${FrameColor}┘"
    $vr = "${FrameColor}│"
    $hr = "${FrameColor}─"
    $hd = "${FrameColor}┬"

    $blankLine = "$BackgroundColor" + (' ' * ($Width + 6))

    Write-Host $blankLine
    Write-Host -Object ($BackgroundColor + $leftIndent + $tl + ($hr * $Width) + $tr + $rightOutdent)

    $count = 0
    $curLine = ''
    while ($myStack.Count -gt 0) {
        if (($curLine + ' ' + $myStack.Peek()).Length -gt ($Width - 2)) {
            Write-Host -Object ("$BackgroundColor$leftIndent{0}$QuoteColor{1,-$($Width)}{0}$rightOutdent" -f $vr, $curLine)
            $curLine = ' ' + $myStack.Dequeue()
        } else {
            $count++
            $curLine += ' ' + $myStack.Dequeue()
        }
    }
    Write-Host -Object ("$BackgroundColor$leftIndent{0}$QuoteColor{1,-$Width}{0}$RightOutdent" -f $vr, $curLine)

    if ($null -ne $attrib) {
        Write-Host -Object ("$BackgroundColor$leftIndent{0}{1}{1}{2}{3}{4}$RightOutdent" -f $bl, $hr, $hd, ($hr * ($Width - 3)), $br)
        Write-Host -Object ("$BackgroundColor$leftIndent {0} $AttribColor{1,-$($Width - 3)}$RightOutdent" -f $bl, $attrib)
    } else {
        Write-Host -Object ("$BackgroundColor$leftIndent{0}{1}{1}{2}{3}{4}$RightOutdent" -f $bl, $hr, $hr, ($hr * ($Width - 3)), $br)
    }

    Write-Host $blankLine
}


function Switch-DarkMode {
    <#
        .SYNOPSIS
            Switch Windows Dark Mode
    #>

    [CmdletBinding()]
    param (
        [Parameter(HelpMessage = 'Only show current state, do not switch')]
        [switch]
        $OnlyShowState
    )

    if (!$IsWindows) {
        Write-Error 'Only supported on Windows'
        return
    }

    $personalizePath = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize'
    $appState = Get-ItemProperty -Path $personalizePath -Name 'AppsUseLightTheme' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'AppsUseLightTheme'
    $osState = Get-ItemProperty -Path $personalizePath -Name 'SystemUsesLightTheme' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'SystemUsesLightTheme'

    if (!$OnlyShowState.IsPresent) {
        Set-ItemProperty -Path $personalizePath -Name 'AppsUseLightTheme' -Value ($appState -eq 0 ? 1 : 0)
        Set-ItemProperty -Path $personalizePath -Name 'SystemUsesLightTheme' -Value ($osState -eq 0 ? 1 : 0)
    }

    $appState = Get-ItemProperty -Path $personalizePath -Name 'AppsUseLightTheme' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'AppsUseLightTheme'
    $osState = Get-ItemProperty -Path $personalizePath -Name 'SystemUsesLightTheme' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'SystemUsesLightTheme'
    $light = "[" + $PSStyle.Background.White + $PSStyle.Foreground.Black + " LIGHT " + $PSStyle.Reset + "]"
    $dark  = "[" +$PSStyle.Background.Black + $PSStyle.Foreground.White + " DARK " + $PSStyle.Reset + "]"

    Write-Output "Apps: $($appState -eq 0 ? $dark : $light) OS: $($osState -eq 0 ? $dark : $light)"
}


function Test-Command {
    <#
    .SYNOPSIS
        Test if a command/exe exists in memory or on path.
    .EXAMPLE
        PS C:\> Test-Command pwsh.exe
        Test if pwsh.exe exists in available in path.
        .EXAMPLE
        PS C:\> Test-Command Get-ChildItem
        Test if Get-ChildItem cmdlet is available.
    .OUTPUTS
        $true / $false
    #>

    [CmdletBinding()]
    param (
        [OutputType([boolean])]
        [Parameter(Mandatory = $true)]
        [string] $Name
    )
    process {
        $null -ne (Resolve-Command $Name -ErrorAction SilentlyContinue)
    }
}


$moduleSw = [System.Diagnostics.Stopwatch]::StartNew()
$script:timers = @()
########
# Debug Mode
########
if (('Desktop' -eq $PSVersionTable.PSEdition) -or ($PSVersionTable.PSVersion.Major -ge 7)) {
    # Detect Shift Key
    try {
        # Check SHIFT state ASAP at startup so I can use that to control verbosity :)
        Add-Type -Assembly PresentationCore, WindowsBase
        if ([System.Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::LeftShift) -OR
            [System.Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::RightShift)) {
            $ProfileDebugMode = 'true'
        }
    } catch {
        # If that didn't work ... oh well.
    }
}

if ($env:MartinsProfileDebugMode) {
    $ProfileDebugMode = 'true'
}

########
# Critical Variables
########
$messagePrefix = "$($PSStyle.Foreground.BrightBlack)${moduleNamespace}$($PSStyle.Reset): "

if ($ProfileDebugMode) {
    WriteDebug '>>> MyProfile Runing in DEBUG Mode <<<'
}

$VerboseDepth = 0
$VerboseBlockName = [System.Collections.Stack]::new()

########
# Setup Profile
########

VerboseBlock 'Config Blocks' {
    $configs = @(Get-ChildItem -Path $PSScriptRoot\config\init.d\*.ps1 -ErrorAction SilentlyContinue ) | Sort-Object -Property Name
    foreach ($item in $configs) {
        WriteDebug "⚙️ $($item.BaseName)"
        . $item.FullName
    }
}

$moduleSw.Stop()
$script:timers += @{ Name = 'Total'; Timer = $moduleSw.ElapsedMilliseconds }

if ($ProfileDebugMode) {
    $longest = ($script:timers | Sort-Object -Property @{ Expression = { $_.Name.Length } } -Descending | Select-Object -First 1).Name.Length
    $script:timers | ForEach-Object {
        WriteDebug ("⌛ {0,-$longest} {1,5} ms" -f $_.Name, $_.Timer)
    }
}

$formats = Get-ChildItem -Path $PSScriptRoot/Formats -Filter '*.ps1xml'
Update-FormatData -PrependPath $formats

$types = Get-ChildItem -Path $PSScriptRoot/Types -Filter '*.ps1xml'
Update-TypeData -PrependPath $types


Write-Information "⏲️ $($PSStyle.Foreground.BrightYellow)MartinsProfile$($PSStyle.Foreground.BrightBlue) processed in $($PSStyle.Foreground.BrightYellow)$($moduleSw.ElapsedMilliseconds)$($PSStyle.Foreground.BrightBlue) ms.$($PSStyle.Reset)" -InformationAction 'Continue'