PowerShellでシステムのスリープ時間を変更する方法

sleep timeoutを変更するときは、active power scheme GUIDを一件だけ取得し、そのschemeのAC/DC STANDBYIDLE indexを保存します。提案値は秒単位でpreviewし、承認後にscheme-bound setac/setdcを終了code付きで実行します。再activateとexact readback、独立rollbackまで同じGUIDへ固定します。

目次

active schemeと旧AC/DC indexを一意に保存する

powercfg /getactiveschemeからGUIDが一件だけ取れることを要求します。WMI power namespaceのSTANDBYIDLE settingから同じschemeのAC/DC rowをexactly oneずつ取得し、old indexとproposed secondsをCreateNew JSONへ保存します。

$ErrorActionPreference='Stop'
function Get-OneActiveScheme {
  $raw=@(& powercfg.exe /getactivescheme);if($LASTEXITCODE -ne 0){throw "getactivescheme failed: $LASTEXITCODE"}
  $matches=[regex]::Matches([string]::Join([Environment]::NewLine,$raw),'[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}')
  $guids=@($matches.Value|ForEach-Object {$_.ToLowerInvariant()}|Sort-Object -Unique)
  if($guids.Count -ne 1){throw 'active scheme GUID is ambiguous'}
  $guids[0]
}
function Get-StandbyIdleIndex([string]$Scheme){
  $setting='29f6c1db-86da-48c5-9fdb-f2b67b1f44da'
  $rows=@(Get-CimInstance -Namespace root/cimv2/power -ClassName Win32_PowerSettingDataIndex -ErrorAction Stop|Where-Object {$_.InstanceID -match [regex]::Escape($Scheme) -and $_.InstanceID -match [regex]::Escape($setting)})
  $ac=@($rows|Where-Object InstanceID -match '\\AC\\');$dc=@($rows|Where-Object InstanceID -match '\\DC\\')
  if($ac.Count -ne 1 -or $dc.Count -ne 1){throw 'AC/DC STANDBYIDLE indices are ambiguous'}
  [pscustomobject]@{Scheme=$Scheme;AcIndex=[uint32]$ac[0].SettingIndexValue;DcIndex=[uint32]$dc[0].SettingIndexValue}
}
$scheme=Get-OneActiveScheme;$old=Get-StandbyIdleIndex $scheme
$newAcSeconds=[uint32]1800;$newDcSeconds=[uint32]900
$statePath=Join-Path 'C:\ProgramData\ITtrip\Power' ("sleep-$([guid]::NewGuid().ToString('N')).json")
$null=New-Item -ItemType Directory -Path (Split-Path $statePath) -Force -ErrorAction Stop
$state=[ordered]@{Stage='Planned';Scheme=$scheme;OldAcIndex=$old.AcIndex;OldDcIndex=$old.DcIndex;NewAcIndex=$newAcSeconds;NewDcIndex=$newDcSeconds}
$bytes=[Text.Encoding]::UTF8.GetBytes(($state|ConvertTo-Json));$stream=[IO.File]::Open($statePath,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None);try{$stream.Write($bytes,0,$bytes.Length);$stream.Flush($true)}finally{$stream.Dispose()}
[pscustomobject]@{StatePath=$statePath;ActiveScheme=$scheme;OldAcSeconds=$old.AcIndex;OldDcSeconds=$old.DcIndex;ProposedAcSeconds=$newAcSeconds;ProposedDcSeconds=$newDcSeconds}

preview後にscheme-bound settingを変更する

tokenにはscheme GUIDとold→new AC/DC secondsを含めます。承認後もactive schemeとold indicesが同じであることを再確認し、/setacvalueindex、/setdcvalueindex、/setactiveの各exit codeを検査します。途中失敗時は保存値へbest-effort復旧してerrorを返します。

