mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
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:
63
tools/CliShim/CommandLine.cpp
Normal file
63
tools/CliShim/CommandLine.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
#include "CommandLine.h"
|
||||
|
||||
namespace CommandLine
|
||||
{
|
||||
std::wstring StripArgumentZero(std::wstring_view commandLine)
|
||||
{
|
||||
const auto isWhitespace = [](const wchar_t character) {
|
||||
return character == L' ' || character == L'\t';
|
||||
};
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
// A non-shell CreateProcessW caller can prepend whitespace; without this skip the scan
|
||||
// below stalls at index 0 and leaks argv[0] into the forwarded tail. This is a deliberate
|
||||
// departure from the CRT, which would report an empty argv[0] instead.
|
||||
while (index < commandLine.size() && isWhitespace(commandLine[index]))
|
||||
{
|
||||
++index;
|
||||
}
|
||||
|
||||
// argv[0] is tokenized differently from every later argument, and the rule that matters is
|
||||
// the CRT's, because that is what every target ends up parsing: FileLocksmithCLI is a
|
||||
// native wmain, and the .NET CLIs receive their string[] from the apphost's wmain. The CRT
|
||||
// (ucrt\startup\argv_parsing.cpp, parse_command_line) toggles an in-quotes flag on every
|
||||
// quote while scanning argv[0] and ends the name at the first whitespace found outside
|
||||
// quotes; a quote never terminates the name and a backslash never escapes one.
|
||||
//
|
||||
// CommandLineToArgvW is the odd one out - it ends a quoted argv[0] at the closing quote,
|
||||
// with no toggling - so following it instead would leak the rest of the program name into
|
||||
// the tail of a partially quoted command line such as
|
||||
// `"%ProgramFiles%"\PowerToys\bin\PowerToys.FancyZones.CLI.exe arg`, which cmd.exe passes
|
||||
// through verbatim. Matching the CRT is what makes the shim transparent: the target sees
|
||||
// the exact tail it would have seen had the caller invoked it directly.
|
||||
bool inQuotes = false;
|
||||
while (index < commandLine.size())
|
||||
{
|
||||
const wchar_t character = commandLine[index];
|
||||
++index;
|
||||
|
||||
if (character == L'"')
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inQuotes && isWhitespace(character))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (index < commandLine.size() && isWhitespace(commandLine[index]))
|
||||
{
|
||||
++index;
|
||||
}
|
||||
|
||||
return std::wstring{ commandLine.substr(index) };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user