From c1168d4b67a5a8f5cabe43e47a7a02ba6865ed43 Mon Sep 17 00:00:00 2001 From: Jean-Paul van Ravensberg <14926452+DevSecNinja@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:22:13 +0200 Subject: [PATCH] fix(windows): respect managed power button policy Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...ge_disable-surface-laptop-power-button.ps1 | 72 ++++++++++- .../SurfaceLaptopPowerButton.Tests.ps1 | 118 +++++++++++++++++- 2 files changed, 184 insertions(+), 6 deletions(-) diff --git a/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1 b/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1 index 1cd6a3cf..dce32cb4 100644 --- a/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1 +++ b/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1 @@ -41,13 +41,54 @@ function Invoke-PowerCfg { throw "powercfg.exe was not found at '$powerCfgPath'." } - $output = & $powerCfgPath @ArgumentList 2>&1 - if ($LASTEXITCODE -ne 0) { + $previousErrorActionPreference = $ErrorActionPreference + try { + # Windows PowerShell 5.1 surfaces native stderr as NativeCommandError. + # Capture it for the exit-code error without treating stderr as success. + $ErrorActionPreference = "Continue" + $output = & $powerCfgPath @ArgumentList 2>&1 + $exitCode = $LASTEXITCODE + } + finally { + $ErrorActionPreference = $previousErrorActionPreference + } + + if ($exitCode -ne 0) { $details = ($output | Out-String).Trim() - throw "powercfg.exe failed with exit code $LASTEXITCODE for '$($ArgumentList -join ' ')': $details" + throw "powercfg.exe failed with exit code $exitCode for '$($ArgumentList -join ' ')': $details" } } +function Get-PowerButtonPolicy { + $policyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Power\PowerSettings\7648EFA3-DD9C-4E3E-B566-50F929386280" + $policy = [pscustomobject]@{ + Path = $policyPath + HasAC = $false + AC = $null + HasDC = $false + DC = $null + } + + if (-not (Test-Path -LiteralPath $policyPath)) { + return $policy + } + + $values = Get-ItemProperty -LiteralPath $policyPath -ErrorAction Stop + $acProperty = $values.PSObject.Properties["ACSettingIndex"] + if ($null -ne $acProperty) { + $policy.HasAC = $true + $policy.AC = [int]$acProperty.Value + } + + $dcProperty = $values.PSObject.Properties["DCSettingIndex"] + if ($null -ne $dcProperty) { + $policy.HasDC = $true + $policy.DC = [int]$dcProperty.Value + } + + return $policy +} + function Disable-SurfaceLaptopPowerButton { [CmdletBinding(SupportsShouldProcess)] param( @@ -58,6 +99,10 @@ function Disable-SurfaceLaptopPowerButton { [scriptblock]$InvokePowerCfg = { param([string[]]$ArgumentList) Invoke-PowerCfg -ArgumentList $ArgumentList + }, + + [scriptblock]$GetPowerButtonPolicy = { + Get-PowerButtonPolicy } ) @@ -71,6 +116,27 @@ function Disable-SurfaceLaptopPowerButton { } } + $policy = & $GetPowerButtonPolicy + if ($policy.HasAC -and $policy.HasDC -and $policy.AC -eq 0 -and $policy.DC -eq 0) { + Write-Host "[OK] Power button action is managed by Group Policy for AC and battery power." -ForegroundColor Green + return [pscustomobject]@{ + Status = "ManagedByPolicy" + Changed = $false + Model = [string]$computerSystem.Model + } + } + + if ($policy.HasAC -or $policy.HasDC) { + $acValue = if ($policy.HasAC) { [string]$policy.AC } else { "" } + $dcValue = if ($policy.HasDC) { [string]$policy.DC } else { "" } + throw ( + "Group Policy owns the Surface Laptop power-button setting but does not enforce " + + "'Take no action' for both power states (ACSettingIndex=$acValue, " + + "DCSettingIndex=$dcValue). Update or remove the policy at '$($policy.Path)'; " + + "dotfiles will not override managed settings." + ) + } + $powerButtonSetting = "7648efa3-dd9c-4e3e-b566-50f929386280" $buttonSubgroup = "4f971e89-eebd-4455-a8de-9e59040e7347" $commands = @( diff --git a/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 b/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 index 52b3568d..879a29e8 100644 --- a/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 +++ b/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 @@ -66,6 +66,27 @@ Describe "Surface Laptop power button script" -Tag "Unit" { }) | Should -BeFalse } + It "reads the documented AC and DC policy values" { + $expectedPath = "HKLM:\SOFTWARE\Policies\Microsoft\Power\PowerSettings\7648EFA3-DD9C-4E3E-B566-50F929386280" + Mock Test-Path { $true } -ParameterFilter { $LiteralPath -eq $expectedPath } + Mock Get-ItemProperty { + [pscustomobject]@{ + ACSettingIndex = 0 + DCSettingIndex = 0 + } + } -ParameterFilter { $LiteralPath -eq $expectedPath } + + $result = Get-PowerButtonPolicy + + $result.Path | Should -Be $expectedPath + $result.HasAC | Should -BeTrue + $result.AC | Should -Be 0 + $result.HasDC | Should -BeTrue + $result.DC | Should -Be 0 + Should -Invoke Get-ItemProperty -Times 1 -Exactly ` + -ParameterFilter { $LiteralPath -eq $expectedPath } + } + It "does not invoke powercfg on non-Surface hardware" { $script:PowerCfgCalls = @() @@ -99,6 +120,14 @@ Describe "Surface Laptop power button script" -Tag "Unit" { -InvokePowerCfg { param([string[]]$ArgumentList) $script:PowerCfgCalls += , $ArgumentList + } ` + -GetPowerButtonPolicy { + [pscustomobject]@{ + HasAC = $false + AC = $null + HasDC = $false + DC = $null + } } $result.Status | Should -Be "Disabled" @@ -117,6 +146,73 @@ Describe "Surface Laptop power button script" -Tag "Unit" { $script:PowerCfgCalls[2] -join " " | Should -Be "/SETACTIVE SCHEME_CURRENT" } + It "does not invoke powercfg when the desired policy manages both power states" { + $script:PowerCfgCalls = @() + $policy = { + [pscustomobject]@{ + Path = "HKLM:\policy" + HasAC = $true + AC = 0 + HasDC = $true + DC = 0 + } + } + $surface = { + [pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Surface Laptop 7" + } + } + $powerCfg = { + param([string[]]$ArgumentList) + $script:PowerCfgCalls += , $ArgumentList + } + + $firstResult = Disable-SurfaceLaptopPowerButton ` + -GetComputerSystem $surface ` + -InvokePowerCfg $powerCfg ` + -GetPowerButtonPolicy $policy + $secondResult = Disable-SurfaceLaptopPowerButton ` + -GetComputerSystem $surface ` + -InvokePowerCfg $powerCfg ` + -GetPowerButtonPolicy $policy + + $firstResult.Status | Should -Be "ManagedByPolicy" + $firstResult.Changed | Should -BeFalse + $secondResult.Status | Should -Be "ManagedByPolicy" + $secondResult.Changed | Should -BeFalse + $script:PowerCfgCalls | Should -BeNullOrEmpty + } + + It "rejects conflicting policy without invoking powercfg" { + $script:PowerCfgCalls = @() + + { + Disable-SurfaceLaptopPowerButton ` + -GetComputerSystem { + [pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Surface Laptop 7" + } + } ` + -InvokePowerCfg { + param([string[]]$ArgumentList) + $script:PowerCfgCalls += , $ArgumentList + } ` + -GetPowerButtonPolicy { + [pscustomobject]@{ + Path = "HKLM:\SOFTWARE\Policies\Microsoft\Power\PowerSettings\7648EFA3-DD9C-4E3E-B566-50F929386280" + HasAC = $true + AC = 1 + HasDC = $true + DC = 0 + } + } + } | Should -Throw "*Group Policy owns*ACSettingIndex=1, DCSettingIndex=0*will not override*" + + $script:PowerCfgCalls | Should -BeNullOrEmpty + } + It "does not invoke powercfg under WhatIf" { $script:PowerCfgCalls = @() @@ -131,6 +227,14 @@ Describe "Surface Laptop power button script" -Tag "Unit" { param([string[]]$ArgumentList) $script:PowerCfgCalls += , $ArgumentList } ` + -GetPowerButtonPolicy { + [pscustomobject]@{ + HasAC = $false + AC = $null + HasDC = $false + DC = $null + } + } ` -WhatIf $result.Status | Should -Be "WhatIf" @@ -138,7 +242,7 @@ Describe "Surface Laptop power button script" -Tag "Unit" { $script:PowerCfgCalls | Should -BeNullOrEmpty } - It "propagates powercfg failures" { + It "propagates native-command failures on unmanaged devices" { { Disable-SurfaceLaptopPowerButton ` -GetComputerSystem { @@ -148,9 +252,17 @@ Describe "Surface Laptop power button script" -Tag "Unit" { } } ` -InvokePowerCfg { - throw "powercfg failed" + throw "powercfg.exe failed with exit code 1" + } ` + -GetPowerButtonPolicy { + [pscustomobject]@{ + HasAC = $false + AC = $null + HasDC = $false + DC = $null + } } - } | Should -Throw "*powercfg failed*" + } | Should -Throw "*powercfg.exe failed with exit code 1*" } }