Private/Config.psm1

using namespace System.IO
using namespace System.Text
using namespace System.Collections.Generic
using namespace System.Collections.Specialized
using namespace System.Management.Automation

using module .\Enums.psm1
using module .\Exceptions.psm1
using module .\Colors.psm1
using module .\FontMan.psm1
using module .\Models.psm1
using module .\MotdGen.psm1
using module .\Utilities.psm1

class InstallRequirements {
  [Requirement[]] $list = @()
  [bool] $resolved = $false
  [string] $jsonPath = [IO.Path]::Combine($(Resolve-Path .).Path, 'requirements.json')

  InstallRequirements() {}
  InstallRequirements([array]$list) { $this.list = $list }
  InstallRequirements([List[array]]$list) { $this.list = $list.ToArray() }
  InstallRequirements([Hashtable]$Map) { $Map.Keys | ForEach-Object { $Map[$_] ? ($this.$_ = $Map[$_]) : $null } }
  InstallRequirements([Requirement]$req) { $this.list += $req }
  InstallRequirements([Requirement[]]$list) { $this.list += $list }

  [void] Resolve() {
    $this.Resolve($false, $false)
  }
  [void] Resolve([switch]$Force, [switch]$What_If) {
    $res = $true; $this.list.ForEach({ $res = $res -and $_.Resolve($Force, $What_If) })
    $this.resolved = $res
  }
  [void] Import() {
    $this.Import($this.JsonPath, $false)
  }
  [void] Import([switch]$throwOnFail) {
    $this.Import($this.JsonPath, $throwOnFail)
  }
  [void] Import([string]$JsonPath, [switch]$throwOnFail) {
    if ([IO.File]::Exists($JsonPath)) { $this.list = Get-Content $JsonPath | ConvertFrom-Json }; return
    if ($throwOnFail) {
      throw [FileNotFoundException]::new("Requirement json file not found: $JsonPath")
    }
  }
  [void] Export() {
    $this.Export($this.JsonPath)
  }
  [void] Export([string]$JsonPath) {
    $this.list | ConvertTo-Json -Depth 1 -Verbose:$false | Out-File $JsonPath
  }
  [string] ToString() {
    return $this | ConvertTo-Json
  }
}

class Requirement {
  [string] $Name
  [version] $Version
  [string] $Description
  [string] $InstallScript
  hidden [PSDataCollection[PsObject]]$Output

  Requirement() {}
  Requirement([array]$arr) {
    $this.Name = $arr[0]
    $this.Version = $arr.Where({ $_ -is [version] })[0]
    $this.Description = $arr.Where({ $_ -is [string] -and $_ -ne $this.Name })[0]
    $__sc = $arr.Where({ $_ -is [scriptblock] })[0]
    $this.InstallScript = ($null -ne $__sc) ? $__sc.ToString() : $arr[-1]
  }
  Requirement([string]$Name, [scriptblock]$InstallScript) {
    $this.Name = $Name
    $this.InstallScript = $InstallScript.ToString()
  }
  Requirement([string]$Name, [string]$Description, [scriptblock]$InstallScript) {
    $this.Name = $Name
    $this.Description = $Description
    $this.InstallScript = $InstallScript.ToString()
  }

  [bool] IsInstalled() {
    try {
      Get-Command $this.Name -Type Application
      return $?
    } catch [CommandNotFoundException] {
      return $false
    } catch {
      throw [InstallException]::new("Failed to check if $($this.Name) is installed", $_.Exception)
    }
  }
  [bool] Resolve() {
    return $this.Resolve($false, $false)
  }
  [bool] Resolve([switch]$Force, [switch]$What_If) {
    $is_resolved = $true; $this.Output = [PSDataCollection[PsObject]]::new()
    $v = [ProgressUtil]::data.ShowProgress
    if (!$this.IsInstalled() -or $Force.IsPresent) {
      $v ? (Write-Console "`n[Resolve requrement] $($this.Name) " -f DarkKhaki -NoNewLine) : $null
      if ([string]::IsNullOrWhiteSpace($this.Description)) {
        $v ? (Write-Console "($($this.Description)) " -f Wheat -NoNewLine) : $null
      }
      $v ? (Write-Console "$($this.Version) " -f Green) : $null
      if ($What_If.IsPresent) {
        $v ? (Write-Console "Would install: $($this.Name)" -f Yellow) : $null
      } else {
        $this.Output += $this.InstallScript | Invoke-Expression
      }
      $is_resolved = $?
    }
    return $is_resolved
  }
}

class ProfileConfig : PsRecord {
  [string]$Title = 'Terminal'
  [Encoding]$Encoding = 'UTF8' # Default is utf-8 Encoding
  [bool]$AutoLogon #Specifies credentials for an account that is used to automatically log on to the computer.
  [bool]$BluetoothTaskbarIconEnabled #Specifies whether to enable the Bluetooth taskbar icon.
  [string]$ComputerName #Specifies the name of the computer.
  [bool]$ConvertibleSlateModePromptPreference # Configure to support prompts triggered by changes to ConvertibleSlateMode. OEMs must make sure that ConvertibleSlateMode is always accurate for their devices.
  [string]$path = [IO.Path]::Combine([Environment]::GetFolderPath('MyDocuments'), 'WindowsPowershell', 'Microsoft.PowerShell_profile.ps1')
  [bool]$AutoUpdate = $true
  [bool]$AutoLoad = $true
  [Microsoft.PowerShell.ExecutionPolicy]$ExecutionPolicy = "RemoteSigned"
  [bool]$CopyProfile = $true
  [bool]$DisableAutoDaylightTimeSet #Specifies whether to enable the destination computer to automatically change between daylight saving time and standard time.
  [array]$Display # Specifies display settings to apply to a destination computer.
  [string[]]$FirstLogonCommands #Specifies commands to run the first time that an end user logs on to the computer. This setting is not supported in Windows 10 in S mode.
  [string[]]$FolderLocations #Specifies the location of the user profile and program data folders.
  [string[]]$LogonCommands #Specifies commands to run when an end user logs on to the computer.
  [array]$NotificationArea #Specifies settings that are related to the system notification area at the far right of the taskbar.
  [string]$SignInMode #Specifies whether users switch to tablet mode by default after signing in.
  [string[]]$TaskbarLinks #Specifies shortcuts to display on the taskbar. You can specify up to three links.
  [string[]]$Themes #Specifies custom elements of the Windows visual style.
  [TimeZone]$TimeZone = [TimeZone]::CurrentTimeZone #Specifies the computer's time zone.
  [string[]]$VisualEffects #Specifies additional display settings.
  [string[]]$WindowsFeatures
  [InstallRequirements]$RequiredModules = @(
    ("pipenv", "Python virtualenv management tool", { Install-PipEnv } ),
    ("oh-my-posh", "A cross platform tool to render your prompt.", { Install-OMP } ),
    ("PSReadLine", "Provides gread command line editing in the PowerShell console host", { Install-Module PSReadLine } ),
    ("posh-git", "Provides prompt with Git status summary information and tab completion for Git commands, parameters, remotes and branch names.", { Install-Module posh-git } ),
    ("PowerType", "A module providing recommendations for common tools. For more information : https://github.com/AnderssonPeter/PowerType", { Install-Module PowerType } ),
    ("PSFzf", "A wrapper module around Fzf (https://github.com/junegunn/fzf).", { Install-Module PSFzf } ),
    ("Terminal-Icons", "A module to add file icons to terminal based on file extensions", { Install-Module Terminal-Icons } )
  )
  [string]$OmpConfig #Path to omp.json

