Skip to content

fix(windows): detect Windows PowerShell in the Night Light script - #742

Merged
DevSecNinja merged 2 commits into
mainfrom
fix/night-light-windows-detection
Aug 16, 2026
Merged

fix(windows): detect Windows PowerShell in the Night Light script#742
DevSecNinja merged 2 commits into
mainfrom
fix/night-light-windows-detection

Conversation

@DevSecNinja

Copy link
Copy Markdown
Owner

Follow-up to #738.

Symptom

Running chezmoi apply on Windows printed:

[SKIP] Night Light is a Windows-only setting.

...on Windows.

Cause

The script guarded with if (-not $IsWindows). $IsWindows is an automatic variable that only exists in PowerShell Core — it is undefined in Windows PowerShell 5.1, so it evaluates to $null and the negated guard is always $true.

And .chezmoi.yaml.tmpl pins exactly that interpreter:

interpreters:
  ps1:
    command: "powershell"      # Windows PowerShell 5.1, PSEdition "Desktop"
    args: ["-NoLogo", "-NoProfile"]

Confirmed locally:

5.1.28000.2704
PSEdition=Desktop
IsWindows defined: False

So the script skipped itself on the only platform it targets. It worked when I tested it during development because I invoked it from pwsh 7.

Fix

Adds Test-WindowsHost, which treats PSEdition -eq "Desktop" as Windows (Desktop edition only ships on Windows) and otherwise falls back to $IsWindows via Get-Variable -ErrorAction SilentlyContinue.

The rest of the script was already 5.1-compatible — no PS7-only syntax — verified by round-tripping the live registry blobs byte for byte under 5.1.

Verification

=== Windows PowerShell 5.1 (chezmoi's interpreter) ===
Applying Night Light configuration (sunset to sunrise, strength 50)...
[OK] Night Light configured (0 setting(s) changed).
=== pwsh 7 ===
Applying Night Light configuration (sunset to sunrise, strength 50)...
[OK] Night Light configured (0 setting(s) changed).

Regression coverage

New Windows host detection block (4 tests):

  • Test-WindowsHost returns true on the current host
  • the script source must not negate $IsWindows directly
  • Test-WindowsHost returns True inside a real powershell.exe 5.1 child process
  • a full script run under 5.1 must not emit Windows-only setting

Night Light suite: 66 passed. Full Pester suite: 857 passed, 0 failed. PSScriptAnalyzer clean.

Note for later (not in this PR)

home/dot_config/powershell/scripts/Sign-PowerShellScripts.ps1 and New-SigningCert.ps1.tmpl use the same -not $IsWindows pattern. They are invoked manually under pwsh 7 so they are not currently broken, but they would misbehave if ever run under Windows PowerShell.

The Night Light script guarded on $IsWindows, which only exists in PowerShell
Core. Chezmoi runs .ps1 scripts with `powershell` (Windows PowerShell 5.1,
PSEdition "Desktop") per the ps1 interpreter in .chezmoi.yaml.tmpl, where the
variable is undefined. The negated guard was therefore always true and the
script skipped itself on the exact platform it targets, printing
"[SKIP] Night Light is a Windows-only setting."

Replaces the guard with Test-WindowsHost, which treats PSEdition "Desktop" as
Windows and otherwise falls back to $IsWindows. The rest of the script was
already 5.1-compatible; verified the codec still round-trips the live registry
blobs byte for byte under 5.1.

Adds regression coverage: the script must not negate $IsWindows directly, and
Test-WindowsHost plus a full script run are exercised in a real Windows
PowerShell 5.1 child process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@DevSecNinja
DevSecNinja enabled auto-merge (squash) August 16, 2026 20:33
Fixing the $IsWindows guard made the script actually run on Windows CI
runners, where it then failed the whole chezmoi apply: a fresh machine has
never used Night Light, so the CloudStore values do not exist and the script
threw "Night Light settings are not initialised".

The script now seeds a baseline instead of throwing. Sunset and sunrise are
deliberately left unset: Windows derives them from the machine location, and
seeding them would risk storing wrong times. With no solar window known, the
state value is left off rather than forced on.

Verified on a live Windows 11 install by backing up both blobs, deleting the
keys, and writing a from-scratch payload: Windows accepts and retains it. The
original settings were restored afterwards.

Renames the helper to Get-NightLightDefaultSetting to satisfy PSScriptAnalyzer
(PSUseShouldProcessForStateChangingFunctions, PSUseSingularNouns) and to match
the Get-WindowsPersonalizationSetting convention in the sibling script.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@DevSecNinja

Copy link
Copy Markdown
Owner Author

Second failure found and fixed

Fixing the $IsWindows guard exposed a real bug that the guard had been masking.

Test Windows Coder Install (Root) and Test Windows Light Installation run install.ps1 -> chezmoi init --apply, which now actually executes this script on the runner. A fresh windows-latest VM has never used Night Light, so the CloudStore values do not exist and the script hit:

throw "Night Light settings are not initialised. ..."

$ErrorActionPreference = "Stop" + chezmoi means that failed the entire apply. The script had never been exercised on a clean machine before, because it was always skipping itself.

Fix

It now seeds a baseline instead of throwing. Sunset and sunrise are deliberately left unset — Windows derives them from the machine location, and seeding them would risk storing wrong times. With no solar window known, the state value is left off rather than forced on.

Verified on real hardware, not just mocks

Backed up both live blobs, deleted the keys, and wrote a from-scratch payload:

writing : 43 42 01 00 0a 02 01 00 2a 06 a1 c2 88 d4 06 2a 2b 0e 1b
          43 42 01 00 02 01 ca 14 0e 15 00 ca 1e 0e 07 00 cf 28 94 3c
          ca 32 00 ca 3c 00 00 00 00 00

t+5s  sunset=0:00 sunrise=0:00 kelvin=3850 solar=True enabled=True
t+30s sunset=0:00 sunrise=0:00 kelvin=3850 solar=True enabled=True

Windows accepts and retains the shorter payload without rejecting or rewriting it. Original settings were restored afterwards and re-verified (sunset 21:06, sunrise 06:25, 3850 K, on).

One incidental discovery worth recording: deleting the keys while logged in causes the shell to immediately rewrite them from memory with a fresh timestamp and an identical payload. These values are actively owned by the running shell, which is why preserving unknown fields on decode matters.

Also

Renamed the helper to Get-NightLightDefaultSetting to clear PSUseShouldProcessForStateChangingFunctions and PSUseSingularNouns, matching the Get-WindowsPersonalizationSetting convention in the sibling script.

Night Light suite: 70 tests. Full suite: 861 passed, 0 failed. Analyzer clean.

@DevSecNinja
DevSecNinja merged commit 0e48c13 into main Aug 16, 2026
19 checks passed
@DevSecNinja
DevSecNinja deleted the fix/night-light-windows-detection branch August 16, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant