diff --git a/PowerShell/Utilities/Logging/Get-BatteryStatus.md b/PowerShell/Utilities/Logging/Get-BatteryStatus.md index f7e233c..c485938 100644 --- a/PowerShell/Utilities/Logging/Get-BatteryStatus.md +++ b/PowerShell/Utilities/Logging/Get-BatteryStatus.md @@ -1,141 +1,159 @@ -# Get-BatteryStatus.ps1 - -## Overview -**Get-BatteryStatus.ps1** is a PowerShell script for Windows 10/11 that prints a **structured battery analysis report directly in the console**. - -It provides a clear, technician-readable summary of battery health, real-world consumption and runtime estimates — without having to open an HTML file. -All data is sourced from a single **`powercfg /batteryreport`** HTML file generated temporarily at runtime. - -## What it shows - -### Machine identity -- Computer name (from `$env`) -- Computer model (from `SYSTEM PRODUCT NAME` in the powercfg report) -- CPU name (via `Win32_Processor`) - -### Battery information -- **Design capacity** *(factory/original maximum)* in **Wh** -- **Current capacity** *(current maximum)* in **Wh** -- **Battery health (%)** computed from Design vs Current capacity - -### Usage statistics -- Number of **analyzed discharge sessions** (parsed from the powercfg report) -- **Average power consumption** in **Watts** across all sessions - -### Battery life estimation -- **Estimated runtime** with the current battery -- **Estimated runtime** with a new (design capacity) battery - -### Conclusion -- **Wear level (%)** -- Potential **runtime gain** from replacing the battery (in minutes) - -## Data sources -All data comes from a single source: - -| Data | Source | -|---|---| -| Computer name | `$env:COMPUTERNAME` | -| Computer model | `powercfg /batteryreport` HTML (`SYSTEM PRODUCT NAME`) | -| Design / Current capacity | `powercfg /batteryreport` HTML (`Installed batteries` table) | -| Discharge sessions (duration + energy) | `powercfg /batteryreport` HTML (`Battery usage` table) | -| CPU name | `Win32_Processor` (CIM, single call — not in powercfg report) | -| Battery presence | `System.Windows.Forms.SystemInformation.PowerStatus` | - -The script generates a **temporary** HTML file in `%TEMP%`, parses the needed values, then **deletes it immediately**. - -## Parameters -This script does not take any custom parameters. -You can use standard PowerShell common parameters such as: -- `-Verbose` to see the path of the generated report and any internal diagnostic messages. - -## Usage -```powershell -.\Get-BatteryStatus.ps1 -``` - -Verbose mode (recommended for troubleshooting): -```powershell -.\Get-BatteryStatus.ps1 -Verbose -``` - -## Output example -```text -==== BATTERY ANALYSIS REPORT ==== - -Computer : HP EliteBook 840 G8 -CPU : Intel Core i5-1135G7 - -Battery information -------------------- -Design Capacity : 53 Wh -Current Capacity : 37 Wh -Battery Health : 70 % - -Usage statistics ----------------- -Analyzed sessions : 34 -Average consumption : 12 W - -Battery life estimation ------------------------ -Estimated runtime (current battery) : 2 h 33 min -Estimated runtime (new battery) : 3 h 43 min - -Conclusion ----------- -Battery wear detected : 24 % - Replacing the battery would increase runtime by ~70 minutes. -``` - -## Help & documentation -```powershell -Get-Help .\Get-BatteryStatus.ps1 -``` - -## Troubleshooting - -### "No battery detected" -You are likely running the script on a desktop, a VM, or a device without a battery. - -### "Failed to generate battery report" -`powercfg` requires elevated privileges on some systems. -Try running PowerShell **as Administrator**. - -### Capacity shows "N/A" -The `powercfg /batteryreport` HTML structure may differ on non-English Windows installations. -Run with `-Verbose` to confirm the report was generated, then inspect the HTML file manually before it is deleted (add a breakpoint or comment out the `finally` block temporarily). - -### No discharge sessions found -This can happen if the machine has only been used on AC power recently, or if the battery report does not contain any discharge entries for the last 7 days. - -## Requirements -- Windows 10 / 11 -- PowerShell 5.1 or PowerShell 7+ -- `powercfg.exe` available (built-in on all Windows versions) -- Administrator rights may be required for `powercfg /batteryreport` - -## License -```text -MIT License - -Copyright (c) 2025 Miiraak - -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. +# Get-BatteryStatus.ps1 + +## Overview +**Get-BatteryStatus.ps1** is a PowerShell script for Windows 11 that prints a **structured battery analysis report directly in the console**. + +It provides a clear, technician-readable summary of battery health, real-world consumption and runtime estimates — without having to open an HTML file. +Data are sourced from a single **`powercfg /batteryreport`** (fallback to WMI for some cases). HTML file generated temporarily at runtime. +It check for warranty status and end date too. + +## What it shows + +### Machine identity +- Computer name (from `$env`) +- Computer model (from `SYSTEM PRODUCT NAME` in the powercfg report) +- CPU name (via `Win32_Processor`) +- Warranty state (via Get-Warranty module) + +### Battery information +- **Design capacity** *(factory/original maximum)* in **Wh** +- **Current capacity** *(current maximum)* in **Wh** +- **Battery health (%)** computed from Design vs Current capacity +- **Cycle count** number of discharges/full recharges + +### Usage statistics +- Number of **analyzed discharge sessions** (parsed from the powercfg report) +- **Average power consumption** in **Watts** across all sessions + +### Battery life estimation +- **Estimated runtime** with the current battery +- **Estimated runtime** with a new (design capacity) battery + +### Conclusion +- **Wear level (%)** +- Potential **runtime gain** from replacing the battery (in minutes) + +## Data sources +| | | +|---|---| +| Computer name | `$env:COMPUTERNAME` | +| Computer model | `powercfg /batteryreport` HTML (`SYSTEM PRODUCT NAME`) | +| Warranty | Powershell module `Get-Warranty` ([here](https://github.com/Miiraak/Get-Warranty)) +| Design / Current capacity | `powercfg /batteryreport` HTML (`Installed batteries` table) | +| Discharge sessions (duration + energy) | `powercfg /batteryreport` HTML (`Battery usage` table) | +| CPU name | `Win32_Processor` (CIM, single call — not in powercfg report) | +| Battery presence | `System.Windows.Forms.SystemInformation.PowerStatus` | +| Cycle count | `powercfg /batteryreport` HTML or `root\wmi BatteryCycleCount` fallback | + +> The script generates a **temporary** HTML file in `%TEMP%`, parses the needed values, then **deletes it immediately**.
+> The module `Get-Warranty` is installed from powershell gallery. After using it, the script **uninstall it immediately** + +## Parameters +This script does not take any custom parameters. +You can use standard PowerShell common parameters such as: +- `-Verbose` to see the path of the generated report and any internal diagnostic messages. + +## Usage +```powershell +.\Get-BatteryStatus.ps1 +``` + +Verbose mode (recommended for troubleshooting): +```powershell +.\Get-BatteryStatus.ps1 -Verbose +``` + +Json output +```powershell +.\Get-BatteryStatus.ps1 -json +``` + +No warranty check +```powershell +.\Get-BatteryStatus.ps1 -NoWarranty +``` + +## Output example +```text +==== BATTERY ANALYSIS REPORT ==== + +Report generated : 2026-03-24 20:03:03 Machine name : RandomHP +Computer : HP EliteBook 840 G8 +CPU : Intel Core i5-1135G7 +Warranty : Active (2028-03-20) + +Battery information +------------------- +Design Capacity : 53 Wh +Current Capacity : 37 Wh +Battery Health : 70 % +Cycle count : 127 + +Usage statistics +---------------- +Analyzed sessions : 34 +Average consumption : 12 W + +Battery life estimation +----------------------- +Estimated runtime (current battery) : 2 h 33 min +Estimated runtime (new battery) : 3 h 43 min + +Conclusion +---------- +Battery wear detected : 24 % + Replacing the battery would increase runtime by ~70 minutes. +``` + +## Help & documentation +```powershell +Get-Help .\Get-BatteryStatus.ps1 +``` + +## Troubleshooting + +### "No battery detected" +You are likely running the script on a desktop, a VM, or a device without a battery. + +### "Failed to generate battery report" +`powercfg` requires elevated privileges on some systems. +Try running PowerShell **as Administrator**. + +### Capacity shows "N/A" +The `powercfg /batteryreport` HTML structure may differ on non-English Windows installations. +Run with `-Verbose` to confirm the report was generated, then inspect the HTML file manually before it is deleted (add a breakpoint or comment out the `finally` block temporarily). + +### No discharge sessions found +This can happen if the machine has only been used on AC power recently, or if the battery report does not contain any discharge entries for the last 7 days. + +## Requirements +- Windows 11 +- PowerShell 5.1 or PowerShell 7+ +- `powercfg.exe` available (built-in on all Windows versions) +- Administrator rights may be required for `powercfg /batteryreport` +- Internet is required for Warranty check. + +## License +```text +MIT License + +Copyright (c) 2025 Miiraak + +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. ``` \ No newline at end of file diff --git a/PowerShell/Utilities/Logging/Get-BatteryStatus.ps1 b/PowerShell/Utilities/Logging/Get-BatteryStatus.ps1 index 1dd0bea..1c3f2b5 100644 --- a/PowerShell/Utilities/Logging/Get-BatteryStatus.ps1 +++ b/PowerShell/Utilities/Logging/Get-BatteryStatus.ps1 @@ -1,312 +1,372 @@ -######################################################################################## -# | -# ███▄ ▄███▓ ██▓ ██▓ ██▀███ ▄▄▄ ▄▄▄ ██ ▄█▀ | -# ▓██▒▀█▀ ██▒▓██▒▓██▒▓██ ▒ ██▒▒████▄ ▒████▄ ██▄█▒ | -# ▓██ ▓██░▒██▒▒██▒▓██ ░▄█ ▒▒██ ▀█▄ ▒██ ▀█▄ ▓███▄░ | -# ▒██ ▒██ ░██░░██░▒██▀▀█▄ ░██▄▄▄▄██░██▄▄▄▄██ ▓██ █▄ | -# ▒██▒ ░██▒░██░░██░░██▓ ▒██▒ ▓█ ▓██▒▓█ ▓██▒▒██▒ █▄ | -# ░ ▒░ ░ ░░▓ ░▓ ░ ▒▓ ░▒▓░ ▒▒ ▓▒█░▒▒ ▓▒█░▒ ▒▒ ▓▒ | -# ░ ░ ░ ▒ ░ ▒ ░ ░▒ ░ ▒░ ▒ ▒▒ ░ ▒ ▒▒ ░░ ░▒ ▒░ | -# ░ ░ ▒ ░ ▒ ░ ░░ ░ ░ ▒ ░ ▒ ░ ░░ ░ | -# ░ ░ ░ ░ ░ ░ ░ ░░ ░ | -# | -# Title : Get-BatteryStatus.ps1 | -# Link : https://github.com/Miiraak/Scripts/tree/master/PowerShell/Utilities/Logging/ | -# Version : 5.4 | -# Category : utilities/logging | -# Target : Windows 10/11 | -# Description : Battery analysis report - health, cycles, consumption & runtime | -######################################################################################## - -<# -.SYNOPSIS - Displays a structured battery analysis report in the console. - -.DESCRIPTION - - Machine identity (computer name, CPU) - - Battery health: Design vs Current capacity, wear % - - Battery cycles: Cycle count (when available) - - Usage statistics: session count + average consumption (W) - - Runtime estimation: current battery vs new battery - - Most data sourced from a single powercfg /batteryreport HTML file. - CPU name falls back to Win32_Processor (powercfg report doesn't include CPU). - -.EXAMPLE - .\Get-BatteryStatus.ps1 - -.EXAMPLE - .\Get-BatteryStatus.ps1 -Verbose -#> - -[CmdletBinding()] -param() - -#region -- Helpers ---------------------------------------------------------------- - -function Format-Runtime ([double]$Hours) { - if ($Hours -le 0) { return "N/A" } - $h = [int][math]::Floor($Hours) - $m = [int][math]::Round(($Hours - $h) * 60) - if ($m -eq 60) { $h++; $m = 0 } - "{0} h {1:D2} min" -f $h, $m -} - -function Write-Header ([string]$Title) { - Write-Host "" - Write-Host $Title -ForegroundColor White - Write-Host ("-" * $Title.Length) -ForegroundColor DarkGray -} - -function Write-KV ([string]$Key, $Value, [int]$Width = 30) { - if ($null -eq $Value -or "$Value" -eq "") { return } - Write-Host ("{0,-$Width}: {1}" -f $Key, $Value) -} - -# Parses "90 005 mWh" or "67 864 mWh" -> int in mWh (removes all non-digits) -function Parse-MWh ([string]$s) { - $clean = $s -replace '[^\d]', '' - if ($clean) { [int]$clean } else { $null } -} - -# Safe int parser for "-" or empty -function Parse-Int ([string]$s) { - if (-not $s) { return $null } - $clean = ($s -replace '[^\d]', '') - if (-not $clean) { return $null } - try { [int]$clean } catch { $null } -} - -#endregion - -#region -- powercfg report -------------------------------------------------------- - -function Get-BatteryReportHtml { - $tmp = Join-Path $env:TEMP ("batteryreport_{0}.html" -f [guid]::NewGuid().ToString("N")) - try { - Write-Verbose "Generating: $tmp" - $p = Start-Process powercfg.exe ` - -ArgumentList "/batteryreport /output `"$tmp`"" ` - -Wait -PassThru -NoNewWindow -ErrorAction Stop - if ($p.ExitCode -ne 0 -or -not (Test-Path $tmp)) { - Write-Verbose "powercfg exited $($p.ExitCode) or file missing." - return $null - } - Get-Content $tmp -Raw -ErrorAction Stop - } - catch { - Write-Verbose "powercfg failed: $($_.Exception.Message)" - $null - } - finally { - Remove-Item $tmp -Force -ErrorAction SilentlyContinue - } -} - -#endregion - -#region -- Parsers ---------------------------------------------------------------- - -function Get-MachineInfoFromHtml ([string]$Html) { - # SYSTEM PRODUCT NAME row -> machine model - $model = if ($Html -match '(?is)SYSTEM\s+PRODUCT\s+NAME\s*\s*]*>\s*([^<]+)') { - $matches[1].Trim() - } - - # CPU is not in the powercfg report -> single targeted CIM call - $cpu = try { - (Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop | - Select-Object -First 1).Name.Trim() - } catch { "N/A" } - - [pscustomobject]@{ - Computer = if ($model) { $model } else { $env:COMPUTERNAME } - CPU = $cpu - } -} - -function Get-CapacitiesFromHtml ([string]$Html) { - if (-not $Html) { return $null } - - # Installed batteries table - $design = if ($Html -match '(?is)DESIGN\s+CAPACITY\s*]*>\s*([\d\s]+)\s*mWh') { - Parse-MWh $matches[1] - } - $full = if ($Html -match '(?is)FULL\s+CHARGE\s+CAPACITY\s*]*>\s*([\d\s]+)\s*mWh') { - Parse-MWh $matches[1] - } - - # Cycle count is often "-" in HTML depending on device/firmware - $cycle = $null - if ($Html -match '(?is)CYCLE\s+COUNT\s*]*>\s*([^<]+)') { - $cycle = Parse-Int $matches[1] - } - - if (-not $design -and -not $full -and $null -eq $cycle) { return $null } - [pscustomobject]@{ - DesignMWh = $design - FullMWh = $full - CycleCount = $cycle - CycleSource = if ($null -ne $cycle) { "powercfg" } else { $null } - } -} - -function Get-CycleCountFallback { - # Some machines expose cycle count via WMI in root\wmi BatteryCycleCount - try { - $cc = Get-CimInstance -Namespace root\wmi -ClassName BatteryCycleCount -ErrorAction Stop | - Select-Object -First 1 - if ($null -ne $cc.CycleCount) { return [int]$cc.CycleCount } - $null - } - catch { - Write-Verbose "Cycle count not available via root\\wmi BatteryCycleCount." - $null - } -} - -function Get-SessionStats ([string]$Html) { - if (-not $Html) { return $null } - - $watts = foreach ($row in [regex]::Matches($Html, '(?is)]*class="[^"]*dc[^"]*"[^>]*>(.*?)')) { - $inner = $row.Groups[1].Value - - $durMatch = [regex]::Match($inner, '(?is)]*class="[^"]*hms[^"]*"[^>]*>\s*(\d+:\d{2}:\d{2})\s*') - if (-not $durMatch.Success) { continue } - - $mwhMatch = [regex]::Match($inner, '(?is)]*class="[^"]*mw[^"]*"[^>]*>\s*([\d\s]+)\s*mWh') - if (-not $mwhMatch.Success) { continue } - - $p = $durMatch.Groups[1].Value -split ':' - $sec = [int]$p[0] * 3600 + [int]$p[1] * 60 + [int]$p[2] - $mWh = Parse-MWh $mwhMatch.Groups[1].Value - - if ($sec -lt 120 -or -not $mWh -or $mWh -le 0) { continue } - - $w = ($mWh / 1000.0) / ($sec / 3600.0) - if ($w -gt 0 -and $w -le 200) { $w } - } - - if (-not $watts) { return $null } - [pscustomobject]@{ - Count = @($watts).Count - AvgWatt = [math]::Round(($watts | Measure-Object -Average).Average, 1) - } -} - -#endregion - -#region -- Battery presence ------------------------------------------------------- - -function Test-BatteryPresent { - Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue - $ps = [System.Windows.Forms.SystemInformation]::PowerStatus - $ps.BatteryChargeStatus -ne [System.Windows.Forms.BatteryChargeStatus]::NoBattery -} - -#endregion - -#region -- Main ------------------------------------------------------------------- - -if (-not (Test-BatteryPresent)) { - Write-Host "No battery detected." -ForegroundColor Red - exit 2 -} - -$html = Get-BatteryReportHtml - -if (-not $html) { - Write-Host "Failed to generate battery report. Try running as Administrator." -ForegroundColor Red - exit 1 -} - -$machineName = $env:COMPUTERNAME -$machine = Get-MachineInfoFromHtml $html -$batt = Get-CapacitiesFromHtml $html -$sessions = Get-SessionStats $html - -$designMWh = if ($batt) { $batt.DesignMWh } else { $null } -$fullMWh = if ($batt) { $batt.FullMWh } else { $null } - -# Cycle count: -# - Prefer powercfg if present and numeric -# - Fallback to root\wmi BatteryCycleCount if available -$cycleCount = if ($batt -and $null -ne $batt.CycleCount) { $batt.CycleCount } else { Get-CycleCountFallback } - -$wearPct = $null -$healthPct = $null -if ($designMWh -and $fullMWh -and $designMWh -gt 0) { - $wearPct = [math]::Round((1 - ($fullMWh / $designMWh)) * 100, 2) - if ($wearPct -lt 0) { $wearPct = 0 } - $healthPct = [math]::Round(100 - $wearPct, 2) -} - -$rtCurrent = $null -$rtNew = $null -$gainMin = $null -if ($sessions -and $sessions.AvgWatt -gt 0) { - # Runtime hours = (energy in Wh) / W ; with mWh: (mWh/1000)/W - if ($fullMWh) { $rtCurrent = ($fullMWh / 1000.0) / $sessions.AvgWatt } - if ($designMWh) { $rtNew = ($designMWh / 1000.0) / $sessions.AvgWatt } - if ($rtCurrent -and $rtNew) { - $gainMin = [int][math]::Round(($rtNew - $rtCurrent) * 60) - } -} - -#endregion - -#region -- Output ----------------------------------------------------------------- - -Write-Host "" -Write-Host "==== BATTERY ANALYSIS REPORT ====" -ForegroundColor Cyan -Write-Host "" -Write-KV "Report generated" (Get-Date -Format "yyyy-MM-dd HH:mm:ss") -Write-KV "Machine name" $machineName -Write-KV "Computer" $machine.Computer -Write-KV "CPU" $machine.CPU - -Write-Header "Battery information" -Write-KV "Design Capacity" $(if ($designMWh) { "$designMWh mWh" } else { "N/A" }) -Write-KV "Current Capacity" $(if ($fullMWh) { "$fullMWh mWh" } else { "N/A" }) -Write-KV "Battery Health" $(if ($null -ne $healthPct) { "$healthPct %" } else { "N/A" }) -Write-KV "Cycle count" $(if ($null -ne $cycleCount){ $cycleCount } else { "N/A" }) - -Write-Header "Usage statistics" -if ($sessions) { - Write-KV "Analyzed sessions" $sessions.Count - Write-KV "Average consumption" "$($sessions.AvgWatt) W" -} else { - Write-Host " No discharge sessions found." -ForegroundColor DarkYellow -} - -Write-Header "Battery life estimation" -if ($rtCurrent -or $rtNew) { - Write-KV "Estimated runtime (current battery)" (Format-Runtime $rtCurrent) 38 - Write-KV "Estimated runtime (new battery)" (Format-Runtime $rtNew) 38 -} else { - Write-Host " Insufficient data for runtime estimation." -ForegroundColor DarkYellow -} - -Write-Header "Conclusion" -if ($null -ne $wearPct) { - if ($wearPct -lt 5) { - Write-Host " Battery is in good condition. No replacement needed." -ForegroundColor Green - } else { - Write-KV "Battery wear detected" "$wearPct %" - if ($null -ne $gainMin -and $gainMin -gt 0) { - Write-Host " Replacing the battery would increase runtime by ~$gainMin minutes." - } elseif ($null -ne $gainMin) { - Write-Host " Battery wear present but runtime gain would be negligible." -ForegroundColor DarkYellow - } else { - Write-Host " Could not estimate runtime gain (missing data)." -ForegroundColor DarkYellow - } - } -} else { - Write-Host " Unable to determine battery wear (capacity data unavailable)." -ForegroundColor DarkYellow -} - -Write-Host "" -Write-Host "Tip: run with -Verbose for detailed query information." -ForegroundColor DarkGray -Pause -exit 0 - -#endregion +##################################################################################################### +# | +# ███▄ ▄███▓ ██▓ ██▓ ██▀███ ▄▄▄ ▄▄▄ ██ ▄█▀ | +# ▓██▒▀█▀ ██▒▓██▒▓██▒▓██ ▒ ██▒▒████▄ ▒████▄ ██▄█▒ | +# ▓██ ▓██░▒██▒▒██▒▓██ ░▄█ ▒▒██ ▀█▄ ▒██ ▀█▄ ▓███▄░ | +# ▒██ ▒██ ░██░░██░▒██▀▀█▄ ░██▄▄▄▄██░██▄▄▄▄██ ▓██ █▄ | +# ▒██▒ ░██▒░██░░██░░██▓ ▒██▒ ▓█ ▓██▒▓█ ▓██▒▒██▒ █▄ | +# ░ ▒░ ░ ░░▓ ░▓ ░ ▒▓ ░▒▓░ ▒▒ ▓▒█░▒▒ ▓▒█░▒ ▒▒ ▓▒ | +# ░ ░ ░ ▒ ░ ▒ ░ ░▒ ░ ▒░ ▒ ▒▒ ░ ▒ ▒▒ ░░ ░▒ ▒░ | +# ░ ░ ▒ ░ ▒ ░ ░░ ░ ░ ▒ ░ ▒ ░ ░░ ░ | +# ░ ░ ░ ░ ░ ░ ░ ░░ ░ | +# | +# Title : Get-BatteryStatus.ps1 | +# Link : https://github.com/Miiraak/Scripts/tree/master/PowerShell/Utilities/Logging/ | +# Version : 6.0 | +# Category : utilities/logging | +# Target : Windows 10/11 | +# Description : Battery analysis report - health, cycles, consumption, runtime & warranty | +##################################################################################################### + +<# +.SYNOPSIS + Displays a structured battery analysis report in the console. + +.DESCRIPTION + - Machine identity (computer name, CPU) + - Battery health: Design vs Current capacity, wear % + - Battery cycles: Cycle count (when available) + - Usage statistics: session count + average consumption (W) + - Runtime estimation: current battery vs new battery + - Warranty: Use serial number to check warranty status on manufacturer's website + - Most data sourced from a single powercfg /batteryreport HTML file. + CPU name falls back to Win32_Processor (powercfg report doesn't include CPU). + +.PARAMETER Json + Output the report as JSON instead of formatted text. + +.PARAMETER NoWarranty + Skip the warranty status check it requires Get-Warranty module and may not be relevant for all users. + It requires internet connection and take more time. + +.PARAMETER Verbose + Show detailed information about the data retrieval and parsing process. + +.EXAMPLE + .\Get-BatteryStatus.ps1 + +.EXAMPLE + .\Get-BatteryStatus.ps1 -Json + +.EXAMPLE + .\Get-BatteryStatus.ps1 -NoWarranty +#> + +[CmdletBinding()] +param( + [switch]$Json, + [switch]$NoWarranty +) + +#region -- Helpers ---------------------------------------------------------------- + +function Format-Runtime ([double]$Hours) { + if ($Hours -le 0) { return "N/A" } + $h = [int][math]::Floor($Hours) + $m = [int][math]::Round(($Hours - $h) * 60) + if ($m -eq 60) { $h++; $m = 0 } + "{0} h {1:D2} min" -f $h, $m +} + +function Write-Header ([string]$Title) { + Write-Host "" + Write-Host $Title -ForegroundColor White + Write-Host ("-" * $Title.Length) -ForegroundColor DarkGray +} + +function Write-KV ([string]$Key, $Value, [int]$Width = 30) { + if ($null -eq $Value -or "$Value" -eq "") { return } + Write-Host ("{0,-$Width}: {1}" -f $Key, $Value) +} + +# Parses "90 005 mWh" or "67 864 mWh" -> int in mWh (removes all non-digits) +function Convert-MWh ([string]$s) { + $clean = $s -replace '[^\d]', '' + if ($clean) { [int]$clean } else { $null } +} + +# Safe int parser for "-" or empty +function Convert-Int ([string]$s) { + if (-not $s) { return $null } + $clean = ($s -replace '[^\d]', '') + if (-not $clean) { return $null } + try { [int]$clean } catch { $null } +} + +#endregion + +#region -- Warranty check via serial number --------------------------------------- + +function Get-WarrantyStatus { + Install-Module -Name Get-Warranty -Force -ErrorAction SilentlyContinue + + $w = try { Get-Warranty -json | ConvertFrom-Json } catch { $null } + if ($w) { + $status = if ($w.warranties.status) { "Active" } else { "Expired" } + Write-KV "Warranty" "$status ($($w.warranties.end))" + } + else { + Write-KV "Warranty" "Unknown" + } + + Uninstall-Module -Name Get-Warranty -AllVersions -Force -ErrorAction SilentlyContinue +} + +#endregion + +#region -- powercfg report -------------------------------------------------------- + +function Get-BatteryReportHtml { + $tmp = Join-Path $env:TEMP ("batteryreport_{0}.html" -f [guid]::NewGuid().ToString("N")) + try { + Write-Verbose "Generating: $tmp" + $p = Start-Process powercfg.exe ` + -ArgumentList "/batteryreport /output `"$tmp`"" ` + -Wait -PassThru -NoNewWindow -ErrorAction Stop + if ($p.ExitCode -ne 0 -or -not (Test-Path $tmp)) { + Write-Verbose "powercfg exited $($p.ExitCode) or file missing." + return $null + } + Get-Content $tmp -Raw -ErrorAction Stop + } + catch { + Write-Verbose "powercfg failed: $($_.Exception.Message)" + $null + } + finally { + Remove-Item $tmp -Force -ErrorAction SilentlyContinue + } +} + +#endregion + +#region -- Parsers ---------------------------------------------------------------- + +function Get-MachineInfoFromHtml ([string]$Html) { + # SYSTEM PRODUCT NAME row -> machine model + $model = if ($Html -match '(?is)SYSTEM\s+PRODUCT\s+NAME\s*\s*]*>\s*([^<]+)') { + $matches[1].Trim() + } + + # CPU is not in the powercfg report -> single targeted CIM call + $cpu = try { + (Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop | + Select-Object -First 1).Name.Trim() + } catch { "N/A" } + + [pscustomobject]@{ + Computer = if ($model) { $model } else { $env:COMPUTERNAME } + CPU = $cpu + } +} + +function Get-CapacitiesFromHtml ([string]$Html) { + if (-not $Html) { return $null } + + # Installed batteries table + $design = if ($Html -match '(?is)DESIGN\s+CAPACITY\s*]*>\s*([\d\s]+)\s*mWh') { + Convert-MWh $matches[1] + } + $full = if ($Html -match '(?is)FULL\s+CHARGE\s+CAPACITY\s*]*>\s*([\d\s]+)\s*mWh') { + Convert-MWh $matches[1] + } + + # Cycle count is often "-" in HTML depending on device/firmware + $cycle = $null + if ($Html -match '(?is)CYCLE\s+COUNT\s*]*>\s*([^<]+)') { + $cycle = Convert-Int $matches[1] + } + + if (-not $design -and -not $full -and $null -eq $cycle) { return $null } + [pscustomobject]@{ + DesignMWh = $design + FullMWh = $full + CycleCount = $cycle + CycleSource = if ($null -ne $cycle) { "powercfg" } else { $null } + } +} + +function Get-CycleCountFallback { + # Some machines expose cycle count via WMI in root\wmi BatteryCycleCount + try { + $cc = Get-CimInstance -Namespace root\wmi -ClassName BatteryCycleCount -ErrorAction Stop | + Select-Object -First 1 + if ($null -ne $cc.CycleCount) { return [int]$cc.CycleCount } + $null + } + catch { + Write-Verbose "Cycle count not available via root\\wmi BatteryCycleCount." + $null + } +} + +function Get-SessionStats ([string]$Html) { + if (-not $Html) { return $null } + + $watts = foreach ($row in [regex]::Matches($Html, '(?is)]*class="[^"]*dc[^"]*"[^>]*>(.*?)')) { + $inner = $row.Groups[1].Value + + $durMatch = [regex]::Match($inner, '(?is)]*class="[^"]*hms[^"]*"[^>]*>\s*(\d+:\d{2}:\d{2})\s*') + if (-not $durMatch.Success) { continue } + + $mwhMatch = [regex]::Match($inner, '(?is)]*class="[^"]*mw[^"]*"[^>]*>\s*([\d\s]+)\s*mWh') + if (-not $mwhMatch.Success) { continue } + + $p = $durMatch.Groups[1].Value -split ':' + $sec = [int]$p[0] * 3600 + [int]$p[1] * 60 + [int]$p[2] + $mWh = Convert-MWh $mwhMatch.Groups[1].Value + + if ($sec -lt 120 -or -not $mWh -or $mWh -le 0) { continue } + + $w = ($mWh / 1000.0) / ($sec / 3600.0) + if ($w -gt 0 -and $w -le 200) { $w } + } + + if (-not $watts) { return $null } + [pscustomobject]@{ + Count = @($watts).Count + AvgWatt = [math]::Round(($watts | Measure-Object -Average).Average, 1) + } +} + +#endregion + +#region -- Battery presence ------------------------------------------------------- + +function Test-BatteryPresent { + Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue + $ps = [System.Windows.Forms.SystemInformation]::PowerStatus + $ps.BatteryChargeStatus -ne [System.Windows.Forms.BatteryChargeStatus]::NoBattery +} + +#endregion + +#region -- Main ------------------------------------------------------------------- + +if (-not (Test-BatteryPresent)) { + Write-Host "No battery detected." -ForegroundColor Red + exit 2 +} + +$html = Get-BatteryReportHtml + +if (-not $html) { + Write-Host "Failed to generate battery report. Try running as Administrator." -ForegroundColor Red + exit 1 +} + +$machineName = $env:COMPUTERNAME +$machine = Get-MachineInfoFromHtml $html +$batt = Get-CapacitiesFromHtml $html +$sessions = Get-SessionStats $html + +$designMWh = if ($batt) { $batt.DesignMWh } else { $null } +$fullMWh = if ($batt) { $batt.FullMWh } else { $null } + +# Cycle count: +# - Prefer powercfg if present and numeric +# - Fallback to root\wmi BatteryCycleCount if available +$cycleCount = if ($batt -and $null -ne $batt.CycleCount) { $batt.CycleCount } else { Get-CycleCountFallback } + +$wearPct = $null +$healthPct = $null +if ($designMWh -and $fullMWh -and $designMWh -gt 0) { + $wearPct = [math]::Round((1 - ($fullMWh / $designMWh)) * 100, 2) + if ($wearPct -lt 0) { $wearPct = 0 } + $healthPct = [math]::Round(100 - $wearPct, 2) +} + +$rtCurrent = $null +$rtNew = $null +$gainMin = $null +if ($sessions -and $sessions.AvgWatt -gt 0) { + # Runtime hours = (energy in Wh) / W ; with mWh: (mWh/1000)/W + if ($fullMWh) { $rtCurrent = ($fullMWh / 1000.0) / $sessions.AvgWatt } + if ($designMWh) { $rtNew = ($designMWh / 1000.0) / $sessions.AvgWatt } + if ($rtCurrent -and $rtNew) { + $gainMin = [int][math]::Round(($rtNew - $rtCurrent) * 60) + } +} + +#endregion + +#region -- Output ----------------------------------------------------------------- + +# Output as JSON if requested (need to implent a function for the warranty part) +if ($Json) { + $output = [pscustomobject]@{ + Generated = (Get-Date).ToString("o") + MachineName = $machineName + Computer = $machine.Computer + CPU = $machine.CPU + DesignMWh = $designMWh + FullMWh = $fullMWh + HealthPct = $healthPct + CycleCount = $cycleCount + SessionCount = if ($sessions) { $sessions.Count } else { $null } + AvgWatt = if ($sessions) { $sessions.AvgWatt } else { $null } + RuntimeCurrent = if ($rtCurrent) { [math]::Round($rtCurrent, 2) } else { $null } + RuntimeNew = if ($rtNew) { [math]::Round($rtNew, 2) } else { $null } + } + Write-Output ($output | ConvertTo-Json -Depth 3) +} +else { + Write-Host "" + Write-Host "==== BATTERY ANALYSIS REPORT ====" -ForegroundColor Cyan + Write-Host "" + Write-KV "Report generated" (Get-Date -Format "yyyy-MM-dd HH:mm:ss") + Write-KV "Machine name" $machineName + Write-KV "Computer" $machine.Computer + Write-KV "CPU" $machine.CPU + + if (-not $NoWarranty) { + Get-WarrantyStatus + } + + Write-Header "Battery information" + Write-KV "Design Capacity" $(if ($designMWh) { "$designMWh mWh" } else { "N/A" }) + Write-KV "Current Capacity" $(if ($fullMWh) { "$fullMWh mWh" } else { "N/A" }) + Write-KV "Battery Health" $(if ($null -ne $healthPct) { "$healthPct %" } else { "N/A" }) + Write-KV "Cycle count" $(if ($null -ne $cycleCount){ $cycleCount } else { "N/A" }) + + Write-Header "Usage statistics" + if ($sessions) { + Write-KV "Analyzed sessions" $sessions.Count + Write-KV "Average consumption" "$($sessions.AvgWatt) W" + } else { + Write-Host " No discharge sessions found." -ForegroundColor DarkYellow + } + + Write-Header "Battery life estimation" + if ($rtCurrent -or $rtNew) { + Write-KV "Estimated runtime (current battery)" (Format-Runtime $rtCurrent) 38 + Write-KV "Estimated runtime (new battery)" (Format-Runtime $rtNew) 38 + } else { + Write-Host " Insufficient data for runtime estimation." -ForegroundColor DarkYellow + } + + Write-Header "Conclusion" + if ($null -ne $wearPct) { + if ($wearPct -lt 5) { + Write-Host " Battery is in good condition. No replacement needed." -ForegroundColor Green + } else { + Write-KV "Battery wear detected" "$wearPct %" + if ($null -ne $gainMin -and $gainMin -gt 0) { + Write-Host " Replacing the battery would increase runtime by ~$gainMin minutes." + } elseif ($null -ne $gainMin) { + Write-Host " Battery wear present but runtime gain would be negligible." -ForegroundColor DarkYellow + } else { + Write-Host " Could not estimate runtime gain (missing data)." -ForegroundColor DarkYellow + } + } + } else { + Write-Host " Unable to determine battery wear (capacity data unavailable)." -ForegroundColor DarkYellow + } +} + +Write-Host "" +Write-Host "Tip: run with -Verbose for detailed query information." -ForegroundColor DarkGray + +exit 0 + +#endregion \ No newline at end of file