Stop onboarding if on battery

We have a customer who uses ImmyBot to build computers, and they report that their success rate is very low. The recent Timezone issue was a problem, and there have been a couple of other issues, but the biggest problem is that they kick off onboarding on laptops and forget to plug them in.

I would like to have a task that runs ahead of everything else for this customer, which aborts the onboarding if it detects that the device is on battery. The idea would be rather than random tasks failing when the device falls asleep or runs out of power, giving the impression that ImmyBot is unreliable, it just fails right at the start and clearly states that it failed because the devices is on battery.

Is it sufficient to throw an exception, or is there a better way? or is this not possible?

James

Personally I’d like to just see a session option for “run sessions if the device has greater than X% battery,” and I’d probably set it at 25%. Then, the option would be to cancel the session or “suspend” the session until AC power is detected, or the battery reaches >X% charge.

I’m okay with whatever gets the customer to stop blaming ImmyBot for their deployments failing because they forgot to plug in their laptops :slight_smile:

Why would that stop the customer from complaining? :joy:

This shouldnt be too difficult.

Rough draft:

Name: Detect-DeviceOnACPower; Type: Function; Language: Powershell

$PowerStatus = Invoke-ImmyCommand {
    try{
        $BatteryStatus = Get-WmiObject -Class BatteryStatus -namespace root\wmi -ErrorAction Stop
        Write-Host $BatteryStatus
        if(!$BatteryStatus.PowerOnline){
            #Cancel
            return 0
        }else{
            #Continue, on AC
            return 1
        }
    }catch{
        #Continue, no power supply
        return -1
    }
}
return $PowerStatus

Then new script:
Name: Cancel if running on battery power; Context: Metascript; Language: Powershell

$Action = Detect-DeviceOnACPower
if ($Action -eq 0){
        Stop-ImmySession -FailureMessage "Device is not on AC Power"
}else{
    Write-Host $Action
}

Finally new Task:
Name: Cancel if running on battery power; Runs Against: Computers; Parameters: Not needed
Scripts: Use separate scripts
Test: Disabled
Get: Disabled
Set: Enabled - Cancel if running on battery power

Set that task to the top priority in your deployments before your maintenance consent and it’ll stop it running if you’re on battery power.

1 Like

I love it!

@Gav, good function/task for Global.

Here’s one using API. I’ve been told WMI can be inaccurate with battery. Still working out the battery lifetime stuff to make it more human-readable

