Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -58,6 +99,10 @@ function Disable-SurfaceLaptopPowerButton {
[scriptblock]$InvokePowerCfg = {
param([string[]]$ArgumentList)
Invoke-PowerCfg -ArgumentList $ArgumentList
},

[scriptblock]$GetPowerButtonPolicy = {
Get-PowerButtonPolicy
}
)

Expand All @@ -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 { "<not configured>" }
$dcValue = if ($policy.HasDC) { [string]$policy.DC } else { "<not configured>" }
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 = @(
Expand Down
118 changes: 115 additions & 3 deletions tests/powershell/SurfaceLaptopPowerButton.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @()

Expand Down Expand Up @@ -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"
Expand All @@ -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 = @()

Expand All @@ -131,14 +227,22 @@ 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"
$result.Changed | Should -BeFalse
$script:PowerCfgCalls | Should -BeNullOrEmpty
}

It "propagates powercfg failures" {
It "propagates native-command failures on unmanaged devices" {
{
Disable-SurfaceLaptopPowerButton `
-GetComputerSystem {
Expand All @@ -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*"
}
}

Expand Down