PowerProfile.Core.psm1

#Requires -Version 5.1

#region Module Script Variables
if ($null -eq $PSStyle) {

    # Lazy support for PSStyle for PS version <7.2.0
    $e = [char]27
    $Script:PSStyle = [PSCustomObject]@{
        Reset               = "$e[0m"
        BlinkOff            = "$e[25m"
        Blink               = "$e[5m"
        BoldOff             = "$e[22m"
        Bold                = "$e[1m"
        HiddenOff           = "$e[28m"
        Hidden              = "$e[8m"
        ReverseOff          = "$e[27m"
        Reverse             = "$e[7m"
        ItalicOff           = "$e[23m"
        Italic              = "$e[3m"
        UnderlineOff        = "$e[24m"
        Underline           = "$e[4m"
        StrikethroughOff    = "$e[29m"
        Strikethrough       = "$e[9m"
        Foreground = @{
            Black           = "$e[30m"
            Red             = "$e[31m"
            Green           = "$e[32m"
            Yellow          = "$e[33m"
            Blue            = "$e[34m"
            Magenta         = "$e[35m"
            Cyan            = "$e[36m"
            White           = "$e[37m"
            BrightBlack     = "$e[90m"
            BrightRed       = "$e[91m"
            BrightGreen     = "$e[92m"
            BrightYellow    = "$e[93m"
            BrightBlue      = "$e[94m"
            BrightMagenta   = "$e[95m"
            BrightCyan      = "$e[96m"
            BrightWhite     = "$e[97m"
        }
        Background = @{
            Black           = "$e[40m"
            Red             = "$e[41m"
            Green           = "$e[42m"
            Yellow          = "$e[43m"
            Blue            = "$e[44m"
            Magenta         = "$e[45m"
            Cyan            = "$e[46m"
            White           = "$e[47m"
            BrightBlack     = "$e[100m"
            BrightRed       = "$e[101m"
            BrightGreen     = "$e[102m"
            BrightYellow    = "$e[103m"
            BrightBlue      = "$e[104m"
            BrightMagenta   = "$e[105m"
            BrightCyan      = "$e[106m"
            BrightWhite     = "$e[107m"
        }
    }

    $Params = @{
        MemberType = 'ScriptMethod'
        InputObject = $PSStyle
        Name = 'FormatHyperlink'
        Value = {
            return $("$e]8;;"+$args[1]+"$e\"+$args[0]+"$e]8;;$e\")
        }
    }
    Add-Member @Params
}

$Script:PoProfileChar = @{
    GeneralPunctuation = @{
        horizontal_ellipsis     = [char]0x2026
        double_exclamation_mark = [char]0x203C
    }
}

$Script:PoProfileEmoji = @{
    Symbols = @{
        white_check_mark        = [char]0x2705
    }
}
#endregion

#region Functions: State
function Set-PoProfileState {

<#
.SYNOPSIS
    Add/change/remove a PowerProfile state item
 
.DESCRIPTION
    Adds an item to the PowerProfile state
 
.PARAMETER Name
    Key name of the state item
 
.PARAMETER Value
    Value of the state item
 
.PARAMETER Remove
    Removes the desired state item
 
.LINK
    https://PowerProfile.sh/
#>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True,Position=0,ParameterSetName='SetKey')]
        [Parameter(Mandatory=$True,Position=0,ParameterSetName='RemoveKey')]
        [string]$Name,

        [Parameter(Mandatory=$True,Position=1,ParameterSetName='SetKey')]
        [AllowEmptyString()]
        [AllowNull()]
        $Value,

        [Parameter(Mandatory=$True,ParameterSetName='RemoveKey')]
        [switch]$Remove
    )

    if (-Not (Get-Variable -Scope Script -Name 'PoProfileState' -ErrorAction Ignore)) {
        Get-PoProfileState
    }

    if ($Value -eq [bool]::TrueString -or $Value -eq [bool]::FalseString) {
        $Value = [System.Convert]::ToBoolean($Value)
    }
    if ($Remove) {
        $PoProfileState.PSObject.Properties.Remove($Name)
    }
    elseif ($null -eq $PoProfileState.PSObject.Properties.Item($Name)) {
        Add-Member -InputObject $PoProfileState -MemberType NoteProperty -Name $Name -Value $Value
    }
    else {
        $PoProfileState.$Name = $Value
    }
}

function Get-PoProfileState {

<#
.SYNOPSIS
    Get PowerProfile state
 
.DESCRIPTION
    Reads the PowerProfile state
 
.PARAMETER Name
    Return the value of a specific state item only
 
.OUTPUTS
    String, when specifying $Name, otherwise PSObject.
 
.LINK
    https://PowerProfile.sh/
#>


    [CmdletBinding()]
    Param(
        [string]$Name
    )

    $p = [System.IO.Path]::Combine(
        $env:XDG_STATE_HOME,
        $(
            if ($IsWindows) {
                'PowerShell'
            } else {
                'powershell'
            }
        ),
        'PowerProfile',
        'PowerProfile.state.json'
    )

    if (-Not (Get-Variable -Scope Script -Name 'PoProfileState' -ErrorAction Ignore)) {
        if ([System.IO.File]::Exists($p)) {
            $Script:PoProfileState = ConvertFrom-Json -InputObject ([System.IO.File]::ReadAllText($p)) -NoEnumerate -Depth 100 -ErrorAction Ignore
        } else {
            [PSCustomObject]$Script:PoProfileState = @{}
        }
    }

    if ($Name) {
        if ($null -ne $PoProfileState.PSObject.Properties.Item($Name)) {
            return $PoProfileState.$Name
        } else {
            return [PSCustomObject]@{}
        }
    } else {
        return $PoProfileState
    }
}

