pwshfetch-test-1.ps1

#!/usr/bin/env pwsh
#requires -version 5

# MIT License
#
# Copyright (c) 2021 Kied Llaentenn and contributers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

<#PSScriptInfo
.VERSION 2.0.0
.GUID 1c26142a-da43-4125-9d70-97555cbb1752
.DESCRIPTION Winfetch is a command-line system information utility for Windows written in PowerShell.
.AUTHOR lptstr
.PROJECTURI https://github.com/lptstr/winfetch
.COMPANYNAME
.COPYRIGHT
.TAGS
.LICENSEURI
.ICONURI
.EXTERNALMODULEDEPENDENCIES
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
#>

<#
.SYNOPSIS
    Winfetch - Neofetch for Windows in PowerShell 5+
.DESCRIPTION
    Winfetch is a command-line system information utility for Windows written in PowerShell.
.PARAMETER image
    Display a pixelated image instead of the usual logo. Imagemagick required.
.PARAMETER genconf
    Download a configuration template. Internet connection required.
.PARAMETER noimage
    Do not display any image or logo; display information only.
.PARAMETER legacylogo
    Use legacy Windows logo.
.PARAMETER blink
    Make the logo blink.
.PARAMETER help
    Display this help message.
.INPUTS
    System.String
.OUTPUTS
    System.String[]
.NOTES
    Run Winfetch without arguments to view core functionality.
#>

[CmdletBinding()]
param(
    [string][alias('i')]$image,
    [switch][alias('g')]$genconf,
    [switch][alias('n')]$noimage,
    [switch][alias('l')]$legacylogo,
    [switch][alias('b')]$blink,
    [switch][alias('h')]$help
)

$e = [char]0x1B

$is_pscore = $PSVersionTable.PSEdition.ToString() -eq 'Core'

$configdir = $env:XDG_CONFIG_HOME, "${env:USERPROFILE}\.config" | Select-Object -First 1
$configPath = "${configdir}/winfetch/config.ps1"

$defaultconfig = 'https://raw.githubusercontent.com/lptstr/winfetch/master/lib/config.ps1'

# ensure configuration directory exists
if (-not (Test-Path -Path $configPath)) {
    [void](New-Item -Path $configPath -Force)
}

# ===== DISPLAY HELP =====
if ($help) {
    if (Get-Command -Name less -ErrorAction Ignore) {
        Get-Help ($MyInvocation.MyCommand.Definition) -Full | less
    } else {
        Get-Help ($MyInvocation.MyCommand.Definition) -Full
    }
    exit 0
}

# ===== GENERATE CONFIGURATION =====
if ($genconf) {
    if ((Get-Item -Path $configPath).Length -gt 0) {
        Write-Host 'ERROR: configuration file already exists!' -f red
        exit 1
    }
    Write-Output "INFO: downloading default config to '$configPath'."
    Invoke-WebRequest -Uri $defaultconfig -OutFile $configPath -UseBasicParsing
    Write-Output 'INFO: successfully completed download.'
    exit 0
}


# ===== VARIABLES =====
$cimSession = New-CimSession
$showDisks = @($env:SystemDrive)
$showPkgs = @("scoop", "choco")
$t = if ($blink) { "5" } else { "1" }


# ===== CONFIGURATION =====
$baseConfig = @(
    "title"
    "dashes"
    "os"
    "computer"
    "kernel"
    "motherboard"
    "uptime"
    "resolution"
    "pkgs"
    "pwsh"
    "terminal"
    "theme"
    "cpu"
    "gpu"
    "process"
    "memory"
    "disk"
    "battery"
    "locale"
    "local_ip"
    "public_ip"
    "blank"
    "colorbar"
)

if ((Get-Item -Path $configPath).Length -gt 0) {
    $config = . $configPath
} else {
    $config = $baseConfig
}

# convert old config style
if ($config.GetType() -eq [string]) {
    $oldConfig = $config.ToLower()
    $config = $baseConfig | Where-Object { $oldConfig.Contains($PSItem) }
    $config += @("blank", "colorbar")
}


