Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Setting a High Performance Power Plan with an SCCM Package

Switch workstations to High Performance (or Ultimate Performance) by GUID, restore the plan if the image hid it, and don't end up with five copies of it.

AT A GLANCESet-PowerPlan.ps1
What it does
Activates a built-in Windows power plan by GUID, restores it from the built-in template if it's missing, and can set the plugged-in sleep timeout on it. Returns what was active before and after.
Requires
  • Windows PowerShell 5.1 (PowerShell 7 works too)
  • powercfg.exe (built in)
Permissions
Local administrator or SYSTEM to change the active plan for the machine.
Runs on
Windows 10/11, Windows Server 2016+
Tested
Parse-checked and dry-run with a mocked powercfg in PowerShell 7.4, including repeat runs

Some machines have no business going to sleep or parking CPU cores: the CAD workstation, the box that renders overnight, the kiosk that has to be awake when someone walks up to it. For those, High Performance is the plan you want, and setting it by hand on each one gets old fast.

The old version of this post ran powercfg -setactive SCHEME_MIN. That's actually right, if confusing: SCHEME_MIN means "minimum power saving," which is High Performance. But it fell over on any machine where the plan had been hidden, and it couldn't do Ultimate Performance at all, since that one is hidden on almost every edition of Windows.

This version works by GUID, so it doesn't care what language Windows is in. It can restore a missing plan from the built-in template, and it restores it under a fixed GUID, so running it every day doesn't leave you with a list of identical plans in Control Panel.

Set-PowerPlan.ps1Download
<#
.SYNOPSIS
    Switches a Windows device to a built-in power plan, creating it first if it's hidden.
.DESCRIPTION
    Works with the plans by GUID rather than by name, so it behaves the same on every
    language of Windows. If the plan you ask for isn't on the machine (Ultimate Performance
    is hidden by default, and some OEM images drop High Performance), -CreateIfMissing
    copies it from the built-in template under a fixed GUID, so running the script twice
    doesn't leave you with two copies.
.PARAMETER Plan
    Balanced, HighPerformance, UltimatePerformance or PowerSaver.
.PARAMETER CreateIfMissing
    Restore the plan from its built-in template if it isn't listed.
.PARAMETER StandbyTimeoutAC
    Optional. Minutes before sleep when plugged in, applied to the chosen plan. 0 means never.
.EXAMPLE
    .\Set-PowerPlan.ps1 -Plan HighPerformance -WhatIf
.EXAMPLE
    .\Set-PowerPlan.ps1 -Plan UltimatePerformance -CreateIfMissing -StandbyTimeoutAC 0
#>
[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(Mandatory)]
    [ValidateSet('Balanced', 'HighPerformance', 'UltimatePerformance', 'PowerSaver')]
    [string]$Plan,
    [switch]$CreateIfMissing,
    [ValidateRange(0, 600)]
    [int]$StandbyTimeoutAC = -1
)

# Built-in template GUIDs, plus the fixed GUID we use if we have to restore a copy.
$plans = @{
    Balanced            = @{ Template = '381b4222-f694-41f0-9685-ff5bb260df2e'; Copy = '0d8f2a6c-4e1b-4c79-a3d5-8b6e2f9c1a07' }
    HighPerformance     = @{ Template = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'; Copy = '3f1c8b2e-7a64-4d0f-9b3e-6c2a1d8e5f47' }
    UltimatePerformance = @{ Template = 'e9a42b02-d5df-448d-aa00-03f14749eb61'; Copy = '5b1a7c3e-9d42-4f6e-8a1b-2c7d9e0f4a61' }
    PowerSaver          = @{ Template = 'a1841308-3541-4fab-bc81-f71556f20b4a'; Copy = 'c7e4a9d1-2b58-4e3a-8f06-1d9b7c3a2e84' }
}
$guidPattern = '[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}'

function Get-PowerSchemes {
    foreach ($line in @(powercfg /list)) {
        if ($line -match "($guidPattern)\s+\((.+?)\)") {
            [pscustomobject]@{ Guid = $Matches[1].ToLower(); Name = $Matches[2]; Active = $line.TrimEnd().EndsWith('*') }
        }
    }
}

$schemes  = @(Get-PowerSchemes)
$previous = $schemes | Where-Object Active | Select-Object -First 1
$wanted   = $plans[$Plan]
$target   = $schemes | Where-Object { $_.Guid -in $wanted.Template, $wanted.Copy } | Select-Object -First 1
$action   = 'NoChange'

if (-not $target) {
    if (-not $CreateIfMissing) {
        Write-Error "The $Plan plan isn't available on this device. Run again with -CreateIfMissing to restore it."
        exit 1
    }
    if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Restore $Plan plan as $($wanted.Copy)")) {
        $null = powercfg /duplicatescheme $wanted.Template $wanted.Copy
        if ($LASTEXITCODE -ne 0) { Write-Error "powercfg couldn't restore the $Plan plan (exit $LASTEXITCODE)."; exit 1 }
        $target = [pscustomobject]@{ Guid = $wanted.Copy; Name = $Plan; Active = $false }
        $action = 'Created'
    }
    else { $target = [pscustomobject]@{ Guid = $wanted.Copy; Name = $Plan; Active = $false }; $action = 'WhatIf' }
}

