Files
Clint Rutkas 784bab1b17 [Deps] Bump System.CommandLine from 2.0.1 to 2.0.12 (#51323)
<!-- 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

Finishes DEP-3 by moving the central System.CommandLine pin from `2.0.1`
to `2.0.12`, the latest 2.0.x servicing release. No code changes are
needed. CLI help, errors, and exit codes stay the same.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: N/A (DEP-3 roadmap item; follows #50884)
<!-- - [ ] 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 (existing tests; version
bump only)
- [ ] **Localization:** All end-user-facing strings can be localized (no
new strings)
- [x] **Dev docs:** Added/updated (`doc/devdocs/cli-conventions.md`)
- [ ] **New binaries:** Added on the required places (none)
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** N/A

<!-- 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

- **Library source is unchanged.** The 2.0.12 commit (dotnet/dotnet
`95017c7`) descends from the 2.0.1 commit (`fad253f`). The last commit
that touches `src/command-line-api/src/System.CommandLine` is `4ad7638`
(2025-11-14) in both, so 2.0.2–2.0.12 are .NET 10 servicing rebuilds.
- **The package layout matches 2.0.1:** the same `net8.0` and
`netstandard2.0` targets, 13 satellite cultures, and no `net8.0`
dependencies. `generateAllFileComponents.ps1` harvests satellites at
build time, so the installer needs no change.
- **AssemblyVersion is now `2.0.12.0`** with the same public key token.
.NET doesn't need binding redirects.
- **Restored anonymously** from `PowerToysPublicDependencies`, where
2.0.12 was already saved. Nothing new was pulled from nuget.org.
NOTICE.md doesn't list this package.

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed

- `msbuild <proj> -t:Restore -p:RestorePackagesConfig=true
-p:SolutionDir=<repo>\`, run with `NUGET_PLUGIN_PATHS` pointing at an
empty folder, for all 10 consumers and their test projects: all exit 0.
- `tools\build\build.ps1 -Platform x64 -Configuration Debug -Path <dir>`
for 11 consumer and test projects: all exit 0, with no new warnings.
- `vstest.console.exe <dll> /Platform:x64`: LightSwitch.Cli 221/221,
AdvancedPaste.Cli 154/154, PowerDisplay.Cli 130/130, DSC 115/115,
ImageResizer 213/213, Awake 7/7, CmdPal Shell 128 passed and 1 skipped.
- Settings.UI.UnitTests: 426/448 pass, including all 19
`SettingsCliTests`. The 22 failures are `ImageResizer`/`KeyboardManager`
view-model tests where `ResourceLoaderInstance` throws
`FileNotFoundException`. The same 22 tests fail on a 2.0.1 baseline.
- A/B test: 31 calls across 7 CLIs (`--help`, unknown command or option,
invalid values) built against 2.0.1 and 2.0.12. stdout, stderr, and exit
codes are byte-identical.
2026-10-27 22:24:17 +00:00

6.9 KiB

CLI Conventions

This document describes the conventions for implementing command-line interfaces (CLI) in PowerToys modules.

PATH-Visible Command Naming and Location

  • Name module CLI command shims PowerToys.<ModuleName>.CLI.exe (for example, PowerToys.ImageResizer.CLI.exe).
  • Install these shims in the bin subfolder of the PowerToys installation directory, which the installer adds to PATH.

Every command is the same PowerToys.CliShim.exe payload (tools/CliShim/) installed under a different name. The shim resolves which CLI to launch from its own file name, forwards the raw argument tail unchanged, shares the caller's console, and returns the CLI's exit code. The CLI runs in a job object owned by the shim, so killing the shim kills the CLI with it; processes the CLI itself starts (the Settings window, for example) break away and survive.

On a per-machine install the bin folder is created with a protected DACL (MachinePathFolderSddl in installer/PowerToysSetupVNext/Common.wxi) so that a custom installation root cannot leave a machine-PATH folder writable by standard users. Author that <CreateFolder> on the same component as the folder's <Environment> PATH entry, so the two cannot drift apart.

Adding a new shim

  1. Add a <CliShim> item to tools/CliShim/CliShimManifest.props with the command name and the target's path relative to bin. Write that path with / separators, and against the installed layout (see Signing and Deployment) - which is where the CLI ends up, not where it is built from.
  2. Add the matching <Component> and <ComponentRef> to installer/PowerToysSetupVNext/CliShims.wxs, using the command name as the File/@Name.

CliShim.vcxproj fails the build if the command names in those two drift apart, build-installer.ps1 fails the build if a RelativeTarget does not resolve to a real executable, and CliShim.UnitTests generates its expectations from the same manifest, so there is no third list to update.

Shim exit codes

The shim returns the target CLI's exit code unchanged. It substitutes one of its own codes only when the CLI never ran, using values outside the range the CLIs use themselves:

Code Meaning
9009 No CLI is mapped to the invoked command name (matches cmd.exe's "command not found").
9010 The mapped target executable is missing from the installation.
9011 The shim could not start the target, including when it cannot resolve its own path.

Library

Use the System.CommandLine library for CLI argument parsing. The version is already defined in Directory.Packages.props:

<PackageVersion Include="System.CommandLine" Version="2.0.12" />

Add the reference to your project:

<PackageReference Include="System.CommandLine" />

Option Naming and Definition

  • Use --kebab-case for long form (e.g., --shrink-only).
  • Use single -x for short form (e.g., -s, -w).
  • Pass the long name first, then any aliases: new Option<bool>("--silent", "-s").
  • Create options using Option<T> and set Description to descriptive help text.
  • Add validators (option.Validators.Add(...), reporting failures with result.AddError(...)) for options that require range or format checking.

RootCommand Setup

  • Create a RootCommand with a brief description.
  • Add all options and arguments to the command (rootCommand.Options.Add(...), rootCommand.Arguments.Add(...)).
  • RootCommand includes the standard --help and --version options. Remove them from rootCommand.Options if the CLI renders its own help or reuses their aliases.

Parsing

  • Use rootCommand.Parse(args) to parse CLI arguments.
  • Extract option values using parseResult.GetValue(option).
  • To run a handler, register it with command.SetAction(parseResult => ...) and call parseResult.Invoke() or parseResult.InvokeAsync().

Parse/Validation Errors

  • On parse/validation errors, print error messages and usage, then exit with non-zero code.

Examples

Reference implementations:

  • Awake: src/modules/Awake/Awake/Program.cs
  • ImageResizer: src/modules/imageresizer/ui/Cli/

Help Output

  • Provide a PrintUsage() method for custom help formatting if needed.

Best Practices

  1. Consistency: Follow existing module patterns.
  2. Documentation: Always provide help text for each option.
  3. Validation: Validate input and provide clear error messages.
  4. Atomicity: Make one logical change per PR; avoid drive-by refactors.
  5. Build/Test Discipline: Build and test synchronously, one terminal per operation.
  6. Style: Follow repo analyzers (.editorconfig, StyleCop) and formatting rules.

Logging Requirements

  • Use ManagedCommon.Logger for consistent logging.
  • Initialize logging early in Main().
  • Use dual output (console + log file) for errors and warnings to ensure visibility.
  • Reference: src/modules/imageresizer/ui/Cli/CliLogger.cs

Telemetry and Privacy Requirements

  • Every module CLI must record usage telemetry through the existing PowerToys telemetry infrastructure, respecting the user's diagnostic data consent.
  • Record a fixed, allowlisted command category and a success flag based on the final exit code. Do not collect raw arguments, file paths, monitor IDs, profile values, or other user-provided content.
  • Telemetry failures must not change command output, exit codes, or exception behavior.
  • Add every new CLI telemetry event to DATA_AND_PRIVACY.md in the same PR, describing its purpose and collected data.
  • Follow Telemetry Events for event naming, privacy tags, and implementation details.

Error Handling

Exit Codes

  • 0: Success
  • 1: General error (parsing, validation, runtime)
  • 2: Invalid arguments (optional)

Exception Handling

  • Always wrap Main() in try-catch for unhandled exceptions.
  • Log exceptions before exiting with non-zero code.
  • Display user-friendly error messages to stderr.
  • Preserve detailed stack traces in log files only.

Testing Requirements

  • Include tests for argument parsing, validation, and edge cases.
  • Place CLI tests in module-specific test projects (e.g., src/modules/[module]/tests/*CliTests.cs).

Signing and Deployment

  • CLI executables are signed automatically in CI/CD.
  • New CLI tools: Add your executable and dll to .pipelines/ESRPSigning_core.json in the signing list.
  • CLI executables are deployed either to the installation root (e.g., C:\Program Files\PowerToys\FancyZonesCLI.exe) or, for WinUI 3 modules, next to their module in WinUI3Apps\ (e.g., C:\Program Files\PowerToys\WinUI3Apps\PowerToys.ImageResizerCLI.exe). PATH-visible shims are deployed to C:\Program Files\PowerToys\bin\, and a shim's RelativeTarget is resolved from that bin folder against the installed layout - not against the source tree.
  • Use self-contained deployment (import Common.SelfContained.props).