Power plan, Fast Startup, and hibernation are three separate Windows knobs that all drift independently. One YAML policy and three small Ruby classes — WMI, the registry, and powercfg.exe — keep them in line.
Step through the build below:
A server that silently drifts onto the Balanced power plan throttles CPU under light load — a performance regression that looks like an application bug until someone thinks to run powercfg /getactivescheme. A server with Fast Startup enabled skips full driver re-initialization on “shutdown,” a classic cause of stale-state bugs after a maintenance reboot. Both are one WMI/registry write away from fixed — if you remember to check every box in the fleet.
This script enforces a small power policy — active plan, Fast Startup, hibernation — declared once in YAML, using three different Windows automation surfaces in one place: WMI for the power plan itself, the registry for Fast Startup, and a shelled-out powercfg.exe for hibernation, since only the supported powercfg path correctly allocates or frees hiberfil.sys.
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# power_plan_enforcer.rb
#
# Idempotent Windows power-policy enforcement across a fleet: makes sure
# each box is on the right power plan (WMI), has Fast Startup set the way
# you want it (registry), and has hibernation on or off as policy demands
# (powercfg.exe). Three different automation surfaces in one small script,
# which is exactly the kind of glue Ruby is good at on Windows -- the
# alternative is three separate PowerShell one-liners nobody remembers to
# run together.
#
# Why this matters operationally: a server that silently drifts onto
# "Balanced" power policy throttles CPU under light load (surprising perf
# regressions that look like application bugs), and a server with Fast
# Startup enabled skips full driver re-init on "shutdown", which is a
# classic cause of stale-state bugs after a maintenance reboot. Both are
# one WMI/registry write away from fixed -- if you remember to check.
#
# Only standard library plus win32ole on Windows: win32ole, win32/registry
# (both Windows-only, required lazily), optparse, yaml, open3.
#
# Everything that actually talks to Windows (WMI, the registry, powercfg)
# is behind a small injectable interface so the reconciliation logic
# (PowerPolicyEnforcer#plan / #apply!) can be fully unit-tested on any
# platform, including this one -- see power_plan_enforcer_test.rb, which
# runs entirely off fixtures.
require 'optparse'
require 'yaml'
# --------------------------------------------------------------------------
# Talks to the root\cimv2\power WMI namespace for power-plan enumeration
# and activation. Only instantiated on a real run; tests inject a fake
# with the same three methods instead.
# --------------------------------------------------------------------------
class WmiPowerPlans
def initialize
require 'win32ole'
@wmi = WIN32OLE.connect('winmgmts:\\\\.\\root\\cimv2\\power')
end
# Returns an Array of { name:, is_active:, instance_id: } hashes.
def list
@wmi.ExecQuery('SELECT * FROM Win32_PowerPlan').to_enum.map do |plan|
{ name: plan.ElementName, is_active: plan.IsActive, instance_id: plan.InstanceID }
end
end
def activate(instance_id)
plan = @wmi.ExecQuery(
"SELECT * FROM Win32_PowerPlan WHERE InstanceID = '#{instance_id.gsub("'", "''")}'"
).to_enum.first
raise "power plan #{instance_id} vanished before activation" unless plan
plan.Activate
end
end
# --------------------------------------------------------------------------
# Fast Startup lives entirely in the registry (HiberbootEnabled). Real
# implementation uses Win32::Registry; tests inject an in-memory Hash.
# --------------------------------------------------------------------------
class WindowsRegistry
KEY_PATH = 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Power'
VALUE = 'HiberbootEnabled'
def read_hiberboot_enabled
require 'win32/registry'
Win32::Registry::HKEY_LOCAL_MACHINE.open(KEY_PATH) do |reg|
reg[VALUE, Win32::Registry::REG_DWORD]
end
rescue Win32::Registry::Error
nil # value not present -- Windows treats this as "unset", not an error
end
def write_hiberboot_enabled(value)
require 'win32/registry'
Win32::Registry::HKEY_LOCAL_MACHINE.open(KEY_PATH, Win32::Registry::KEY_WRITE) do |reg|
reg.write(VALUE, Win32::Registry::REG_DWORD, value)
end
end
end
# --------------------------------------------------------------------------
# Hibernation on/off isn't a simple registry flag -- turning it on/off
# through the supported path allocates/frees hiberfil.sys, which only
# powercfg.exe does correctly. Real implementation shells out; tests
# inject a fake runner (same Open3-wrapping pattern used elsewhere in this
# repo, e.g. deploy_webhook_orchestrator's RetryingHttpClient).
# --------------------------------------------------------------------------
class PowercfgControl
Result = Struct.new(:success, :stdout, :stderr, keyword_init: true)
def hibernation_enabled?
require 'win32/registry'
Win32::Registry::HKEY_LOCAL_MACHINE.open('SYSTEM\\CurrentControlSet\\Control\\Power') do |reg|
reg['HibernateEnabled', Win32::Registry::REG_DWORD] == 1
end
rescue Win32::Registry::Error
false
end
def set_hibernation(enabled)
require 'open3'
stdout, stderr, status = Open3.capture3('powercfg.exe', '/hibernate', enabled ? 'on' : 'off')
Result.new(success: status.success?, stdout: stdout, stderr: stderr)
end
end
# --------------------------------------------------------------------------
# Core reconciler. Depends only on the three small interfaces above (each
# with #list/#activate, #read_/#write_hiberboot_enabled, and
# #hibernation_enabled?/#set_hibernation) -- never on WIN32OLE or
# Win32::Registry directly, which is what makes it testable off Windows.
# --------------------------------------------------------------------------
class PowerPolicyEnforcer
Action = Struct.new(:kind, :detail, :payload, keyword_init: true) do
def describe = "#{kind.to_s.upcase.ljust(20)} #{detail}"
end
def initialize(policy, power_plans:, registry:, powercfg:, logger: method(:puts))
@policy = policy
@power_plans = power_plans
@registry = registry
@powercfg = powercfg
@logger = logger
end
def plan
actions = []
actions.concat(plan_power_plan)
actions.concat(plan_fast_startup)
actions.concat(plan_hibernation)
actions
end
def apply!(actions, dry_run: true)
actions.each do |action|
@logger.call((dry_run ? '[dry-run] ' : '[apply] ') + action.describe)
next if dry_run
execute(action)
end
end
private
def plan_power_plan
return [] unless @policy['active_plan']
plans = @power_plans.list
target = plans.find { |p| p[:name].casecmp?(@policy['active_plan']) }
unless target
@logger.call("WARNING: power plan #{@policy['active_plan'].inspect} not found on this system; skipping")
return []
end
return [] if target[:is_active]
currently_active = plans.find { |p| p[:is_active] }
[Action.new(kind: :activate_power_plan,
detail: "#{currently_active && currently_active[:name]} -> #{target[:name]} (#{target[:instance_id]})",
payload: target[:instance_id])]
end
def plan_fast_startup
return [] unless @policy.key?('fast_startup_enabled')
wanted = @policy['fast_startup_enabled'] ? 1 : 0
current = @registry.read_hiberboot_enabled
return [] if current == wanted
[Action.new(kind: :set_fast_startup, detail: "HiberbootEnabled #{current.inspect} -> #{wanted}", payload: wanted)]
end
def plan_hibernation
return [] unless @policy.key?('hibernation_enabled')
wanted = @policy['hibernation_enabled']
current = @powercfg.hibernation_enabled?
return [] if current == wanted
[Action.new(kind: :set_hibernation, detail: "hibernation #{current} -> #{wanted}", payload: wanted)]
end
def execute(action)
case action.kind
when :activate_power_plan
@power_plans.activate(action.payload)
when :set_fast_startup
@registry.write_hiberboot_enabled(action.payload)
when :set_hibernation
result = @powercfg.set_hibernation(action.payload)
unless result.success
@logger.call(" -> FAILED: powercfg /hibernate #{action.payload ? 'on' : 'off'}: #{result.stderr}")
end
else
raise "unknown action kind #{action.kind}"
end
end
end
# --------------------------------------------------------------------------
# CLI
# --------------------------------------------------------------------------
if __FILE__ == $PROGRAM_NAME
options = { apply: false }
OptionParser.new do |opts|
opts.banner = 'Usage: power_plan_enforcer.rb --policy policy.yml [--apply]'
opts.on('--policy PATH', 'YAML policy describing the desired power state (required)') { |v| options[:policy] = v }
opts.on('--apply', 'Actually make the changes (default: dry-run only)') { options[:apply] = true }
opts.on('-h', '--help', 'Show this help') { puts opts; exit 0 }
end.parse!(ARGV)
unless options[:policy]
warn 'error: --policy is required'
exit 4
end
unless RUBY_PLATFORM =~ /mswin|mingw|cygwin/
warn 'This script talks to WMI and the Windows registry and can only run for real on Windows.'
warn 'Run power_plan_enforcer_test.rb instead to exercise its logic on this platform.'
exit 4
end
policy = YAML.safe_load_file(options[:policy])
enforcer = PowerPolicyEnforcer.new(
policy,
power_plans: WmiPowerPlans.new,
registry: WindowsRegistry.new,
powercfg: PowercfgControl.new
)
actions = enforcer.plan
if actions.empty?
puts 'No drift detected -- power policy already matches spec.'
exit 0
end
puts "#{actions.size} change(s) needed:"
enforcer.apply!(actions, dry_run: !options[:apply])
puts "\nDry run only -- re-run with --apply to make these changes." unless options[:apply]
exit 0
end
Three small collaborators, each wrapping exactly one automation surface: WmiPowerPlans connects to the root\cimv2\power WMI namespace and calls .Activate on the matching Win32_PowerPlan; WindowsRegistry reads/writes the HiberbootEnabled DWORD that actually controls Fast Startup (there’s no WMI class for it); PowercfgControl reads current hibernation state from the registry but always writes through powercfg.exe /hibernate on|off, since that’s the only path that correctly manages the hibernation file on disk.
PowerPolicyEnforcer depends only on those three small interfaces — never directly on WIN32OLE or Win32::Registry — which is exactly what makes its planning and execution logic fully unit-testable on a platform that has neither. That’s not a workaround bolted on for this post: it’s the same dependency-injection shape several other Windows scripts in this repo already use (scheduled-task-audit, win-profile-cleanup) so their core logic can be verified even though the real WMI/registry calls can’t run here.
$ ruby power_plan_enforcer_test.rb # (real WMI/registry/powercfg only exist on Windows -- this run exercises # PowerPolicyEnforcer's logic through fixture doubles, per the README) Scenario 1: wrong active power plan is detected and corrected ok - exactly one action planned (got 1) ok - action is activate_power_plan ok - targets the High performance plan instance id ok - Activate was actually called on the right plan Scenario 2: already-compliant plan is a no-op ok - no actions when the active plan already matches the policy Scenario 3: fast-startup and hibernation drift are both corrected ok - both drifted settings planned (got [:set_fast_startup, :set_hibernation]) ok - dry-run touches neither the registry nor powercfg ok - HiberbootEnabled actually written to 0 ok - powercfg /hibernate off actually invoked ok - registry fake reflects the new value ok - powercfg fake reflects hibernation now off Scenario 4: unspecified policy keys are left untouched ok - no fast-startup/hibernation actions when those keys are absent from the policy All scenarios passed.
Full script + README on GitHub: ruby-devops-toolkit/power-plan-enforcer
What you need
- Ruby with win32ole and win32/registry — both ship with the standard Windows Ruby build (RubyInstaller for Windows); neither exists on Linux/macOS Ruby, which is why the CLI checks
RUBY_PLATFORMand refuses to run for real anywhere else. - Administrator privileges for
--apply— changing the active power plan, the registry, and runningpowercfg /hibernateall require elevation. powercfg.exeonPATH(present by default on every supported Windows version).
How it works, end to end
Policy keys map one-to-one onto the three collaborators:
active_plan→ WmiPowerPlans lists everyWin32_PowerPlan, matches byElementName(case-insensitively), and callsActivateonly if the match isn’t already the active plan.fast_startup_enabled→ WindowsRegistry reads/writesHiberbootEnabledunderHKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Power.hibernation_enabled→ PowercfgControl reads the current state from the registry but writes throughpowercfg.exe /hibernate on|offso the hibernation file is actually managed correctly.- Any key you leave out of the policy is never touched — there’s no default guessed for an unspecified setting.
power_plan_enforcer.rb
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# power_plan_enforcer.rb
#
# Idempotent Windows power-policy enforcement across a fleet: makes sure
# each box is on the right power plan (WMI), has Fast Startup set the way
# you want it (registry), and has hibernation on or off as policy demands
# (powercfg.exe). Three different automation surfaces in one small script,
# which is exactly the kind of glue Ruby is good at on Windows -- the
# alternative is three separate PowerShell one-liners nobody remembers to
# run together.
#
# Why this matters operationally: a server that silently drifts onto
# "Balanced" power policy throttles CPU under light load (surprising perf
# regressions that look like application bugs), and a server with Fast
# Startup enabled skips full driver re-init on "shutdown", which is a
# classic cause of stale-state bugs after a maintenance reboot. Both are
# one WMI/registry write away from fixed -- if you remember to check.
#
# Only standard library plus win32ole on Windows: win32ole, win32/registry
# (both Windows-only, required lazily), optparse, yaml, open3.
#
# Everything that actually talks to Windows (WMI, the registry, powercfg)
# is behind a small injectable interface so the reconciliation logic
# (PowerPolicyEnforcer#plan / #apply!) can be fully unit-tested on any
# platform, including this one -- see power_plan_enforcer_test.rb, which
# runs entirely off fixtures.
require 'optparse'
require 'yaml'
# --------------------------------------------------------------------------
# Talks to the root\cimv2\power WMI namespace for power-plan enumeration
# and activation. Only instantiated on a real run; tests inject a fake
# with the same three methods instead.
# --------------------------------------------------------------------------
class WmiPowerPlans
def initialize
require 'win32ole'
@wmi = WIN32OLE.connect('winmgmts:\\\\.\\root\\cimv2\\power')
end
# Returns an Array of { name:, is_active:, instance_id: } hashes.
def list
@wmi.ExecQuery('SELECT * FROM Win32_PowerPlan').to_enum.map do |plan|
{ name: plan.ElementName, is_active: plan.IsActive, instance_id: plan.InstanceID }
end
end
def activate(instance_id)
plan = @wmi.ExecQuery(
"SELECT * FROM Win32_PowerPlan WHERE InstanceID = '#{instance_id.gsub("'", "''")}'"
).to_enum.first
raise "power plan #{instance_id} vanished before activation" unless plan
plan.Activate
end
end
# --------------------------------------------------------------------------
# Fast Startup lives entirely in the registry (HiberbootEnabled). Real
# implementation uses Win32::Registry; tests inject an in-memory Hash.
# --------------------------------------------------------------------------
class WindowsRegistry
KEY_PATH = 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Power'
VALUE = 'HiberbootEnabled'
def read_hiberboot_enabled
require 'win32/registry'
Win32::Registry::HKEY_LOCAL_MACHINE.open(KEY_PATH) do |reg|
reg[VALUE, Win32::Registry::REG_DWORD]
end
rescue Win32::Registry::Error
nil # value not present -- Windows treats this as "unset", not an error
end
def write_hiberboot_enabled(value)
require 'win32/registry'
Win32::Registry::HKEY_LOCAL_MACHINE.open(KEY_PATH, Win32::Registry::KEY_WRITE) do |reg|
reg.write(VALUE, Win32::Registry::REG_DWORD, value)
end
end
end
# --------------------------------------------------------------------------
# Hibernation on/off isn't a simple registry flag -- turning it on/off
# through the supported path allocates/frees hiberfil.sys, which only
# powercfg.exe does correctly. Real implementation shells out; tests
# inject a fake runner (same Open3-wrapping pattern used elsewhere in this
# repo, e.g. deploy_webhook_orchestrator's RetryingHttpClient).
# --------------------------------------------------------------------------
class PowercfgControl
Result = Struct.new(:success, :stdout, :stderr, keyword_init: true)
def hibernation_enabled?
require 'win32/registry'
Win32::Registry::HKEY_LOCAL_MACHINE.open('SYSTEM\\CurrentControlSet\\Control\\Power') do |reg|
reg['HibernateEnabled', Win32::Registry::REG_DWORD] == 1
end
rescue Win32::Registry::Error
false
end
def set_hibernation(enabled)
require 'open3'
stdout, stderr, status = Open3.capture3('powercfg.exe', '/hibernate', enabled ? 'on' : 'off')
Result.new(success: status.success?, stdout: stdout, stderr: stderr)
end
end
# --------------------------------------------------------------------------
# Core reconciler. Depends only on the three small interfaces above (each
# with #list/#activate, #read_/#write_hiberboot_enabled, and
# #hibernation_enabled?/#set_hibernation) -- never on WIN32OLE or
# Win32::Registry directly, which is what makes it testable off Windows.
# --------------------------------------------------------------------------
class PowerPolicyEnforcer
Action = Struct.new(:kind, :detail, :payload, keyword_init: true) do
def describe = "#{kind.to_s.upcase.ljust(20)} #{detail}"
end
def initialize(policy, power_plans:, registry:, powercfg:, logger: method(:puts))
@policy = policy
@power_plans = power_plans
@registry = registry
@powercfg = powercfg
@logger = logger
end
def plan
actions = []
actions.concat(plan_power_plan)
actions.concat(plan_fast_startup)
actions.concat(plan_hibernation)
actions
end
def apply!(actions, dry_run: true)
actions.each do |action|
@logger.call((dry_run ? '[dry-run] ' : '[apply] ') + action.describe)
next if dry_run
execute(action)
end
end
private
def plan_power_plan
return [] unless @policy['active_plan']
plans = @power_plans.list
target = plans.find { |p| p[:name].casecmp?(@policy['active_plan']) }
unless target
@logger.call("WARNING: power plan #{@policy['active_plan'].inspect} not found on this system; skipping")
return []
end
return [] if target[:is_active]
currently_active = plans.find { |p| p[:is_active] }
[Action.new(kind: :activate_power_plan,
detail: "#{currently_active && currently_active[:name]} -> #{target[:name]} (#{target[:instance_id]})",
payload: target[:instance_id])]
end
def plan_fast_startup
return [] unless @policy.key?('fast_startup_enabled')
wanted = @policy['fast_startup_enabled'] ? 1 : 0
current = @registry.read_hiberboot_enabled
return [] if current == wanted
[Action.new(kind: :set_fast_startup, detail: "HiberbootEnabled #{current.inspect} -> #{wanted}", payload: wanted)]
end
def plan_hibernation
return [] unless @policy.key?('hibernation_enabled')
wanted = @policy['hibernation_enabled']
current = @powercfg.hibernation_enabled?
return [] if current == wanted
[Action.new(kind: :set_hibernation, detail: "hibernation #{current} -> #{wanted}", payload: wanted)]
end
def execute(action)
case action.kind
when :activate_power_plan
@power_plans.activate(action.payload)
when :set_fast_startup
@registry.write_hiberboot_enabled(action.payload)
when :set_hibernation
result = @powercfg.set_hibernation(action.payload)
unless result.success
@logger.call(" -> FAILED: powercfg /hibernate #{action.payload ? 'on' : 'off'}: #{result.stderr}")
end
else
raise "unknown action kind #{action.kind}"
end
end
end
# --------------------------------------------------------------------------
# CLI
# --------------------------------------------------------------------------
if __FILE__ == $PROGRAM_NAME
options = { apply: false }
OptionParser.new do |opts|
opts.banner = 'Usage: power_plan_enforcer.rb --policy policy.yml [--apply]'
opts.on('--policy PATH', 'YAML policy describing the desired power state (required)') { |v| options[:policy] = v }
opts.on('--apply', 'Actually make the changes (default: dry-run only)') { options[:apply] = true }
opts.on('-h', '--help', 'Show this help') { puts opts; exit 0 }
end.parse!(ARGV)
unless options[:policy]
warn 'error: --policy is required'
exit 4
end
unless RUBY_PLATFORM =~ /mswin|mingw|cygwin/
warn 'This script talks to WMI and the Windows registry and can only run for real on Windows.'
warn 'Run power_plan_enforcer_test.rb instead to exercise its logic on this platform.'
exit 4
end
policy = YAML.safe_load_file(options[:policy])
enforcer = PowerPolicyEnforcer.new(
policy,
power_plans: WmiPowerPlans.new,
registry: WindowsRegistry.new,
powercfg: PowercfgControl.new
)
actions = enforcer.plan
if actions.empty?
puts 'No drift detected -- power policy already matches spec.'
exit 0
end
puts "#{actions.size} change(s) needed:"
enforcer.apply!(actions, dry_run: !options[:apply])
puts "\nDry run only -- re-run with --apply to make these changes." unless options[:apply]
exit 0
end
How this was verified
- None of
WIN32OLE,Win32::Registry, orpowercfg.exeexist on Linux — and even on real Windows, flipping a box’s power plan mid test run is not something a test suite should do for real. power_plan_enforcer_test.rbexercisesPowerPolicyEnforcerentirely againstFakePowerPlans/FakeRegistry/FakePowercfgdoubles shaped exactly like the real interfaces — the same fixture-double pattern this repo uses for its other WMI-dependent scripts.- Scenario 1 — system on “Balanced,” policy wants “High performance”: exactly one
activate_power_planaction, targeting the right instance id, and.activateis confirmed called for real in apply mode. - Scenario 2 — already-compliant plan produces zero actions (idempotency).
- Scenario 3 — Fast Startup and hibernation both drifted; both get corrected, dry-run touches neither, and the fakes reflect the new values after a real apply.
- Scenario 4 — a policy that omits a setting entirely leaves it completely alone — no accidental writes for unspecified keys.
- All four run live in this Linux sandbox — see the output tab above.
Common issues
- “This script talks to WMI and the Windows registry and can only run for real on Windows” — you ran it somewhere other than Windows; use the test suite to exercise the logic anywhere else.
- “power plan not found on this system” — the plan name must match
Win32_PowerPlan.ElementNameexactly (case-insensitively); runpowercfg /listto see the exact names — OEM-branded plans often don’t match the stock names. - Fast Startup change doesn’t seem to take effect — it only matters if hibernation is enabled at all, since Fast Startup is implemented as a partial hibernate.
- powercfg /hibernate off fails with access denied — you’re not elevated;
--applygenuinely needs an administrator shell for all three surfaces.
Where to take this next
- Monitor/disk/sleep timeout enforcement via
Win32_PowerSettingDataIndex— deliberately left out here to stay testable; those classes are keyed by GUID pairs, considerably more involved. - A
--fleet hosts.txtmode connecting to each host’s WMI over DCOM instead of only the local machine. - A
--jsonplan output to feed a fleet-wide compliance dashboard.