Files
PowerToys/tools/CliShim.UnitTests/CommandLineTests.cpp

74 lines
3.2 KiB
C++
Raw Normal View History

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
2026-08-14 14:32:27 +08:00
// 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 <string>
#include <CppUnitTest.h>
#include "CommandLine.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace CliShimUnitTests
{
TEST_CLASS(CommandLineTests)
{
public:
TEST_METHOD(StripArgumentZero_ReturnsForwardedTail)
{
struct Case
{
const wchar_t* commandLine;
const wchar_t* expected;
};
const Case cases[] = {
{ L"PowerToys.FancyZones.CLI arg", L"arg" },
{ L"PowerToys.FancyZones.CLI a b c", L"a b c" },
{ L"PowerToys.FancyZones.CLI", L"" },
{ L"PowerToys.FileLocksmith.CLI", L"" },
{ LR"("C:\Program Files\PowerToys\bin\PowerToys.FancyZones.CLI.exe" arg)", L"arg" },
{ LR"("C:\Program Files\PowerToys\bin\PowerToys.FancyZones.CLI.exe")", L"" },
// Quotes only toggle, so a quoted argv[0] does not end at the closing quote: an
// argument glued to it is part of the program name. CommandLineToArgvW would
// forward `--help` here; the CRT - and so every target CLI - sees no arguments.
{ LR"("C:\bin\PowerToys.FancyZones.CLI.exe"--help)", L"" },
// Partially quoted program names - the batch idiom of quoting only the variable,
// `"%ProgramFiles%"\PowerToys\bin\...`, which cmd.exe passes through verbatim - are
// resolved exactly as the CRT resolves them, so the target sees the same tail it
// would have seen had the caller invoked it directly rather than through the shim.
{ LR"("C:\Program Files"\PowerToys\bin\PowerToys.FancyZones.CLI.exe arg)", L"arg" },
{ LR"(C:\Program" Files"\PowerToys\bin\PowerToys.FancyZones.CLI.exe arg)", L"arg" },
{ LR"("C:\Program Files"\PowerToys\bin\PowerToys.FancyZones.CLI.exe)", L"" },
{ LR"("C:\bin\PowerToys.FancyZones.CLI.exe" "a b")", LR"("a b")" },
{ LR"(PowerToys.FancyZones.CLI --path "C:\a b\c.png")", LR"(--path "C:\a b\c.png")" },
{ L"PowerToys.FancyZones.CLI\targ", L"arg" },
{ L"PowerToys.FancyZones.CLI \t arg", L"arg" },
// Non-shell CreateProcessW callers can prepend whitespace; argv[0] must not leak.
{ L" PowerToys.FancyZones.CLI arg", L"arg" },
{ L" PowerToys.FancyZones.CLI", L"" },
{ LR"( "C:\bin\PowerToys.FancyZones.CLI.exe" arg)", L"arg" },
{ L"", L"" },
// An unterminated argv[0] quote consumes the remaining command line.
{ LR"("C:\Program Files\app)", L"" },
};
for (const Case& testCase : cases)
{
const std::wstring actual = CommandLine::StripArgumentZero(testCase.commandLine);
const std::wstring message = std::wstring(L"input: <") + testCase.commandLine + L">";
Assert::AreEqual(std::wstring(testCase.expected), actual, message.c_str());
}
}
};
}