function Save-PoProfileState {
    # do not write permanent state as root to avoid
    # file permission hickups on *Unix
    if (-Not $IsWindows -and $null -ne $env:IsElevated) {
        return
    }

    $p = [System.IO.Path]::Combine(
        $env:XDG_STATE_HOME,
        $(
            if ($IsWindows) {
                'PowerShell'
            } else {
                'powershell'
            }
        ),
        'PowerProfile',
        'PowerProfile.state.json'
    )

    if (($PoProfileState.PSObject.Properties).Count -gt 0) {
        $baseDir = Split-Path -Path $p
        if (-Not ([System.IO.Directory]::Exists($baseDir))) {
            $null = New-Item -Type Container -Force $baseDir -ErrorAction Stop
        }
        ConvertTo-Json $PoProfileState -Compress -Depth 100 | Set-Content -Path $p -Encoding ASCII
    } elseif ([System.IO.File]::Exists($p)) {
        Remove-Item -Path $p -ErrorAction Ignore
    }
}

function Remove-PoProfileState {

<#
.SYNOPSIS
    Remove PowerProfile state item
 
.DESCRIPTION
    Removes a state item from PowerProfile
 
.PARAMETER Name
    Key name of the state item
 
.LINK
    https://PowerProfile.sh/
#>


    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSObject])]
    Param(
        [Parameter(Mandatory=$True)]
        [string]$Name
    )

    Try {
        Set-PoProfileState -Remove -Name $Name
    }
    Catch {
        Write-Error $_.Exception.Message
        return
    }
}

