<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Add Workspaces **list** and synchronous **launch** through a thin native CLI and shared GUI/CLI execution core. Related to #49900; capture/create/CRUD are excluded. Usage and contracts: `doc\devdocs\modules\workspaces-cli.md`. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: N/A - partial scope of #49900. <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [x] **Tests:** Added/updated and all pass (scoped local runs below; CI/release gates remain) - [x] **Localization:** All end-user-facing strings can be localized - [x] **Dev docs:** Added/updated - [x] **New binaries:** Added on the required places - [x] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [x] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - current integration: `installer\PowerToysSetupVNext\CliShims.wxs` - [x] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - existing discovery; session-peer companion signing updated - [x] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - N/A: existing signing pipeline - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: N/A - follow-up not filed. <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments ### Shared execution, conditional process boundary ```mermaid flowchart TB EDITOR["Editor process<br/>C# / WPF"] -->|"start GUI Launcher"| ADAPTER SHELL["Terminal / script"] -->|"CLI shim starts CLI"| ADAPTER subgraph CALLER["Caller process: CLI.exe OR Launcher.exe in GUI mode"] ADAPTER["Thin entry adapter"] START["WorkspacesSession::Start<br/>snapshot + context check"] LOCAL["WorkspacesSession::Execute()<br/>function inside the caller process"] ADAPTER -->|"in-process"| START START -->|"verified Medium: in-process"| LOCAL LOCAL -.->|"typed return via Start"| ADAPTER end subgraph HOST["Elevated callers only: Medium host process<br/>PowerToys.WorkspacesLauncher.exe"] HANDOFF["Host entry<br/>receive and validate SessionHandoff"] REMOTE["WorkspacesSession::Execute()<br/>function inside this host process"] HANDOFF -->|"function call within the same process"| REMOTE end START -->|"elevated: create host<br/>read-only snapshot + restricted handles"| HANDOFF REMOTE -.->|"private anonymous result pipe"| START START -.->|"host outcome returned"| ADAPTER LOCAL <-->|"named pipes"| ARRANGER["WindowArranger process"] REMOTE <-->|"named pipes"| ARRANGER LOCAL <-->|"named pipes"| UI["Existing WPF Launcher UI process"] REMOTE <-->|"named pipes"| UI classDef shared fill:#dbeafe,stroke:#2563eb,color:#172554 class START,LOCAL,REMOTE shared ``` The caller box represents **one of the two entry executables**, not both in one process. Blue nodes are code statically linked from `WorkspacesLib.lib`, not a DLL or shared service. **Medium GUI/CLI callers execute in their own process without another Launcher host.** Only elevated callers create a verified Medium host through the existing `run_non_elevated` helper; it calls the same `WorkspacesLib\LaunchExecution.cpp` core. No elevated fallback. **`Execute()` is a function, not another EXE.** The host entry and its `Execute()` share the same process ID. Execution uses a worker thread inside that process, not another Launcher process. The two `Execute()` nodes represent the same library implementation running in alternative caller/host contexts. - **Shared behavior:** one preparation, launch, arrangement, confirmation and history flow. GUI retains progress; CLI shows only required existing confirmation UI. GUI Cancel and console cancellation use the shared stop/result path; already-started apps survive. Arranger callbacks are stopped and joined before their helper owner is released. An unresponsive inline Shell call yields `outcomeUnknown` before the one-shot adapter exits, rather than killing a thread or claiming cancellation succeeded. - **Contracts:** `list` is read-only; summary JSON reuses existing names. All arranged = `completed`/0; partial progress = `partial`/10; no progress = `failed`/10. Incomplete, fractional or invalid final states yield `outcomeUnknown`/9. Persistence verification failure retains exit 11 and displays application results in both text and JSON modes. The existing `LaunchedAndMoved` enum and numeric IPC values are unchanged. - **Persistence/integration:** preserve IDs, initialize missing legacy IDs, update history best-effort; unsaved GUI drafts do not write saved configuration/history. Only shim `bin` goes on PATH. Editor sibling-tool resolution is executable-relative. - **Telemetry:** one frontend `Workspaces_CLICommand` event when optional diagnostics are enabled, following #50507's fixed command category + success model. No workspace names/IDs, paths, arguments or error text; the Medium host does not double-count. Uses the existing native telemetry channel; documented in `DATA_AND_PRIVACY.md`. - **Release IPC:** exact session-owner PID and official CLI/Launcher identities remain checked. CI companion signing now includes CLI, Launcher, arranger and Launcher UI; production signature checks are not disabled. - **Console isolation:** console workspace targets get their own console; inline execution does not detach, redirect or share the CLI's console. - **Launch and Edit:** a missing temporary draft now falls back to the saved workspace before GUID validation; malformed existing drafts still fail. The existing UI regression explicitly checks that no draft is present. - **GUI file errors:** unreadable saved files and drafts show the existing localized file-reading dialog with the correct source filename; malformed-file dialogs also retain that context. Public CLI error JSON is unchanged. - **UI-test transport:** shared value setters place target options before `--` and pass selectors/values literally. This fixes the leading-dash command-line inputs rejected by CI's winapp 0.7.1 without changing Workspaces test payloads or assertions. ### Changed-file breakdown Full PR versus main, not just the latest commit. Each file is counted once; test projects and fixtures are grouped under tests. These are file categories, not process or library boundaries. | Category | Files | |---|---:| | CLI adapter, output, resources and project | 7 | | Shared session/execution entry, contracts, IPC, storage and telemetry | 19 | | Launcher engine/host adapter, arranger and UI | 18 | | Managed storage and Editor path compatibility | 4 | | Common helpers | 9 | | Tests and fixtures | 24 | | Build, installer and signing | 6 | | Documentation and privacy catalog | 4 | | **Total** | **91** | The Launcher engine sources are compiled into `WorkspacesLib` even though their source files remain under `WorkspacesLauncher`; the table does not imply a separate engine process. **Scope/known follow-ups:** general Editor save/conflict redesign and the pre-existing localized-name/package-version matching bug are deferred. Final GUI progress presentation still needs follow-up; this update does not claim to fix its spinner-before-close behavior. The native C++ parser is retained by the PR author's decision; a managed System.CommandLine migration is not included. This does not amend the repository-wide CLI convention. <details> <summary>List output and shared confirmation UI</summary>   </details> <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed x64 Debug builds of the affected product and UI-test/fixture projects passed; Release core, ModuleInterface, arranger, Launcher and CLI builds also passed. The pre-GUI-error scoped native run passed **160 tests**. Two deterministic paused-dispatch regressions reproduced `0xC0000005` before the teardown fix (snapshot and final-result callbacks); both now return acknowledged cancellation. Test hooks are compiled only into the fixture, not production builds. **48 signing-selection** tests passed earlier against unchanged selection logic. The preceding missing-draft fix passed **39 focused tests** plus the complete local-host `winapp` Launch and Edit flow, with settings/pointer/Editor state restored. This is targeted host validation, not full local OS-matrix sign-off. The GUI error follow-up `8ea5619586` passed **33 focused native tests in each of Debug and Release**, including a regression that failed before the fix. Both real saved-file/draft error dialogs were driven locally with UIA; original configuration bytes and draft absence were restored. The CI transport follow-up `e0945376e6` passed **19 focused shared-harness tests in each of Debug and Release**. Using the hash-verified winapp **0.7.1** archive from the failing CI revision, the original invocation reproduced `invalid_arguments`; the fixed wrappers passed **nine owned-UI setter checks** for leading options, quoted text, empty text, and negative numeric values. The TextBox, editable ComboBox, NumberBox, and Slider setters share the corrected argument boundary. This is focused wrapper validation, not a claim that the full Workspaces suite ran locally. <details> <summary>Commands and results</summary> Used the verified side-by-side SDK 10.0.303; no global/repository SDK or package-source change. With `$root='D:\PowerToys-workspace-cli'`, from each affected project directory: ```powershell & "$root\tools\build\build.ps1" -Platform x64 -Configuration Debug -ExtraArgs @("/p:SolutionDir=$root\", '/p:BuildProjectReferences=false', '/p:WorkspacesCliLiveTests=true') # Passed, exit 0. & "$root\tools\build\build.ps1" -Platform x64 -Configuration Release -ExtraArgs @("/p:SolutionDir=$root\", '/p:BuildProjectReferences=false') # Passed, exit 0, for core/arranger/Launcher/CLI. ``` Missing Release UI dependencies were first built with project references enabled: ```powershell # From src\modules\Workspaces\WorkspacesLauncherUI: & "$root\tools\build\build.ps1" -Platform x64 -Configuration Release -ExtraArgs @("/p:SolutionDir=$root\") # Passed, exit 0. ``` Pre-GUI-error scoped native run (`$evidence` is the local report directory): ```powershell $env:WORKSPACES_CLI_LIVE_TESTS='1' & 'C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe' "$root\x64\Debug\tests\Workspaces\Workspaces.Lib.UnitTests.dll" '/TestCaseFilter:FullyQualifiedName~CliContractTests|FullyQualifiedName~CliJsonOutputTests|FullyQualifiedName~CliRuntimeTests|FullyQualifiedName~CliHandoffTests|FullyQualifiedName~CliWorkerTests|FullyQualifiedName~WorkspaceStoreTests|FullyQualifiedName~SignatureVerificationTests|FullyQualifiedName~SignatureTrustTests|FullyQualifiedName~ExecutableTargetTests|FullyQualifiedName~ElevatedLaunchGateTests|FullyQualifiedName~PendingLaunchApprovalTests' "/ResultsDirectory:$evidence" '/Logger:trx;LogFileName=teardown-race-green.trx' '/Logger:console;verbosity=minimal' # Passed: 160; failed/skipped: 0; exit 0. & 'C:\Program Files\Microsoft Visual Studio\18\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe' "$root\x64\Debug\tests\Workspaces\Workspaces.Lib.UnitTests.dll" '/TestCaseFilter:FullyQualifiedName~CliContractTests|FullyQualifiedName~JsonUtilsTests' "/ResultsDirectory:$evidence" '/Logger:trx;LogFileName=missing-draft-headless.trx' '/Logger:console;verbosity=minimal' # Passed after the draft fix: 39; failed/skipped: 0; exit 0. Invoke-Pester -Script 'D:\PowerToys-workspace-cli\.pipelines\tests\uiTestSigningSelection.Tests.ps1' -PassThru # Passed: 48; failed: 0. git diff --check # Passed, exit 0. ``` Telemetry lifecycle tests use a recording sink and synthetic commands; they neither transmit telemetry nor change diagnostic consent. Recorded host UI actions (3892 was this run's owned Editor PID, not a reusable PID): ```powershell winapp ui 'invoke' 'EditButton' '-a' '3892' '--json' winapp ui 'invoke' 'LaunchEditButton' '-a' '3892' '--json' winapp ui 'invoke' 'SnapshotButton' '-a' '3892' '--json' winapp ui 'invoke' 'SaveButton' '-a' '3892' '--json' # All passed, exit 0; fixture startup, exact placement, recapture and persisted IDs were asserted. ``` </details> Latest shared-harness build/test commands, from `src\common\UITestAutomation.Next.UnitTests`: ```powershell & "$root\tools\build\build.ps1" -Platform x64 -Configuration Debug -ExtraArgs @("/p:SolutionDir=$root\") & "$root\tools\build\build.ps1" -Platform x64 -Configuration Release -ExtraArgs @("/p:SolutionDir=$root\") # Both passed, exit 0, including analyzers. & "$root\x64\Debug\tests\UITestAutomation.Next.UnitTests\net10.0-windows10.0.26100.0\UITestAutomation.Next.UnitTests.exe" --filter 'FullyQualifiedName~WinappCliTests|FullyQualifiedName~SessionTests' --report-trx --report-trx-filename ci-set-value-unit.trx --results-directory $evidence & "$root\x64\Release\tests\UITestAutomation.Next.UnitTests\net10.0-windows10.0.26100.0\UITestAutomation.Next.UnitTests.exe" --filter 'FullyQualifiedName~WinappCliTests|FullyQualifiedName~SessionTests' --report-trx --report-trx-filename ci-set-value-unit-release.trx --results-directory $evidence # Each passed: 19; failed/skipped: 0; exit 0. ``` **CI status:** [391837](https://dev.azure.com/shine-oss/62769a65-d989-45e3-803c-9ad21fcf2ea2/_build/results?buildId=391837) **succeeded** on current head `e0945376e6`, tested as merge `e27f537028` with main `79ceeee01c`. SDK/x64/ARM64 builds passed, and **34/34 Workspaces UI tests passed on each of Windows 10 x64, Windows 11 x64, and Windows 11 ARM64**, with zero failures or skips. Finished 2026-10-26 16:18:46 UTC. The preceding [391820](https://dev.azure.com/shine-oss/62769a65-d989-45e3-803c-9ad21fcf2ea2/_build/results?buildId=391820) on `8ea5619586` passed product builds but had 29/34 UI tests pass on each platform. All five failures were leading-dash textbox values rejected by winapp 0.7.1; the shared helper was identical to main and predates this PR. The setter fix resolves those failures without changing Workspaces product code or weakening assertions. Fresh High-parent/UAC, installer servicing and broader mixed-DPI validation remain release gates. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 664ee900-ce2d-4112-ac57-395314d818dc
17 KiB
Workspaces command-line interface
Related issue: #49900. This interface lists saved workspaces and launches one through the existing Workspaces engine. It does not capture, create, import, delete or edit workspace configurations.
Installation
The installer adds only <PowerToys installation>\bin to PATH. PowerToys.Workspaces.CLI.exe
in that folder is the shared native CLI shim, not the application payload. It launches
..\PowerToys.WorkspacesCLI.exe using the installed-relative mapping.
Do not add the installation root or WinUI3Apps to PATH. Those folders contain application/runtime
DLLs and are not command directories. The wrapper avoids advertising those DLL locations through
PATH; it does not replace the operating system's DLL security rules.
The manifest and WiX component list are checked together. The real CLI and shared shim are included in release signing, and installer shutdown recognizes both process names. See CLI conventions.
Commands
PowerToys.Workspaces.CLI.exe list [--json]
PowerToys.Workspaces.CLI.exe list --id <guid> [--details] [--json]
PowerToys.Workspaces.CLI.exe list --name <name> [--details] [--json]
PowerToys.Workspaces.CLI.exe launch --id <guid> [--timeout <seconds>] [--json]
PowerToys.Workspaces.CLI.exe launch --name <name> [--timeout <seconds>] [--json]
Help/version do not require an enabled module. Both operational commands reject user-disabled or policy-disabled Workspaces. Configured GPO overrides the user toggle exactly as in Settings. The Runner need not be running when the effective module configuration is enabled.
Use exactly one selector for launch or detail. GUID matching accepts braces and either case, without rewriting stored IDs. Name matching is exact, ordinal and case-insensitive; ambiguous names fail rather than selecting the first workspace. An explicit missing selector target is an error; an enabled module with no workspace file has an empty unfiltered list.
Data contract
--json returns one UTF-8 document. Successful list responses contain only view and
workspaces at the top level, without a schemaVersion, command, state or result wrapper.
Launch responses and errors keep the envelope with schemaVersion, command, state and
result or error. Failed queries return a structured error and a nonzero exit code, never an
empty successful list.
Public JSON is formatted with line breaks and two-space indentation, followed by a newline,
including when stdout is redirected. It is one JSON object, not a quoted JSON string or
newline-delimited JSON records. Formatting does not alter stored field names or values.
The private worker/confirmation protocol is unchanged.
Warnings are structured separately and never replace an application failure.
Summary entries are a subset of the existing stored workspace JSON:
{
"view": "summary",
"workspaces": [
{
"id": "{6CF910A2-D2E0-436D-A50E-41432A88452A}",
"name": "Development",
"applications": [
{ "application": "Notepad" },
{ "application": "Microsoft Edge" }
]
}
]
}
Both unfiltered and selected lists contain view: "summary" or "detail" and a top-level
workspaces array, even for one selected workspace or no workspaces. Scripts can read
.workspaces directly. Summary preserves application order, duplicates, field names, types
and values; no apps, per-app name, or count alias is added.
Detail reuses the native workspace serializer, including existing kebab-case names, timestamp units and monitor/DPI configuration. Paths and command arguments may contain sensitive data: request detail deliberately and protect redirected output. Runtime handles/process IDs are not part of stored workspace JSON.
Launch results contain the workspace and operation IDs, per-application results and a separate
persistence status. An observed arranged state does not certify that a document/tab has loaded
or that an application is ready for business use.
If launch-history verification fails with persistenceUnverified, exit code 11 is preserved:
text mode writes the error to stderr and the observed application results to stdout; JSON mode
includes both in its single response. Do not rerun the workspace merely to recover those results.
Per-application launched preserves the native Launched state: the application launch succeeded,
but window arrangement was not confirmed. A localized message explains this distinction.
arrangeFailed is reserved for a native failed result without a recorded application-launch error;
recorded launch errors remain launchFailed. arranged means the engine confirmed arrangement.
Aggregate state distinguishes launch progress from fully confirmed restoration:
| Application outcomes | Aggregate state | Exit code |
|---|---|---|
Every application is arranged |
completed |
0 |
At least one is launched or arranged, but not all are arranged |
partial |
10 |
None is launched or arranged (for example, all skipped or failed) |
failed |
10 |
Thus skipped plus launched is partial, not failed. Exit codes remain unchanged:
unconfirmed arrangement is not counted as a fully restored workspace.
Final arranger responses must contain a known outcome for every requested application.
Waiting, fractional/out-of-range numeric states, missing/duplicate applications and invalid results
are rejected before applying or acknowledging completion. They produce outcomeUnknown (exit 9), not an application-level
unknown with exit 10. Native Launched remains valid and is reported as launched.
Launch behavior
- Launch waits for an explicit final arranger result. There is no accepted-only/no-wait mode.
- The default total deadline is 120 seconds;
--timeoutaccepts 1 through 600. - At the deadline the shared launcher cancels its pending confirmation and allows up to two seconds for
execution to acknowledge completion. An acknowledged timeout reports
timeout; an absent, malformed or unresponsive final result reportsoutcomeUnknown. This does not extend app-launch or approval time. An unresponsive in-process operation retains its owned state until the Shell call returns or the one-shot adapter exits after reporting the error; no unsafe thread termination is used. - Workspaces progress/editor windows are suppressed. The existing Workspaces signature confirmation dialog, application windows and normal Windows UAC remain possible.
- Console applications launched as workspace targets get their own console rather than sharing the CLI's console. The CLI keeps its original console and standard streams.
- Both ordinary and administrator terminals use the same command. The shared session layer runs directly inside a verified Medium caller; only an elevated caller starts a Medium Launcher host in the same user's desktop session. Existing saved elevation flags still determine individual app/UAC behavior; the administrator caller does not implicitly elevate every app.
- Existing windows follow the saved
move-existing-windowssetting. Another launch in the same session returns busy rather than queueing or taking over. - Ctrl+C requests stopping further launches. Already-started applications are not killed and window positions are not rolled back. A blocked operation without acknowledgment is reported as unknown outcome, not canceled.
- A timeout does not mean that a Windows consent dialog was dismissed. Do not automatically retry: launch is not idempotent and repeating it may open more applications.
- The shared executor and arranger use the same immutable selected snapshot. Final history writes read the current file under a shared writer lock and do not replace it with the old launch snapshot. The initial snapshot exchange waits for both IPC endpoints within its startup deadline before sending the first request, so asynchronous pipe creation cannot discard it or its reply.
- Before launching legacy workspaces, the Medium executor assigns and safely persists only missing application IDs. Existing IDs are preserved; malformed or duplicate existing IDs are errors. Concurrent edits or failed ID persistence stop the launch before applications start. Listing remains read-only, and a later canceled/timed-out launch does not undo completed ID initialization.
- The foreground CLI changes the console output code page only while writing output, flushes those writes and restores it before returning to other work; launch waits leave it unchanged. Elevated callers receive the host result through a private pipe, independent of console output and CLI localization.
- File logging is best-effort. An unavailable log directory/file does not prevent launching;
the final launch result includes a
loggingUnavailablewarning without changing application outcomes.
Shared session and administrator routing
The CLI and GUI launcher are adapters over WorkspacesSession::Start. The entry and execution core
are statically linked from WorkspacesLib, including the existing Launcher/AppLauncher implementation.
Start selects an immutable snapshot and checks context. A Medium caller invokes the shared Execute
in the same process, without another Launcher process, snapshot mapping or result pipe. An elevated
caller creates a verified Medium PowerToys.WorkspacesLauncher.exe host that invokes the same Execute.
Unsupported contexts fail; no path executes the core at High IL.
Execute owns one preparation/launch/history flow and returns a typed outcome; only the CLI adapter
creates the public JSON, text and exit-code representation.
flowchart TD
C["GUI Launcher / CLI"] --> S["WorkspacesSession::Start<br/>WorkspacesLib.lib"]
S --> M{"Execution context"}
M -->|"Verified Medium"| E["Execute in the caller process"]
M -->|"Elevated"| H["Create a verified Medium Launcher host"]
H --> HE["Same Execute implementation in the host"]
M -->|"Unsupported"| F["Fail without launching"]
E --> R["Typed result returned in-process"]
HE --> P["Private result pipe back to Start"]
The GUI retains progress presentation and its invocation context, including unsaved Launch-and-Edit
drafts; CLI mode only displays required confirmation UI. No Editor save-workflow changes are added.
Launch and Edit can start an already-saved workspace without a temporary draft. A missing
temp-workspaces.json falls back to saved configuration; an existing malformed draft remains an
error, and a valid matching draft takes precedence.
The GUI displays the existing localized file-read or invalid-file dialog when configuration loading
fails, identifying the saved workspace file or temporary draft that caused the error. This local
diagnostic context does not change the public CLI error JSON.
The progress window's Cancel button and CLI cancellation both stop the shared launch wait and return
an acknowledged canceled result without requiring normal arranger completion. Pending application
launches stop, already-started applications remain open, and operation-owned workers and the launch
lock are released. Non-canceled launches still require a complete, validated arranger result.
Teardown stops and joins arranger callbacks before releasing their helper owner, so an
in-flight snapshot or final-result callback cannot access an already-cleared helper pointer.
For elevated callers, shared session routing validates the desktop shell's user/session/Medium integrity, creates a suspended host through the common Explorer-parent helper, and verifies the actual child's token before allowing execution.
The child receives its operation handles through a bounded, read-only bootstrap pipe. The frontend pins the exact created PID; both sides verify the expected module image/version and Microsoft signature in Release. User SID/session must match, and the child must be non-elevated. Only a read-only snapshot, wait-only cancellation/owner-lifetime handles and the existing result writer are duplicated. No token handle or general administrator process capability is handed to the worker.
The existing run_non_elevated helper owns Explorer selection, startup attributes and process
creation. Its owning overload supports suspended creation and parent validation; the existing
PID-only API delegates to that implementation. The module's shared SessionHandoff owns bootstrap
identity/capability transfer for both GUI and CLI; neither adapter maintains an independent
de-elevation or worker implementation. Other users of
RunNonElevatedEx retain their existing COM-based behavior.
No normal same-user Explorer, a different-user administrator, wrong session, identity mismatch
or failed transfer yields deElevationFailed; the CLI never falls back to elevated execution.
No extra console is opened. UAC for individual configured elevated apps remains separate.
The arranger accepts only the exact caller PID with a matching installed directory/version and one
of the official CLI/Launcher executable names. Release retains signature verification. PR UI-test
setup signs the CLI, Launcher, arranger and Launcher UI using the existing companion-signing mechanism;
it does not disable production authentication.
Unverified executable confirmation
Workspaces' existing shared launch gate checks eligible EXE targets configured to run as administrator. This is separate from Windows UAC and from IPC peer authentication. The CLI does not change the signature policy or create a permanent trust list.
When a decision is required, both GUI and CLI use Launcher::requestApproval,
PendingLaunchApproval, LauncherUIHelper and the existing SignatureWarningWindow.
There is no independent CLI approval state machine or terminal A/S prompt.
- The CLI lazily starts the existing launcher UI in confirmation-only mode. It shows the same warning dialog without opening the launch-progress window.
- Allow is enabled only after the existing rendered acknowledgement. It authorizes only the current checked target; Windows UAC may still follow.
- Skip, Escape or closing the dialog skips the application. Ctrl+C cancels the CLI operation.
- The launch deadline includes decision time. Cancellation/expiry dismisses the pending warning and never approves it. The confirmation host exits if its owning launch worker exits.
- Redirecting stdin/stdout/stderr does not suppress the dialog or automatically approve anything. A usable interactive desktop is still required, and unavailable UI is reported explicitly.
- The shared launcher retains the checked file throughout confirmation and launch. Existing stale/duplicate reply handling and signature/UAC policies are unchanged.
Only the elevated caller's host uses private NUL standard streams; inline execution leaves the caller's streams intact. Confirmation uses the existing launcher UI message protocol on operation-scoped pipes, separate from the CLI result stream. The native endpoint verifies the UI module image/version and requires its Microsoft signature in Release.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Requested operation completed, possibly with nonessential history-save warnings |
| 1 | Unexpected command/runtime error |
| 2 | Invalid command, options or selector syntax |
| 3 | Workspace not found |
| 4 | Ambiguous workspace name |
| 5 | Another launch owns the session |
| 6 | Disabled by user/policy, policy lookup failure or component unavailable; inspect error.code |
| 7 | Unsupported caller context, failed de-elevation, arranger startup failure or required consent declined |
| 8 | Invalid/unreadable saved configuration, conflicting legacy ID initialization or failed ID persistence |
| 9 | Deadline or unknown outcome |
| 10 | One or more applications failed, were skipped, or lack confirmed arrangement/required approval |
| 11 | Persistence replacement could not be verified; app results remain separate |
| 12 | Acknowledged cancellation |
| 9009-9011 | Shared shim could not resolve/find/start its target |
Stable machine error codes distinguish reasons such as disabledByUser, disabledByPolicy,
workspaceNotFound, deElevationFailed, consentDenied and outcomeUnknown; human messages are resource-backed.
Per-app skipped, confirmationRequired, confirmationTimedOut, confirmationFailed and
canceled are not aliases for a Windows UAC denial. Skipping an app means the requested workspace
was not fully restored, so it is not reported as all-success.
CLI telemetry
When optional PowerToys diagnostics are enabled, the foreground CLI emits one Workspaces_CLICommand
usage event per invocation, following the Settings CLI convention. Only a fixed CommandName
(list, launch, help, version, none, or unknown) and Successful (exit code zero) are recorded.
Partial restoration, cancellation and command failures are not counted as successful. No workspace
names/IDs, paths, argument values or error text are included. The Medium host does not emit this
command event, so elevated calls are not counted twice. Existing GUI launch telemetry is unchanged.
Compatibility boundaries
The CLI does not enable modules, migrate storage to another format, or add a permanent service. General Editor snapshot-conflict handling and save-workflow redesign are not included. Basic atomic file IO and targeted launch metadata updates remain, but an older Editor snapshot is not merged with concurrent configuration/application metadata changes when the Editor saves. The public CLI contract covers the reused detail shape: future breaking storage changes must not silently change that public output, including unversioned successful list responses. Future creation APIs may reuse appropriate payload types, but query/result envelopes and system-owned IDs are not automatically writable input.