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で再現されます。

コメント