  ProfileConfig() {}

  [xml] ToXML() {
    return $this.PsObject.Properties | Export-Clixml
  }
  [string] ToJSON() {
    return $this.PsObject.Properties | Select-Object Name, value | ConvertTo-Json
  }
  [string] ToString() {
    return [string]::Empty
  }
}


class PsProfile : PsRecord {
  [HostOs] $HostOS
  [PsObject] $PROFILE
  [string[]] $ompJson
  [char] $swiglyChar
  [Hashtable] $colors
  [PsObject] $PSVersn
  [bool] $CurrExitCode
  [char] $DirSeparator
  [string] $Home_Indic
  [string] $WindowTitle
  [string] $leadingChar
  [string] $trailngChar
  [IO.FileInfo] $LogFile
  [IO.FileInfo] $OmpJsonFile
  [string] $Default_Term_Ascii
  [string[]] $Default_Dependencies
  [string] $WINDOWS_TERMINAL_JSON_PATH
  [Int32] $realLASTEXITCODE = $LASTEXITCODE
  [PSCustomObject] $TERMINAL_Settings

  # brackets, dots, newline
  hidden [string] $b1
  hidden [string] $b2
  hidden [string] $dt
  hidden [string] $nl

  PsProfile () {
    $this.Set_Defaults()
  }
  [void] Initialize() {
    $this.Initialize(@())
  }
  [void] Initialize([string[]]$DependencyModules) {
    $this.Initialize(@(), $false)
  }
  [void] Initialize([string[]]$DependencyModules, [bool]$force) {
    $v = [ProgressUtil]::data.ShowProgress
    $v ? (Write-Console '[PsProfile] Set variable defaults' -f DarkKhaki) : $null
    $this.Set_Defaults()
    $v ? (Write-Console '[PsProfile] Resolving Requirements ... (a one-time process)' -f DarkKhaki) : $null
    # Inspired by: https://dev.to/ansonh/customize-beautify-your-windows-terminal-2022-edition-541l
    if ($null -eq (Get-PSRepository -Name PSGallery -ErrorAction Ignore)) {
      throw 'PSRepository named PSGallery was Not found!'
    }
    Set-PSRepository PSGallery -InstallationPolicy Trusted;
    $PackageProviderNames = (Get-PackageProvider -ListAvailable).Name
    $requiredModules = $this.Default_Dependencies + $DependencyModules | Sort-Object -Unique
    foreach ($name in @("NuGet", "PowerShellGet")) {
      if ($force) {
        Install-PackageProvider $name -Force
      } elseif ($PackageProviderNames -notcontains $name) {
        Install-PackageProvider $name -Force
      } else {
        $v ? (Write-Console "PackageProvider '$name' is already Installed." -f Wheat) : $null
      }
    }
    # Install requied modules: # we could use Install-Module -Name $_ but it fails sometimes
    $requiredModules | Resolve-Module
    # BeautifyTerminal :
    if ($this.IsTerminalInstalled()) {
      $this.AddColorScheme()
    } else {
      Write-Warning "Windows terminal is not Installed!"
    }
    $this.InstallNerdFont()
    $this.InstallOhMyPosh() # instead of: winget install JanDeDobbeleer.OhMyPosh
    Set-PSReadLineOption -PredictionSource HistoryAndPlugin -PredictionViewStyle ListView # Optional
    # WinFetch
    Install-Script -Name winfetch -AcceptLicense

    if (!(Test-Path -Path $env:USERPROFILE/.config/winfetch/config.ps1)) {
      winfetch -genconf
    }
    (Get-Content $env:USERPROFILE/.config/winfetch/config.ps1).Replace('# $ShowDisks = @("*")', '$ShowDisks = @("*")') | Set-Content $env:USERPROFILE/.config/winfetch/config.ps1
    (Get-Content $env:USERPROFILE/.config/winfetch/config.ps1).Replace('# $memorystyle', '$memorystyle') | Set-Content $env:USERPROFILE/.config/winfetch/config.ps1
    (Get-Content $env:USERPROFILE/.config/winfetch/config.ps1).Replace('# $diskstyle', '$diskstyle') | Set-Content $env:USERPROFILE/.config/winfetch/config.ps1
    # RESET current exit code:
    $this.CurrExitCode = $true

    $v ? (Write-Console '[PsProfile] Load configuration settings ...' -f DarkKhaki) : $null
    $this.LoadConfiguration()

    Set-Variable -Name Colors -Value $($this.colors) -Scope Global -Visibility Public -Option AllScope
    if ($force) {
      # Invoke-Command -ScriptBlock $Load_Profile_Functions
      $this.CurrExitCode = $? -and $($this.Create_Prompt_Function())
    } else {
      if (!$($this.IsInitialised())) {
        # Invoke-Command -ScriptBlock $Load_Profile_Functions
        $this.CurrExitCode = $? -and $($this.Create_Prompt_Function())
      } else {
        $v ? (Write-Console "[PsProfile] is already Initialized, Skipping ..." -f Wheat) : $null
      }
    }
    [void]$this.GetPsProfile() # create the $profile file if it does not exist
    $this.Set_TerminalUI()
    $v ? (Write-Console "[PsProfile] Displaying a welcome message/MOTD ..." -f DarkKhaki) : $null
    $this.Write_Term_Ascii()
  }
  [bool] IsInitialised() {
    return (Get-Variable -Name IsPromptInitialised -Scope Global).Value
  }
  [void] LoadConfiguration() {
    $this.TERMINAL_Settings = $this.GetTerminalSettings();
  }
  [PSCustomObject] GetTerminalSettings() {
    if ($this.IsTerminalInstalled()) {
      if ([IO.Directory]::Exists($this.WINDOWS_TERMINAL_JSON_PATH)) {
        return (Get-Content $($this.WINDOWS_TERMINAL_JSON_PATH) -Raw | ConvertFrom-Json)
      }
      Write-Warning "Could not find WINDOWS_TERMINAL_JSON_PATH!"
    } else {
      Write-Warning "Windows terminal is not installed!"
    }
    return $null
  }
  [void] SaveTerminalSettings([PSCustomObject]$settings) {
    # Save changes (Write Windows Terminal settings)
    $settings | ConvertTo-Json -Depth 32 | Set-Content $($this.WINDOWS_TERMINAL_JSON_PATH)
  }
  hidden [void] AddColorScheme() {
    $settings = $this.GetTerminalSettings();
    if ($null -ne $settings) {
      $sonokaiSchema = [PSCustomObject]@{
        name                = "Sonokai Shusia"
        background          = "#2D2A2E"
        black               = "#1A181A"
        blue                = "#1080D0"
        brightBlack         = "#707070"
        brightBlue          = "#22D5FF"
        brightCyan          = "#7ACCD7"
        brightGreen         = "#A4CD7C"
        brightPurple        = "#AB9DF2"
        brightRed           = "#F882A5"
        brightWhite         = "#E3E1E4"
        brightYellow        = "#E5D37E"
        cursorColor         = "#FFFFFF"
        cyan                = "#3AA5D0"
        foreground          = "#E3E1E4"
        green               = "#7FCD2B"
        purple              = "#7C63F2"
        red                 = "#F82F66"
        selectionBackground = "#FFFFFF"
        white               = "#E3E1E4"
        yellow              = "#E5DE2D"
      }
      # Check color schema added before or not?
      if ($settings.schemes | Where-Object -Property name -EQ $sonokaiSchema.name) {
        Write-Host "[PsProfile] Terminal Color Theme was added before"
      } else {
        $settings.schemes += $sonokaiSchema
        # Check default profile has colorScheme or not
        if ($settings.profiles.defaults | Get-Member -Name 'colorScheme' -MemberType Properties) {
          $settings.profiles.defaults.colorScheme = $sonokaiSchema.name
        } else {
          $settings.profiles.defaults | Add-Member -MemberType NoteProperty -Name 'colorScheme' -Value $sonokaiSchema.name
        }
        $this.SaveTerminalSettings($settings);
      }
    } else {
      Write-Warning "[PsProfile] Could not get TerminalSettings, please make sure windowsTerminal is installed."
    }
  }
  hidden [void] InstallNerdFont() {
    $this.InstallNerdFont('FiraCode')
  }
  hidden [void] InstallNerdFont([string]$FontName) {
    #Requires -Version 3.0
    [ValidateNotNullorEmpty()][string]$FontName = $FontName
    $v = [ProgressUtil]::data.ShowProgress
    if ([FontMan]::GetInstalledFonts().Contains("$FontName")) {
      $v ? (Write-Console "[PsProfile] Font '$FontName' is already installed!" -f Wheat) : $null
      return
    }
    $v ? (Write-Console "Install required Font: ($FontName)" -f Magenta) : $null
    [IO.DirectoryInfo]$FiraCodeExpand = [IO.Path]::Combine($env:temp, 'FiraCodeExpand')
    if (![System.IO.Directory]::Exists($FiraCodeExpand.FullName)) {
      $fczip = [IO.FileInfo][IO.Path]::Combine($env:temp, 'FiraCode.zip')
      if (![IO.File]::Exists($fczip.FullName)) {
        Invoke-WebRequest -Uri https://github.com/ryanoasis/nerd-fonts/releases/latest/download/FiraCode.zip -OutFile $fczip.FullName
      }
      Expand-Archive -Path $fczip.FullName -DestinationPath $FiraCodeExpand.FullName
      Remove-Item -Path $fczip.FullName -Recurse
    }
    # Install Fonts
    # the Install-Font function is found in PSWinGlue module
    "PSWinGlue" | Resolve-Module
    Import-Module -Name PSWinGlue -WarningAction silentlyContinue
    # Elevate to Administrative
    if (!$this.IsAdministrator()) {
      $command = "Install-Font -Path '{0}'" -f ([IO.Path]::Combine($env:temp, 'FiraCodeExpand'))
      Start-Process powershell.exe -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command {$command}" -Verb RunAs -Wait -WindowStyle Minimized
    }
    $settings = $this.GetTerminalSettings()
    if ($null -ne $settings) {
      # Check default profile has font or not
      if ($settings.profiles.defaults | Get-Member -Name 'font' -MemberType Properties) {
        $settings.profiles.defaults.font.face = 'FiraCode Nerd Font'
      } else {
        $settings.profiles.defaults | Add-Member -MemberType NoteProperty -Name 'font' -Value $(
          [PSCustomObject]@{
            face = 'FiraCode Nerd Font'
          }
        ) | ConvertTo-Json
      }
      $this.SaveTerminalSettings($settings);
    } else {
      Write-Warning "Could update Terminal font settings!"
    }
  }
  hidden [void] InstallWinget() {
    Write-Host "Install winget" -ForegroundColor Magenta
    $wngt = 'Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle'
    $deps = ('Microsoft.VCLibs.x64.14.00.Desktop.appx', 'Microsoft.UI.Xaml.x64.appx')
    $PPref = Get-Variable -Name progressPreference -Scope Global; $progressPreference = 'silentlyContinue'
    $IPref = Get-Variable -Name InformationPreference -Scope Global; $InformationPreference = "Continue"
    if ([bool](Get-Command winget -Type Application -ea Ignore) -and !$PSBoundParameters.ContainsKey('Force')) {
      Write-Host "Winget is already Installed." -ForegroundColor Green -NoNewline; Write-Host " Use -Force switch to Overide it."
      return
    }
    # Prevent Error code: 0x80131539
    # https://github.com/PowerShell/PowerShell/issues/13138
    Get-Command Add-AppxPackage -ErrorAction Ignore
    if ($Error[0]) {
      if ($Error[0].GetType().FullName -in ('System.Management.Automation.CmdletInvocationException', 'System.Management.Automation.CommandNotFoundException')) {
        Write-Host "Module Appx is not loaded by PowerShell Core! Importing ..." -ForegroundColor Yellow
        Import-Module -Name Appx -UseWindowsPowerShell -Verbose:$false -WarningAction SilentlyContinue
      }
    }
    Write-Host "[0/3] Downloading WinGet and its dependencies ..." -ForegroundColor Green
    Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile $wngt
    Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile $deps[0]
    Invoke-WebRequest -Uri https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.7.3/Microsoft.UI.Xaml.2.7.x64.appx -OutFile $deps[1];

    Write-Host "[1/3] Installing $($deps[0]) ..." -ForegroundColor Green
    Add-AppxPackage -Path $deps[0]

    Write-Host "[2/3] Installing $($deps[1]) ..." -ForegroundColor Green
    Add-AppxPackage -Path $deps[1]

    Write-Host "[3/3] Installing $wngt ..." -ForegroundColor Green
    Add-AppxPackage -Path $wngt
    # cleanup
    $deps + $wngt | ForEach-Object { Remove-Item ($_ -as 'IO.FileInfo').FullName -Force -ErrorAction Ignore }
    # restore
    $progressPreference = $PPref
    $InformationPreference = $IPref
  }
  [void] InstallOhMyPosh() {
    $this.InstallOhMyPosh($true, $false)
  }
  [void] InstallOhMyPosh([bool]$AllUsers, [bool]$force) {
    $ompath = Get-Variable OH_MY_POSH_PATH -Scope Global -ValueOnly -ErrorAction Ignore
    if ($null -eq $ompath) { $this.Set_Defaults() }; $v = [ProgressUtil]::data.ShowProgress
    $ompdir = [IO.DirectoryInfo]::new((Get-Variable OH_MY_POSH_PATH -Scope Global -ValueOnly))
    if (!$ompdir.Exists) { [void]$this.Create_Directory($ompdir.FullName) }
    if (!$force -and [bool](Get-Command oh-my-posh -Type Application -ErrorAction Ignore)) {
      $v ? (Write-Console "oh-my-posh is already Installed; moing on ..." -f Wheat) : $null
      return
    }
    # begin installation
    $v ? (Write-Console "Install OhMyPosh" -f Magenta) : $null
    $installer = ''; $installInstructions = "`nHey friend`n`nThis installer is only available for Windows.`nIf you're looking for installation instructions for your operating system,`nplease visit the following link:`n"
    $Host_OS = $this.HostOS.ToString()
    if ($Host_OS -eq "MacOS") {
      $v ? (Write-Console "`n$installInstructions`n`nhttps://ohmyposh.dev/docs/installation/macos`n" -f Wheat) : $null
    } elseif ($Host_OS -eq "Linux") {
      $v ? (Write-Console "`n$installInstructions`n`nhttps://ohmyposh.dev/docs/installation/linux" -f Wheat) : $null
    } elseif ($Host_OS -eq "Windows") {
      $arch = (Get-CimInstance -Class Win32_Processor -Property Architecture).Architecture | Select-Object -First 1
      switch ($arch) {
        0 { $installer = "install-386.exe" }
        5 { $installer = "install-arm64.exe" }
        9 {
          if ([Environment]::Is64BitOperatingSystem) {
            $installer = "install-amd64.exe"
          } else {
            $installer = "install-386.exe"
          }
        }
        12 { $installer = "install-arm64.exe" }
      }
      if ([string]::IsNullOrEmpty($installer)) {
        throw "`nThe installer for system architecture ($(@{
                    0 = 'X86'
                    5 = 'ARM'
                    9 = 'AMD64/32'
                    12 = 'Surface'
                }.$arch)) is not available.`n"

      }
      $v ? (Write-Console "Downloading $installer..." -f DarkKhaki) : $null
      $omp_installer = [IO.FileInfo]::new("aux"); $ret_count = 0
      do {
        $omp_installer = [IO.FileInfo]::new([IO.Path]::Combine($env:TEMP, ([System.IO.Path]::GetRandomFileName() -replace '\.\w+$', '.exe'))); $ret_count++
      } while (![IO.File]::Exists($omp_installer.FullName) -and $ret_count -le 10)
      $url = "https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/$installer"
      [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
      Invoke-WebRequest -OutFile $omp_installer.FullName -Uri $url
      $v ? (Write-Console 'Running installer...' -f DarkKhaki) : $null
      $installMode = "/CURRENTUSER"
      if ($AllUsers) {
        $installMode = "/ALLUSERS"
      }

      & "$omp_installer" /VERYSILENT $installMode | Out-Null
      $omp_installer | Remove-Item
      #todo: refresh the shell
    } else {
      throw "Error: Could not determine the Host operating system."
    }
  }
  [bool] IsTerminalInstalled() {
    $IsInstalled = $true
    # check in wt if added to %PATH%
    $defWtpath = (Get-Command wt.exe -Type Application -ErrorAction Ignore).Source
    if (![string]::IsNullOrWhiteSpace($defWtpath)) {
      return $IsInstalled
    }
    # check generic paths: https://stackoverflow.com/questions/62894666/path-and-name-of-exe-file-of-windows-terminal-preview
    $genericwt = [IO.FileInfo]::new("$env:LocalAppData/Microsoft/WindowsApps/wt.exe")
    $msstorewt = [IO.FileInfo]::new("$env:LocalAppData/Microsoft/WindowsApps/Microsoft.WindowsTerminal_8wekyb3d8bbwe/wt.exe")
    $IsInstalled = $IsInstalled -and ($genericwt.Exists -or $msstorewt.Exists)
    return $IsInstalled
  }
  [bool] IsAdministrator() {
    return [bool]([Security.Principal.WindowsIdentity]::GetCurrent().Groups -match 'S-1-5-32-544')
  }
  [IO.FileInfo] GetPsProfile() {
    # This method will return the profile file (CurrentUserCurrentHost) and creates a new one if it does not already exist.
    $Documents_Path = $this.GetDocumentsPath()
    if (!$Documents_Path.Exists) {
      New-Item -Path $Documents_Path -ItemType Directory
    }
    # Manually get CurrentUserCurrentHost profile file
    $prof = [IO.FileInfo]::new([IO.Path]::Combine($Documents_Path, 'PowerShell', 'Microsoft.PowerShell_profile.ps1'))
    if (!$prof.Exists) { $prof = $this.GetPsProfile($prof) }
    return $prof
  }
  [IO.FileInfo] GetPsProfile([IO.FileInfo]$file) {
    if (!$file.Directory.Exists) { [void]$this.Create_Directory($file.Directory.FullName) }
    $file = New-Item -ItemType File -Path $file.FullName
    $this.Add_OMP_To_Profile($file)
    return $file
  }
  [IO.DirectoryInfo] GetDocumentsPath() {
    $User_docs_Path = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::MyDocuments)
    $Documents_Path = if (![IO.Path]::Exists($User_docs_Path)) {
      $UsrProfile = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::UserProfile)
      $mydocsPath = if (![IO.Path]::Exists($UsrProfile)) {
        $commondocs = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::CommonDocuments)
        if (![IO.Path]::Exists($commondocs)) {
          $all_spec_docs_paths = (0..61 | ForEach-Object { (New-Object â€“COM "Shell.Application").NameSpace($_).Self.Path }).Where({ $_ -like "*$([IO.Path]::DirectorySeparatorChar)Documents" }).Foreach({ $_ })
          if ($all_spec_docs_paths.count -eq 0) {
            throw [System.InvalidOperationException]::new("Could not find Documents Path")
          }
          $all_spec_docs_paths[0]
        }
        $commondocs
      } else {
        [IO.Path]::Combine($UsrProfile, 'Documents')
      }
      $mydocsPath
    } else {
      $User_docs_Path
    }
    return $Documents_Path -as [IO.DirectoryInfo]
  }
  [string] Get_omp_Json() {
    if ([string]::IsNullOrWhiteSpace("$([string]$this.ompJson) ".Trim())) {
      return $this.get_omp_Json('omp.json', [uri]::new('https://gist.github.com/chadnpc/b106f0e618bb9bbef86611824fc37825'))
    }
    return $this.ompJson
  }
  [string] Get_omp_Json([string]$fileName, [uri]$gisturi) {
    Write-Host "Fetching the latest $fileName" -ForegroundColor Green;
    $gistId = $gisturi.Segments[-1];
    $jsoncontent = Invoke-WebRequest "https://gist.githubusercontent.com/chadnpc/$gistId/raw/$fileName" -Verbose:$false | Select-Object -ExpandProperty Content
    if ([string]::IsNullOrWhiteSpace("$jsoncontent ".Trim())) {
      throw [System.IO.InvalidDataException]::NEW('FAILED to get valid json string gtom github gist')
    }
    return $jsoncontent
  }
  [void] Set_omp_Json() {
    $this.set_omp_Json('omp.json', [uri]::new('https://gist.github.com/chadnpc/b106f0e618bb9bbef86611824fc37825'))
  }
  [void] Set_omp_Json([string]$fileName, [uri]$gisturi) {
    if ($null -eq $this.OmpJsonFile.FullName) { $this.Set_Defaults() }
    $this.OmpJsonFile = [IO.FileInfo]::New([IO.Path]::Combine($(Get-Variable OH_MY_POSH_PATH -Scope Global -ValueOnly -ErrorAction Ignore), 'themes', 'p10k_classic.omp.json'))
    if (!$this.OmpJsonFile.Exists) {
      if (!$this.OmpJsonFile.Directory.Exists) { [void]$this.Create_Directory($this.OmpJsonFile.Directory.FullName) }
      $this.OmpJsonFile = New-Item -ItemType File -Path ([IO.Path]::Combine($this.OmpJsonFile.Directory.FullName, $this.OmpJsonFile.Name))
      $this.get_omp_Json($fileName, $gisturi) | Out-File ($this.OmpJsonFile.FullName) -Encoding utf8
    } else {
      Write-Host "Found $($this.OmpJsonFile)" -ForegroundColor Green
    }
    $this.ompJson = [IO.File]::ReadAllLines($this.OmpJsonFile.FullName)
  }
  [void] Set_Defaults() {
    Write-Verbose "Set defaults ..."
    $this.Default_Dependencies = @('Terminal-Icons', 'PSReadline', 'Pester', 'Posh-git', 'PSWinGlue', 'PowerShellForGitHub');
    $this.swiglyChar = [char]126;
    $this.DirSeparator = [System.IO.Path]::DirectorySeparatorChar;
    $this.nl = [Environment]::NewLine;
    # UTF8 Characters from https://www.w3schools.com/charsets/ref_utf_box.asp
    $this.dt = [string][char]8230;
    $this.b1 = [string][char]91;
    $this.b2 = [string][char]93;
    $this.Home_Indic = $this.swiglyChar + [IO.Path]::DirectorySeparatorChar;
    $this.PSVersn = (Get-Variable PSVersionTable).Value;
    $this.leadingChar = [char]9581 + [char]9592;
    $this.trailngChar = [char]9584 + [char]9588;
    $this.LogFile;
    $this.colors = $this.Get_Colors()
    # Prevent tsl errors
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    # Set the default launch ascii : chadnpc. but TODO: add a way to load it from a config instead of hardcoding it.
    $this.Default_Term_Ascii = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('bSUBJQElASVuJW0lbiUgACAAIAAgACAAIAAgAG0lASUBJQElbiVtJW4lCgADJW0lASVuJQMlAyUDJSAAIAAgAG0lbiUgACAAAyVtJQElbiUDJW8lcCVuJQoAAyUDJSAAAyUDJQMlbSUBJQElbiVtJW4lASVuJQMlAyUgAAMlAyVuJW0lbSUBJQElbiUBJQElbiUKAAMlcCUBJW8lAyUDJQMlbSUgAAMlfAADJW0lbiVuJQMlIAADJQMlAyUDJXwAbSVuJQMlbSUBJW8lCgADJW0lASVuJQMlAyVwJXAlbyVwJW4lAyUDJQMlAyVwJQElbyUDJQMlcCVuJQMlASUrJXAlASVuJQoAcCVvJSAAcCVvJXAlASVvJQElASVvJW8lbyVwJW8lASUBJW4lcCUBJQElbyUBJQElbyUBJQElbyUKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIABwJW8lCgAiAFEAdQBpAGMAawAgAHAAcgBvAGQAdQBjAHQAaQB2AGUAIAB0AGUAYwBoACIA'));
    $this.WINDOWS_TERMINAL_JSON_PATH = [IO.Path]::Combine($env:LocalAppdata, 'Packages', 'Microsoft.WindowsTerminal_8wekyb3d8bbwe', 'LocalState', 'settings.json');
    # Initialize or Reload $PROFILE and the core functions necessary for displaying your custom prompt.
    $p = [PSObject]::new(); Get-Variable PROFILE -ValueOnly | Get-Member -Type NoteProperty | ForEach-Object {
      $p | Add-Member -Name $_.Name -MemberType NoteProperty -Value ($_.Definition.split('=')[1] -as [IO.FileInfo])
    }
    $this.PROFILE = $p
    # Set Host UI DEFAULS
    $this.WindowTitle = $this.GetWindowTitle()
    if (!(Get-Variable OH_MY_POSH_PATH -ValueOnly -ErrorAction Ignore)) {
      New-Variable -Name OH_MY_POSH_PATH -Scope Global -Option Constant -Value ([IO.Path]::Combine($env:LOCALAPPDATA, 'Programs', 'oh-my-posh')) -Force
    }
    $this.HostOS = $(if ($(Get-Variable PSVersionTable -Value).PSVersion.Major -le 5 -or $(Get-Variable IsWindows -Value)) { "Windows" }elseif ($(Get-Variable IsLinux -Value)) { "Linux" }elseif ($(Get-Variable IsMacOS -Value)) { "MacOS" }else { "UNKNOWN" });
    $this.OmpJsonFile = [IO.FileInfo]::New([IO.Path]::Combine($(Get-Variable OH_MY_POSH_PATH -Scope Global -ValueOnly), 'themes', 'p10k_classic.omp.json'))
  }
  [void] Add_OMP_To_Profile() {
    $this.Add_OMP_To_Profile($this.GetPsProfile())
  }
  [void] Add_OMP_To_Profile([IO.FileInfo]$File) {
    Write-Host "Checking for OH_MY_POSH in Profile ... " -ForegroundColor Yellow
    if ([string]::IsNullOrWhiteSpace("$([string]$this.ompJson) ".Trim())) {
      try {
        $this.ompJson = $this.get_omp_Json()
      } catch [System.Net.Http.HttpRequestException], [System.Net.Sockets.SocketException] {
        throw [System.Exception]::New('gist.githubusercontent.com:443 Please check your internet')
      }
    }
    if (!$this.OmpJsonFile.Exists) {
      Set-Content -Path ($this.OmpJsonFile.FullName) -Value ($this.ompJson) -Force
    }
    $sb = [System.Text.StringBuilder]::new()
    [void]$sb.AppendLine('# Enable Oh My Posh Theme Engine')
    [void]$sb.AppendLine('oh-my-posh --init --shell pwsh --config ~/AppData/Local/Programs/oh-my-posh/themes/p10k_classic.omp.json | Invoke-Expression')
    if (!(Select-String -Path $File.FullName -Pattern "oh-my-posh" -SimpleMatch -Quiet)) {
      # TODO: #3 Move configuration to directory instead of manipulating original profile file
      Write-Host "Add OH_MY_POSH to Profile ... " -NoNewline -ForegroundColor DarkYellow
      Add-Content -Path $File.FullName -Value $sb.ToString()
      Write-Host "Done." -ForegroundColor Yellow
    } else {
      Write-Host "OH_MY_POSH is added to Profile." -ForegroundColor Green
    }
  }
  [void] Set_TerminalUI() {
    (Get-Variable -Name Host -ValueOnly).UI.RawUI.WindowTitle = $this.WindowTitle
    (Get-Variable -Name Host -ValueOnly).UI.RawUI.ForegroundColor = "White"
    (Get-Variable -Name Host -ValueOnly).PrivateData.ErrorForegroundColor = "DarkGray"
  }
  [string] GetWindowTitle() {
    $Title = ($this.GetCurrentProces().Path -as [IO.FileInfo]).BaseName
    try {
      $user = [Security.Principal.WindowsIdentity]::GetCurrent()
      [void][Security.Principal.WindowsIdentity]::GetAnonymous()
      $ob = [Security.Principal.WindowsPrincipal]::new($user)
      $UserRole = [PSCustomObject]@{
        'HasUserPriv'  = [bool]$ob.IsInRole([Security.Principal.WindowsBuiltinRole]::User)
        'HasAdminPriv' = [bool]$ob.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
        'HasSysPriv'   = [bool]$ob.IsInRole([Security.Principal.WindowsBuiltinRole]::SystemOperator)
        'IsPowerUser'  = [bool]$ob.IsInRole([Security.Principal.WindowsBuiltinRole]::PowerUser)
        'IsGuest'      = [bool]$ob.IsInRole([Security.Principal.WindowsBuiltinRole]::Guest)
      }
      $UserRole.PSObject.TypeNames.Insert(0, 'Security.User.RoleProperties')
      if ($UserRole.IsPowerUser) {
        $Title += $($this.b1 + 'E' + $this.b2) # IsElevated
      }
      if ($UserRole.HasSysPriv) {
        $Title += $($this.b1 + 'S' + $this.b2) # IsSYSTEM
      }
      # Admin indicator not neded, since Windows11 build 22557.1
      if ($UserRole.HasAdminPriv) { $Title += ' (Admin)' }
      if ($UserRole.HasUserPriv -and !($UserRole.HasAdminPriv)) { $Title += ' (User)' }
    } catch [System.PlatformNotSupportedException] {
      $Title += $($this.b1 + $this.b2 + ' (User)')
    }
    return $Title
  }
  [void] Write_Term_Ascii() {
    if ($null -eq ($this.PSVersn)) { $this.Set_Defaults() }
    [double]$MinVern = 5.1
    [double]$CrVersn = ($this.PSVersn | Select-Object @{l = 'vern'; e = { "{0}.{1}" -f $_.PSVersion.Major, $_.PSVersion.Minor } }).vern
    if ($null -ne ($this.colors)) {
      Write-Host ''; # i.e: Writing to the console in 24-bit colors can only work with PowerShell versions lower than '5.1'
      if ($CrVersn -gt $MinVern) {
        # Write-ColorOutput -ForegroundColor DarkCyan $($this.Default_Term_Ascii)
        Write-Host "$($this.Default_Term_Ascii)" -ForegroundColor Green
      } else {
        $this.Write_RGB($this.Default_Term_Ascii, 'SlateBlue')
      }
      Write-Host ''
    }
  }
  [void] Resolve_Module([string[]]$names) {
    if (!$(Get-Variable Resolve_Module_fn -ValueOnly -Scope global -ErrorAction Ignore)) {
      # Write-Verbose "Fetching the Resolve_Module script (One-time/Session only)";
      Set-Variable -Name Resolve_Module_fn -Scope global -Option ReadOnly -Value ([scriptblock]::Create($((Invoke-RestMethod -Method Get https://api.github.com/gists/7629f35f93ae89a525204bfd9931b366).files.'Resolve-Module.ps1'.content)))
    }
    . $(Get-Variable Resolve_Module_fn -ValueOnly -Scope global)
    Resolve-Module -Name $Names
  }
  [void] Write_RGB([string]$Text, $ForegroundColor) {
    $this.Write_RGB($Text, $ForegroundColor, $true)
  }
  [void] Write_RGB([string]$Text, [string]$ForegroundColor, [bool]$NoNewLine) {
    $this.Write_RGB($Text, $ForegroundColor, 'Black')
  }
  [void] Write_RGB([string]$Text, [string]$ForegroundColor, [string]$BackgroundColor) {
    $this.Write_RGB($Text, $ForegroundColor, $BackgroundColor, $true)
  }
  [void] Write_RGB([string]$Text, [string]$ForegroundColor, [string]$BackgroundColor, [bool]$NoNewLine) {
    $escape = [char]27 + '['; $24bitcolors = $this.colors
    $resetAttributes = "$($escape)0m";
    $psBuild = $this.PSVersn.PSVersion.Build
    [double]$VersionNum = $($this.PSVersn.PSVersion.ToString().split('.')[0..1] -join '.')
    if ([bool]$($VersionNum -le [double]5.1)) {
      [rgb]$Background = [rgb]::new(1, 36, 86);
      [rgb]$Foreground = [rgb]::new(255, 255, 255);
      $f = "$($escape)38;2;$($Foreground.Red);$($Foreground.Green);$($Foreground.Blue)m"
      $b = "$($escape)48;2;$($Background.Red);$($Background.Green);$($Background.Blue)m"
      $f = "$($escape)38;2;$($24bitcolors.$ForegroundColor.Red);$($24bitcolors.$ForegroundColor.Green);$($24bitcolors.$ForegroundColor.Blue)m"
      $b = "$($escape)48;2;$($24bitcolors.$BackgroundColor.Red);$($24bitcolors.$BackgroundColor.Green);$($24bitcolors.$BackgroundColor.Blue)m"
      if ([bool](Get-Command Write-Info -ErrorAction SilentlyContinue)) {
        Write-Info ($f + $b + $Text + $resetAttributes) -NoNewLine:$NoNewLine
      } else {
        Write-Host ($f + $b + $Text + $resetAttributes) -NoNewline:$NoNewLine
      }
    } else {
      throw [System.Management.Automation.RuntimeException]::new("Writing to the console in 24-bit colors can only work with PowerShell versions lower than '5.1 build 14931' or above.`nBut yours is '$VersionNum' build '$psBuild'")
    }
  }
  [PsObject] GetCurrentProces() {
    $chost = Get-Variable Host -ValueOnly; $process = Get-Process -Id $(Get-Variable pid -ValueOnly) | Get-Item
    $versionTable = Get-Variable PSVersionTable -ValueOnly
    return [PsObject]@{
      Path           = $process.Fullname
      FileVersion    = $process.VersionInfo.FileVersion
      PSVersion      = $versionTable.PSVersion.ToString()
      ProductVersion = $process.VersionInfo.ProductVersion
      Edition        = $versionTable.PSEdition
      Host           = $chost.name
      Culture        = $chost.CurrentCulture
      Platform       = $versionTable.platform
    }
  }
  [void] SetConsoleTitle([string]$Title) {
    $chost = Get-Variable Host -ValueOnly
    $width = ($chost.UI.RawUI.MaxWindowSize.Width * 2)
    if ($chost.Name -ne "ConsoleHost") {
      Write-Warning "This command must be run from a PowerShell console session. Not the PowerShell ISE or Visual Studio Code or similar environments."
    } elseif (($title.length -ge $width)) {
      Write-Warning "Your title is too long. It needs to be less than $width to fit your current console."
    } else {
      $chost.ui.RawUI.WindowTitle = $Title
    }
  }
  [hashtable] Get_Colors() {
    $c = @{}; [Color].GetProperties().Name.ForEach({ $c[$_] = [color]::$_ })
    return $c
  }
  hidden [void] Create_Prompt_Function() {
    # Creates the Custom prompt function & if nothing goes wrong then shows a welcome Ascii Art
    $this.CurrExitCode = $true
    try {
      # Creates the prompt function
      $null = New-Item -Path function:prompt -Value $([scriptblock]::Create({
            if (!$this.IsInitialised()) {
              Write-Verbose "[PsProfile] Initializing, Please wait ..."
              $this.Initialize()
            }
            try {
              if ($NestedPromptLevel -ge 1) {
                $this.trailngChar += [string][char]9588
                # $this.leadingChar += [string][char]9592
              }
              # Grab th current loaction
              $location = "$((Get-Variable ExecutionContext).Value.SessionState.Path.CurrentLocation.Path)";
              $shortLoc = $this.Get_Short_Path($location, $this.dt)
              $IsGitRepo = if ([bool]$(try { Test-Path .git -ErrorAction silentlyContinue }catch { $false })) { $true }else { $false }
              $(Get-Variable Host).Value.UI.Write(($this.leadingChar))
              Write-Host -NoNewline $($this.b1);
              Write-Host $([Environment]::UserName).ToLower() -NoNewline -ForegroundColor Magenta;
              Write-Host $([string][char]64) -NoNewline -ForegroundColor Gray;
              Write-Host $([System.Net.Dns]::GetHostName().ToLower()) -NoNewline -ForegroundColor DarkYellow;
              Write-Host -NoNewline "$($this.b2) ";
              if ($location -eq "$env:UserProfile") {
                Write-Host $($this.Home_Indic) -NoNewline -ForegroundColor DarkCyan;
              } elseif ($location.Contains("$env:UserProfile")) {
                $location = $($location.replace("$env:UserProfile", "$($this.swiglyChar)"));
                if ($location.Length -gt 25) {
                  $location = $this.Get_Short_Path($location, $this.dt)
                }
                Write-Host $location -NoNewline -ForegroundColor DarkCyan
              } else {
                Write-Host $shortLoc -NoNewline -ForegroundColor DarkCyan
              }
              # add a newline
              if ($IsGitRepo) {
                Write-Host $((Write-VcsStatus) + "`n")
              } else {
                Write-Host "`n"
              }
            } catch {
              #Do this if a terminating exception happens#
              # if ($_.Exception.WasThrownFromThrowStatement) {
              # [System.Management.Automation.ErrorRecord]$_ | Write-Log $($this.LogFile.FullName)
              # }
              $(Get-Variable Host).Value.UI.WriteErrorLine("[PromptError] [$($_.FullyQualifiedErrorId)] $($_.Exception.Message) # see the Log File : $($this.LogFile.FullName) $($this.nl)")
            } finally {
              #Do this after the try block regardless of whether an exception occurred or not
              Set-Variable -Name LASTEXITCODE -Scope Global -Value $($this.realLASTEXITCODE)
            }
            Write-Host ($this.trailngChar)
          }
        )
      ) -Force
      $this.CurrExitCode = $this.CurrExitCode -and $?
    } catch {
      $this.CurrExitCode = $false
      # Write-Log -ErrorRecord $_
    } finally {
      Set-Variable -Name IsPromptInitialised -Value $($this.CurrExitCode) -Visibility Public -Scope Global;
    }
  }
  hidden [string] Get_Short_Path() {
    $curr_location = $(Get-Variable ExecutionContext).Value.SessionState.Path.CurrentLocation.Path
    return $this.get_short_Path($curr_location, 2, 2, $this.DirSeparator, [char]8230)
  }
  hidden [string] Get_Short_Path([string]$Path) {
    return $this.get_short_Path($Path, 2, 2, $this.DirSeparator, [char]8230)
  }
  hidden [string] Get_Short_Path([string]$Path, [char]$TruncateChar) {
    return $this.get_short_Path($Path, 2, 2, $this.DirSeparator, $TruncateChar)
  }
  hidden [string] Get_Short_Path([string]$Path, [int]$KeepBefore, [int]$KeepAfter, [Char]$Separator, [char]$TruncateChar) {
    # [int]$KeepBefore, # Number of parts to keep before truncating. Default value is 2.
    # [int]$KeepAfter, # Number of parts to keep after truncating. Default value is 1.
    # [Char]$Separator, # Path separator character.
    $Path = (Resolve-Path -Path $Path).Path;
    $Path = $Path.Replace(([System.IO.Path]::DirectorySeparatorChar), $this.DirSeparator)
    [ValidateRange(1, [int32]::MaxValue)][int]$KeepAfter = $KeepAfter
    $Separator = $Separator.ToString(); $TruncateChar = $TruncateChar.ToString()
    $splitPath = $Path.Split($Separator, [System.StringSplitOptions]::RemoveEmptyEntries)
    if ($splitPath.Count -gt ($KeepBefore + $KeepAfter)) {
      $outPath = [string]::Empty
      for ($i = 0; $i -lt $KeepBefore; $i++) {
        $outPath += $splitPath[$i] + $Separator
      }
      $outPath += "$($TruncateChar)$($Separator)"
      for ($i = ($splitPath.Count - $KeepAfter); $i -lt $splitPath.Count; $i++) {
        if ($i -eq ($splitPath.Count - 1)) {
          $outPath += $splitPath[$i]
        } else {
          $outPath += $splitPath[$i] + $Separator
        }
      }
    } else {
      $outPath = $splitPath -join $Separator
      if ($splitPath.Count -eq 1) {
        $outPath += $Separator
      }
    }
    return $outPath
  }
  hidden [System.IO.DirectoryInfo] Create_Directory([string]$Path) {
    $nF = @(); $d = [System.IO.DirectoryInfo]::New((Get-Variable ExecutionContext).Value.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path))
    ([ProgressUtil]::data.ShowProgress) ? (Write-Console "Creating Directory '$($d.FullName)' ..." -f DarkKhaki) : $null
    while (!$d.Exists) { $nF += $d; $d = $d.Parent }
    [Array]::Reverse($nF); $nF | ForEach-Object { $_.Create() }
    return $d
  }
}