function Reset-PoProfileState {

<#
.SYNOPSIS
    Resets PowerProfile state on the local machine
 
.DESCRIPTION
    Wipes the entire PowerProfile state on the local machine
 
.LINK
    https://PowerProfile.sh/
#>


    [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')]
    Param(
        [switch]$Force
    )

    $p = [System.IO.Path]::Combine(
        $env:XDG_STATE_HOME,
        $(
            if ($IsWindows) {
                'PowerShell'
            } else {
                'powershell'
            }
        ),
        'PowerProfile',
        'PowerProfile.state.json'
    )

    if ([System.IO.File]::Exists($p)) {
        if ($Force -or $PSCmdlet.ShouldProcess($p)) {
            Remove-Item -Path $p -ErrorAction Ignore -Confirm:$false
            Remove-Variable -Scope Script -Name PoProfileState -ErrorAction Ignore -Confirm:$false
            [PSCustomObject]$Script:PoProfileState = @{}
        }
    }
}
#endregion

#region Functions: Utilitites
function Get-PoProfileSubDirs {
    [OutputType([array])]
    Param (
        [Parameter(Mandatory=$true)]
        [string]${Name},

        [bool]${Platform}=$true,

        [bool]${Architecture}=$true,

        [bool]${Machine}=$true,

        [ValidateSet('None','Core','Desktop','All')]
        [string]${PSEditions}='All'
    )

    $PlatformDirectory = '_Platform_' + $(if ($IsMacOS) {'macOS'} elseif ($IsLinux) {'Linux'} else {'Windows'})
    $ArchDirectory = '_Arch_' + ($env:PROCESSOR_ARCHITECTURE).ToUpper()
    $MachineDirectory = '_Machine_' + ($env:COMPUTERNAME).ToUpper()

    # List profile directories in scope
    @(
        if ($Architecture) {$ArchDirectory}
        if ($Machine) {$MachineDirectory}
        if ($Platform) {
            if (($PSEdition -eq 'Core') -or ($Name -notmatch '^Profile_.*')) {
                if (-Not $IsWindows) {
                    '_Platform_NonWindows'
                    if ($Architecture) {[System.IO.Path]::Combine('_Platform_NonWindows',$ArchDirectory)}
                }
                $PlatformDirectory
                if ($Architecture) {[System.IO.Path]::Combine($PlatformDirectory,$ArchDirectory)}
            }
        }
        if ($PSEdition -eq 'Core') {
            if (($PSEditions -eq 'All') -or ($PSEditions -eq 'Core')) {
                '_PSEdition_Core'
                if ($Architecture) {[System.IO.Path]::Combine('_PSEdition_Core',$ArchDirectory)}
                if ($Machine) {[System.IO.Path]::Combine('_PSEdition_Core',$MachineDirectory)}
            }
            if ($IsWindows) {
                if (($PSEditions -eq 'All') -or ($PSEditions -eq 'Core')) {
                    ([System.IO.Path]::Combine($PlatformDirectory,'_PSEdition_Core'))
                    if ($Architecture) {[System.IO.Path]::Combine($PlatformDirectory,'_PSEdition_Core',$ArchDirectory)}
                }
                if (($PSEditions -eq 'All') -or ($PSEditions -eq 'Desktop')) {
                    ([System.IO.Path]::Combine($PlatformDirectory,'_PSEdition_Desktop'))
                    if ($Architecture) {[System.IO.Path]::Combine($PlatformDirectory,'_PSEdition_Desktop',$ArchDirectory)}
                    if ($Machine) {[System.IO.Path]::Combine($PlatformDirectory,'_PSEdition_Desktop',$MachineDirectory)}
                }
            }
        }
    )
}

function Add-EnvPath {

<#
.SYNOPSIS
    Brief synopsis about the function.
 
.DESCRIPTION
    Detailed explanation of the purpose of this function.
#>


    [CmdletBinding(PositionalBinding=$false)]
    [OutputType([System.Void])]
    Param(
        [string]$Name='PATH',

        [Parameter(Mandatory=$true,Position=0)]
        [AllowEmptyString()]
        [string[]]$Value,

        [switch]$Prepend
    )

    $p = [System.Environment]::GetEnvironmentVariable($Name)
    if ($p) {
        [System.Collections.ArrayList]$p = $p.Split([System.IO.Path]::PathSeparator)
    } else {
        [System.Collections.ArrayList]$p = @()
    }

    foreach ($v in $Value) {
        foreach ($i in $v.Split([System.IO.Path]::PathSeparator)) {
            if ($i -notin $p) {
                if ($Prepend) {
                    $null = $p.Insert(0,$i)
                } else {
                    $null = $p.Add($i)
                }
            }
        }
    }

    [System.Environment]::SetEnvironmentVariable(
        $Name,
        (
            [System.Environment]::ExpandEnvironmentVariables($p -Join [System.IO.Path]::PathSeparator)),
            [System.EnvironmentVariableTarget]::Process
        )
}

function Resolve-RealPath {
<#
.SYNOPSIS
    Implementation of Unix realpath().
 
.DESCRIPTION
    Implementation of Unix realpath().
 
.PARAMETER Path
    Path must exist
 
.LINK
    https://github.com/PowerProfile/psprofile-common
#>


    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string] $Path
    )

    if (-Not ([System.IO.File]::Exists($Path)) -and -Not ([System.IO.Directory]::Exists($Path))) {
        return $Path
    }

    $Path = Resolve-Path -Path $Path

    [string[]]$parts = $Path.TrimStart([IO.Path]::DirectorySeparatorChar).Split([IO.Path]::DirectorySeparatorChar)
    [string]$realPath = if (-Not $IsWindows) { [IO.Path]::DirectorySeparatorChar } else { '' }
    $OldPWD = $PWD
    $changedPath = $false
    foreach ($part in $parts) {

        # Change to the path to allow resolving
        # relative path in link
        if ([System.IO.Directory]::Exists($realPath)) {
            Push-Location $realPath
            $changedPath = $true
        }

        $realPath = [System.IO.Path]::Combine($realPath,$part)
        $item = Get-Item $realPath -Force -ErrorAction Ignore

        if ($null -ne $item.Target) {
            $realPath = Resolve-RealPath -Path $item.Target
        }
    }

    if ($changedPath) {
        Push-Location $OldPWD
    }

    $realPath
}

function Get-PoProfileContent {
    [OutputType([PSCustomObject])]
    Param()
    if ($null -eq $Script:PoProfileContent) {
        $Params = @{
            Directories = @(
                $(
                    $p = [System.IO.Path]::Combine($PSScriptRoot,'PSProfile')
                    if ([System.IO.Directory]::Exists($p)) {
                        $p
                    }
                )
                $(
                    foreach ($d in @(Get-Module -ListAvailable -Name ([System.IO.Path]::Combine($PROFILEHOME,'Modules','PowerProfile.*')))) {
                        $p = [System.IO.Path]::Combine(
                            (Split-Path $d.Path),
                            'PSProfile'
                        )
                        if ([System.IO.Directory]::Exists($p)) {
                            $p
                        }
                    }
                )
                $PROFILEHOME
            )
            Profiles = @(Get-PoProfileProfilesList)
        }
        $Script:PoProfileContent = Find-PoProfileContent @Params
    }
    $PoProfileContent
}

function Get-PoProfileProfilesList {
    [OutputType([array])]
    Param()

    if ($null -eq $Script:PoProfileProfilesList) {
        $Script:PoProfileProfilesList = @(
            'Profile'
            'Profile_' + (($env:LC_PSHOST.ToLower() -replace ' ','') -replace '_','')
            if ($null -ne $env:TERM_PROGRAM -and $env:TERM_PROGRAM -ne $env:PSHOST_PROGRAM) {
                'Profile_' + (($env:LC_TERMINAL.ToLower() -replace ' ','') -replace '_','')
            }
        )
    }
    $PoProfileProfilesList
}

