Add native CLI shims for PowerToys command-line tools (#48631)

## Summary of the Pull Request

Adds a native C++ multi-call shim that exposes existing PowerToys CLIs
through `PATH`. The shims are installed under the PowerToys `bin`
subfolder and follow the `PowerToys.<ModuleName>.CLI.exe` naming
convention. The launcher preserves the raw argument tail, shares the
caller's console, and returns the target process exit code.

| PATH-visible command | Target executable |
| --- | --- |
| `PowerToys.FancyZones.CLI.exe` | `FancyZonesCLI.exe` |
| `PowerToys.ImageResizer.CLI.exe` |
`WinUI3Apps/PowerToys.ImageResizerCLI.exe` |
| `PowerToys.FileLocksmith.CLI.exe` | `FileLocksmithCLI.exe` |
| `PowerToys.PowerDisplay.CLI.exe` |
`WinUI3Apps/PowerToys.PowerDisplay.Cli.exe` |

proof of this work:
<img width="1044" height="294" alt="image"
src="https://github.com/user-attachments/assets/b659c552-5c08-4430-85c3-eba48f286eb0"
/>
<img width="1137" height="244" alt="image"
src="https://github.com/user-attachments/assets/5fed493f-dc30-428d-a618-bf612ccf3635"
/>

<img width="1727" height="868" alt="image"
src="https://github.com/user-attachments/assets/5c32fd2e-4a3a-4138-b968-fb5434eebec3"
/>


## PR Checklist

- [x] Closes: #48634
- [x] **Communication:** Discussed with core contributors in this PR
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** CLI diagnostic messages are not localized
- [x] **Dev docs:** Updated CLI naming and installation conventions
- [x] **New binaries:** Added on the required places
  - [x] Signing JSON
  - [x] WiX installer entries
- [x] CI builds through `PowerToys.slnx`; no dedicated YML step is
required
  - [x] The existing release pipeline covers the solution and installer
- [x] **Documentation updated:** `doc/devdocs/cli-conventions.md`

## Detailed Description of the Pull Request / Additional comments

- Uses one native launcher binary for all commands and resolves the
target from the invoked shim filename.
- Installs PATH-visible shims under `PowerToys\bin`.
- Keeps the existing module CLI binaries and their deployment locations
unchanged.
- Rejects the previous unsuffixed and `*cli` command aliases.

## Validation Steps Performed

- Built `tools/CliShim.UnitTests/CliShim.UnitTests.vcxproj` in
`Release|x64`: 0 warnings, 0 errors.
- Ran `CliShim.UnitTests.dll` with `vstest.console.exe`: 5/5 tests
passed.
- Verified the CLI manifest, WiX command names, and `bin` installation
directory are synchronized.
- Ran `git diff --check`.

---------

Co-authored-by: Yu Leng <yuleng@microsoft.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d11c4221-248f-44a9-85fb-7017ed43f4ce
This commit is contained in:
moooyo
2026-08-14 14:32:27 +08:00
committed by GitHub
parent becc96f59c
commit ae416c045a
18 changed files with 1340 additions and 2 deletions

View File

@@ -0,0 +1,85 @@
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<?include $(sys.CURRENTDIR)\Common.wxi?>
<!--
PATH-visible shims forward to the installed PowerToys CLIs. Every command is the same
PowerToys.CliShim.exe payload installed under a different name; the shim resolves its target
from that name. tools\CliShim\CliShimManifest.props is the source of truth for the list and
the CliShim project fails the build if the two drift apart, so keep the File/@Name values and
that manifest in sync.
-->
<Fragment>
<DirectoryRef Id="CliFolder">
<Component Id="CliShim_PowerToys_FancyZones_CLI_exe" Guid="63800E2A-21B9-4A92-AD25-9C1DC7593F4F" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="CliShim_PowerToys_FancyZones_CLI_exe" Value="" KeyPath="yes" />
</RegistryKey>
<File Source="$(var.BinDir)CliShim\PowerToys.CliShim.exe" Name="PowerToys.FancyZones.CLI.exe" Id="CliShim_PowerToys.FancyZones.CLI.exe" Checksum="yes" />
</Component>
<Component Id="CliShim_PowerToys_ImageResizer_CLI_exe" Guid="2BC274AA-FECD-4A85-BF70-06670A6B99A5" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="CliShim_PowerToys_ImageResizer_CLI_exe" Value="" KeyPath="yes" />
</RegistryKey>
<File Source="$(var.BinDir)CliShim\PowerToys.CliShim.exe" Name="PowerToys.ImageResizer.CLI.exe" Id="CliShim_PowerToys.ImageResizer.CLI.exe" Checksum="yes" />
</Component>
<Component Id="CliShim_PowerToys_FileLocksmith_CLI_exe" Guid="2DD830DD-78D4-46EE-B23B-3EAB3D838EDF" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="CliShim_PowerToys_FileLocksmith_CLI_exe" Value="" KeyPath="yes" />
</RegistryKey>
<File Source="$(var.BinDir)CliShim\PowerToys.CliShim.exe" Name="PowerToys.FileLocksmith.CLI.exe" Id="CliShim_PowerToys.FileLocksmith.CLI.exe" Checksum="yes" />
</Component>
<Component Id="CliShim_PowerToys_PowerDisplay_CLI_exe" Guid="E98A36C9-384E-48CB-A844-A6A49E4138D9" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="CliShim_PowerToys_PowerDisplay_CLI_exe" Value="" KeyPath="yes" />
</RegistryKey>
<File Source="$(var.BinDir)CliShim\PowerToys.CliShim.exe" Name="PowerToys.PowerDisplay.CLI.exe" Id="CliShim_PowerToys.PowerDisplay.CLI.exe" Checksum="yes" />
</Component>
<!-- MSI broadcasts WM_SETTINGCHANGE; terminals open before install must be reopened. -->
<?if $(var.PerUser) = "true" ?>
<Component Id="cli_env_path_user" Guid="87AA86E8-81E8-44CB-9519-FC70CF7BEA56" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="cli_env_path_user" Value="" KeyPath="yes" />
</RegistryKey>
<Environment Id="AddCliToUserPath" Name="PATH" Action="set" Part="last" System="no" Value="[CliFolder]" />
</Component>
<?else?>
<Component Id="cli_env_path_machine" Guid="739F3916-2247-4F1B-8BBD-0949A5B870F7" Bitness="always64">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="cli_env_path_machine" Value="" KeyPath="yes" />
</RegistryKey>
<Environment Id="AddCliToMachinePath" Name="PATH" Action="set" Part="last" System="yes" Value="[CliFolder]" />
<!--
On the same component as the PATH entry on purpose: bin is only worth locking down
because it is on the machine PATH, and this way the two cannot drift apart.
CreateFolders applies the DACL before InstallFiles writes the shims, so the shims
inherit it and need no PermissionEx of their own.
-->
<CreateFolder>
<PermissionEx Sddl="$(var.MachinePathFolderSddl)" />
</CreateFolder>
</Component>
<?endif?>
</DirectoryRef>
<ComponentGroup Id="CliShimsComponentGroup">
<Component Id="RemoveCliFolder" Guid="831C56D8-9FEB-41B2-8A8C-BD49487479BB" Directory="CliFolder">
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
<RegistryValue Type="string" Name="RemoveCliFolder" Value="" KeyPath="yes" />
</RegistryKey>
<RemoveFolder Id="RemoveFolderCliFolder" Directory="CliFolder" On="uninstall" />
</Component>
<ComponentRef Id="CliShim_PowerToys_FancyZones_CLI_exe" />
<ComponentRef Id="CliShim_PowerToys_ImageResizer_CLI_exe" />
<ComponentRef Id="CliShim_PowerToys_FileLocksmith_CLI_exe" />
<ComponentRef Id="CliShim_PowerToys_PowerDisplay_CLI_exe" />
<?if $(var.PerUser) = "true" ?>
<ComponentRef Id="cli_env_path_user" />
<?else?>
<ComponentRef Id="cli_env_path_machine" />
<?endif?>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -54,5 +54,21 @@
<?define UpgradeCodeGUID="42B84BF7-5FBF-473B-9C8B-049DC16F7708" ?>
<?endif?>
<!--
DACL for every folder PowerToys adds to the machine PATH.
D:P is the point: it drops inherited ACEs. A per-machine INSTALLFOLDER is overridable
(PowerToys.wxs InstallFolder), and a custom root such as C:\PowerToys inherits
"Authenticated Users: Modify" from the drive root - which would let any standard user plant
binaries in a folder that every process on the machine searches. SYSTEM and Administrators
keep full control; BUILTIN\Users keep read + execute so non-admins can still run the commands.
Author this on the same component as the folder's Environment PATH entry, so a new
PATH-visible folder cannot ship without it. Per-user installs live under %LOCALAPPDATA% and
must NOT use this - InstallPrivileges is "limited" there, and this SDDL would leave the
installing user with only read + execute on their own install.
-->
<?define MachinePathFolderSddl="D:PAI(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)(A;OICI;GRGX;;;BU)(A;OICIIO;GA;;;CO)" ?>
<?define BinX32Dir="$(var.RepoDir)x86\$(var.Configuration)\" ?>
</Include>

View File

@@ -28,7 +28,7 @@
<RegistryValue Type="string" Name="SecureDSCModulesFolder" Value="" KeyPath="yes" />
</RegistryKey>
<CreateFolder>
<PermissionEx Sddl="D:PAI(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)(A;OICI;GRGX;;;BU)(A;OICIIO;GA;;;CO)" />
<PermissionEx Sddl="$(var.MachinePathFolderSddl)" />
</CreateFolder>
</Component>
<?endif?>

View File

@@ -136,6 +136,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
<Compile Include="Settings.wxs" />
<Compile Include="ShortcutGuide.wxs" />
<Compile Include="Tools.wxs" />
<Compile Include="CliShims.wxs" />
<Compile Include="MouseWithoutBorders.wxs" />
<Compile Include="WinUI3Applications.wxs" />
<Compile Include="MonacoSRC.wxs" />

View File

@@ -67,6 +67,7 @@
<ComponentGroupRef Id="DscResourcesComponentGroup" />
<ComponentGroupRef Id="WindowsAppSDKComponentGroup" />
<ComponentGroupRef Id="ToolComponentGroup" />
<ComponentGroupRef Id="CliShimsComponentGroup" />
<ComponentGroupRef Id="MonacoSRCHeatGenerated" />
<ComponentGroupRef Id="WorkspacesComponentGroup" />
<ComponentGroupRef Id="CmdPalComponentGroup" />
@@ -299,6 +300,7 @@
</Directory>
</Directory>
<Directory Id="ToolsFolder" Name="Tools" />
<Directory Id="CliFolder" Name="bin" />
</Directory>
</StandardDirectory>
<StandardDirectory Id="ProgramMenuFolder">