class dotProfile {
  #.LINK
  # https://pastebin.com/raw/rdqSxHT8
  # usage: $Banner.Write(19, $false, $true)
  static [cliart]$Banner = [cliart]::Create("H4sIAAAAAAAAA61S0QrCMAz8IF8E+wdOmBQcE22lb0Mn1FUtgvj70s7ZtOvaID4cpDlyl9Ar6odUupTqfhlDnH1ueMd6mHkMP+WR00jusNosn5TLyoKpirKuh+vNXN21lA+APex8hvd6f/V4zSnRzboH3xLdfMAbonlNND85PgY7g+ShfpTfg/qA8zAzcMf4DYXJ7Pd/y3RexE4qUfSAM2g+ph/UAuyT87BYjPVGHkeTWXZ1ufWzU1F2a/GZzfBY/XCfSQ8AlryhtpmF/wthshDmN5JpNP+LfsIjxPQNb9ytVCVoBQAA", ("Build. Ship. Repeat."))
  static [ProfileConfig]$config = @{}

  dotProfile() { }
  dotProfile([string]$Json) {
  }
  hidden [string] Get_Short_Path() {
    $curr_location = $(Get-Variable ExecutionContext).Value.SessionState.Path.CurrentLocation.Path
    return $this.get_short_Path($curr_location, 2, 2, $this.DirSeparator, [char]8230)
  }
  hidden [string] Get_Short_Path([string]$Path) {
    return $this.get_short_Path($Path, 2, 2, $this.DirSeparator, [char]8230)
  }
  hidden [string] Get_Short_Path([string]$Path, [char]$TruncateChar) {
    return $this.get_short_Path($Path, 2, 2, $this.DirSeparator, $TruncateChar)
  }
  hidden [string] Get_Short_Path([string]$Path, [int]$KeepBefore, [int]$KeepAfter, [Char]$Separator, [char]$TruncateChar) {
    # [int]$KeepBefore, # Number of parts to keep before truncating. Default value is 2.
    # [int]$KeepAfter, # Number of parts to keep after truncating. Default value is 1.
    # [Char]$Separator, # Path separator character.
    $Path = (Resolve-Path -Path $Path).Path;
    $Path = $Path.Replace(([System.IO.Path]::DirectorySeparatorChar), $this.DirSeparator)
    [ValidateRange(1, [int32]::MaxValue)][int]$KeepAfter = $KeepAfter
    $Separator = $Separator.ToString(); $TruncateChar = $TruncateChar.ToString()
    $splitPath = $Path.Split($Separator, [System.StringSplitOptions]::RemoveEmptyEntries)
    if ($splitPath.Count -gt ($KeepBefore + $KeepAfter)) {
      $outPath = [string]::Empty
      for ($i = 0; $i -lt $KeepBefore; $i++) {
        $outPath += $splitPath[$i] + $Separator
      }
      $outPath += "$($TruncateChar)$($Separator)"
      for ($i = ($splitPath.Count - $KeepAfter); $i -lt $splitPath.Count; $i++) {
        if ($i -eq ($splitPath.Count - 1)) {
          $outPath += $splitPath[$i]
        } else {
          $outPath += $splitPath[$i] + $Separator
        }
      }
    } else {
      $outPath = $splitPath -join $Separator
      if ($splitPath.Count -eq 1) {
        $outPath += $Separator
      }
    }
    return $outPath
  }
  hidden [System.IO.DirectoryInfo] Create_Directory([string]$Path) {
    $nF = @(); $d = [System.IO.DirectoryInfo]::New((Get-Variable ExecutionContext).Value.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path))
    ([ProgressUtil]::data.ShowProgress) ? (Write-Console "Creating Directory '$($d.FullName)' ..." -f DarkKhaki) : $null
    while (!$d.Exists) { $nF += $d; $d = $d.Parent }
    [Array]::Reverse($nF); $nF | ForEach-Object { $_.Create() }
    return $d
  }
}