# ===== IMAGE =====
$img = if (-not $noimage) {
    if ($image) {
        if (-not (Get-Command -Name magick -ErrorAction Ignore)) {
            Write-Host 'ERROR: Imagemagick must be installed to print custom images.' -f red
            Write-Host 'hint: if you have Scoop installed, try `scoop install imagemagick`.' -f yellow
            exit 1
        }

        $COLUMNS = 35
        $CURR_ROW = ""
        $CHAR = [Text.Encoding]::UTF8.GetString(@(226, 150, 128)) # 226,150,136
        $upper, $lower = @(), @()

        if ($image -eq 'wallpaper') {
            $image = (Get-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name Wallpaper).Wallpaper
        }
        if (-not (Test-Path -path $image)) {
            Write-Host 'ERROR: Specified image or wallpaper does not exist.' -f red
            exit 1
        }
        $pixels = @((magick convert -thumbnail "${COLUMNS}x" $image txt:-).Split("`n"))
        foreach ($pixel in $pixels) {
            # ignore comments in output
            if ($pixel.StartsWith("#")) { continue }

            $col, $row = [regex]::Match($pixel, "(\d+),(\d+):").Groups[1, 2].Value
            $r, $g, $b = [regex]::Match($pixel, "\((\d+).*?,(\d+).*?,(\d+).*?,(\d+).*?\)").Groups[1, 2, 3].Value

            if (($row % 2) -eq 0) {
                $upper += "${r};${g};${b}"
            } else {
                $lower += "${r};${g};${b}"
            }

            if (($row % 2) -eq 1 -and $col -eq ($COLUMNS - 1)) {
                $i = 0
                while ($i -lt $COLUMNS) {
                    $CURR_ROW += "${e}[38;2;$($upper[$i]);48;2;$($lower[$i])m${CHAR}"
                    $i++
                }
                "${CURR_ROW}${e}[0m"

                $CURR_ROW = ""
                $upper = @()
                $lower = @()
            }
        }
    } elseif ($legacylogo) {
        @(
            "${e}[${t};31m ,.=:!!t3Z3z., "
            "${e}[${t};31m :tt:::tt333EE3 "
            "${e}[${t};31m Et:::ztt33EEE ${e}[32m@Ee., ..,"
            "${e}[${t};31m ;tt:::tt333EE7 ${e}[32m;EEEEEEttttt33#"
            "${e}[${t};31m :Et:::zt333EEQ. ${e}[32mSEEEEEttttt33QL"
            "${e}[${t};31m it::::tt333EEF ${e}[32m@EEEEEEttttt33F "
            "${e}[${t};31m ;3=*^``````'*4EEV ${e}[32m:EEEEEEttttt33@. "
            "${e}[${t};34m ,.=::::it=., ${e}[31m`` ${e}[32m@EEEEEEtttz33QF "
            "${e}[${t};34m ;::::::::zt33) ${e}[32m'4EEEtttji3P* "
            "${e}[${t};34m :t::::::::tt33 ${e}[33m:Z3z.. ${e}[32m```` ${e}[33m,..g. "
            "${e}[${t};34m i::::::::zt33F ${e}[33mAEEEtttt::::ztF "
            "${e}[${t};34m ;:::::::::t33V ${e}[33m;EEEttttt::::t3 "
            "${e}[${t};34m E::::::::zt33L ${e}[33m@EEEtttt::::z3F "
            "${e}[${t};34m{3=*^``````'*4E3) ${e}[33m;EEEtttt:::::tZ`` "
            "${e}[${t};34m `` ${e}[33m:EEEEtttt::::z7 "
            "${e}[${t};33m 'VEzjt:;;z>*`` "
        )
    } else {
        @(
            "${e}[${t};34m ....,,:;+ccllll"
            "${e}[${t};34m ...,,+:; cllllllllllllllllll"
            "${e}[${t};34m,cclllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34m "
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34mllllllllllllll lllllllllllllllllll"
            "${e}[${t};34m``'ccllllllllll lllllllllllllllllll"
            "${e}[${t};34m ``' \\*:: :ccllllllllllllllll"
            "${e}[${t};34m ````````''*::cll"
            "${e}[${t};34m ````"
        )
    }
}


# ===== BLANK =====
function info_blank {
    return @{}
}


# ===== COLORBAR =====
function info_colorbar {
    return @(
        @{
            title   = ""
            content = ('{0}[0;40m{1}{0}[0;41m{1}{0}[0;42m{1}{0}[0;43m{1}{0}[0;44m{1}{0}[0;45m{1}{0}[0;46m{1}{0}[0;47m{1}{0}[0m') -f $e, ' '
        },
        @{
            title   = ""
            content = ('{0}[0;100m{1}{0}[0;101m{1}{0}[0;102m{1}{0}[0;103m{1}{0}[0;104m{1}{0}[0;105m{1}{0}[0;106m{1}{0}[0;107m{1}{0}[0m') -f $e, ' '
        }
    )
}