function Get-PowerStatus {
    $PowerResults = Invoke-ImmyCommand -Context System {
    $typeName = 'PowerStatusHelper'

    # Check if the type is already loaded
    if (-not ([System.Management.Automation.PSTypeName]$typeName).Type) {
        Add-Type -TypeDefinition @"
        using System;
        using System.Runtime.InteropServices;

        public class $typeName
        {
            [StructLayout(LayoutKind.Sequential)]
            public struct SYSTEM_POWER_STATUS
            {
                public byte ACLineStatus;
                public byte BatteryFlag;
                public byte BatteryLifePercent;
                public byte Reserved1;
                public uint BatteryLifeTime;
                public uint BatteryFullLifeTime;
            }

            [DllImport("kernel32.dll")]
            public static extern bool GetSystemPowerStatus(out SYSTEM_POWER_STATUS lpSystemPowerStatus);
        }
"@
    }

    $powerStatus = New-Object -TypeName "$typeName+SYSTEM_POWER_STATUS"
    [PowerStatusHelper]::GetSystemPowerStatus([ref]$powerStatus) | Out-Null
    $acLineStatus = switch ($powerStatus.ACLineStatus) {
        0 { 'Not Pugged In' }
        1 { 'Plugged In' }
        255 { 'Unknown' }
        default { 'Unknown' }
    }
    $batteryFlagValues = @{
        1 = 'High, more than 66%'
        2 = 'Low, less than 33%'
        4 = 'Critical, less than 5%'
        8 = 'Charging'
        128 = 'No system battery'
    }
    $batteryFlags = @()
    foreach ($flagValue in $batteryFlagValues.Keys) {
        if (($powerStatus.BatteryFlag -band $flagValue) -eq $flagValue) {
            $batteryFlags += $flagValue
        }
    }
    $batteryFlagDescriptions = $batteryFlags | ForEach-Object {
        $batteryFlagValues[$_]
    }
    $batteryFlag = if ($batteryFlagDescriptions) {
        $batteryFlagDescriptions -join ', '
    } else {
        'Unknown'
    }
    $batteryLifePercent = $powerStatus.BatteryLifePercent
    $batteryLifeTime = if ($powerStatus.BatteryLifeTime -ne 0xFFFFFFFF) {
        $batteryLifeTime = [TimeSpan]::FromSeconds($powerStatus.BatteryLifeTime).ToString()
    } else {
        'Unknown'
    }

    $batteryFullLifeTime = if ($powerStatus.BatteryFullLifeTime -ne 0xFFFFFFFF) {
        $batteryFullLifeTime = [TimeSpan]::FromSeconds($powerStatus.BatteryFullLifeTime).ToString()
    } else {
        'Unknown'
    }
    [PSCustomObject]@{
        ACLineStatus          = $acLineStatus
        BatteryFlag           = $batteryFlag
        BatteryLifePercent    = $batteryLifePercent
        BatteryLifeTime       = $batteryLifeTime
        BatteryFullLifeTime   = $batteryFullLifeTime
    }
    return
    }
    return $PowerResults
}

I just added this to one of our local Immy modules recently, but I think it’d be good for global. Although I think it could do better as maybe an inventory key

@James_Harper @Dakota_Lewis
Check out the task “Stop Session for Battery Powered Machine” in global.
There is two functions now “Stop-SessionOnBatteryStatus” & “Get-PowerStatus”

That’s amazing, @Gav. Thanks!

I don’t think it’s within reason at the present time, but it would be cool if this were a preflight check for every task (if the target is determined to be a portable) just for assurance. But for now, I’m going to make this a cross-tenant deployment that runs before anything else lol

@Dakota_Lewis preflight doesn’t work that way. Preflight only checks before first running a session or after a reboot to ensure the computer is ready to continue running the session, it doesn’t check between each action in a session.

Personally I wouldn’t use this as the very first deployment in a session. I would put some of my priority items like making sure my RMM, ScreenConnect, LocalAdmin etc deployments like these that are usually quick to run are still in compliance first in my ordering and then order the “Stop Session for Battery Powered Machine” deployment after some of these quick priority deployments.
That one is a bit “each to their own” preference approach of course!

Ah, that’s unfortunate but understandable. I think it’s just a good check to have, especially when installing software. If something powers off in the middle an install, it’d be easy to get corruption.

In any case, I totally get that perspective, but currently we aren’t really utilizing the “remote” aspect of Immy (aside from a few resets here and there). About 90% of Immy’s deployments in our environment are still on my bench, so I’m not too concerned about RMM stuff being installed right away. I just want to be able to start a couple deployments at the end of my shift and be assured I won’t come back to a dead computer in the morning lol

Little off topic for this one but somewhat related.
If you haven’t seen the “Stop Onboarding if Reboots are Suppressed” task, this helps when a tech attempts to run onboarding session with reboots supressed, which as you would know will cause failures like renaming isn’t possible with out a reboot.
The two of these combined I could see being helpful for work bench setup. I was using the “Stop Onboarding if Reboots are Suppressed” deployment for a while and it helps as a reminder especially with co-managed teams to allow reboots for onboarding.

Interesting! On the onboarding form I typically never touch anything aside from the parameter configurations, so it slipped my mind that it’s even possible to suppress reboots during onboarding. Thanks for the pro tip, I might implement that as a failsafe for the other techs.