function Find-PoProfileContent {
    [OutputType([PSCustomObject])]
    param(
        [string[]]$Directories=$PROFILEHOME,
        [string[]]$Profiles='Profile'
    )

    [PSCustomObject]$return = @{
        Profiles   = @{}
        Config     = @{}
        ConfigDirs = @{}
        Functions  = @{}
        Modules    = @()
        Scripts    = @()
    }
    $keys = @('Config','Functions','Modules','Scripts')

    # PSProfile directory or PowerProfile bundle directories
    foreach ($Directory in $Directories) {
        if ($Directory -ne $PROFILEHOME) {
            $p = [System.IO.Path]::Combine($Directory,'Modules')
            if (
                [System.IO.Directory]::Exists($p) -and
                @([System.IO.Directory]::EnumerateDirectories($p,'*','TopDirectoryOnly')).Count -gt 0
            ) {
                $return.Modules += $p
            }
            $p = [System.IO.Path]::Combine($Directory,'Scripts')
            if (
                [System.IO.Directory]::Exists($p) -and
                @([System.IO.Directory]::EnumerateFiles($p,'*.ps1','TopDirectoryOnly')).Count -gt 0
            ) {
                $return.Scripts += $p
            }
        }

        # User Profiles
        foreach ($PoPr in $Profiles) {
            foreach ($PoPrDir in @('EveryProfile',$PoPr)) {
                $SubDirs = @('') + @(Get-PoProfileSubDirs -Name (Split-Path -Leaf $PoPrDir) -PSEditions $PSEdition)

                foreach ($SubDir in $SubDirs) {
                    $p = [System.IO.Path]::Combine($Directory,$PoPrDir,$SubDir)
                    if ([System.IO.Directory]::Exists($p)) {
                        [string[]]$files = [System.IO.Directory]::EnumerateFiles($p,'*.ps1','TopDirectoryOnly')
                        [array]::Sort($files)
                        foreach ($file in $files) {
                            if ($file -match '\.Test\.ps1$') {
                                continue
                            }
                            if( $null -eq $return.Profiles.$PoPr ) {
                                $return.Profiles.$PoPr = @{}
                            }
                            $Node = Split-Path -Leaf $file
                            $return.Profiles.$PoPr.$Node = $file
                        }

                        foreach ($key in $keys) {
                            $p = [System.IO.Path]::Combine($Directory,$PoPrDir,$SubDir,$key)
                            if ([System.IO.Directory]::Exists($p)) {
                                switch -Exact ($key) {

                                    Config {

                                        # Top directory for single config files
                                        [string[]]$files = [System.IO.Directory]::EnumerateFiles($p,'*','TopDirectoryOnly')
                                        [array]::Sort($files)
                                        foreach ($file in $files) {
                                            $Node = Split-Path -Leaf $file
                                            if( $null -eq $return.$key.$PoPr ) {
                                                $return.$key.$PoPr = @{}
                                            }
                                            $return.$key.$PoPr.$Node = $file
                                        }

                                        # Sub directories for multi-file configuration assets
                                        [string[]]$dirs = [System.IO.Directory]::EnumerateDirectories($p,'*','TopDirectoryOnly')
                                        foreach ($dir in $dirs) {
                                            $Node = Split-Path -Leaf $dir
                                            if( $null -eq $return.'ConfigDirs'.$PoPr ) {
                                                $return.'ConfigDirs'.$PoPr = @{}
                                            }
                                            [string[]]$files = [System.IO.Directory]::EnumerateFiles($dir,'*','TopDirectoryOnly')
                                            [array]::Sort($files)
                                            foreach ($file in $files) {
                                                $fNode = Split-Path -Leaf $file
                                                if( $null -eq $return.'ConfigDirs'.$PoPr.$Node ) {
                                                    $return.'ConfigDirs'.$PoPr.$Node = @{}
                                                }
                                                $return.'ConfigDirs'.$PoPr.$Node.$fNode = $file
                                            }
                                        }

                                        Break
                                    }

                                    Functions {
                                        [string[]]$files = [System.IO.Directory]::EnumerateFiles($p,'*.ps1','AllDirectories')
                                        [array]::Sort($files)
                                        foreach ($file in $files) {
                                            if ($file -match '\.Test\.ps1$') {
                                                continue
                                            }
                                            $Node = Split-Path -Leaf $file
                                            $return.$key.$Node = $file
                                        }

                                        Break
                                    }

                                    Modules {
                                        if (@([System.IO.Directory]::EnumerateDirectories($p,'*','TopDirectoryOnly')).Count -gt 0) {
                                            $return.$key += $p
                                        }

                                        Break
                                    }

                                    Scripts {
                                        if (@([System.IO.Directory]::EnumerateFiles($p,'*.ps1','TopDirectoryOnly')).Count -gt 0) {
                                            $return.$key += $p
                                        }

                                        Break
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    $return
}
#endregion

#region Functions: Write
function Write-PoProfileItemProgress {
    Param(
        [string]$ProfileTitle,

        [string]$ItemTitle,

        [AllowEmptyString()]
        [string]$ItemCategory,

        [string]$ItemText,
        [string]$ItemTextColor=$PSStyle.Foreground.BrightBlack,
        [Int]$Depth
    )

    if ($IsCommand -or $IsNonInteractive -or ($null -ne $env:PSLVL) -or ($null -ne $PSDebugContext)) {
        return
    }

    $Script:PoProfileProgressScriptCategoryShowed = $false
    $Script:PoProfileProgressScriptTitleShowed = $false

    if ($PSBoundParameters.ContainsKey('ProfileTitle') -and $ProfileTitle -ne $PoProfileProgressTitle) {
        if ($PoProfileProgressItemTextShowed) {
            Write-Host ''
        }
        if ($PoProfileProgressItemTitleShowed) {
            Write-Host ''
        }
        $Script:PoProfileProgressTitleShowed = $false
        $Script:PoProfileProgressItemTitleShowed = $false
        $Script:PoProfileProgressItemTextShowed = $false
        $Script:PoProfileProgressDepth = 1
        $Script:PoProfileProgressCounter = 1
        $Script:PoProfileProgressTitle = $ProfileTitle
    }
    if ($PSBoundParameters.ContainsKey('ItemTitle') -and $ItemTitle -ne $PoProfileProgressItemTitle) {
        if ($PoProfileProgressItemTextShowed) {
            Write-Host ''
        }
        $Script:PoProfileProgressItemTitleShowed = $false
        $Script:PoProfileProgressItemCategoryShowed = $false
        $Script:PoProfileProgressItemTextShowed = $false
        $Script:PoProfileProgressDepth = 1
        $Script:PoProfileProgressItemTitle = $ItemTitle
    }
    if ($PSBoundParameters.ContainsKey('ItemCategory') -and $ItemCategory -ne $PoProfileProgressItemCategory) {
        if ($PoProfileProgressItemTextShowed) {
            Write-Host ''
        }
        $Script:PoProfileProgressItemCategoryShowed = $false
        $Script:PoProfileProgressItemTextShowed = $false
        $Script:PoProfileProgressItemCategory = $ItemCategory
    }
    if ($PSBoundParameters.ContainsKey('Depth') -and $Depth -ge 1 -and $Depth -ne $PoProfileProgressDepth) {
        $Script:PoProfileProgressDepth = $Depth
    }
    if (-not $PSBoundParameters.ContainsKey('ItemText')) {
        return
    }

    if (-Not $PoProfileProgressTitleShowed) {
        $Script:PoProfileProgressTitleShowed = $true
        Write-Host ($PSStyle.Foreground.BrightGreen + $PoProfileProgressTitle + ':' + $PSStyle.Reset)
    }

    if (-Not $PoProfileProgressItemTitleShowed) {
        $Script:PoProfileProgressItemTitleShowed = $true
        $Number = (' ' * (3 - (@($PoProfileProgressCounter.ToString().Length, 3) | Measure-Object -Minimum).Minimum)) + $PoProfileProgressCounter
        $Script:PoProfileProgressCounter++
        Write-Host ($PSStyle.Foreground.BrightWhite + $PSStyle.Bold + "${Number}. ${Script:PoProfileProgressItemTitle}:" + $PSStyle.Reset)
    }

    $Indentation = ' ' * (5 + $Script:PoProfileProgressDepth)

    if (-Not $PoProfileProgressItemCategoryShowed) {
        $Script:PoProfileProgressItemCategoryShowed = $true
        if ($PoProfileProgressItemCategory -and '' -ne $PoProfileProgressItemCategory) {
            $Script:PoProfileProgressDepth++
            Write-Host ($Indentation + $PSStyle.Foreground.Yellow + $PoProfileProgressItemCategory + ':' + $PSStyle.Reset)
            $Indentation = " $Indentation"
        }
    }

    if ($PoProfileProgressItemTextShowed) {
        $ItemText = " ${ItemText}"
    } elseif ($PoProfileProgressItemCategoryShowed) {
        $ItemText = "${Indentation}${ItemText}"
    } else {
        $ItemText = "${Indentation} ${ItemText}"
    }
    $Script:PoProfileProgressItemTextShowed = $true
    Write-Host "${ItemTextColor}${ItemText}$($PSStyle.Reset)" -NoNewline
}

function Write-PoProfileProgress {
    Param(
        [string]$ProfileTitle,

        [AllowEmptyString()]
        [string]$ScriptCategory,

        [string[]]$ScriptTitle,

        [ValidateSet('Progress','Verbose','Note','Confirmation','Information','Warning','Error')]
        [string]${ScriptTitleType}='Progress',

        [switch]$NoCounter
    )

    if ($IsCommand -or $IsNonInteractive -or ($null -ne $env:PSLVL) -or ($null -ne $PSDebugContext)) {
        return
    }

    if ($PoProfileProgressItemTextShowed) {
        Write-Host ''
    }
    $Script:PoProfileProgressItemTitleShowed = $false
    $Script:PoProfileProgressItemCategoryShowed = $false
    $Script:PoProfileProgressItemTextShowed = $false

    if ($PSBoundParameters.ContainsKey('ProfileTitle') -and $ProfileTitle -ne $PoProfileProgressTitle) {
        if ($PoProfileProgressScriptTitleShowed) {
            Write-Host ''
        }
        $Script:PoProfileProgressTitleShowed = $false
        $Script:PoProfileProgressScriptTitleShowed = $false
        $Script:PoProfileProgressDepth = 1
        $Script:PoProfileProgressCounter = 1
        $Script:PoProfileProgressTitle = $ProfileTitle
    }
    if ($PSBoundParameters.ContainsKey('ScriptCategory') -and $ScriptCategory -ne $PoProfileProgressScriptCategory) {
        $Script:PoProfileProgressScriptCategoryShowed = $false
        $Script:PoProfileProgressScriptTitleShowed = $false
        $Script:PoProfileProgressScriptCategory = $ScriptCategory
    }
    if (-not $PSBoundParameters.ContainsKey('ScriptTitle')) {
        return
    }

    if (-Not $PoProfileProgressTitleShowed) {
        $Script:PoProfileProgressTitleShowed = $true
        Write-Host ($PSStyle.Foreground.BrightGreen + $PoProfileProgressTitle + ':' + $PSStyle.Reset)
    }

    if (-Not $PoProfileProgressScriptCategoryShowed) {
        $Script:PoProfileProgressScriptCategoryShowed = $true
        if ($PoProfileProgressScriptCategory -and '' -ne $PoProfileProgressScriptCategory) {
            Write-Host (' ' + $PSStyle.Foreground.Yellow + $PoProfileProgressScriptCategory + ':' + $PSStyle.Reset)
        }
    }

    $Script:PoProfileProgressScriptTitleShowed = $true

    if ($ScriptTitleType -eq 'Confirmation') {
        $Color = $PSStyle.Foreground.BrightGreen
        $ShowSquare = '✔'
    } elseif ($ScriptTitleType -eq 'Information') {
        $Color = $PSStyle.Foreground.BrightCyan
        $ShowSquare = 'i'
    } elseif ($ScriptTitleType -eq 'Warning') {
        $Color = $PSStyle.Foreground.BrightYellow
        $ShowSquare = '!'
    } elseif ($ScriptTitleType -eq 'Error') {
        $Color = $PSStyle.Foreground.BrightRed
        $ShowSquare = 'X'
    } elseif ($ScriptTitleType -eq 'Note') {
        $Color = $PSStyle.Foreground.BrightBlue
    } elseif ($ScriptTitleType -eq 'Verbose') {
        $Color = $PSStyle.Foreground.BrightBlack
    } else {
        $Color = $PSStyle.Foreground.White
    }

    if ($ShowSquare) {
        Write-Host (
            " $Color" + $PSStyle.Reverse + " $ShowSquare " + $PSStyle.ReverseOff +
            $(
                if ($Color -eq $PSStyle.Foreground.BrightRed) { $Color = $PSStyle.Foreground.Red }
                $count = 1
                $Color + $(
                    foreach ($Line in $ScriptTitle) {
                        if ($count -eq 1) {
                            $Prefix = ' '
                        } else {
                            $Prefix = ' '
                        }
                        "${Prefix}${Line}" + [System.Environment]::NewLine
                        $count++
                    }
                )
            ) + $PSStyle.Reset
        )
    } else {
        if ($NoCounter) {
            $Prefix = ' '
            $Suffix = ''
        } else {
            $Prefix =  (' ' * (3 - (@($PoProfileProgressCounter.ToString().Length, 3) | Measure-Object -Minimum).Minimum)) + $PoProfileProgressCounter + '. '
            $Script:PoProfileProgressCounter++
            $Suffix = ' ...'
        }
        Write-Host ( $PSStyle.Bold + "${Prefix}${ScriptTitle}${Suffix}" + $PSStyle.Reset)
    }
}
#endregion

#region Functions: PowerShell Core
function pwsh {
    [System.Collections.ArrayList]$a = $args
    if ($IsLogin -and $a[0] -notmatch '(?i)^-L(o(g(in?)?)?)?$') {
        $a.Insert(0,'-l')
    }
    if ($a -notmatch '^-NoL.*') {
        if ($IsLogin) {
            $a.Insert(1,'-nol')
        } else {
            $a.Insert(0,'-nol')
        }
    }
    $env:SHELL + " $a" | Invoke-Expression
}
Set-Alias -Name pwsh-preview -Value pwsh
#endregion

#region System Environment
switch -Regex (@([System.Environment]::GetCommandLineArgs())) {
    '(?i)^-C(o(m(m(a(nd?)?)?)?)?)?$' {
        $Params = @{
            Scope = 'Script'
            Name = 'IsCommand'
            Value = $true
            Option = 'ReadOnly'
            Description = 'PowerShell was started with command line argument -Command'
        }
        Set-Variable @Params
        continue
    }
    '(?i)^-L(o(g(in?)?)?)?$' {
        $Params = @{
            Scope = 'Script'
            Name = 'IsLogin'
            Value = $true
            Option = 'ReadOnly'
            Description = 'PowerShell was started as a login shell'
        }
        Set-Variable @Params
        continue
    }
    '(?i)^-NoE(x(it?)?)?$' {
        $Params = @{
            Scope = 'Script'
            Name = 'IsNoExit'
            Value = $true
            Option = 'ReadOnly'
            Description = 'PowerShell was started with command line argument -NoExit'
        }
        Set-Variable @Params
        continue
    }
    '(?i)^-NonI(n(t(e(r(a(c(t(i(ve?)?)?)?)?)?)?)?)?)?$' {
        $Params = @{
            Scope = 'Script'
            Name = 'IsNonInteractive'
            Value = $true
            Option = 'ReadOnly'
            Description = 'PowerShell was explicitly started in non-interactive mode'
        }
        Set-Variable @Params
        continue
    }
}
if ($null -eq $IsCommand) {
    $Params = @{
        Scope = 'Script'
        Name = 'IsCommand'
        Value = $false
        Option = 'ReadOnly'
    }
    Set-Variable @Params
}
if ($null -eq $IsLogin) {
    $Params = @{
        Scope = 'Script'
        Name = 'IsLogin'
        Value = $false
        Option = 'ReadOnly'
    }
    Set-Variable @Params
}
if ($null -eq $IsNoExit) {
    $Params = @{
        Scope = 'Script'
        Name = 'IsNoExit'
        Value = $false
        Option = 'ReadOnly'
    }
    Set-Variable @Params
}
if ($null -eq $IsNonInteractive) {
    if ($null -eq [System.Environment]::UserInteractive) {
        $v = $true
    } else {
        $v = $false
    }
    $Params = @{
        Scope = 'Script'
        Name = 'IsNonInteractive'
        Value = $v
        Option = 'ReadOnly'
    }
    Set-Variable @Params
}

# Cross-platform compatibility for Windows PowerShell
if ($PSEdition -eq 'Desktop') {
    $Params = @{
        Scope = 'Script'
        Name = 'IsCoreCLR'
        Value = $false
        Option = 'ReadOnly'
    }
    Set-Variable @Params

    $Params = @{
        Scope = 'Script'
        Name = 'IsLinux'
        Value = $false
        Option = 'ReadOnly'
    }
    Set-Variable @Params

    $Params = @{
        Scope = 'Script'
        Name = 'IsMacOS'
        Value = $false
        Option = 'ReadOnly'
    }
    Set-Variable @Params

    $Params = @{
        Scope = 'Script'
        Name = 'IsWindows'
        Value = $true
        Option = 'ReadOnly'
    }
    Set-Variable @Params
}

# env:SHLVL + env:PSLVL
$PPID = (Get-Process -Id $PID).Parent.Id
if ($PPID) {
    $ParentProcessName = (Get-Process -Id $PPID -ErrorAction Ignore).ProcessName
    if ($ParentProcessName -match '(?i)^-?((pwsh(?:-preview)?|powershell)|bash|zsh|sh|csh|dash|ksh|tcsh).*$') {
        if ($null -ne $matches[2] ) {
            if($env:PSLVL) {
                $env:PSLVL = [int]$env:PSLVL + 1
            } else {
                $env:PSLVL = 1
            }
        }
        if($env:SHLVL) {
            $env:SHLVL = [int]$env:SHLVL + 1
        } else {
            $env:SHLVL = 1
        }
    } else {
        $env:SHLVL = 0
    }
}
else {
    $env:SHLVL = 0
}

$PROFILEHOME = Split-Path $PROFILE
$env:PSHOST_PROGRAM  = (Split-Path -Leaf $PROFILE.CurrentUserCurrentHost).Replace('Microsoft.','') -replace '_profile\.[^.]*$',''
$env:LC_PSHOST = $(
    if ($env:PSHOST_PROGRAM -eq 'PowerShell') {
        if ($PSEdition -eq 'Desktop') {
            'Windows PowerShell'
        } else {
            'PowerShell'
        }
    } else {
        (($env:PSHOST_PROGRAM -replace '(?-i)([a-z]{3,})([A-Z])','$1 $2') -replace '_',' ').Trim()
    }
)

if ($null -eq $env:PSLVL) {
    if ($IsWindows) {
        $PSType = if ($PSEdition -eq 'Desktop') { 'WindowsPowerShell' } else { 'PowerShell' }
        $PSMyDocumentsPath = [System.IO.Path]::Combine(([System.Environment]::GetFolderPath('MyDocuments')),$PSType)
        # $PSProgramFilesPath = [System.IO.Path]::Combine(([System.Environment]::GetFolderPath('ProgramFiles')),$PSType)

        $env:HOSTNAME        = $env:COMPUTERNAME
        $env:USER            = $env:USERNAME
        $env:SHELL           = (Get-Process -Id $PID).Path
        $env:XDG_DATA_HOME   = [System.Environment]::GetFolderPath('LocalApplicationData')
        $env:XDG_CONFIG_HOME = [System.Environment]::GetFolderPath('ApplicationData')
        $env:XDG_STATE_HOME  = [System.IO.Path]::Combine($env:XDG_DATA_HOME,'State')
        $env:XDG_CACHE_HOME  = [System.IO.Path]::Combine($env:XDG_DATA_HOME,'Cache')

        $WindowsPrincipal = [Security.Principal.WindowsPrincipal]::new([Security.Principal.WindowsIdentity]::GetCurrent())
        if ($WindowsPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -eq 1) {
            $env:IsElevated = $true
        }

        if ($env:PSModulePath.Contains($env:OneDrive) -or $env:PSModulePath.Contains($env:OneDriveCommercial)) {
            $env:IsProfileRedirected = $true
        }
    }
    else {
        if ($env:SHLVL -eq 0) {
            if ($IsMacOS) {
                if (
                    -Not $IsLogin -and
                    ([System.IO.File]::Exists('/usr/libexec/path_helper'))
                ) {
                    function setenv ($n,$v) {[System.Environment]::SetEnvironmentVariable($n,$v)}
                    /usr/libexec/path_helper -c | Invoke-Expression -ErrorAction Ignore
                }

                # Homebrew support
                $(
                    if ([System.IO.File]::Exists('/opt/homebrew/bin/brew')) {
                        /opt/homebrew/bin/brew shellenv
                    }
                    elseif ([System.IO.File]::Exists('/usr/local/bin/brew')) {
                        /usr/local/bin/brew shellenv
                    }
                ) | Invoke-Expression -ErrorAction Ignore
            }
            else {
                # Homebrew on Linux support
                $(
                    if ([System.IO.File]::Exists("$HOME/.linuxbrew/bin/brew")) {
                        "$HOME/.linuxbrew/bin/brew shellenv"
                    }
                    elseif ([System.IO.File]::Exists('/home/linuxbrew/.linuxbrew/bin/brew')) {
                        '/home/linuxbrew/.linuxbrew/bin/brew shellenv'
                    }
                ) | Invoke-Expression -ErrorAction Ignore
            }
        }

        $PSMyDocumentsPath = [System.IO.Path]::Combine(([System.Environment]::GetFolderPath('LocalApplicationData')),'powershell')
        # $PSProgramFilesPath = '/usr/local/share/powershell'

        $env:PROCESSOR_ARCHITECTURE = $(uname -m).ToUpper() | ForEach-Object { if ($_ -eq 'X86_64') {'AMD64'} else {$_} }
        $env:COMPUTERNAME           = $(hostname -s).ToUpper()
        $env:HOSTNAME               = $env:COMPUTERNAME
        $env:USERNAME               = $env:USER
        $env:SHELL                  = (Get-Process -Id $PID).Path
        $env:XDG_DATA_HOME          = [System.IO.Path]::Combine(([System.Environment]::GetFolderPath('UserProfile')),'.local','share')
        $env:XDG_CONFIG_HOME        = [System.IO.Path]::Combine(([System.Environment]::GetFolderPath('UserProfile')),'.config')
        $env:XDG_STATE_HOME         = [System.IO.Path]::Combine(([System.Environment]::GetFolderPath('UserProfile')),'.local','state')
        $env:XDG_CACHE_HOME         = [System.IO.Path]::Combine(([System.Environment]::GetFolderPath('UserProfile')),'.cache')

        if (0 -eq (id -u)) {
            $env:IsElevated = $true
        }

        # env:PSModulePath
        $env:PSModulePath += [System.IO.Path]::PathSeparator + [System.IO.Path]::Combine($PROFILEHOME,'Modules')

        $REALPROFILEHOME = Resolve-RealPath $PROFILEHOME
        if (
            $REALPROFILEHOME.Contains("$HOME/Library/Mobile Documents") -or
            $REALPROFILEHOME.Contains("$HOME/Library/CloudStorage")
        ) {
            $env:IsProfileRedirected = $true
        }
    }

    # env:TERM_PROGRAM
    if (-Not $env:TERM_PROGRAM) {
        $TerminalName = $ParentProcessName -replace '(?:\(|Server)?(-.*)?$'
        if ($env:LC_TERMINAL) {
            $env:TERM_PROGRAM = $env:LC_TERMINAL
        }
        elseif (
            ($env:PSHOST_PROGRAM -ne 'PowerShell') -and
            ($env:PSHOST_PROGRAM -ne 'WindowsPowerShell')
        ) {
            $env:TERM_PROGRAM = $env:PSHOST_PROGRAM
        }
        elseif (
            $TerminalName -eq 'WindowsTerminal'
        ) {
            $env:TERM_PROGRAM = $TerminalName
        }
    }
    if ($env:TERM_PROGRAM -and -not $env:LC_TERMINAL) {
        $env:LC_TERMINAL = (($env:TERM_PROGRAM -replace '(?-i)([a-z]{3,})([A-Z])','$1 $2') -replace '_',' ').Trim()
    }

    # Add Scripts directories to env:PATH
    $p = [System.IO.Path]::Combine($PSMyDocumentsPath,'Scripts')
    if ([System.IO.Directory]::Exists($p)) {
        $env:PATH += [System.IO.Path]::PathSeparator + $p
    }
    if ($PSMyDocumentsPath -ne $PROFILEHOME) {
        $p = [System.IO.Path]::Combine($PROFILEHOME,'Scripts')
        if ([System.IO.Directory]::Exists($p)) {
            $env:PATH += [System.IO.Path]::PathSeparator + $p
        }
    }
}
#endregion

Get-PoProfileContent

$Exports = @{
    Alias = @(
        'pwsh-preview'
    )
    Variable = @(
        'IsCommand'
        'IsLogin'
        'IsNoExit'
        'IsNonInteractive'
        'IsCoreCLR'
        'IsLinux'
        'IsMacOS'
        'IsWindows'
        'PROFILEHOME'
        'PSStyle'
        'PoProfileEmoji'
        'PoProfileChar'
    )
}
Export-ModuleMember @Exports