if (-not $target.Active -and $action -ne 'WhatIf') {
    if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Activate $($target.Name) ($($target.Guid))")) {
        powercfg /setactive $target.Guid
        if ($LASTEXITCODE -ne 0) { Write-Error "powercfg /setactive failed (exit $LASTEXITCODE)."; exit 1 }
        $action = if ($action -eq 'Created') { 'CreatedAndActivated' } else { 'Activated' }
    }
    else { $action = 'WhatIf' }
}

if ($StandbyTimeoutAC -ge 0 -and $action -ne 'WhatIf') {
    # SUB_SLEEP / STANDBYIDLE takes seconds; set it on the target plan, not whatever happens to be active.
    if ($PSCmdlet.ShouldProcess($target.Name, "Set AC sleep timeout to $StandbyTimeoutAC minute(s)")) {
        powercfg /setacvalueindex $target.Guid SUB_SLEEP STANDBYIDLE ($StandbyTimeoutAC * 60)
        powercfg /setactive $target.Guid
    }
}

$active = Get-PowerSchemes | Where-Object Active | Select-Object -First 1
[pscustomobject]@{
    ComputerName = $env:COMPUTERNAME
    Requested    = $Plan
    Previous     = if ($previous) { $previous.Name } else { $null }
    Active       = if ($active) { $active.Name } else { $null }
    ActiveGuid   = if ($active) { $active.Guid } else { $null }
    Action       = $action
}

Parameters

ParameterTypeDefaultWhat it's for
-Planstring—Required. Balanced, HighPerformance, UltimatePerformance or PowerSaver.
-CreateIfMissingswitch—If the plan isn't listed on the machine, restore it from the built-in template before activating it.
-StandbyTimeoutACint-1Minutes before sleep when plugged in, set on the chosen plan. 0 means never. Leave it off to keep the plan's own setting.

Run it

Preview the switch to High Performance.

.\Set-PowerPlan.ps1 -Plan HighPerformance -WhatIf

High Performance, and never sleep while plugged in.

.\Set-PowerPlan.ps1 -Plan HighPerformance -CreateIfMissing -StandbyTimeoutAC 0

Ultimate Performance for a render box. It's hidden by default, so restore it.

.\Set-PowerPlan.ps1 -Plan UltimatePerformance -CreateIfMissing

As a ConfigMgr program command line.

powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Set-PowerPlan.ps1 -Plan HighPerformance -CreateIfMissing

What you'll see

Example outputvalues are illustrative
ComputerName : PC-0142
Requested    : HighPerformance
Previous     : Balanced
Active       : High performance
ActiveGuid   : 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
Action       : Activated

How it works

  1. List what's there. It runs powercfg /list and pulls out each plan's GUID, name, and whether it's the active one (the line ending in *). Only the GUID and the layout are relied on, not the wording.
  2. Find the plan you asked for. It looks for either the built-in GUID or the fixed GUID the script uses for a restored copy. Either one counts.
  3. Restore it if needed. With -CreateIfMissing, it runs powercfg /duplicatescheme <template> <fixed GUID>. Passing the destination GUID is the important part. Without it, powercfg invents a new GUID every time, and a daily deployment slowly fills Control Panel with copies.
  4. Activate it. powercfg /setactive with the GUID, then a check of the exit code, so a failure is reported as a failure and not as a mystery.
  5. Optional sleep timeout. -StandbyTimeoutAC sets the plugged-in sleep value on that plan with /setacvalueindex, in seconds, and re-applies the plan so it takes effect now.
  6. Report. You get the previous plan, the active plan and what the script did: NoChange, Activated, Created, CreatedAndActivated or WhatIf.

For deployment, a package with a program is fine for a one-time switch. If you want machines to stay on the plan, make it a configuration item: a tiny discovery script that compares powercfg /getactivescheme against the GUID, and this script as the remediation.

Take it further

  • Report before you change. powercfg /getactivescheme in a Run Scripts job across a collection shows you who's on what before you touch anything.
  • Tune the plan, not just the choice. powercfg /setacvalueindex can set disk, USB selective suspend and processor minimum state the same way the script sets sleep.
  • Export a golden plan. Tune one machine, powercfg /export the plan, and /import it with a fixed GUID everywhere else.

Things that'll trip you up

  • Modern Standby laptops only really do Balanced. On devices that use Modern Standby, Windows shows only the Balanced plan and uses the power mode slider on top of it. You can force a restored plan onto one, but you're fighting the platform. Leave those on Balanced and set the slider instead.
  • A script sets it; it doesn't keep it set. Anyone with admin rights can switch plans back. If it has to stay put, use the "Select an active power plan" Group Policy setting, or run this as a configuration item with remediation.
  • Battery life goes with it. High Performance applies on battery too. Fine for a desktop, rough on a laptop that leaves the desk. Target a collection of desktops, not "All Workstations."
  • Plan names are translated, GUIDs aren't. That's why the script matches on GUIDs. If you adapt it, don't switch to matching "High performance" by name, or it breaks the first time it meets a German install.