$token="SET-SLEEP SCHEME=$($state.Scheme) AC=$($state.OldAcIndex)->$($state.NewAcIndex) DC=$($state.OldDcIndex)->$($state.NewDcIndex)"
if((Read-Host "active schemeと提案秒数を確認し、変更する場合は $token") -ne $token){throw 'sleep-time change not approved'}
if((Get-OneActiveScheme) -cne $state.Scheme){throw 'active scheme changed after preview'}
$before=Get-StandbyIdleIndex $state.Scheme
if($before.AcIndex -ne $state.OldAcIndex -or $before.DcIndex -ne $state.OldDcIndex){throw 'STANDBYIDLE baseline changed after preview'}
try{
  $null=& powercfg.exe /setacvalueindex $state.Scheme SUB_SLEEP STANDBYIDLE $state.NewAcIndex;if($LASTEXITCODE -ne 0){throw "setacvalueindex failed: $LASTEXITCODE"}
  $null=& powercfg.exe /setdcvalueindex $state.Scheme SUB_SLEEP STANDBYIDLE $state.NewDcIndex;if($LASTEXITCODE -ne 0){throw "setdcvalueindex failed: $LASTEXITCODE"}
  $null=& powercfg.exe /setactive $state.Scheme;if($LASTEXITCODE -ne 0){throw "setactive failed: $LASTEXITCODE"}
}catch{
  $null=& powercfg.exe /setacvalueindex $state.Scheme SUB_SLEEP STANDBYIDLE $state.OldAcIndex
  $null=& powercfg.exe /setdcvalueindex $state.Scheme SUB_SLEEP STANDBYIDLE $state.OldDcIndex
  $null=& powercfg.exe /setactive $state.Scheme
  throw
}

reactivate後にexact readbackする

active GUIDとAC/DC indicesを再取得し、提案値との完全一致を確認します。一方だけの成功や別schemeのactive化を完了扱いにしません。

$after=Get-StandbyIdleIndex $state.Scheme
$active=Get-OneActiveScheme
if($active -cne $state.Scheme -or $after.AcIndex -ne $state.NewAcIndex -or $after.DcIndex -ne $state.NewDcIndex){throw 'active scheme or STANDBYIDLE readback mismatch'}
$state.Stage='AppliedVerified';$state|ConvertTo-Json|Set-Content -LiteralPath $statePath -Encoding UTF8 -ErrorAction Stop
[pscustomobject]@{Stage=$state.Stage;ActiveScheme=$active;AcSeconds=$after.AcIndex;DcSeconds=$after.DcIndex}

standalone rollbackで保存値へ戻す

fresh sessionでstateを読み、active schemeとcurrent new indicesをbindしてから別tokenを要求します。保存済みold AC/DCを同じschemeへ設定し、再activateとreadbackが完全一致した場合だけRollbackVerifiedです。

# standalone rollback: fresh sessionで保存済みscheme/old indicesへ戻す。
$statePath='PASTE_STATE_PATH';$state=Get-Content -LiteralPath $statePath -Raw -ErrorAction Stop|ConvertFrom-Json
if($state.Stage -ne 'AppliedVerified'){throw 'rollback requires applied-and-verified state'}
if((Get-OneActiveScheme) -cne $state.Scheme){throw 'rollback refused: another scheme is active'}
$current=Get-StandbyIdleIndex $state.Scheme
if($current.AcIndex -ne $state.NewAcIndex -or $current.DcIndex -ne $state.NewDcIndex){throw 'rollback refused: current indices differ from applied values'}
$token="ROLLBACK-SLEEP SCHEME=$($state.Scheme) AC=$($state.OldAcIndex) DC=$($state.OldDcIndex)"
if((Read-Host "保存済み値へ戻す場合は $token") -ne $token){throw 'rollback not approved'}
$null=& powercfg.exe /setacvalueindex $state.Scheme SUB_SLEEP STANDBYIDLE $state.OldAcIndex;if($LASTEXITCODE -ne 0){throw "rollback setac failed: $LASTEXITCODE"}
$null=& powercfg.exe /setdcvalueindex $state.Scheme SUB_SLEEP STANDBYIDLE $state.OldDcIndex;if($LASTEXITCODE -ne 0){throw "rollback setdc failed: $LASTEXITCODE"}
$null=& powercfg.exe /setactive $state.Scheme;if($LASTEXITCODE -ne 0){throw "rollback setactive failed: $LASTEXITCODE"}
$restored=Get-StandbyIdleIndex $state.Scheme;$active=Get-OneActiveScheme
if($active -cne $state.Scheme -or $restored.AcIndex -ne $state.OldAcIndex -or $restored.DcIndex -ne $state.OldDcIndex){throw 'rollback exact readback failed'}
[pscustomobject]@{Stage='RollbackVerified';ActiveScheme=$active;AcSeconds=$restored.AcIndex;DcSeconds=$restored.DcIndex}

単位と0の意味

STANDBYIDLE indexは秒です。0はneverを意味する構成があるため、minutes表示との混同を避け、AC/DCそれぞれの業務policyを明示します。hibernateやdisplay timeoutは別settingです。

受入条件

複数GUID、AC/DC row欠落、preview後のscheme/index変化、任意commandのnonzero exit、readback mismatchでは停止します。clean fixtureでは同じGUIDのold→new→oldが両indexで再現されます。

公式情報・参考資料

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次