# ===== OS =====
function info_os {
    return @{
        title   = "OS"
        content = if ($IsWindows -or $PSVersionTable.PSVersion.Major -eq 5) {
            $os = Get-CimInstance -ClassName Win32_OperatingSystem -Property Caption,OSArchitecture -CimSession $cimSession
            "$($os.Caption.TrimStart('Microsoft ')) [$($os.OSArchitecture)]"
        } else {
            ($PSVersionTable.OS).TrimStart('Microsoft ')
        }
    }
}


# ===== MOTHERBOARD =====
function info_motherboard {
    $motherboard = Get-CimInstance Win32_BaseBoard -CimSession $cimSession -Property Manufacturer,Product
    return @{
        title = "Motherboard"
        content = "{0} {1}" -f $motherboard.Manufacturer, $motherboard.Product
    }
}


# ===== TITLE =====
function info_title {
    return @{
        title   = ""
        content = "${e}[1;34m{0}${e}[0m@${e}[1;34m{1}${e}[0m" -f [Environment]::UserName,$env:COMPUTERNAME
    }
}


# ===== DASHES =====
function info_dashes {
    $length = [Environment]::UserName.Length + $env:COMPUTERNAME.Length + 1
    return @{
        title   = ""
        content = "-" * $length
    }
}


# ===== COMPUTER =====
function info_computer {
    $compsys = Get-CimInstance -ClassName Win32_ComputerSystem -Property Manufacturer,Model -CimSession $cimSession
    return @{
        title   = "Host"
        content = '{0} {1}' -f $compsys.Manufacturer, $compsys.Model
    }
}


# ===== KERNEL =====
function info_kernel {
    return @{
        title   = "Kernel"
        content = if ($IsWindows -or $PSVersionTable.PSVersion.Major -eq 5) {
            "$([System.Environment]::OSVersion.Version)"
        } else {
            "$(uname -r)"
        }
    }
}


# ===== UPTIME =====
function info_uptime {
    @{
        title   = "Uptime"
        content = $(switch ((Get-Date) - (Get-CimInstance -ClassName Win32_OperatingSystem -Property LastBootUpTime -CimSession $cimSession).LastBootUpTime) {
            ({ $PSItem.Days -eq 1 }) { '1 day' }
            ({ $PSItem.Days -gt 1 }) { "$($PSItem.Days) days" }
            ({ $PSItem.Hours -eq 1 }) { '1 hour' }
            ({ $PSItem.Hours -gt 1 }) { "$($PSItem.Hours) hours" }
            ({ $PSItem.Minutes -eq 1 }) { '1 minute' }
            ({ $PSItem.Minutes -gt 1 }) { "$($PSItem.Minutes) minutes" }
        }) -join ' '
    }
}


# ===== RESOLUTION =====
function info_resolution {
    Add-Type -AssemblyName System.Windows.Forms
    $Displays = New-Object System.Collections.Generic.List[System.Object];
    foreach ($monitor in [System.Windows.Forms.Screen]::AllScreens) {
        $Displays.Add("$($monitor.Bounds.Size.Width)x$($monitor.Bounds.Size.Height)");
    }

    return @{
        title   = "Resolution"
        content = $Displays -join ' '
    }
}


# ===== TERMINAL =====
# this section works by getting the parent processes of the current powershell instance.
function info_terminal {
    if (-not $is_pscore) {
        $parent = Get-Process -Id (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $PID" -Property ParentProcessId -CimSession $cimSession).ParentProcessId
        for () {
            if ($parent.ProcessName -in 'powershell', 'pwsh', 'winpty-agent', 'cmd', 'zsh', 'bash') {
                $parent = Get-Process -Id (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $($parent.ID)" -Property ParentProcessId -CimSession $cimSession).ParentProcessId
                continue
            }
            break
        }
    } else {
        $parent = (Get-Process -Id $PID).Parent
        for () {
            if ($parent.ProcessName -in 'powershell', 'pwsh', 'winpty-agent', 'cmd', 'zsh', 'bash') {
                $parent = (Get-Process -Id $parent.ID).Parent
                continue
            }
            break
        }
    }
    try {
        $terminal = switch ($parent.ProcessName) {
            { $PSItem -in 'explorer', 'conhost' } { 'Windows Console' }
            'Console' { 'Console2/Z' }
            'ConEmuC64' { 'ConEmu' }
            'WindowsTerminal' { 'Windows Terminal' }
            'FluentTerminal.SystemTray' { 'Fluent Terminal' }
            'Code' { 'Visual Studio Code' }
            default { $PSItem }
        }
    } catch {
        $terminal = $parent.ProcessName
    }

    return @{
        title   = "Terminal"
        content = $terminal
    }
}


# ===== THEME =====
function info_theme {
    $themeinfo = Get-ItemProperty -Path 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize' -Name SystemUsesLightTheme, AppsUseLightTheme
    $systheme = if ($themeinfo.SystemUsesLightTheme) { "Light" } else { "Dark" }
    $apptheme = if ($themeinfo.AppsUseLightTheme) { "Light" } else { "Dark" }
    return @{
        title = "Theme"
        content = "System - $systheme, Apps - $apptheme"
    }
}


# ===== CPU/GPU =====
function info_cpu {
    return @{
        title   = "CPU"
        content = (Get-CimInstance -ClassName Win32_Processor -Property Name -CimSession $cimSession).Name
    }
}

function info_gpu {
    return @{
        title   = "GPU"
        content = (Get-CimInstance -ClassName Win32_VideoController -Property Name -CimSession $cimSession).Name
    }
}


# ===== PROCESS =====
function info_process {
    return @{
        title   = "Processes"
        content = "$((Get-Process).Count) ($((Get-CimInstance -ClassName Win32_Processor -Property LoadPercentage -CimSession $cimSession).LoadPercentage)% load)"
    }
}


# ===== MEMORY =====
function info_memory {
    $m = Get-CimInstance -ClassName Win32_OperatingSystem -Property TotalVisibleMemorySize,FreePhysicalMemory -CimSession $cimSession
    $total = $m.TotalVisibleMemorySize / 1mb
    $used = ($m.TotalVisibleMemorySize - $m.FreePhysicalMemory) / 1mb
    return @{
        title   = "Memory"
        content = ("{0:f1} GiB / {1:f1} GiB" -f $used,$total)
    }
}


# ===== DISK USAGE =====
function info_disk {
    [System.Collections.ArrayList]$lines = @()

    function to_units($value) {
        if ($value -gt 1tb) {
            return "$([math]::round($value / 1tb, 1))T"
        } else {
            return "$([math]::floor($value / 1gb))G"
        }
    }

    $disks = Get-CimInstance -ClassName Win32_LogicalDisk -Property Size,FreeSpace -CimSession $cimSession

    foreach ($diskInfo in $disks) {
        foreach ($diskLetter in $showDisks) {
            if ($diskInfo.DeviceID -eq $diskLetter -or $diskLetter -eq "*") {
                $total = $diskInfo.Size
                $used = $total - $diskInfo.FreeSpace
                $usage = [math]::floor(($used / $total * 100))
                [void]$lines.Add(@{
                    title   = "Disk ($($diskInfo.DeviceID))"
                    content = "$(to_units $used) / $(to_units $total) ($usage%)"
                })
                break
            }
        }
    }

    return $lines
}


# ===== POWERSHELL VERSION =====
function info_pwsh {
    return @{
        title   = "Shell"
        content = "PowerShell v$($PSVersionTable.PSVersion)"
    }
}


# ===== PACKAGES =====
function info_pkgs {
    $pkgs = @()

    if ("choco" -in $ShowPkgs -and (Get-Command -Name choco -ErrorAction Ignore)) {
        $chocopkg = (& clist -l)[-1].Split(' ')[0] - 1

        if ($chocopkg) {
            $pkgs += "$chocopkg (choco)"
        }
    }

    if ("scoop" -in $ShowPkgs) {
        if (Test-Path "~/scoop/apps") {
            $scoopdir = "~/scoop/apps"
        } elseif (Get-Command -Name scoop -ErrorAction Ignore) {
            $scoop = & scoop which scoop
            $scoopdir = (Resolve-Path "$(Split-Path -Path $scoop)\..\..\..").Path
        }

        if ($scoopdir) {
            $scooppkg = (Get-ChildItem -Path $scoopdir -Directory).Count - 1
        }

        if ($scooppkg) {
            $pkgs += "$scooppkg (scoop)"
        }
    }

    if (-not $pkgs) {
        $pkgs = "(none)"
    }

    return @{
        title   = "Packages"
        content = $pkgs -join ', '
    }
}


# ===== BATTERY =====
function info_battery {
    $battery = Get-CimInstance Win32_Battery -CimSession $cimSession -Property BatteryStatus,EstimatedChargeRemaining,EstimatedRunTime,TimeToFullCharge

    if (-not $battery) {
        return @{
            title = "Battery"
            content = "(none)"
        }
    }

    $power = Get-CimInstance BatteryStatus -Namespace root\wmi -CimSession $cimSession -Property Charging,PowerOnline

    $status = if ($power.Charging) {
        "Charging"
    } elseif ($power.PowerOnline) {
        "Plugged in"
    } else {
        "Discharging"
    }

    $timeRemaining = if ($power.Charging) {
        $battery.TimeToFullCharge
    } else {
        $battery.EstimatedRunTime
    }

    # don't show time remaining if windows hasn't properly reported it yet
    $timeFormatted = if ($timeRemaining -and $timeRemaining -lt 71582788) {
        $hours = [math]::floor($timeRemaining / 60)
        $minutes = $timeRemaining % 60
        ", ${hours}h ${minutes}m"
    }

    return @{
        title = "Battery"
        content = "$($battery.EstimatedChargeRemaining)% ($status$timeFormatted)"
    }
}


# ===== LOCALE =====
function info_locale {
    # `Get-WinUserLanguageList` has a regression bug on PowerShell v7.1+
    # https://github.com/PowerShell/PowerShellModuleCoverage/issues/18
    # Fallback to `Get-WinSystemLocale` (which might be slightly inaccurate) for such cases
    return @{
        title = "Locale"
        content = if ($PSVersionTable.PSVersion -like "7.1.*") {
            "$((Get-WinHomeLocation).HomeLocation) - $((Get-WinSystemLocale).DisplayName)"
        } else {
            "$((Get-WinHomeLocation).HomeLocation) - $((Get-WinUserLanguageList)[0].LocalizedName)"
        }
    }
}


# ===== IP =====
function info_local_ip {
    $indexDefault = Get-NetRoute -DestinationPrefix 0.0.0.0/0 | Sort-Object -Property RouteMetric | Select-Object -First 1 | Select-Object -ExpandProperty ifIndex
    $local_ip = Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $indexDefault | Select-Object -ExpandProperty IPAddress
    return @{
        title = "Local IP"
        content = $local_ip
    }
}

function info_public_ip {
    try {
        $public_ip = (Resolve-DnsName -Name myip.opendns.com -Server resolver1.opendns.com).IPAddress
    } catch {}

    if (-not $public_ip) {
        $public_ip = Invoke-RestMethod -Uri https://ifconfig.me/ip
    }

    return @{
        title = "Public IP"
        content = $public_ip
    }
}


# reset terminal sequences and display a newline
Write-Host "$e[0m"

# write logo
foreach ($line in $img) {
    Write-Host " $line"
}

# move cursor to top of image and to column 40
if ($img) {
    Write-Host -NoNewLine "$e[$($img.Length)A$e[40G"
    $writtenLines = 0
}

# write info
foreach ($item in $config) {
    if (Test-Path Function:"info_$item") {
        $info = & "info_$item"
    } else {
        $info = @{ title = "$e[31mfunction 'info_$item' not found" }
    }

    if (-not $info) {
        continue
    }

    if ($info -isnot [array]) {
        $info = @($info)
    }

    foreach ($line in $info) {
        $output = "$e[1;34m$($line["title"])$e[0m"

        if ($line["title"] -and $line["content"]) {
            $output += ": "
        }

        $output += "$($line["content"])`n"

        # move cursor to column 40
        if ($img) {
            $output += "$e[40G"
            $writtenLines++
        }

        Write-Host -NoNewLine $output
    }
}

# move cursor back to the bottom
if ($img) {
    Write-Host -NoNewLine "$e[$($img.Length - $writtenLines)B"
}

# print 2 newlines
Write-Host "`n"

# ___ ___ ___
# | __/ _ \| __|
# | _| (_) | _|
# |___\___/|_|
#