pwsh-neofetch.psm1
Function Initialize-NeofetchConfig { param ( [switch]$Force ) $ESC = [char]27 $RESET = "$ESC[0m" $BOLD = "$ESC[1m" $CYAN = "$ESC[36m" $GREEN = "$ESC[32m" $YELLOW = "$ESC[33m" $asciiSavePath = Join-Path $env:USERPROFILE ".neofetch_ascii" $threadsSavePath = Join-Path $env:USERPROFILE ".neofetch_threads" $cacheExpirationPath = Join-Path $env:USERPROFILE ".neofetch_cache_expiration" $profileNamePath = Join-Path $env:USERPROFILE ".neofetch_profile_name" $isFirstRun = -not( (Test-Path $threadsSavePath) -or (Test-Path $cacheExpirationPath) -or (Test-Path $profileNamePath) -or (Test-Path $asciiSavePath) ) if (-not $isFirstRun -and -not $Force) { Write-Host "`n${BOLD}${YELLOW}Neofetch configuration already exists.${RESET}" -NoNewline Write-Host " Use ${BOLD}-init -Force${RESET} to reconfigure anyway." Write-Host "Or use ${BOLD}-reload${RESET} to reset all configurations to defaults." return } # Clear screen and show welcome Clear-Host Write-Host "`n${BOLD}${CYAN}=============================${RESET}" Write-Host "${BOLD}${CYAN} Windows Neofetch Setup${RESET}" Write-Host "${BOLD}${CYAN}=============================${RESET}" Write-Host "`nWelcome to the ${BOLD}Windows Neofetch${RESET} configuration wizard!" Write-Host "This will help you set up your initial preferences." Write-Host "You can always change these later with specific commands.`n" # Terminal Profile Configuration Write-Host "${BOLD}${CYAN}[1/4] Terminal Profile Configuration${RESET}" Write-Host "This helps identify the correct terminal font and appearance settings." Write-Host "Common values: 'Windows PowerShell', 'PowerShell', 'Command Prompt'" $defaultProfileName = "Windows PowerShell" $profileName = Read-Host "Enter your Windows Terminal profile name [Default: $defaultProfileName]" if ([string]::IsNullOrWhiteSpace($profileName)) { $profileName = $defaultProfileName Write-Host "Using default profile: ${BOLD}$defaultProfileName${RESET}" } else { Write-Host "Setting profile to: ${BOLD}$profileName${RESET}" } $profileName | Out-File -FilePath $profileNamePath -Force # Thread Configuration Write-Host "`n${BOLD}${CYAN}[2/4] Thread Configuration${RESET}" Write-Host "This sets how many CPU threads Neofetch will use to gather system information." Write-Host "Higher values can be faster but may use more system resources." $processorCount = [Environment]::ProcessorCount $recommendedThreads = [Math]::Min(4, $processorCount) do { $maxThreadsInput = Read-Host "Enter max threads (1-$processorCount) [Default: $recommendedThreads]" if ([string]::IsNullOrWhiteSpace($maxThreadsInput)) { $maxThreads = $recommendedThreads break } if ([int]::TryParse($maxThreadsInput, [ref]$maxThreads)) { if ($maxThreads -ge 1 -and $maxThreads -le $processorCount) { break } } Write-Host "${YELLOW}Please enter a valid number between 1 and $processorCount${RESET}" } while ($true) Write-Host "Setting max threads to: ${BOLD}$maxThreads${RESET}" $maxThreads | Out-File -FilePath $threadsSavePath -Force # Cache Configuration Write-Host "`n${BOLD}${CYAN}[3/4] Cache Configuration${RESET}" Write-Host "Neofetch can cache system information to speed up repeated runs." $enableCache = $true $cacheOption = Read-Host "Would you like to enable caching? (Y/n)" if ($cacheOption -match "^[Nn]") { $enableCache = $false Write-Host "Caching will be ${BOLD}disabled${RESET} (system info will be gathered fresh each time)" # Setting cache expiration to 0 indicates caching is disabled $cacheExpiration = 0 $cacheExpiration | Out-File -FilePath $cacheExpirationPath -Force } else { Write-Host "Caching is ${BOLD}enabled${RESET} (system info will be cached between runs)" Write-Host "This sets how long (in seconds) before cache is refreshed." $defaultExpiration = 1800 $defaultExpirationMin = $defaultExpiration / 60 do { $cacheExpirationInput = Read-Host "Enter cache expiration in seconds [Default: $defaultExpiration (${defaultExpirationMin}min)]" if ([string]::IsNullOrWhiteSpace($cacheExpirationInput)) { $cacheExpiration = $defaultExpiration break } if ([int]::TryParse($cacheExpirationInput, [ref]$cacheExpiration) -and $cacheExpiration -ge 0) { break } Write-Host "${YELLOW}Please enter a valid positive number${RESET}" } while ($true) Write-Host "Setting cache expiration to: ${BOLD}$cacheExpiration seconds${RESET}" $cacheExpiration | Out-File -FilePath $cacheExpirationPath -Force } # ASCII Art Configuration Write-Host "`n${BOLD}${CYAN}[4/4] ASCII Art Configuration${RESET}" Write-Host "Neofetch displays an ASCII art logo next to system information." Write-Host "You can use the default Windows logo or specify a custom art file." $useCustomArt = $false $customArtOption = Read-Host "Would you like to use custom ASCII art? (y/N)" if ($customArtOption -match "^[Yy]") { $useCustomArt = $true Write-Host "`nTo set custom ASCII art, use this command after setup completes:" Write-Host "${BOLD}neofetch -asciiart \"C:\path\to\your\ascii_art.txt\"${RESET}" } else { Write-Host "Using default Windows logo ASCII art" } # Summary Write-Host "`n${BOLD}${GREEN}Setup Complete!${RESET}" Write-Host "Your Neofetch configuration has been saved with the following settings:" Write-Host "${BOLD}Terminal Profile:${RESET} $profileName" Write-Host "${BOLD}Max Threads:${RESET} $maxThreads (of $processorCount available)" if ($cacheExpiration -eq 0) { Write-Host "${BOLD}Caching:${RESET} Disabled (fresh data will be gathered each time)" } else { Write-Host "${BOLD}Caching:${RESET} Enabled" Write-Host "${BOLD}Cache Expiration:${RESET} $cacheExpiration seconds ($($cacheExpiration/60) minutes)" } Write-Host "${BOLD}ASCII Art:${RESET} $(if($useCustomArt){"Custom (not set yet)"}else{"Default Windows logo"})" Write-Host "`n${BOLD}${CYAN}Helpful Commands:${RESET}" Write-Host "- ${BOLD}neofetch${RESET} - Run neofetch with your settings" Write-Host "- ${BOLD}neofetch -help${RESET} - Show all available commands" Write-Host "- ${BOLD}neofetch -changes${RESET} - Display current configuration" Write-Host "- ${BOLD}neofetch -reload${RESET} - Reset all settings to defaults" $runNeofetch = Read-Host "`nRun neofetch now? (Y/n)" if ($runNeofetch -notmatch "^[Nn]") { Write-Host "`nRunning neofetch with your new settings...`n" Start-Sleep -Seconds 1 return $true } else { return $false } } function Reset-NeofetchConfiguration { $cacheFile = Join-Path $env:TEMP "neofetch_cache.xml" $configFiles = @( $cacheFile, (Join-Path $env:USERPROFILE ".neofetch_ascii"), (Join-Path $env:USERPROFILE ".neofetch_cache_expiration"), (Join-Path $env:USERPROFILE ".neofetch_threads"), (Join-Path $env:USERPROFILE ".neofetch_profile_name") ) $reloadCount = 0 foreach ($file in $configFiles) { if (Test-Path $file) { Remove-Item -Path $file -Force $reloadCount++ } } $testFilePath = Join-Path $env:TEMP "neofetch_disk_test.dat" if (Test-Path $testFilePath) { Remove-Item -Path $testFilePath -Force $reloadCount++ } return $reloadCount } function Invoke-SystemBenchmark { param ( [switch]$Quiet ) if (-not $Quiet) { Write-Host "`nRunning system benchmark, please wait..." -ForegroundColor Cyan } $benchmarkResults = @{} $startTime = Get-Date # CPU Test if (-not $Quiet) { Write-Host "Running CPU test..." -ForegroundColor Gray } $cpuStartTime = Get-Date $primeCount = 0 function Test-IsPrime { param ([int]$number) if ($number -lt 2) { return $false } if ($number -eq 2) { return $true } if ($number % 2 -eq 0) { return $false } $boundary = [math]::Floor([math]::Sqrt($number)) for ($i = 3; $i -le $boundary; $i += 2) { if ($number % $i -eq 0) { return $false } } return $true } for ($i = 3; $i -lt 100000; $i += 2) { if (Test-IsPrime -number $i) { $primeCount++ } } $cpuEndTime = Get-Date $cpuSeconds = ($cpuEndTime - $cpuStartTime).TotalSeconds $cpuScore = [math]::Round(($primeCount / $cpuSeconds) * 10, 2) $benchmarkResults.CPU = @{ Score = $cpuScore Time = $cpuSeconds Unit = "primes/sec" Raw = $primeCount } # Memory Test if (-not $Quiet) { Write-Host "Running memory test..." -ForegroundColor Gray } $memStartTime = Get-Date $arraySize = 100000000 $memoryArray = New-Object object[] $arraySize for ($i = 0; $i -lt $arraySize; $i++) { $memoryArray[$i] = $i } $sum = 0 for ($i = 0; $i -lt $arraySize; $i++) { $sum += $memoryArray[$i] } $memEndTime = Get-Date $memSeconds = ($memEndTime - $memStartTime).TotalSeconds $memScore = [math]::Round(($arraySize / $memSeconds) / 10000, 2) $benchmarkResults.Memory = @{ Score = $memScore Time = $memSeconds Unit = "MB/sec" Raw = $arraySize } # Disk Test if (-not $Quiet) { Write-Host "Running disk test..." -ForegroundColor Gray } $diskStartTime = Get-Date $testFilePath = Join-Path $env:TEMP "neofetch_disk_test.dat" $testFileSize = 1024MB $writeTest = Measure-Command { $randomData = New-Object byte[] $testFileSize $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider $rng.GetBytes($randomData) [System.IO.File]::WriteAllBytes($testFilePath, $randomData) } $readTest = Measure-Command { $data = [System.IO.File]::ReadAllBytes($testFilePath) } if (Test-Path $testFilePath) { Remove-Item -Path $testFilePath -Force } $diskEndTime = Get-Date $diskSeconds = ($diskEndTime - $diskStartTime).TotalSeconds $writeSpeed = [math]::Round($testFileSize / $writeTest.TotalSeconds / 1MB, 2) $readSpeed = [math]::Round($testFileSize / $readTest.TotalSeconds / 1MB, 2) $diskScore = [math]::Round(($writeSpeed + $readSpeed) / 2, 2) $benchmarkResults.Disk = @{ Score = $diskScore WriteSpeed = $writeSpeed ReadSpeed = $readSpeed Unit = "MB/sec" Time = $diskSeconds } # Composite score $compositeScore = [math]::Round( ($benchmarkResults.CPU.Score * 0.4) + ($benchmarkResults.Memory.Score * 0.3) + ($benchmarkResults.Disk.Score * 0.3), 2) $ratingTable = @{ "Excellent" = 90 "Very Good" = 70 "Good" = 50 "Average" = 30 "Below Average" = 15 "Poor" = 0 } $rating = "Poor" foreach ($kvp in $ratingTable.GetEnumerator() | Sort-Object -Property Value -Descending) { if ($compositeScore -ge $kvp.Value) { $rating = $kvp.Key break } } $benchmarkResults.Composite = @{ Score = $compositeScore Rating = $rating Time = (Get-Date) - $startTime } return $benchmarkResults } function Show-BenchmarkResults { param ( [Parameter(Mandatory=$true)] [hashtable]$Results ) $ESC = [char]27 $RESET = "$ESC[0m" $BOLD = "$ESC[1m" $CYAN = "$ESC[36m" $GREEN = "$ESC[32m" $YELLOW = "$ESC[33m" $RED = "$ESC[31m" Write-Host "`n$BOLD${CYAN}System Benchmark Results:$RESET" Write-Host "" Write-Host "$BOLD${CYAN}CPU Performance:$RESET" Write-Host " Score: $($Results.CPU.Score) ($($Results.CPU.Unit))" Write-Host " Time: $($Results.CPU.Time) seconds" Write-Host "`n$BOLD${CYAN}Memory Performance:$RESET" Write-Host " Score: $($Results.Memory.Score) ($($Results.Memory.Unit))" Write-Host " Time: $($Results.Memory.Time) seconds" Write-Host "`n$BOLD${CYAN}Disk I/O Performance:$RESET" Write-Host " Score: $($Results.Disk.Score) ($($Results.Disk.Unit))" Write-Host " Write: $($Results.Disk.WriteSpeed) MB/sec" Write-Host " Read: $($Results.Disk.ReadSpeed) MB/sec" $ratingColor = switch ($Results.Composite.Rating) { "Excellent" { $GREEN } "Very Good" { $GREEN } "Good" { $GREEN } "Average" { $YELLOW } "Below Average" { $YELLOW } "Poor" { $RED } default { $RESET } } Write-Host "`n$BOLD${CYAN}Overall Performance:$RESET" Write-Host " Composite Score: $($Results.Composite.Score)" Write-Host " Rating: $ratingColor$($Results.Composite.Rating)$RESET" Write-Host " Total Benchmark Time: $($Results.Composite.Time.TotalSeconds) seconds" Write-Host "" } function Set-ProfileNameSetting { param ( [string]$ProfileName ) $ProfileNamePath = Join-Path $env:USERPROFILE ".neofetch_profile_name" $ProfileName | Out-File -FilePath $ProfileNamePath -Force return $true } function Reset-ProfileNameDefault { $ProfileNamePath = Join-Path $env:USERPROFILE ".neofetch_profile_name" if (Test-Path $ProfileNamePath) { Remove-Item -Path $ProfileNamePath -Force return $true } return $false } function Reset-CacheExpirationDefault { $ExpirationSavePath = Join-Path $env:USERPROFILE ".neofetch_cache_expiration" if (Test-Path $ExpirationSavePath) { Remove-Item -Path $ExpirationSavePath -Force return $true } return $false } function Reset-ThreadsDefault { $ThreadsSavePath = Join-Path $env:USERPROFILE ".neofetch_threads" if (Test-Path $ThreadsSavePath) { Remove-Item -Path $ThreadsSavePath -Force return $true } return $false } function Get-SystemInfoFast { param ( [switch]$NoCacheMode, [int]$MaxThreadsOverride = 0, [int]$CacheExpirationOverride = 0 ) $cacheableParams = @("OS", "Host", "Kernel", "Resolution", "WM", "CPU", "GPU", "Terminal", "TerminalFont", "Shell") $threadsSavePath = Join-Path $env:USERPROFILE ".neofetch_threads" $maxCoresForPool = 4 if ($MaxThreadsOverride -gt 0) { $MaxThreadsOverride | Out-File -FilePath $threadsSavePath -Force $maxCoresForPool = $MaxThreadsOverride } elseif (Test-Path $threadsSavePath) { try { $savedThreads = [int](Get-Content -Path $threadsSavePath -Raw) if ($savedThreads -gt 0) { $maxCoresForPool = $savedThreads } } catch {} } $maxCoresForPool = [System.Math]::Min($maxCoresForPool, [Environment]::ProcessorCount) $cachePath = Join-Path $env:TEMP "neofetch_cache.xml" $cacheExpirationPath = Join-Path $env:USERPROFILE ".neofetch_cache_expiration" [int]$cacheExpirationSeconds = 1800 if ($CacheExpirationOverride -gt 0) { $CacheExpirationOverride | Out-File -FilePath $cacheExpirationPath -Force $cacheExpirationSeconds = $CacheExpirationOverride } elseif (Test-Path $cacheExpirationPath) { try { $savedExpiration = [int](Get-Content -Path $cacheExpirationPath -Raw) if ($savedExpiration -gt 0) { $cacheExpirationSeconds = $savedExpiration } } catch {} } $cacheMaxAge = [TimeSpan]::FromSeconds($cacheExpirationSeconds) $results = @{} $useCache = -not $NoCacheMode -and $cacheExpirationSeconds -ne 0 if ($useCache -and (Test-Path $cachePath)) { $cacheFile = Get-Item $cachePath $cacheAge = (Get-Date) - $cacheFile.LastWriteTime if ($cacheAge -lt $cacheMaxAge) { try { $cachedData = Import-Clixml -Path $cachePath $useCache = $true foreach ($key in $cachedData.Keys) { if ($key -in $cacheableParams) { $results[$key] = $cachedData[$key] } } } catch { $useCache = $false Write-Verbose "Error reading cache: $_" } } else { $useCache = $false } } else { $useCache = $false } $runspacePool = [runspacefactory]::CreateRunspacePool(1, $maxCoresForPool) $runspacePool.Open() $scriptblocks = @{} $runspaces = @{} $handles = @{} $results.UserName = [System.Environment]::UserName $results.HostName = $env:COMPUTERNAME $results.UserHost = "$([System.Environment]::UserName)@$($env:COMPUTERNAME)" if (-not $useCache -or -not $results.ContainsKey("OS")) { $OSName = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName).ProductName $OSArch = if ([Environment]::Is64BitOperatingSystem) { "64-bit" } else { "32-bit" } $results.OS = "$OSName $OSArch" } if (-not $useCache -or -not $results.ContainsKey("Kernel")) { $results.Kernel = [System.Environment]::OSVersion.Version.ToString() } if (-not $useCache -or -not $results.ContainsKey("Shell")) { $results.Shell = "PowerShell " + $PSVersionTable.PSVersion.ToString() } $scriptblocks.Host = { $ManufacturerKey = "HKLM:\HARDWARE\DESCRIPTION\System\BIOS" $Manufacturer = (Get-ItemProperty -Path $ManufacturerKey -Name SystemManufacturer -ErrorAction SilentlyContinue).SystemManufacturer $Model = (Get-ItemProperty -Path $ManufacturerKey -Name SystemProductName -ErrorAction SilentlyContinue).SystemProductName return "$Manufacturer $Model" } $scriptblocks.Uptime = { try { $OperatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem -Property LastBootUpTime -ErrorAction Stop $BootTime = $OperatingSystem.LastBootUpTime $CurrentTime = Get-Date $Uptime = $CurrentTime - $BootTime return "$($Uptime.Days) days, $($Uptime.Hours) hours, $($Uptime.Minutes) mins" } catch { return "Unknown" } } $scriptblocks.Packages = { try { return (Get-Package | Measure-Object).Count } catch { return "Unknown" } } $scriptblocks.Resolution = { try { $DisplayInfo = Get-CimInstance -ClassName Win32_VideoController -Property CurrentHorizontalResolution, CurrentVerticalResolution -ErrorAction Stop if ($DisplayInfo.CurrentHorizontalResolution) { $Resolution = "$($DisplayInfo.CurrentHorizontalResolution)x$($DisplayInfo.CurrentVerticalResolution)" return $Resolution -replace "\s", "" } return "Unknown" } catch { return "Unknown" } } $scriptblocks.WM = { try { $ExplorerProcess = Get-Process -Name explorer -ErrorAction SilentlyContinue if (-not $ExplorerProcess) { if ((Get-WindowsFeature -Name Server-Gui-Shell -ErrorAction SilentlyContinue).InstallState -ne 'Installed') { return "Server Core (No GUI)" } } return "Windows Explorer" } catch { return "Windows Explorer" } } $scriptblocks.Terminal = { return $Host.Name } $scriptblocks.TerminalFont = { try { $wtSettingsPath = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json" $profileNamePath = Join-Path $env:USERPROFILE ".neofetch_profile_name" $defaultProfileName = "Windows PowerShell" if (-not (Test-Path $wtSettingsPath)) { return "Settings file not found" } $settings = Get-Content $wtSettingsPath -Raw | ConvertFrom-Json $profileNameToUse = $defaultProfileName if (Test-Path $profileNamePath) { try { $savedProfileName = Get-Content -Path $profileNamePath -Raw if ($savedProfileName -and $savedProfileName.Trim() -ne "") { $profileNameToUse = $savedProfileName.Trim() } } catch { } } $profile = $settings.profiles.list | Where-Object { $_.name -eq $profileNameToUse } if (-not $profile -and $settings.defaultProfile) { $defaultGuid = $settings.defaultProfile $profile = $settings.profiles.list | Where-Object { $_.guid -eq $defaultGuid } $profileNameToUse = if ($profile -and $profile.name) { $profile.name } else { "Default" } } if (-not $profile) { return "No matching profile found for profile name '$profileNameToUse'" } $font = if ($profile.font -and $profile.font.face) { $profile.font.face } elseif ($settings.profiles.defaults -and $settings.profiles.defaults.font -and $settings.profiles.defaults.font.face) { $settings.profiles.defaults.font.face } else { "Unknown" } return "$font [Profile: $profileNameToUse]" } catch { return "Terminal font detection error: $_" } } $scriptblocks.CPU = { try { $CPUInfo = Get-CimInstance -ClassName Win32_Processor -Property Name, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed -ErrorAction Stop $CPU = $CPUInfo.Name $CPUCores = $CPUInfo.NumberOfCores $CPUSpeed = [math]::Round($CPUInfo.MaxClockSpeed / 1000, 1) return "$CPU ($CPUCores) @ $CPUSpeed" + "GHz" } catch { return "Unknown" } } $scriptblocks.GPU = { try { $GPUInfoAll = Get-CimInstance -ClassName Win32_VideoController -ErrorAction Stop $DiscreteGPU = $GPUInfoAll | Where-Object { $_.Description -match "NVIDIA|AMD|GeForce|Radeon|Quadro|FirePro|RTX|GTX" } | Select-Object -First 1 if ($DiscreteGPU) { return $DiscreteGPU.Description } else { return ($GPUInfoAll | Select-Object -First 1).Description } } catch { return "Unknown" } } $scriptblocks.GPUMemory = { try { $nvidiaSmiPath = "C:\Windows\System32\nvidia-smi.exe" if (Test-Path $nvidiaSmiPath) { $nvidiaSmiOutput = & $nvidiaSmiPath --query-gpu=memory.used,memory.total --format=csv,noheader,nounits if ($nvidiaSmiOutput) { $memoryInfo = $nvidiaSmiOutput.Trim().Split(',') if ($memoryInfo.Count -ge 2) { $usedVRAM = [int]$memoryInfo[0].Trim() $totalVRAM = [int]$memoryInfo[1].Trim() $vramPercent = [math]::Round(($usedVRAM / $totalVRAM) * 100) return "${usedVRAM}MiB / ${totalVRAM}MiB (${vramPercent}%)" } } } return "Unknown" } catch { return "Unknown" } } $scriptblocks.Memory = { try { $MemInfo = Get-CimInstance -ClassName Win32_OperatingSystem -Property TotalVisibleMemorySize, FreePhysicalMemory -ErrorAction Stop $TotalRAM = [math]::Round($MemInfo.TotalVisibleMemorySize / 1MB, 2) $FreeRAM = [math]::Round($MemInfo.FreePhysicalMemory / 1MB, 2) $UsedRAM = [math]::Round($TotalRAM - $FreeRAM, 2) $RAMPercent = [math]::Round(($UsedRAM / $TotalRAM) * 100) return "${UsedRAM}GiB / ${TotalRAM}GiB (${RAMPercent}%)" } catch { return "Unknown" } } $scriptblocks.DiskUsage = { try { $Disk = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$env:SystemDrive'" -ErrorAction Stop | Select-Object Size, FreeSpace $DiskTotal = [math]::Round($Disk.Size / 1GB, 2) $DiskFree = [math]::Round($Disk.FreeSpace / 1GB, 2) $DiskUsed = [math]::Round($DiskTotal - $DiskFree, 2) $DiskPercent = [math]::Round(($DiskUsed / $DiskTotal) * 100) return "$env:SystemDrive ${DiskUsed}GB / ${DiskTotal}GB (${DiskPercent}%)" } catch { return "Unknown" } } $scriptblocks.Battery = { try { $Battery = Get-CimInstance -ClassName Win32_Battery -ErrorAction Stop if ($Battery) { $BatteryPercent = $Battery.EstimatedChargeRemaining $ChargingStatus = switch ($Battery.BatteryStatus) { 1 { "Discharging" } 2 { "AC Power" } 3 { "Fully Charged" } 4 { "Low" } 5 { "Critical" } 6 { "Charging" } 7 { "Charging and High" } 8 { "Charging and Low" } 9 { "Charging and Critical" } 10 { "Undefined" } 11 { "Partially Charged" } default { "Unknown" } } return "$BatteryPercent% [$ChargingStatus]" } return "No battery detected" } catch { return "No battery detected" } } $paramsToGather = @() foreach ($param in $cacheableParams) { if (-not $useCache -or -not $results.ContainsKey($param)) { $paramsToGather += $param } } foreach ($key in $scriptblocks.Keys) { if ($key -notin $cacheableParams) { $paramsToGather += $key } } foreach ($key in $paramsToGather) { if ($scriptblocks.ContainsKey($key)) { $runspaces[$key] = [powershell]::Create().AddScript($scriptblocks[$key]) $runspaces[$key].RunspacePool = $runspacePool $handles[$key] = $runspaces[$key].BeginInvoke() } } foreach ($key in $runspaces.Keys) { $results[$key] = $runspaces[$key].EndInvoke($handles[$key]) $runspaces[$key].Dispose() } if ($paramsToGather.Where({ $_ -in $cacheableParams }).Count -gt 0) { try { $cacheData = @{} foreach ($key in $cacheableParams) { if ($results.ContainsKey($key)) { $cacheData[$key] = $results[$key] } } $cacheData | Export-Clixml -Path $cachePath -Force } catch { Write-Verbose "Error saving cache: $_" } } $runspacePool.Close() $runspacePool.Dispose() return @{ UserHost = $results.UserHost OS = $results.OS Host = $results.Host Kernel = $results.Kernel Uptime = $results.Uptime Packages = $results.Packages Shell = $results.Shell Resolution = $results.Resolution WM = $results.WM Terminal = $results.Terminal TerminalFont = $results.TerminalFont CPU = $results.CPU GPU = $results.GPU GPUMemory = $results.GPUMemory Memory = $results.Memory DiskUsage = $results.DiskUsage Battery = $results.Battery } } function Get-ColorBlocks { $ESC = [char]27 $RESET = "$ESC[0m" $row1 = "$ESC[40m $RESET$ESC[41m $RESET$ESC[42m $RESET$ESC[43m $RESET$ESC[44m $RESET$ESC[45m $RESET$ESC[46m $RESET$ESC[47m $RESET" $row2 = "$ESC[100m $RESET$ESC[101m $RESET$ESC[102m $RESET$ESC[103m $RESET$ESC[104m $RESET$ESC[105m $RESET$ESC[106m $RESET$ESC[107m $RESET" return @($row1, $row2) } function Get-DefaultAsciiArt { return @( " .oodMMMM", " .oodMMMMMMMMMMMMM", " ..oodMMM MMMMMMMMMMMMMMMMMMM", "oodMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "MMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMM", "``^^^^^^MMMMMMM MMMMMMMMMMMMMMMMMMM", " ````````^^^^ ^^MMMMMMMMMMMMMMMMM", " ````````^^^^^^MMMM" ) } function Show-LiveUsageGraphs { param ( [ValidateRange(0.1, 60)] [double]$RefreshRateSeconds = 2, [int]$GraphHeightParam = 20, [int]$GraphWidthParam = 80 ) $ESC = [char]27 $RESET = "$ESC[0m" $BOLD = "$ESC[1m" $MAGENTA = "$ESC[35m" $CpuLineColor = "$ESC[36m" $RamLineColor = "$ESC[32m" $GpuUtilLineColor = "$ESC[31m" $VramUtilLineColor = "$ESC[33m" $AxisColor = "$ESC[90m" $GridColor = "$ESC[38;5;237m" $CpuBarColor = $CpuLineColor $RamBarColor = $RamLineColor $GpuBarColor = $GpuUtilLineColor $VramBarColor = $VramUtilLineColor $ErrorColor = "$ESC[31m" $GRAY = "$ESC[90m" $maxHistoryLength = $GraphWidthParam * 2 [System.Collections.Generic.List[double]]$cpuHistory = New-Object System.Collections.Generic.List[double] [System.Collections.Generic.List[double]]$ramHistory = New-Object System.Collections.Generic.List[double] [System.Collections.Generic.List[double]]$gpuUtilHistory = New-Object System.Collections.Generic.List[double] [System.Collections.Generic.List[double]]$vramUtilHistory = New-Object System.Collections.Generic.List[double] $summaryBarWidth = 40 function Set-CursorPosition { param ([int]$X, [int]$Y) $Host.UI.RawUI.CursorPosition = New-Object System.Management.Automation.Host.Coordinates $X, $Y } function Clear-Line { param([int]$Y) Set-CursorPosition 0 $Y Write-Host (" " * $Host.UI.RawUI.BufferSize.Width) -NoNewline Set-CursorPosition 0 $Y } function Format-UsageBar { param ( [string]$Label, [double]$Percentage, [string]$AdditionalInfo = "", [int]$PassedBarWidth ) $Percentage = [Math]::Max(0, [Math]::Min(100, $Percentage)) $filledChars = [Math]::Round(($Percentage / 100) * $PassedBarWidth) $emptyChars = $PassedBarWidth - $filledChars $barColorToUse = "$ESC[32m" switch -Wildcard ($Label) { "*CPU*" { $barColorToUse = $CpuBarColor } "*RAM*" { $barColorToUse = $RamBarColor } "*GPU*" { $barColorToUse = $GpuBarColor } "*VRAM*" { $barColorToUse = $VramBarColor } } if ($Percentage -ge 90) { $barColorToUse = $ErrorColor } elseif ($Percentage -ge 70) { if($Label -notmatch "CPU"){ $barColorToUse = $VramUtilLineColor }} # Yellowish for warning $paddedLabel = "{0,-10}" -f $Label $barSegment = "[" + ($barColorToUse + ("█" * $filledChars) + $RESET) + ("░" * $emptyChars) + "]" $percentStr = "{0,5:N1}%" -f $Percentage $result = "${paddedLabel}: ${barSegment} ${percentStr}" if ($AdditionalInfo) { $result += " ($AdditionalInfo)" } return $result } function Get-LiveCpuUsage { try { # $cpuLoad = (Get-CimInstance -ClassName Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average $cpuLoad = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples.CookedValue return [Math]::Round($cpuLoad, 1) } catch { return $null } } function Get-LiveRamUsage { try { $os = Get-CimInstance -ClassName Win32_OperatingSystem $totalMemoryMB = [Math]::Round($os.TotalVisibleMemorySize / 1KB, 0) $freeMemoryMB = [Math]::Round($os.FreePhysicalMemory / 1KB, 0) $usedMemoryMB = $totalMemoryMB - $freeMemoryMB $ramPercentUsed = if ($totalMemoryMB -gt 0) { [Math]::Round(($usedMemoryMB / $totalMemoryMB) * 100, 1) } else { 0 } return @{ TotalMB = $totalMemoryMB; UsedMB = $usedMemoryMB; Percent = $ramPercentUsed } } catch { return $null } } function Get-LiveNvidiaGpuUsage { try { $smiPath = if (Test-Path "C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe") { "C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe" } elseif (Test-Path "C:\Windows\System32\nvidia-smi.exe") { "C:\Windows\System32\nvidia-smi.exe" } else { return $null } $output = & $smiPath --query-gpu=utilization.gpu,memory.total,memory.used --format=csv,noheader,nounits if (-not $output) { return $null } $stats = $output.Trim() -split ',' if ($stats.Count -lt 3) { return $null } $gpuUtilization = [int]($stats[0].Trim()) $vramTotalMB = [int]$stats[1].Trim() $vramUsedMB = [int]$stats[2].Trim() $vramPercentUsed = if ($vramTotalMB -gt 0) { [Math]::Round(($vramUsedMB / $vramTotalMB) * 100, 1) } else { 0 } return @{ GPUUtilization = $gpuUtilization; VRAMTotalMB = $vramTotalMB; VRAMUsedMB = $vramUsedMB; VRAMPercent = $vramPercentUsed } } catch { return $null } } function RenderCharacterLineGraph { param ( [System.Collections.Generic.List[double]]$RenderCpuHistory, [System.Collections.Generic.List[double]]$RenderRamHistory, [System.Collections.Generic.List[double]]$RenderGpuUtilHistory, [System.Collections.Generic.List[double]]$RenderVramUtilHistory, [int]$RenderGraphHeight, [int]$RenderGraphWidth, [string]$RenderCpuColor, [string]$RenderRamColor, [string]$RenderGpuUtilColor, [string]$RenderVramUtilColor, [string]$RenderAxisColor, [string]$RenderGridColor, [string]$RenderResetColor ) $outputGraphLines = [System.Collections.Generic.List[string]]::new() $LineChars = @{ h = '─' # Horizontal line v = '│' # Vertical line ul = '┐' # Corner: up-left (connects to left, goes down) ur = '┌' # Corner: up-right (connects to right, goes down) dl = '┘' # Corner: down-left (connects to left, goes up) dr = '└' # Corner: down-right (connects to right, goes up) cross = '┼' # Intersection of horizontal and vertical # T-junctions t_down = '┬' # Horizontal line, stem down t_up = '┴' # Horizontal line, stem up t_left = '┤' # Vertical line, stem left t_right = '├' # Vertical line, stem right } $cpuPointChar = '*'; $ramPointChar = '+'; $gpuPointChar = '#'; $vramPointChar = '@' $gridDotChar = '·' $yAxisLabels = @{ 0 = "100% "; ([Math]::Floor($RenderGraphHeight * 0.25)) = " 75% "; ([Math]::Floor($RenderGraphHeight * 0.5)) = " 50% "; ([Math]::Floor($RenderGraphHeight * 0.75)) = " 25% "; ($RenderGraphHeight -1) = " 0% " } $yAxisLabelWidth = 6 $canvas = New-Object 'string[,]' $RenderGraphHeight, $RenderGraphWidth $canvasColors = New-Object 'string[,]' $RenderGraphHeight, $RenderGraphWidth $horizontalGridPositions = @(0, [Math]::Floor($RenderGraphHeight*0.25), [Math]::Floor($RenderGraphHeight*0.5), [Math]::Floor($RenderGraphHeight*0.75), ($RenderGraphHeight-1)) for ($y = 0; $y -lt $RenderGraphHeight; $y++) { for ($x = 0; $x -lt $RenderGraphWidth; $x++) { $canvas[$y, $x] = " "; $canvasColors[$y,$x] = $RenderResetColor if ($horizontalGridPositions -contains $y -and ($x % 10 -eq 0)) { $canvas[$y, $x] = $gridDotChar; $canvasColors[$y,$x] = $RenderGridColor } } } function Set-CanvasCharScoped { param ([int]$y, [int]$x, [string]$charToDraw, [string]$colorForChar) if ($y -lt 0 -or $y -ge $RenderGraphHeight -or $x -lt 0 -or $x -ge $RenderGraphWidth) { return } $existingChar = $canvas[$y, $x] $existingColor = $canvasColors[$y, $x] $isBlankOrGrid = ($existingChar -eq " " -or $existingChar -eq $gridDotChar) if ($isBlankOrGrid) { $canvas[$y, $x] = $charToDraw $canvasColors[$y, $x] = $colorForChar return } $finalChar = $charToDraw $finalColor = $colorForChar $merged = $false if ($charToDraw -eq $LineChars.h) { $merged = $true switch ($existingChar) { $LineChars.v { $finalChar = $LineChars.cross; $finalColor = $RenderAxisColor; break } # V + H -> ┼ (neutral) $LineChars.ul { $finalChar = $LineChars.t_down; break } # ┐ + H -> ┬ $LineChars.ur { $finalChar = $LineChars.t_down; break } # ┌ + H -> ┬ $LineChars.dl { $finalChar = $LineChars.t_up; break } # ┘ + H -> ┴ $LineChars.dr { $finalChar = $LineChars.t_up; break } # └ + H -> ┴ $LineChars.t_left { $finalChar = $LineChars.cross; break } # ┤ + H -> ┼ $LineChars.t_right { $finalChar = $LineChars.cross; break } # ├ + H -> ┼ $LineChars.h { $finalChar = $LineChars.h; break; } $LineChars.t_down { $finalChar = $LineChars.t_down; break; } $LineChars.t_up { $finalChar = $LineChars.t_up; break; } $LineChars.cross { $finalChar = $LineChars.cross; break; } default { $merged = $false } } } elseif ($charToDraw -eq $LineChars.v) { $merged = $true switch ($existingChar) { $LineChars.h { $finalChar = $LineChars.cross; $finalColor = $RenderAxisColor; break } # H + V -> ┼ (neutral) $LineChars.ul { $finalChar = $LineChars.t_left; break } # ┐ + V -> ┤ $LineChars.ur { $finalChar = $LineChars.t_right; break } # ┌ + V -> ├ $LineChars.dl { $finalChar = $LineChars.t_left; break } # ┘ + V -> ┤ $LineChars.dr { $finalChar = $LineChars.t_right; break } # └ + V -> ├ $LineChars.t_down { $finalChar = $LineChars.cross; break } # ┬ + V -> ┼ $LineChars.t_up { $finalChar = $LineChars.cross; break } # ┴ + V -> ┼ $LineChars.v { $finalChar = $LineChars.v; break; } $LineChars.t_left { $finalChar = $LineChars.t_left; break; } $LineChars.t_right { $finalChar = $LineChars.t_right; break; } $LineChars.cross { $finalChar = $LineChars.cross; break; } default { $merged = $false } } } elseif (($charToDraw -eq $LineChars.ul) -or ($charToDraw -eq $LineChars.ur) -or ` ($charToDraw -eq $LineChars.dl) -or ($charToDraw -eq $LineChars.dr)) { if ($existingChar -eq $LineChars.h) { if (($charToDraw -eq $LineChars.ul) -or ($charToDraw -eq $LineChars.ur)) { $finalChar = $LineChars.t_down } else { $finalChar = $LineChars.t_up } $merged = $true } elseif ($existingChar -eq $LineChars.v) { if (($charToDraw -eq $LineChars.ul) -or ($charToDraw -eq $LineChars.dl)) { $finalChar = $LineChars.t_left } else { $finalChar = $LineChars.t_right } $merged = $true } } if (-not $merged) { $finalChar = $charToDraw } $canvas[$y, $x] = $finalChar $canvasColors[$y, $x] = $finalColor } function PlotSingleMetricLine { param ( [System.Collections.Generic.List[double]]$HistoryToPlot, [string]$MetricLineColor, [int]$PlotGraphHeight, [int]$PlotGraphWidth ) $validPoints = @() for ($x = 0; $x -lt $PlotGraphWidth; $x++) { $histIndex = $HistoryToPlot.Count - $PlotGraphWidth + $x if ($histIndex -ge 0 -and $histIndex -lt $HistoryToPlot.Count) { $value = $HistoryToPlot[$histIndex] if ($null -ne $value) { $y = [Math]::Floor(($PlotGraphHeight - 1) * (1 - ($value / 100.0))) $y = [Math]::Max(0, [Math]::Min($PlotGraphHeight - 1, $y)) $validPoints += @{X = $x; Y = $y; Value = $value} } } } if ($validPoints.Count -eq 0) { return } Set-CanvasCharScoped $validPoints[0].Y $validPoints[0].X $LineChars.h $MetricLineColor for ($i = 1; $i -lt $validPoints.Count; $i++) { $current = $validPoints[$i] $previous = $validPoints[$i-1] if (($current.X - $previous.X) -gt 1) { Set-CanvasCharScoped $current.Y $current.X $LineChars.h $MetricLineColor continue } if ($current.Y -eq $previous.Y) { Set-CanvasCharScoped $current.Y $current.X $LineChars.h $MetricLineColor } else { $isRising = $current.Y -lt $previous.Y if ($isRising) { Set-CanvasCharScoped $previous.Y $current.X $LineChars.dl $MetricLineColor for ($y_vert = ($current.Y + 1); $y_vert -lt $previous.Y; $y_vert++) { Set-CanvasCharScoped $y_vert $current.X $LineChars.v $MetricLineColor } Set-CanvasCharScoped $current.Y $current.X $LineChars.ur $MetricLineColor } else { Set-CanvasCharScoped $previous.Y $current.X $LineChars.ul $MetricLineColor for ($y_vert = ($previous.Y + 1); $y_vert -lt $current.Y; $y_vert++) { Set-CanvasCharScoped $y_vert $current.X $LineChars.v $MetricLineColor } Set-CanvasCharScoped $current.Y $current.X $LineChars.dr $MetricLineColor } } } } PlotSingleMetricLine $RenderVramUtilHistory $RenderVramUtilColor $RenderGraphHeight $RenderGraphWidth PlotSingleMetricLine $RenderGpuUtilHistory $RenderGpuUtilColor $RenderGraphHeight $RenderGraphWidth PlotSingleMetricLine $RenderRamHistory $RenderRamColor $RenderGraphHeight $RenderGraphWidth PlotSingleMetricLine $RenderCpuHistory $RenderCpuColor $RenderGraphHeight $RenderGraphWidth for ($y = 0; $y -lt $RenderGraphHeight; $y++) { $line = ""; $yLabel = $yAxisLabels[$y] if ($yLabel) { $line += "$RenderAxisColor$($yLabel.PadRight($yAxisLabelWidth))$RenderResetColor" } else { $line += " " * $yAxisLabelWidth } for ($x = 0; $x -lt $RenderGraphWidth; $x++) { $charToPrint = $canvas[$y, $x]; $colorForChar = $canvasColors[$y, $x] if ($colorForChar -eq $RenderResetColor -and $charToPrint -ne " " -and $charToPrint -eq $gridDotChar){ $colorForChar = $RenderGridColor } elseif ($colorForChar -eq $RenderResetColor -and $charToPrint -ne " ") { $colorForChar = $RenderAxisColor } $line += "$colorForChar$charToPrint$RenderResetColor" } $outputGraphLines.Add($line) } $xAxisLine = (" " * $yAxisLabelWidth) + "$RenderAxisColor$($LineChars.dl)" + ($LineChars.h * ($RenderGraphWidth-2)) + "$($LineChars.dr)$RenderResetColor" if ($RenderGraphWidth -lt 2) {$xAxisLine = (" " * $yAxisLabelWidth) + "$RenderAxisColor$($LineChars.h * $RenderGraphWidth)$RenderResetColor"} # Handle very small widths $outputGraphLines.Add($xAxisLine) $timeLabelsLine = (" " * $yAxisLabelWidth) $lbl_neg_full = "-$($RenderGraphWidth)s"; $lbl_neg_half = "-$([int]($RenderGraphWidth/2))s"; $lbl_zero = "0s " $timeLabelLength = $RenderGraphWidth $availableSpaceForLabels = $timeLabelLength - $lbl_zero.Length $pos_neg_full = 0 $pos_neg_half = [Math]::Max(0, [int]($availableSpaceForLabels / 2) - [int]($lbl_neg_half.Length / 2)) $pos_zero = [Math]::Max(0, $availableSpaceForLabels - $lbl_zero.Length) $tempTimeLine = (" " * $timeLabelLength) function InsertString {param($original, $insert, $position) return $original.Substring(0, $position) + $insert + $original.Substring($position + $insert.Length) } if (($pos_neg_full + $lbl_neg_full.Length) -le $timeLabelLength) { $tempTimeLine = InsertString $tempTimeLine $lbl_neg_full $pos_neg_full } if (($pos_neg_half + $lbl_neg_half.Length) -le $timeLabelLength) { $tempTimeLine = InsertString $tempTimeLine $lbl_neg_half $pos_neg_half } $tempTimeLine = InsertString $tempTimeLine $lbl_zero ([Math]::Max(0, $timeLabelLength - $lbl_zero.Length)) $timeLabelsLine += "$RenderAxisColor$tempTimeLine$RenderResetColor" $outputGraphLines.Add($timeLabelsLine) $legend = (" " * $yAxisLabelWidth) + "$RenderCpuColor$($cpuPointChar) CPU$RenderResetColor " + "$RenderRamColor$($ramPointChar) RAM$RenderResetColor " + "$RenderGpuUtilColor$($gpuPointChar) GPU$RenderResetColor " + "$RenderVramUtilColor$($vramPointChar) VRAM$RenderResetColor" $outputGraphLines.Add($legend) return $outputGraphLines } try { [Console]::CursorVisible = $false $lastUpdateTime = [DateTime]::MinValue $smiPath1 = "C:\Program Files\NVIDIA Corporation\NVSMI\nvidia-smi.exe" $smiPath2 = "C:\Windows\System32\nvidia-smi.exe" $nvidiaSmiPathFound = (Test-Path $smiPath1) -or (Test-Path $smiPath2) $continueLoop = $true $psHost = Get-Host $originalBufferWidth = $psHost.UI.RawUI.BufferSize.Width $originalBufferHeight = $psHost.UI.RawUI.BufferSize.Height $requiredBufferWidth = $GraphWidthParam + 15 $requiredBufferHeight = $GraphHeightParam + 15 if ($originalBufferWidth -lt $requiredBufferWidth -or $originalBufferHeight -lt $requiredBufferHeight) { try { $newBufferSize = $psHost.UI.RawUI.BufferSize $newBufferSize.Width = [Math]::Max($originalBufferWidth, $requiredBufferWidth) $newBufferSize.Height = [Math]::Max($originalBufferHeight, $requiredBufferHeight) $psHost.UI.RawUI.BufferSize = $newBufferSize } catch { Write-Warning "Could not resize console buffer. Graph may not display optimally." } } $totalDisplayLines = $GraphHeightParam + 12 Clear-Host $titleLine = "${BOLD}${MAGENTA}Live System Character Graph (Press 'q' or ESC to quit)${RESET}" Write-Host $titleLine Write-Host ("-" * ($GraphWidthParam + 15)) for ($i = 0; $i -lt $totalDisplayLines; $i++) { Write-Host "" } $startY = 2 while ($continueLoop) { $now = Get-Date if ([Console]::KeyAvailable) { $key = [Console]::ReadKey($true) if ($key.KeyChar -eq 'q' -or $key.KeyChar -eq 'Q' -or $key.Key -eq 'Escape') { $continueLoop = $false; break } } if (($now - $lastUpdateTime).TotalSeconds -ge $RefreshRateSeconds) { $lastUpdateTime = $now $cpuUsageVal = Get-LiveCpuUsage $ramUsageInfoVal = Get-LiveRamUsage $gpuUsageInfoVal = if ($nvidiaSmiPathFound) { Get-LiveNvidiaGpuUsage } else { $null } $cpuHistory.Add($cpuUsageVal) if ($ramUsageInfoVal) { $ramHistory.Add($ramUsageInfoVal.Percent) } else { $ramHistory.Add($null) } if ($gpuUsageInfoVal) { $gpuUtilHistory.Add($gpuUsageInfoVal.GPUUtilization) $vramUtilHistory.Add($gpuUsageInfoVal.VRAMPercent) } else { $gpuUtilHistory.Add($null); $vramUtilHistory.Add($null) } foreach($arr in @($cpuHistory, $ramHistory, $gpuUtilHistory, $vramUtilHistory)) { while ($arr.Count -gt $maxHistoryLength) { $arr.RemoveAt(0) } } $displayLines = [System.Collections.Generic.List[string]]::new() $graphCanvasLines = RenderCharacterLineGraph -RenderCpuHistory $cpuHistory ` -RenderRamHistory $ramHistory -RenderGpuUtilHistory $gpuUtilHistory -RenderVramUtilHistory $vramUtilHistory ` -RenderGraphHeight $GraphHeightParam -RenderGraphWidth $GraphWidthParam ` -RenderCpuColor $CpuLineColor -RenderRamColor $RamLineColor -RenderGpuUtilColor $GpuUtilLineColor -RenderVramUtilColor $VramUtilLineColor ` -RenderAxisColor $AxisColor -RenderGridColor $GridColor -RenderResetColor $RESET foreach ($graphLine in $graphCanvasLines) { $displayLines.Add($graphLine) } $displayLines.Add("") $displayLines.Add("${BOLD}Current Usage:${RESET}") if ($null -ne $cpuUsageVal) { $displayLines.Add((Format-UsageBar -Label "CPU" -Percentage $cpuUsageVal -PassedBarWidth $summaryBarWidth)) } else { $displayLines.Add("CPU: ${ErrorColor}Error fetching data${RESET}") } if ($null -ne $ramUsageInfoVal) { $displayLines.Add((Format-UsageBar -Label "RAM" -Percentage $ramUsageInfoVal.Percent -AdditionalInfo "$($ramUsageInfoVal.UsedMB)MB/$($ramUsageInfoVal.TotalMB)MB" -PassedBarWidth $summaryBarWidth)) } else { $displayLines.Add("RAM: ${ErrorColor}Error fetching data${RESET}") } if ($nvidiaSmiPathFound) { if ($null -ne $gpuUsageInfoVal) { $displayLines.Add((Format-UsageBar -Label "GPU Util" -Percentage $gpuUsageInfoVal.GPUUtilization -PassedBarWidth $summaryBarWidth)) $displayLines.Add((Format-UsageBar -Label "VRAM" -Percentage $gpuUsageInfoVal.VRAMPercent -AdditionalInfo "$($gpuUsageInfoVal.VRAMUsedMB)MB/$($gpuUsageInfoVal.VRAMTotalMB)MB" -PassedBarWidth $summaryBarWidth)) } else { $displayLines.Add("GPU Util: ${ErrorColor}NVIDIA SMI Error${RESET}") $displayLines.Add("VRAM: ${ErrorColor}NVIDIA SMI Error${RESET}") } } else { $displayLines.Add("GPU Util: ${GRAY}nvidia-smi N/A${RESET}") $displayLines.Add("VRAM: ${GRAY}nvidia-smi N/A${RESET}") } $displayLines.Add(("-" * ($GraphWidthParam + 15))) $displayLines.Add("${GRAY}Updated: $(Get-Date -Format 'HH:mm:ss')${RESET} | Refresh: ${RefreshRateSeconds}s") $currentY = $startY foreach ($line in $displayLines) { Set-CursorPosition 0 $currentY Write-Host (" " * $Host.UI.RawUI.BufferSize.Width) -NoNewline Set-CursorPosition 0 $currentY Write-Host $line $currentY++ } } Start-Sleep -Milliseconds 50 } } finally { [Console]::CursorVisible = $true try { $newBufferSize = $psHost.UI.RawUI.BufferSize $newBufferSize.Width = $originalBufferWidth $newBufferSize.Height = $originalBufferHeight $psHost.UI.RawUI.BufferSize = $newBufferSize } catch {} Clear-Host } } function Get-ASCIIArt { param ( [string]$CustomArtPath, [switch]$UseDefaultArt ) $asciiSavePath = Join-Path $env:USERPROFILE ".neofetch_ascii" $changesMade = $false $changeDescription = "" $hasColors = $false if ($UseDefaultArt) { if (Test-Path $asciiSavePath) { Remove-Item -Path $asciiSavePath -Force $changesMade = $true $changeDescription = "Reset to default ASCII art" } $defaultArt = Get-DefaultAsciiArt return @{ Art = $defaultArt Changed = $changesMade ChangeDescription = $changeDescription HasColors = $hasColors } } if ($CustomArtPath -and (Test-Path $CustomArtPath)) { $art = Get-Content -Path $CustomArtPath -Raw -Encoding UTF8 $artLines = $art -split "`n" | ForEach-Object { $_.TrimEnd() } $art | Out-File -FilePath $asciiSavePath -Force -Encoding UTF8 $changesMade = $true $changeDescription = "ASCII art changed to use file: $CustomArtPath" $hasColors = $art -match "\$ESC\[" return @{ Art = $artLines Changed = $changesMade ChangeDescription = $changeDescription HasColors = $hasColors } } if (Test-Path $asciiSavePath) { $art = Get-Content -Path $asciiSavePath -Raw -Encoding UTF8 $artLines = $art -split "`n" | ForEach-Object { $_.TrimEnd() } $hasColors = $art -match "\$ESC\[" return @{ Art = $artLines Changed = $changesMade ChangeDescription = $changeDescription HasColors = $hasColors } } $defaultArt = Get-DefaultAsciiArt return @{ Art = $defaultArt Changed = $changesMade ChangeDescription = $changeDescription HasColors = $hasColors } } function Get-ProcessedASCIIArt { param ( [string]$CustomArtPath, [switch]$UseDefaultArt ) $asciiResult = Get-ASCIIArt -CustomArtPath $CustomArtPath -UseDefaultArt:$UseDefaultArt $art = $asciiResult.Art $hasColors = $asciiResult.HasColors $processedArt = @() $maxVisibleLength = 0 foreach ($line in $art) { $visibleLine = if ($hasColors) { $line -replace "\$ESC\[[0-9;]*m", "" } else { $line } $visibleLength = $visibleLine.Length if ($visibleLength -gt $maxVisibleLength) { $maxVisibleLength = $visibleLength } } foreach ($line in $art) { $visibleLine = if ($hasColors) { $line -replace "\$ESC\[[0-9;]*m", "" } else { $line } $visibleLength = $visibleLine.Length $paddingNeeded = $maxVisibleLength - $visibleLength $processedArt += [PSCustomObject]@{ OriginalLine = $line VisibleLength = $visibleLength PaddingNeeded = $paddingNeeded } } return @{ ProcessedArt = $processedArt MaxVisibleLength = $maxVisibleLength Changed = $asciiResult.Changed ChangeDescription = $asciiResult.ChangeDescription HasColors = $hasColors } } function Show-Usage { Write-Host "" Write-Host "Windows Neofetch Usage:" -ForegroundColor Cyan Write-Host " neofetch [options]" -ForegroundColor White Write-Host "" Write-Host "Options:" -ForegroundColor Cyan Write-Host " -init Run the configuration wizard to set up neofetch preferences." -ForegroundColor White Write-Host " -Force Force reconfiguration even if config files already exist (use with -init)." -ForegroundColor White Write-Host " -asciiart <path> Path to a text file containing ASCII art to use instead of the default Windows logo." -ForegroundColor White Write-Host " The ASCII art can contain ANSI color codes for colored output." -ForegroundColor White Write-Host " -defaultart Reset to use the default Windows ASCII art, removing any custom art." -ForegroundColor White Write-Host " -changes Display information about what configurations have been changed from default." -ForegroundColor White Write-Host " -maxThreads <n> Limit the maximum number of threads used (default: 4 or number of CPU cores, whichever is lower)." -ForegroundColor White Write-Host " -defaultthreads Reset to use the default number of threads (4)." -ForegroundColor White Write-Host " -profileName <name> Set the Windows Terminal profile name to use for font detection." -ForegroundColor White Write-Host " Common values are 'Windows PowerShell' or 'PowerShell'" -ForegroundColor White Write-Host " -defaultprofile Reset terminal profile to default (Windows PowerShell)." -ForegroundColor White Write-Host " -cacheExpiration <n> Set the cache expiration period in seconds (default: 1800 = 30 min)." -ForegroundColor White Write-Host " -defaultcache Reset cache expiration to default (1800 seconds = 30 minutes)." -ForegroundColor White Write-Host " -nocache Disable caching and force fresh data collection." -ForegroundColor White Write-Host " -minimal Display a minimal view with only essential system information." -ForegroundColor White Write-Host " -benchmark Run a system benchmark and display results." -ForegroundColor White Write-Host " -live Display live CPU, RAM, GPU, and VRAM usage graphs." -ForegroundColor White Write-Host " -reset Reset all configuration files and caches to defaults." -ForegroundColor White Write-Host " -help Display this help message." -ForegroundColor White Write-Host "" Write-Host "Examples:" -ForegroundColor Cyan Write-Host " neofetch" -ForegroundColor White Write-Host " neofetch -init" -ForegroundColor White Write-Host " neofetch -init -Force" -ForegroundColor White Write-Host " neofetch -asciiart `"Drive:\path\to\ascii\art.txt`"" -ForegroundColor White Write-Host " neofetch -defaultart" -ForegroundColor White Write-Host " neofetch profileName `"PowerShell`"" -ForegroundColor White Write-Host " neofetch -changes" -ForegroundColor White Write-Host " neofetch -maxThreads 8" -ForegroundColor White Write-Host " neofetch -benchmark" -ForegroundColor White Write-Host "" } # Main function that will be exported and called by the alias function neofetch { [CmdletBinding()] param( [Parameter(Mandatory=$false)] [string]$asciiart = $null, [Parameter(Mandatory=$false)] [switch]$help = $false, [Parameter(Mandatory=$false)] [switch]$changes = $false, [Parameter(Mandatory=$false)] [switch]$defaultart = $false, [Parameter(Mandatory=$false)] [int]$maxThreads = 0, [Parameter(Mandatory=$false)] [switch]$defaultthreads = $false, [Parameter(Mandatory=$false)] [switch]$nocache = $false, [Parameter(Mandatory=$false)] [int]$cacheExpiration = 0, [Parameter(Mandatory=$false)] [switch]$defaultcache = $false, [Parameter(Mandatory=$false)] [string]$profileName = $null, [Parameter(Mandatory=$false)] [switch]$defaultprofile = $false, [Parameter(Mandatory=$false)] [switch]$minimal = $false, [Parameter(Mandatory=$false)] [switch]$benchmark = $false, [Parameter(Mandatory=$false)] [switch]$reload = $false, [Parameter(Mandatory=$false)] [switch]$init = $false, [Parameter(Mandatory=$false)] [switch]$Force = $false, [Parameter(Mandatory=$false)] [switch]$live = $false ) $ESC = [char]27 $RESET = "$ESC[0m" $BOLD = "$ESC[1m" $CYAN = "$ESC[36m" $WHITE = "$ESC[37m" $configFiles = @( (Join-Path $env:USERPROFILE ".neofetch_ascii"), (Join-Path $env:USERPROFILE ".neofetch_cache_expiration"), (Join-Path $env:USERPROFILE ".neofetch_threads"), (Join-Path $env:USERPROFILE ".neofetch_profile_name") ) $isFirstRun = -not( (Test-Path $configFiles[0]) -or (Test-Path $configFiles[1]) -or (Test-Path $configFiles[2]) -or (Test-Path $configFiles[3]) ) if ($isFirstRun -and -not ($help -or $changes -or $defaultart -or $maxThreads -or $defaultthreads -or $nocache -or $cacheExpiration -or $defaultcache -or $profileName -or $defaultprofile -or $minimal -or $benchmark -or $reload -or $asciiart -or $live)) { Write-Host "${BOLD}${CYAN}First run detected!${RESET} Starting initial setup..." -ForegroundColor Cyan Start-Sleep -Seconds 1 $init = $true } if ($init) { $runAfterInit = Initialize-NeofetchConfig -Force:$Force if (-not $runAfterInit) { return } } if ($reload) { $reloadCount = Reset-NeofetchConfiguration Write-Host "Neofetch has been reset! Cleared $reloadCount configuration and cache files." -ForegroundColor Cyan Write-Host "Next run will use default settings and rebuild the cache." -ForegroundColor Cyan return } if ($help) { Show-Usage return } if ($benchmark) { $benchmarkResults = Invoke-SystemBenchmark Show-BenchmarkResults -Results $benchmarkResults return } if ($profileName) { $profileNamePath = Join-Path $env:USERPROFILE ".neofetch_profile_name" $profileName | Out-File -FilePath $profileNamePath -Force Write-Host "Profile name changed to: $profileName" -ForegroundColor Cyan return } if ($cacheExpiration -gt 0) { $cacheExpirationPath = Join-Path $env:USERPROFILE ".neofetch_cache_expiration" $cacheExpiration | Out-File -FilePath $cacheExpirationPath -Force Write-Host "Cache expiration is set to $cacheExpiration second(s)" -ForegroundColor Cyan return } if ($defaultcache) { $cacheChanged = Reset-CacheExpirationDefault if ($cacheChanged) { Write-Host "Cache expiration reset to default (1800 seconds = 30 minutes)" -ForegroundColor Cyan } else { Write-Host "Cache expiration was already set to default" -ForegroundColor Cyan } return } if ($defaultprofile) { $profileChanged = Reset-ProfileNameDefault if ($profileChanged) { Write-Host "Profile name reset to default (Windows PowerShell)" -ForegroundColor Cyan } else { Write-Host "Profile name was already set to default" -ForegroundColor Cyan } return } if ($defaultthreads) { $threadsChanged = Reset-ThreadsDefault if ($threadsChanged) { Write-Host "Threads reset to default (4)" -ForegroundColor Cyan } else { Write-Host "Threads were already set to default" -ForegroundColor Cyan } return } if ($live) { Show-LiveUsageGraphs return } # Handle the changes flag if ($changes) { $asciiResult = Get-ASCIIArt -CustomArtPath $asciiart -UseDefaultArt:$defaultart $changesMade = $asciiResult.Changed $changeDescription = $asciiResult.ChangeDescription if ($changesMade) { Write-Host "ASCII Changes: $changeDescription" -ForegroundColor Cyan } else { $asciiSavePath = Join-Path $env:USERPROFILE ".neofetch_ascii" if (Test-Path $asciiSavePath) { Write-Host "ASCII Changes: ASCII art has been changed from default" -ForegroundColor Cyan } else { Write-Host "ASCII Changes: None" -ForegroundColor Cyan } } $cachePath = Join-Path $env:TEMP "neofetch_cache.xml" if (Test-Path $cachePath) { $cacheFile = Get-Item $cachePath $cacheAge = (Get-Date) - $cacheFile.LastWriteTime $cacheMinutes = [Math]::Round($cacheAge.TotalMinutes, 1) Write-Host "Cache Status: Enabled" -ForegroundColor Cyan Write-Host "Cache Location: $cachePath" -ForegroundColor Cyan Write-Host "Cache Age: $cacheMinutes minutes old" -ForegroundColor Cyan $cacheExpirationPath = Join-Path $env:USERPROFILE ".neofetch_cache_expiration" [int]$defaultExpiration = 1800 [int]$configuredExpiration = $defaultExpiration if (Test-Path $cacheExpirationPath) { try { $savedExpiration = [int](Get-Content -Path $cacheExpirationPath -Raw) if ($savedExpiration -gt 0) { $configuredExpiration = $savedExpiration } } catch {} } $expirationMinutes = [Math]::Round($configuredExpiration / 60, 1) Write-Host "Cache Expiration: $configuredExpiration seconds ($expirationMinutes minutes)" -ForegroundColor Cyan if ($configuredExpiration -ne $defaultExpiration) { Write-Host " - Custom expiration setting (default is 1800 seconds = 30 minutes)" -ForegroundColor White } try { $cachedData = Import-Clixml -Path $cachePath Write-Host "Cached Parameters:" -ForegroundColor Cyan foreach ($key in $cachedData.Keys) { Write-Host " - $key" -ForegroundColor White } } catch { Write-Host "Error reading cache: $_" -ForegroundColor Red } } else { Write-Host "Cache Status: No cache file found" -ForegroundColor Cyan } $threadsSavePath = Join-Path $env:USERPROFILE ".neofetch_threads" [int]$defaultThreads = 4 [int]$configuredThreads = $defaultThreads if ($maxThreads -gt 0) { $configuredThreads = $maxThreads } elseif (Test-Path $threadsSavePath) { try { $savedThreads = [int](Get-Content -Path $threadsSavePath -Raw) if ($savedThreads -gt 0) { $configuredThreads = $savedThreads } } catch {} } $actualThreads = [System.Math]::Min($configuredThreads, [Environment]::ProcessorCount) Write-Host "Thread Configuration:" -ForegroundColor Cyan Write-Host " - Configured: $configuredThreads threads" -ForegroundColor White if ($configuredThreads -ne $defaultThreads) { Write-Host " - Custom thread setting (default is $defaultThreads)" -ForegroundColor White } Write-Host " - Actually using: $actualThreads threads (of $([Environment]::ProcessorCount) available)" -ForegroundColor White $profileNamePath = Join-Path $env:USERPROFILE ".neofetch_profile_name" $defaultProfileName = "Windows PowerShell" $configuredProfileName = $defaultProfileName if ($profileName) { $configuredProfileName = $profileName } elseif (Test-Path $profileNamePath) { try { $savedProfileName = Get-Content -Path $profileNamePath -Raw if ($savedProfileName -and $savedProfileName.Trim() -ne "") { $configuredProfileName = $savedProfileName.Trim() } } catch {} } Write-Host "Terminal Profile Configuration:" -ForegroundColor Cyan Write-Host " - Profile Name: $configuredProfileName" -ForegroundColor White if ($configuredProfileName -ne $defaultProfileName) { Write-Host " - Custom profile setting (default is $defaultProfileName)" -ForegroundColor White } return } # Main neofetch display function function Show-WindowsNeofetch { param ( [string]$AsciiArtPath, [switch]$UseDefaultArt, [switch]$UseMinimal, [switch]$NoCacheMode, [int]$MaxThreadsValue, [int]$CacheExpirationValue ) $SysInfo = Get-SystemInfoFast -NoCacheMode:$NoCacheMode -MaxThreadsOverride $MaxThreadsValue -CacheExpirationOverride $CacheExpirationValue $processedAsciiResult = Get-ProcessedASCIIArt -CustomArtPath $AsciiArtPath -UseDefaultArt:$UseDefaultArt $processedWindowsLogo = $processedAsciiResult.ProcessedArt $maxLogoLineLength = $processedAsciiResult.MaxVisibleLength $changesMade = $processedAsciiResult.Changed $changeDescription = $processedAsciiResult.ChangeDescription $hasColors = $processedAsciiResult.HasColors try { $consoleWidth = $Host.UI.RawUI.WindowSize.Width if ($consoleWidth -le 0) { $consoleWidth = 80 } } catch { $consoleWidth = 80 } $colorBlocks = Get-ColorBlocks $infoLines = @( "$BOLD${CYAN}OS:$RESET $($SysInfo.OS)", "$BOLD${CYAN}Host:$RESET $($SysInfo.Host)", "$BOLD${CYAN}Kernel:$RESET $($SysInfo.Kernel)", "$BOLD${CYAN}Uptime:$RESET $($SysInfo.Uptime)", "$BOLD${CYAN}Packages:$RESET $($SysInfo.Packages)", "$BOLD${CYAN}Shell:$RESET $($SysInfo.Shell)", "$BOLD${CYAN}Resolution:$RESET $($SysInfo.Resolution)", "$BOLD${CYAN}WM:$RESET $($SysInfo.WM)", "$BOLD${CYAN}Terminal:$RESET $($SysInfo.Terminal)", "$BOLD${CYAN}Terminal Font:$RESET $($SysInfo.TerminalFont)", "$BOLD${CYAN}CPU:$RESET $($SysInfo.CPU)", "$BOLD${CYAN}GPU:$RESET $($SysInfo.GPU)", "$BOLD${CYAN}GPU Memory:$RESET $($SysInfo.GPUMemory)", "$BOLD${CYAN}Memory:$RESET $($SysInfo.Memory)", "$BOLD${CYAN}Disk:$RESET $($SysInfo.DiskUsage)", "$BOLD${CYAN}Battery:$RESET $($SysInfo.Battery)", "", $colorBlocks[0], $colorBlocks[1] ) $leftPadding = 2 $spaceBetween = 4 Write-Host "" $header = "$BOLD$CYAN$($SysInfo.UserHost)$RESET" if ($UseMinimal) { $leftGap = " " * 2 Write-Host "$leftGap$header" Write-Host "$leftGap$CYAN----------------------$RESET" $minimalInfos = @( "$BOLD${CYAN}Kernel:$RESET $($SysInfo.Kernel)", "$BOLD${CYAN}Uptime:$RESET $($SysInfo.Uptime)", "$BOLD${CYAN}Packages:$RESET $($SysInfo.Packages)", "$BOLD${CYAN}GPU Memory:$RESET $($SysInfo.GPUMemory)", "$BOLD${CYAN}Memory:$RESET $($SysInfo.Memory)", "$BOLD${CYAN}Battery:$RESET $($SysInfo.Battery)" ) foreach ($info in $minimalInfos) { Write-Host "$leftGap$info" } Write-Host "" return @{ changesMade = $changesMade changeDescription = $changeDescription } } if ($processedWindowsLogo.Count -gt 0) { if ($hasColors) { $logoLine = $processedWindowsLogo[0].OriginalLine $paddingNeeded = $processedWindowsLogo[0].PaddingNeeded Write-Host (" " * $leftPadding)$logoLine((" " * ($spaceBetween + $paddingNeeded)) + $header) } else { $logoLine = $processedWindowsLogo[0].OriginalLine.PadRight($maxLogoLineLength) Write-Host (" " * $leftPadding)$logoLine((" " * $spaceBetween) + $header) } $divider = "$CYAN----------------------$RESET" if ($hasColors -and $processedWindowsLogo.Count -gt 1) { $logoLine = $processedWindowsLogo[1].OriginalLine $paddingNeeded = $processedWindowsLogo[1].PaddingNeeded Write-Host (" " * $leftPadding)$logoLine((" " * ($spaceBetween + $paddingNeeded)) + $divider) } elseif ($processedWindowsLogo.Count -gt 1) { $logoLine = $processedWindowsLogo[1].OriginalLine.PadRight($maxLogoLineLength) Write-Host (" " * $leftPadding)$logoLine((" " * $spaceBetween) + $divider) } for ($i = 0; $i -lt [Math]::Max($processedWindowsLogo.Count - 2, $infoLines.Count); $i++) { $logoIndex = $i + 2 $logoItem = if ($logoIndex -lt $processedWindowsLogo.Count) { $processedWindowsLogo[$logoIndex] } else { $null } $logoLine = if ($logoItem) { $logoItem.OriginalLine } else { "" } $paddingNeeded = if ($logoItem) { $logoItem.PaddingNeeded } else { $maxLogoLineLength } $infoLine = if ($i -lt $infoLines.Count) { $infoLines[$i] } else { "" } if ($hasColors -and $logoLine) { Write-Host (" " * $leftPadding)$logoLine((" " * ($spaceBetween + $paddingNeeded)) + $infoLine) } else { $paddedLogoLine = $logoLine.PadRight($maxLogoLineLength) Write-Host (" " * $leftPadding)$paddedLogoLine((" " * $spaceBetween) + $infoLine) } } } else { Write-Host $header Write-Host "$CYAN----------------------$RESET" foreach ($infoLine in $infoLines) { Write-Host $infoLine } } Write-Host "" return @{ changesMade = $changesMade changeDescription = $changeDescription } } $startTime = Get-Date $result = Show-WindowsNeofetch -AsciiArtPath $asciiart -UseDefaultArt:$defaultart -UseMinimal:$minimal -NoCacheMode:$nocache -MaxThreadsValue $maxThreads -CacheExpirationValue $cacheExpiration $endTime = Get-Date $executionTime = ($endTime - $startTime).TotalSeconds Write-Host "Script execution time: $executionTime seconds" -ForegroundColor Gray } Export-ModuleMember -Function neofetch |