From a596d8aacce1458a9798465959bd839f96df7eb2 Mon Sep 17 00:00:00 2001 From: Jean-Paul van Ravensberg <14926452+DevSecNinja@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:58:57 +0200 Subject: [PATCH 1/3] feat(windows): disable Surface Laptop power button Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6c0af4ce-01e4-448b-bc3c-6f7d7ee92610 --- README.md | 4 + docs/customization.md | 5 + ...sable-surface-laptop-power-button.ps1.tmpl | 113 +++++++++++++ .../SurfaceLaptopPowerButton.Tests.ps1 | 151 ++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl create mode 100644 tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 diff --git a/README.md b/README.md index e7268bd3..d11c7f9f 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,10 @@ The repository supports two installation modes: - **Light mode** (servers, CI, codespaces): Essential tools only - **Full mode** (dev servers, workstations): Full development tooling including Task and mise +On Windows, full mode also applies opinionated workstation settings. On +Microsoft Surface Laptop models, the hardware power button is set to **Do +nothing** for both AC and battery power. + The mode is auto-detected based on: - Hostname patterns (SVLDEV* = full, SVL* = light) - Environment (codespaces, devcontainer, CI = light) diff --git a/docs/customization.md b/docs/customization.md index 506339bb..93756a53 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -21,6 +21,11 @@ The repository supports two installation modes: - **Full mode** (dev servers, workstations) — Full development tooling including Task and mise. +On Windows, full mode also applies opinionated workstation settings. Microsoft +Surface Laptop models have the hardware power button set to **Do nothing** for +both AC and battery power, preventing an accidental press next to Delete from +suspending or shutting down the computer. + The mode is auto-detected based on: - Hostname patterns (`SVL*DEV*` = full, `SVL*` = light). diff --git a/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl b/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl new file mode 100644 index 00000000..4e0a7894 --- /dev/null +++ b/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl @@ -0,0 +1,113 @@ +# {{ if and (eq .chezmoi.os "windows") (eq .installType "full") }} +#!/usr/bin/env pwsh +# Disable the hardware power button on Microsoft Surface Laptop models. +# Runs in the opinionated full profile and is idempotent. + +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + "PSAvoidUsingWriteHost", + "", + Justification = "Matches existing chezmoi setup script progress output." +)] +param( + [switch]$SkipApply +) + +$ErrorActionPreference = "Stop" + +function Test-SurfaceLaptop { + param( + [AllowNull()] + [object]$ComputerSystem + ) + + if ($null -eq $ComputerSystem) { + return $false + } + + $manufacturer = [string]$ComputerSystem.Manufacturer + $model = [string]$ComputerSystem.Model + + return $manufacturer.Trim() -eq "Microsoft Corporation" -and + $model.Trim() -match "^Surface Laptop(?:\s|$)" +} + +function Invoke-PowerCfg { + param( + [Parameter(Mandatory = $true)] + [string[]]$ArgumentList + ) + + $powerCfgPath = Join-Path $env:SystemRoot "System32\powercfg.exe" + if (-not (Test-Path -LiteralPath $powerCfgPath -PathType Leaf)) { + throw "powercfg.exe was not found at '$powerCfgPath'." + } + + $output = & $powerCfgPath @ArgumentList 2>&1 + if ($LASTEXITCODE -ne 0) { + $details = ($output | Out-String).Trim() + throw "powercfg.exe failed with exit code $LASTEXITCODE for '$($ArgumentList -join ' ')': $details" + } +} + +function Disable-SurfaceLaptopPowerButton { + [CmdletBinding(SupportsShouldProcess)] + param( + [scriptblock]$GetComputerSystem = { + Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop + }, + + [scriptblock]$InvokePowerCfg = { + param([string[]]$ArgumentList) + Invoke-PowerCfg -ArgumentList $ArgumentList + } + ) + + $computerSystem = & $GetComputerSystem + if (-not (Test-SurfaceLaptop -ComputerSystem $computerSystem)) { + Write-Host "[SKIP] Power button unchanged: this device is not a Microsoft Surface Laptop." -ForegroundColor Yellow + return [pscustomobject]@{ + Status = "NotSurfaceLaptop" + Changed = $false + Model = [string]$computerSystem.Model + } + } + + $powerButtonSetting = "7648efa3-dd9c-4e3e-b566-50f929386280" + $buttonSubgroup = "4f971e89-eebd-4455-a8de-9e59040e7347" + $commands = @( + [pscustomobject]@{ + ArgumentList = @("/SETACVALUEINDEX", "SCHEME_CURRENT", $buttonSubgroup, $powerButtonSetting, "0") + } + [pscustomobject]@{ + ArgumentList = @("/SETDCVALUEINDEX", "SCHEME_CURRENT", $buttonSubgroup, $powerButtonSetting, "0") + } + [pscustomobject]@{ + ArgumentList = @("/SETACTIVE", "SCHEME_CURRENT") + } + ) + + $model = [string]$computerSystem.Model + if (-not $PSCmdlet.ShouldProcess($model, "Set the AC and battery power button action to Do nothing")) { + return [pscustomobject]@{ + Status = "WhatIf" + Changed = $false + Model = $model + } + } + + foreach ($command in $commands) { + & $InvokePowerCfg -ArgumentList $command.ArgumentList + } + + Write-Host "[OK] Disabled the power button on $model for AC and battery power." -ForegroundColor Green + return [pscustomobject]@{ + Status = "Disabled" + Changed = $true + Model = $model + } +} + +if (-not $SkipApply) { + Disable-SurfaceLaptopPowerButton | Out-Null +} +# {{ end }} diff --git a/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 b/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 new file mode 100644 index 00000000..d18259a7 --- /dev/null +++ b/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 @@ -0,0 +1,151 @@ +#Requires -Version 7.0 +<# +.SYNOPSIS + Tests the Surface Laptop power-button configuration. +#> + +BeforeAll { + $script:RepoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent + $script:ScriptPath = Join-Path $script:RepoRoot ` + "home\.chezmoiscripts\windows\run_onchange_disable-surface-laptop-power-button.ps1.tmpl" + + $scriptContent = Get-Content -LiteralPath $script:ScriptPath -Raw + . ([scriptblock]::Create($scriptContent)) -SkipApply +} + +Describe "Surface Laptop power button script" -Tag "Unit" { + It "exists and has valid PowerShell syntax" { + $script:ScriptPath | Should -Exist + + $errors = $null + [System.Management.Automation.Language.Parser]::ParseFile( + $script:ScriptPath, + [ref]$null, + [ref]$errors + ) | Out-Null + + $errors | Should -BeNullOrEmpty + } + + It "is limited to the Windows full profile" { + $content = Get-Content -LiteralPath $script:ScriptPath -Raw + + $content | Should -Match 'eq \.chezmoi\.os "windows"' + $content | Should -Match 'eq \.installType "full"' + } + + It "recognizes Microsoft Surface Laptop models" { + Test-SurfaceLaptop -ComputerSystem ([pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Surface Laptop 7th Edition" + }) | Should -BeTrue + } + + It "recognizes Surface Laptop Studio models" { + Test-SurfaceLaptop -ComputerSystem ([pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Surface Laptop Studio 2" + }) | Should -BeTrue + } + + It "does not match other Surface form factors" { + Test-SurfaceLaptop -ComputerSystem ([pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Surface Pro 11th Edition" + }) | Should -BeFalse + } + + It "requires Microsoft as the manufacturer" { + Test-SurfaceLaptop -ComputerSystem ([pscustomobject]@{ + Manufacturer = "Contoso" + Model = "Surface Laptop 7" + }) | Should -BeFalse + } + + It "does not invoke powercfg on non-Surface hardware" { + $script:PowerCfgCalls = @() + + $result = Disable-SurfaceLaptopPowerButton ` + -GetComputerSystem { + [pscustomobject]@{ + Manufacturer = "Dell Inc." + Model = "XPS 13" + } + } ` + -InvokePowerCfg { + param([string[]]$ArgumentList) + $script:PowerCfgCalls += , $ArgumentList + } + + $result.Status | Should -Be "NotSurfaceLaptop" + $result.Changed | Should -BeFalse + $script:PowerCfgCalls | Should -BeNullOrEmpty + } + + It "disables the current plan power button action for AC and battery power" { + $script:PowerCfgCalls = @() + + $result = Disable-SurfaceLaptopPowerButton ` + -GetComputerSystem { + [pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Surface Laptop 7" + } + } ` + -InvokePowerCfg { + param([string[]]$ArgumentList) + $script:PowerCfgCalls += , $ArgumentList + } + + $result.Status | Should -Be "Disabled" + $result.Changed | Should -BeTrue + $script:PowerCfgCalls.Count | Should -Be 3 + $script:PowerCfgCalls[0] -join " " | Should -Be ( + "/SETACVALUEINDEX SCHEME_CURRENT " + + "4f971e89-eebd-4455-a8de-9e59040e7347 " + + "7648efa3-dd9c-4e3e-b566-50f929386280 0" + ) + $script:PowerCfgCalls[1] -join " " | Should -Be ( + "/SETDCVALUEINDEX SCHEME_CURRENT " + + "4f971e89-eebd-4455-a8de-9e59040e7347 " + + "7648efa3-dd9c-4e3e-b566-50f929386280 0" + ) + $script:PowerCfgCalls[2] -join " " | Should -Be "/SETACTIVE SCHEME_CURRENT" + } + + It "does not invoke powercfg under WhatIf" { + $script:PowerCfgCalls = @() + + $result = Disable-SurfaceLaptopPowerButton ` + -GetComputerSystem { + [pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Surface Laptop 7" + } + } ` + -InvokePowerCfg { + param([string[]]$ArgumentList) + $script:PowerCfgCalls += , $ArgumentList + } ` + -WhatIf + + $result.Status | Should -Be "WhatIf" + $result.Changed | Should -BeFalse + $script:PowerCfgCalls | Should -BeNullOrEmpty + } + + It "propagates powercfg failures" { + { + Disable-SurfaceLaptopPowerButton ` + -GetComputerSystem { + [pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Surface Laptop 7" + } + } ` + -InvokePowerCfg { + throw "powercfg failed" + } + } | Should -Throw "*powercfg failed*" + } +} From 8fccb3993f31f61516082f88bccdaf9ac61ae6cf Mon Sep 17 00:00:00 2001 From: Jean-Paul van Ravensberg <14926452+DevSecNinja@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:29:03 +0200 Subject: [PATCH 2/3] fix(windows): detect Surface Laptop SMBIOS names Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6c0af4ce-01e4-448b-bc3c-6f7d7ee92610 --- ...n_onchange_disable-surface-laptop-power-button.ps1.tmpl | 2 +- tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl b/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl index 4e0a7894..2c862977 100644 --- a/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl +++ b/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl @@ -28,7 +28,7 @@ function Test-SurfaceLaptop { $model = [string]$ComputerSystem.Model return $manufacturer.Trim() -eq "Microsoft Corporation" -and - $model.Trim() -match "^Surface Laptop(?:\s|$)" + $model.Trim() -match "^(?:Microsoft )?Surface Laptop(?:[\s,]|$)" } function Invoke-PowerCfg { diff --git a/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 b/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 index d18259a7..78bfc0e5 100644 --- a/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 +++ b/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 @@ -41,6 +41,13 @@ Describe "Surface Laptop power button script" -Tag "Unit" { }) | Should -BeTrue } + It "recognizes the Surface Laptop 7 SMBIOS model" { + Test-SurfaceLaptop -ComputerSystem ([pscustomobject]@{ + Manufacturer = "Microsoft Corporation" + Model = "Microsoft Surface Laptop, 7th Edition" + }) | Should -BeTrue + } + It "recognizes Surface Laptop Studio models" { Test-SurfaceLaptop -ComputerSystem ([pscustomobject]@{ Manufacturer = "Microsoft Corporation" From f36ad75e317dac8499ab24cf92b76893c25b0612 Mon Sep 17 00:00:00 2001 From: Jean-Paul van Ravensberg <14926452+DevSecNinja@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:43:51 +0200 Subject: [PATCH 3/3] fix(windows): apply Surface power policy in all modes Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6c0af4ce-01e4-448b-bc3c-6f7d7ee92610 --- README.md | 5 ++--- docs/customization.md | 7 +++---- ...nchange_disable-surface-laptop-power-button.ps1} | 4 +--- tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 | 13 +++++-------- 4 files changed, 11 insertions(+), 18 deletions(-) rename home/.chezmoiscripts/windows/{run_onchange_disable-surface-laptop-power-button.ps1.tmpl => run_onchange_disable-surface-laptop-power-button.ps1} (96%) diff --git a/README.md b/README.md index d11c7f9f..f6578a5e 100644 --- a/README.md +++ b/README.md @@ -372,9 +372,8 @@ The repository supports two installation modes: - **Light mode** (servers, CI, codespaces): Essential tools only - **Full mode** (dev servers, workstations): Full development tooling including Task and mise -On Windows, full mode also applies opinionated workstation settings. On -Microsoft Surface Laptop models, the hardware power button is set to **Do -nothing** for both AC and battery power. +On Windows, Microsoft Surface Laptop models always have the hardware power +button set to **Do nothing** for both AC and battery power. The mode is auto-detected based on: - Hostname patterns (SVLDEV* = full, SVL* = light) diff --git a/docs/customization.md b/docs/customization.md index 93756a53..1663c239 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -21,10 +21,9 @@ The repository supports two installation modes: - **Full mode** (dev servers, workstations) — Full development tooling including Task and mise. -On Windows, full mode also applies opinionated workstation settings. Microsoft -Surface Laptop models have the hardware power button set to **Do nothing** for -both AC and battery power, preventing an accidental press next to Delete from -suspending or shutting down the computer. +On Windows, Microsoft Surface Laptop models always have the hardware power +button set to **Do nothing** for both AC and battery power, preventing an +accidental press next to Delete from suspending or shutting down the computer. The mode is auto-detected based on: diff --git a/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl b/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1 similarity index 96% rename from home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl rename to home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1 index 2c862977..262f517d 100644 --- a/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1.tmpl +++ b/home/.chezmoiscripts/windows/run_onchange_disable-surface-laptop-power-button.ps1 @@ -1,7 +1,6 @@ -# {{ if and (eq .chezmoi.os "windows") (eq .installType "full") }} #!/usr/bin/env pwsh # Disable the hardware power button on Microsoft Surface Laptop models. -# Runs in the opinionated full profile and is idempotent. +# Runs on Windows and is idempotent. [Diagnostics.CodeAnalysis.SuppressMessageAttribute( "PSAvoidUsingWriteHost", @@ -110,4 +109,3 @@ function Disable-SurfaceLaptopPowerButton { if (-not $SkipApply) { Disable-SurfaceLaptopPowerButton | Out-Null } -# {{ end }} diff --git a/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 b/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 index 78bfc0e5..986c09de 100644 --- a/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 +++ b/tests/powershell/SurfaceLaptopPowerButton.Tests.ps1 @@ -7,10 +7,9 @@ BeforeAll { $script:RepoRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent $script:ScriptPath = Join-Path $script:RepoRoot ` - "home\.chezmoiscripts\windows\run_onchange_disable-surface-laptop-power-button.ps1.tmpl" + "home\.chezmoiscripts\windows\run_onchange_disable-surface-laptop-power-button.ps1" - $scriptContent = Get-Content -LiteralPath $script:ScriptPath -Raw - . ([scriptblock]::Create($scriptContent)) -SkipApply + . $script:ScriptPath -SkipApply } Describe "Surface Laptop power button script" -Tag "Unit" { @@ -27,11 +26,9 @@ Describe "Surface Laptop power button script" -Tag "Unit" { $errors | Should -BeNullOrEmpty } - It "is limited to the Windows full profile" { - $content = Get-Content -LiteralPath $script:ScriptPath -Raw - - $content | Should -Match 'eq \.chezmoi\.os "windows"' - $content | Should -Match 'eq \.installType "full"' + It "is a non-template Windows script" { + $script:ScriptPath | Should -Match '\.ps1$' + $script:ScriptPath | Should -Not -Match '\.tmpl$' } It "recognizes Microsoft Surface Laptop models" {