Files
Yu LengandYu Leng 2742ac2048 [Light Switch] Add CLI for status and theme control (#50459)
## Summary of the Pull Request

Adds `PowerToys.LightSwitch.CLI.exe` for querying and controlling the
running Light Switch service. Scripts can read status, set or toggle the
configured theme targets, and enable or disable the existing schedule,
with JSON output and documented exit codes.

Closes #49904.

## PR Checklist

- [x] Closes: #49904
- [x] **Communication:** Discussed with core contributors.
- [x] **Tests:** Added/updated; 140 native, 221 managed CLI, and 49
Light Switch settings tests pass locally.
- [x] **Localization:** User-facing text uses localized resources with
English fallback. JSON fields, protocol values, and exit codes remain
invariant.
- [x] **Dev docs:** Added/updated.
- [x] **New binaries:** Added to the solution, signing list, CLI shim
manifest, and installer shim components.
- [ ] **Documentation updated:** External PowerToys documentation PR.

## Detailed Description of the Pull Request / Additional comments

- `src/modules/LightSwitch/LightSwitch.Cli/` adds `status`, `light`,
`dark`, `toggle`, `schedule enable [--mode]`, and `schedule disable`.
All commands support `--json`; help and version work without the
service. Enabling a schedule from `Off` requires an explicit existing
mode: `fixed-hours`, `sunset-to-sunrise`, or `follow-night-light`.
- `src/modules/LightSwitch/LightSwitchService/CliPipeServer.cpp` and
`CliProtocol.cpp` implement bounded, versioned request/response IPC with
user/session/logon isolation, server identity checks in the client,
timeouts, and shutdown handling. The CLI requires a running service and
does not automatically retry mutations after an uncertain result.
-
`src/modules/LightSwitch/LightSwitchService/LightSwitchStateManager.cpp`
exposes serialized theme operations and status snapshots using the
existing scheduling rules.
`src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp` submits
counted toggle requests to the service so theme writes and
manual-override updates occur together. Theme access failures and
partial changes are reported to callers; verified recovery preserves
downstream theme notifications.
- Schedule commands update the existing `scheduleMode` setting and
complete the worker's settings handling before replying. Duplicate
settings notifications preserve manual overrides, and settings loaded
before a delayed notification are not mistaken for an external theme
change. Overrides account for sleep, clock changes, and unavailable
Night Light data.
`src/settings-ui/Settings.UI/SettingsXAML/Views/LightSwitchPage.xaml.cs`
refreshes after settings-file replacement. Concurrent editors retain the
existing whole-file save behavior.
- CLI help, text labels, and service diagnostics use localized
resources. Available CLI and parser satellite resources are packaged
without requiring missing translations. Machine-readable protocol and
JSON values remain stable.
- `tools/CliShim/CliShimManifest.props`,
`installer/PowerToysSetupVNext/CliShims.wxs`, and
`.pipelines/ESRPSigning_core.json` register deployment and signing. The
CLI and service are included in BugReport diagnostics. Usage, protocol
behavior, and exit codes are documented in
`doc/devdocs/modules/lightswitch-cli.md`.

## Validation Steps Performed

- Latest x64 Debug builds passed for `LightSwitchService.UnitTests`,
`LightSwitch.Cli.UnitTests`, and `Settings.UI.UnitTests`, including
their Light Switch service, CLI, and Settings UI dependencies. Each
build returned exit code 0 with 0 warnings and 0 errors.
- 140 native service tests passed via `vstest.console.exe`, covering
protocol/identity/framing, shutdown and queue handling, settings
persistence, manual overrides, recovery, and schedule boundaries.
- 221 managed CLI tests passed via `vstest.console.exe`, covering
parsing, output/exit codes, bounded IPC, response validation, and
resource-based output.
- 49 Light Switch settings tests passed via `vstest.console.exe` with
`FullyQualifiedName~LightSwitch`, covering the ViewModel and settings
watcher. This was a filtered run, not the complete Settings test suite.
- `git diff --check` passed. The cleanup preserves behavior, removes
redundant theme-observation and observer-stop calls, and removes unused
imports; existing regression tests cover the affected paths.

Desktop theme/hotkey acceptance, real translated-satellite acceptance,
ARM64/Release verification, and full installer acceptance are not
covered by these latest checks.

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2026-10-09 13:47:20 +08:00

10 KiB

Light Switch CLI

PowerToys.LightSwitch.CLI.exe queries and controls the running Light Switch service in the current Windows user session. Enable Light Switch in PowerToys before using it. The CLI does not start PowerToys or enable the module.

On its first startup, the service creates the default settings if the module's settings file does not exist: scheduling is off and both theme targets are enabled. Existing, malformed, or unreadable files are never replaced with defaults. This also supports enabling the module through preconfigured general settings or policy before visiting the settings page.

The PATH-visible executable is a CLI shim. Its target, PowerToys.LightSwitch.Cli.exe, is installed in the PowerToys installation root. Terminals opened before installation may need to be reopened to pick up PATH.

Commands

PowerToys.LightSwitch.CLI.exe status
PowerToys.LightSwitch.CLI.exe status --json
PowerToys.LightSwitch.CLI.exe light
PowerToys.LightSwitch.CLI.exe dark
PowerToys.LightSwitch.CLI.exe toggle
PowerToys.LightSwitch.CLI.exe schedule disable
PowerToys.LightSwitch.CLI.exe schedule enable --mode fixed-hours
PowerToys.LightSwitch.CLI.exe schedule enable --mode sunset-to-sunrise
PowerToys.LightSwitch.CLI.exe schedule enable --mode follow-night-light

light and dark set the targets selected in Light Switch settings (system, applications, or both). Repeating the same command does not toggle the manual override flag. A theme different from the current automatic plan is held using Light Switch's existing manual override behavior, until a scheduled boundary or a relevant settings change.

toggle uses the same service operation as the shortcut. It retains the existing behavior: invert each selected target, toggle manual override, then evaluate the schedule. In particular, mixed system/application themes can be reconciled to the automatic plan when the operation releases an existing override.

schedule disable saves the existing scheduleMode property as Off. schedule enable --mode selects one of the existing modes and uses the values already saved for its times, coordinates, and offsets. It changes only the schedule mode; it does not reset time values or record a previous mode.

Without --mode, schedule enable succeeds without changing an already-enabled schedule. If the mode is Off, it fails with exit code 2 and asks for an explicit mode. Re-selecting the current mode does not clear a manual theme override.

All commands support --json. --help and --version work without the service. Boolean options also accept explicit values, such as --help=true, --version:true, and --json false. A false help or version option does not suppress the command. Options after the literal -- remain arguments. Parser directives such as [parse] and [suggest] are unsupported and are rejected as arguments, including when supplied through response files.

Output and exit codes

Successful service responses use a versioned JSON envelope:

{
  "version": 1,
  "success": true,
  "state": {
    "systemTheme": "dark",
    "appsTheme": "dark",
    "changeSystem": true,
    "changeApps": true,
    "scheduleMode": "FixedHours",
    "manualOverride": true
  }
}

The mode is the service's accepted configuration, not its last applied-mode cache. System and application themes are read separately. Reading a theme can fail; an error response may include a state with that value set to unknown.

Errors contain version, success: false, and an error object with stable code and human-readable message fields. When available, state describes the observed result, including partial theme changes. JSON output is one object on stdout. Without --json, human-readable errors go to stderr; logs do not mix with JSON.

Help, text-mode labels, and human-readable messages use localized resources, with English fallback when a translation is unavailable. The service produces its messages in its own UI language. JSON property names, types, theme and schedule-mode values, error codes, version numbers, and exit codes stay invariant. Only the human-readable help and error.message fields may vary by language; consumers should inspect success, error.code, and structured state values. JSON serialization escapes translated text, including quotes and line breaks.

Exit code Meaning
0 The command completed successfully.
1 Execution, theme access, or protocol failure.
2 Invalid arguments or invalid configuration.
3 The service is unavailable or cannot accept the request.
4 Timeout or a lost connection after sending a request; the result may be unknown.

The shim can additionally return its documented 9009-9011 startup errors.

A successful schedule command has saved the mode and completed the service's settings handling, including any required Night Light observer lifecycle change. Observer initialization runs asynchronously. The settings window continues to refresh through its existing file watcher. Concurrent settings editors retain the existing whole-file save behavior: a later save, including a stale UI snapshot, can replace an earlier update. The CLI does not add field-level conflict merging.

A request timeout does not roll back Windows changes. The CLI never automatically retries a mutation, especially toggle. Query status before deciding whether to issue another command. Windows theme broadcasts can take time; successful theme application does not mean every application has finished redrawing or that a downstream PowerDisplay profile has completed.

Implementation

The C# CLI parses arguments with the repository's pinned System.CommandLine package. It connects to the service through a duplex named pipe, verifies the server executable, sends one request, reads one response, and closes.

The pipe name is PowerToys_LightSwitch_Cli_<sessionId>. Its access control restricts callers to the service's user/logon scope and rejects remote clients. After reading a bounded frame, the service obtains the connection's identification token and verifies its user SID, desktop session, and logon SID. This accepts the same user's linked UAC tokens while rejecting independent logons. The service reverts to its own identity before parsing or executing commands. Wire messages are single-line JSON encoded as BOM-less UTF-16LE, with a maximum of 32,768 UTF-16 code units per request or response. Version 1 requests contain version, command, and, only for schedule-enable, an optional mode.

Wire command names are status, light, dark, toggle, schedule-enable, and schedule-disable. Wire modes use the existing setting values: FixedHours, SunsetToSunrise, and FollowNightLight. Unknown versions, commands, modes, and unexpected properties are rejected before execution.

Theme commands and snapshots use the existing StateManager mutex. A command uses one configuration snapshot throughout its theme application and state update. Schedule requests are handed to the existing service worker because it owns the Night Light observer's lifetime. Observer joins never run while holding the StateManager lock.

The shortcut submits a counted toggle request to the service instead of modifying Windows and sending a delayed completion notification. The Command Palette's existing toggle event still reaches that shortcut adapter. This preserves the number of requests received by the module and avoids an old completion event cancelling a newer CLI override. Both the shortcut and Command Palette request path restart an exited service before submitting their toggle. This does not change the standalone CLI's requirement for an already-running service.

Manual overrides use a full local date/time baseline. Scheduled boundaries that pass during sleep expire an override, while a backward clock adjustment does not masquerade as midnight. Sun times are cached by their full calendar date. New manual commands account for already-occurring schedule or Night Light transitions before establishing their override; delayed notifications cannot cancel that newer choice. Unchanged settings notifications preserve detected external theme choices while genuine plan changes still take effect.

The worker also reconciles Night Light on minute ticks, recovering from a settings read or theme write failure without requiring another notification. An unavailable Night Light value is not treated as off: automatic scheduling waits for a successful read, preserving the last confirmed state and any manual override. A manual command issued while Night Light is unreadable establishes its boundary baseline on recovery, so an older, unobserved transition does not cancel the newer choice. If a scheduled theme write takes effect but its readback fails, PowerDisplay is notified once the same planned state is confirmed. New plans and successful manual commands supersede that pending notification; a failed manual write does not discard it. Notifications use the verified theme snapshot without rereading it immediately before sending the event. An explicit theme command also acknowledges an external choice that has not yet been polled, even when no Windows write is needed. If a command fails, external changes on targets it did not write are still handled against the current plan; the command's own partial writes are not treated as external overrides. A later successful retry can confirm an earlier write whose verification failed without losing the PowerDisplay notification. Calculated sun times share the schedule command's settings lock and are saved only while their effective configuration remains current. An older calculation cannot restore a mode that a completed schedule command has replaced.

Validation

The managed tests cover argument parsing, text/JSON output, exit codes, bounded pipe reads, server identity, and response validation. Native tests cover strict request parsing, pipe lifetime/framing, theme operations with fake Windows dependencies, and settings handling without changing the desktop theme.

Manual acceptance should include all three target selections, mixed themes, shortcut/CLI sequences, Off-to-mode transitions, all three automatic modes, delayed settings notifications, settings read/write failures, and stopping the module while a client is connected.