Battery health is of course something that over time diminishes, and tends to creep up on you when you least expect it, yes, we have all been caught out with a laptop dying in the middle of a meeting..
Typically IT departments react when the machine is thrown through the door of the IT department with a note saying please fix, and all too often it is a case that the battery itself is long since out of warranty. So wouldn’t it be cool if both those managing environments, along with the end-user were made aware of battery health issues before they became critical?
Endpoint Analytics FTW
Extending on Configuration Baselines I would have created in the past, I have ported a solution which monitors for the battery falling outside of a set threshold, typically this is less than 30-40%. OEM warranty replacements often use around these figures within the warranty period in order to validate a replacement is required, and having booked more than a fair share of battery replacements in my admin days, it is also clear that many of them use the built in “PowerCFG.exe /BatteryReport” details when validating a replacement.
Monitoring & Toast Notifications
With Endpoint Analytics we have the opportunity to not only monitor for what ever we opt to call via our script, but we can also run a remediation action. Obviously when your battery is well on its way to being goosed, there isn’t anything you can do in terms of remediation, however, you can of course do something like generate a toast style notification for the end-user.
So in the below scripts I am simply querying WMI to determine the manufacturer specifications and the current fully charged capacity value for the battery, then should it fail to reach a predetermined value which you can specify (40% being the recommended value), a notification will be invoked where the battery falls below that value:
Kind of cool I thought, but obviously don’t go overboard and run the detection every hour, otherwise you might invoke the Get-Laptop -Transform Frisbee -Target “Company\IT Department” -AlternativeTarget “OpenWindow” command.
Creating The Proactive Remediation Task
- Launch the Microsoft Endpoint Portal – https://endpoint.microsoft.com
- Click on Reports
- Click on Endpoint Analytics (Assuming you have already set this up)
- Click on Proactive Remediations
- Click on Create Script Package
- Use the following script for your Detection Script:
function CheckBatteryHealth { # Check for presence of battery and check where present If (Get-WmiObject win32_battery) { # Check machine type and other info [string]$SerialNumber = (Get-WmiObject win32_bios).SerialNumber # Maximum Acceptable Health Perentage $MinHealth = "40" # Multiple Battery handling $BatteryInstances = Get-WmiObject -Namespace "ROOT\WMI" -Class "BatteryStatus" | Select-Object -ExpandProperty InstanceName ForEach($BatteryInstance in $BatteryInstances){ # Set Variables for health check $BatteryDesignSpec = Get-WmiObject -Namespace "ROOT\WMI" -Class "BatteryStaticData" | Where-Object -Property InstanceName -EQ $BatteryInstance | Select-Object -ExpandProperty DesignedCapacity $BatteryFullCharge = Get-WmiObject -Namespace "ROOT\WMI" -Class "BatteryFullChargedCapacity" | Where-Object -Property InstanceName -EQ $BatteryInstance | Select-Object -ExpandProperty FullChargedCapacity # Fall back WMI class for Microsoft Surface devices if ($BatteryDesignSpec -eq $null -or $BatteryFullCharge -eq $null -and ((Get-WmiObject -Class Win32_BIOS | Select-Object -ExpandProperty Manufacturer) -match "Microsoft")) { # Attempt to call WMI provider if (Get-WmiObject -Class MSBatteryClass -Namespace "ROOT\WMI") { $MSBatteryInfo = Get-WmiObject -Class MSBatteryClass -Namespace "root\wmi" | Where-Object -Property InstanceName -EQ $BatteryInstance | Select-Object FullChargedCapacity, DesignedCapacity # Set Variables for health check $BatteryDesignSpec = $MSBatteryInfo.DesignedCapacity $BatteryFullCharge = $MSBatteryInfo.FullChargedCapacity } } if ($BatteryDesignSpec -gt $null -and $BatteryFullCharge -gt $null) { # Determine battery replacement required [int]$CurrentHealth = ($BatteryFullCharge/$BatteryDesignSpec) * 100 if ($CurrentHealth -le $MinHealth) { $ReplaceBattery = $true # Generate Battery Report $ReportingPath = Join-Path -Path $env:SystemDrive -ChildPath "Reports" if (-not (Test-Path -Path $ReportingPath)) { New-Item -Path $ReportingPath -ItemType Dir | Out-Null } $ReportOutput = Join-Path -Path $ReportingPath -ChildPath $('\Battery-Report-' + $SerialNumber + '.html') # Run Windows battery health report Start-Process PowerCfg.exe -ArgumentList "/BatteryReport /OutPut $ReportOutput" -Wait -WindowStyle Hidden # Output replacement message and flag for remediation step Write-Output "Battery replacement required - $CurrentHealth% of manufacturer specifications" exit 1 } else { # Output replacement not required values $ReplaceBattery = $false Write-Output "Battery status healthy: $($CurrentHealth)% of manufacturer specifications" # Not exiting here so that second battery can be checked } } else { # Output battery not present Write-Output "Battery not present in system." exit 0 } } } else { # Output battery value condition check error Write-Output "Unable to obtain battery health information from WMI" exit 0 } } CheckBatteryHealth
- Now use the following code for your Toast notification (making sure to include your own image paths)
function Display-ToastNotification() { $Load = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] $Load = [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] # Load the notification into the required format $ToastXML = New-Object -TypeName Windows.Data.Xml.Dom.XmlDocument $ToastXML.LoadXml($Toast.OuterXml) # Display the toast notification try { [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($App).Show($ToastXml) } catch { Write-Output -Message 'Something went wrong when displaying the toast notification' -Level Warn Write-Output -Message 'Make sure the script is running as the logged on user' -Level Warn } } # Check for required entries in registry for when using Powershell as application for the toast # Register the AppID in the registry for use with the Action Center, if required $RegPath = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings' $App = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe' # Multiple Battery handling $BatteryInstances = Get-WmiObject -Namespace "ROOT\WMI" -Class "BatteryStatus" | Select-Object -ExpandProperty InstanceName ForEach($BatteryInstance in $BatteryInstances){ # Set Variables for health check $BatteryDesignSpec = Get-WmiObject -Namespace "ROOT\WMI" -Class "BatteryStaticData" | Where-Object -Property InstanceName -EQ $BatteryInstance | Select-Object -ExpandProperty DesignedCapacity $BatteryFullCharge = Get-WmiObject -Namespace "ROOT\WMI" -Class "BatteryFullChargedCapacity" | Where-Object -Property InstanceName -EQ $BatteryInstance | Select-Object -ExpandProperty FullChargedCapacity # Fall back WMI class for Microsoft Surface devices if ($BatteryDesignSpec -eq $null -or $BatteryFullCharge -eq $null -and ((Get-WmiObject -Class Win32_BIOS | Select-Object -ExpandProperty Manufacturer) -match "Microsoft")) { # Attempt to call WMI provider if (Get-WmiObject -Class MSBatteryClass -Namespace "ROOT\WMI") { $MSBatteryInfo = Get-WmiObject -Class MSBatteryClass -Namespace "root\wmi" | Where-Object -Property InstanceName -EQ $BatteryInstance | Select-Object FullChargedCapacity, DesignedCapacity # Set Variables for health check $BatteryDesignSpec = $MSBatteryInfo.DesignedCapacity $BatteryFullCharge = $MSBatteryInfo.FullChargedCapacity } } # Determine battery replacement required [int]$CurrentHealth = ($BatteryFullCharge/$BatteryDesignSpec) * 100 # Setting image variables $LogoImageUri = "YOURLOGIMAGEURLHERE" $HeroImageUri = "YOURHEROIMAGEURLHERE" $LogoImage = "$env:TEMP\ToastLogoImage.png" $HeroImage = "$env:TEMP\ToastHeroImage.png" $MinHealth = 40 If($CurrentHealth -le $MinHealth){ #Fetching images from uri Invoke-WebRequest -Uri $LogoImageUri -OutFile $LogoImage Invoke-WebRequest -Uri $HeroImageUri -OutFile $HeroImage #Defining the Toast notification settings #ToastNotification Settings $Scenario = 'reminder' # <!-- Possible values are: reminder | short | long --> # Load Toast Notification text $AttributionText = "MSEndpointMgr" $HeaderText = "Battery Replacement Required" $TitleText = "Your device battery health is currently operating at $CurrentHealth% of manufacturers specifications." $BodyText1 = "It is recommended that your battery is replaced as soon as possible." $BodyText2 = "Please contact IT and request a battery replacement. Thank you in advance." # Creating registry entries if they don't exists if (-NOT (Test-Path -Path "$RegPath\$App")) { New-Item -Path "$RegPath\$App" -Force New-ItemProperty -Path "$RegPath\$App" -Name 'ShowInActionCenter' -Value 1 -PropertyType 'DWORD' } # Make sure the app used with the action center is enabled if ((Get-ItemProperty -Path "$RegPath\$App" -Name 'ShowInActionCenter' -ErrorAction SilentlyContinue).ShowInActionCenter -ne '1') { New-ItemProperty -Path "$RegPath\$App" -Name 'ShowInActionCenter' -Value 1 -PropertyType 'DWORD' -Force } # Formatting the toast notification XML [xml]$Toast = @" <toast scenario="$Scenario"> <visual> <binding template="ToastGeneric"> <image placement="hero" src="$HeroImage"/> <image id="1" placement="appLogoOverride" hint-crop="circle" src="$LogoImage"/> <text placement="attribution">$AttributionText</text> <text>$HeaderText</text> <group> <subgroup> <text hint-style="title" hint-wrap="true" >$TitleText</text> </subgroup> </group> <group> <subgroup> <text hint-style="body" hint-wrap="true" >$BodyText1</text> </subgroup> </group> <group> <subgroup> <text hint-style="body" hint-wrap="true" >$BodyText2</text> </subgroup> </group> </binding> </visual> <actions> <action activationType="system" arguments="dismiss" content="$DismissButtonContent"/> </actions> </toast> "@ #Send the notification Display-ToastNotification Exit 0 } else{$CurrentHealth} }
- At this point you should have a screen similar to the below:
- Assign the Proactive Remediation and let it do its thing..
Endpoint Analytics Reporting
From an Intune admin perspective, compliance then can be tracked through the Endpoint Analytics reporting node:
Drilling into the device status, and then adding the “Pre-remediation detection output” column, you can then see the output from the script, examples:
Battery Report
To assist with any calls to an OEM for a battery replacement, the script will also generate a battery report using the before mentioned PowerCFG.exe command. Below are snippets from this report, which will be dropped into a “Reports” folder on the C:\
Over time of course the full charge capacity will diminish, so the above screenshots are not a true reflection on this as the machine in question is only around 6 months old, but you get the idea.
Conclusion
So this is just another quick way that Endpoint Analytics can be used, I hope you have enjoyed reading this post. Thanks goes out to Martin Bengtsson and Jan Ketil Skanke for inspiration/code for the toast notification.
Update: Special shout out to Jason Cody, PFE with Microsoft, for giving the heads up and code addition for Surface devices with multiple batteries #CommunityRocks
Add comment