doc/devdocs/cli-conventions.md
This document describes the conventions for implementing command-line interfaces (CLI) in PowerToys modules.
PowerToys.<ModuleName>.CLI.exe (for example, PowerToys.ImageResizer.CLI.exe).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.
<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.<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.
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. |
Use the System.CommandLine library for CLI argument parsing. This is already defined in Directory.Packages.props:
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
Add the reference to your project:
<PackageReference Include="System.CommandLine" />
--kebab-case for long form (e.g., --shrink-only).-x for short form (e.g., -s, -w).["--silent", "-s"].Option<T> with descriptive help text.RootCommand with a brief description.Parser(rootCommand).Parse(args) to parse CLI arguments.parseResult.GetValueForOption().Parser directly; RootCommand.Parse() may not be available with the pinned System.CommandLine version.Reference implementations:
src/modules/Awake/Awake/Program.cssrc/modules/imageresizer/ui/Cli/PrintUsage() method for custom help formatting if needed..editorconfig, StyleCop) and formatting rules.ManagedCommon.Logger for consistent logging.Main().src/modules/imageresizer/ui/Cli/CliLogger.cs0: Success1: General error (parsing, validation, runtime)2: Invalid arguments (optional)Main() in try-catch for unhandled exceptions.src/modules/[module]/tests/*CliTests.cs)..pipelines/ESRPSigning_core.json in the signing list.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.Common.SelfContained.props).