Files
Boliang ZhangandCopilot 06e6ae43e2 feat(workspaces): add list and launch CLI (#51114)
<!-- 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>

![Workspaces CLI
list](https://github.com/user-attachments/assets/3755c810-0b85-4afe-b80f-f17f12e7f07d)
![Shared confirmation UI; controlled probe, no target
executed](https://github.com/user-attachments/assets/fbd09160-725d-47b7-907e-307bb2d749dc)

</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
2026-10-27 11:29:49 +08:00
..