-
Notifications
You must be signed in to change notification settings - Fork 7.6k
[File Locksmith] Add CLI support #44047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
|
@shuaiyuanxx would you mind posting a table with the commands, aliases and what they do in the PR description? I can then use that for adding it to the docs :) |
| } | ||
|
|
||
| ss << L"Waiting for files to be unlocked..." << std::endl; | ||
| while (true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No timeout. If the files remain locked, the program will loop indefinitely.
| std::wstringstream ss; | ||
| ss << L"Usage: FileLocksmithCLI.exe [options] <path1> [path2] ...\n" | ||
| << L"Options:\n" | ||
| << L" --kill Kill processes locking the files\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need localization for these message
| ss << L"Files unlocked." << std::endl; | ||
| break; | ||
| } | ||
| Sleep(1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1s maybe too long
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces a command-line interface (CLI) for the File Locksmith module, enabling users to query and manage file locks from the command line. The implementation creates a new FileLocksmithCLI project that links against FileLocksmithLib to reuse core process-scanning functionality.
Key changes:
- New FileLocksmithCLI console application with support for JSON output, process termination, and waiting for file unlock
- Refactored FileLocksmithLib to support static linking via the FILELOCKSMITH_LIB_STATIC preprocessor definition
- Added FileLocksmithCLI.exe to the code signing pipeline
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/modules/FileLocksmith/FileLocksmithCLI/main.cpp | New CLI implementation with command-line parsing and multiple execution modes (query, kill, wait, JSON output) |
| src/modules/FileLocksmith/FileLocksmithCLI/pch.h | Precompiled header for CLI project (contains duplicate header guards) |
| src/modules/FileLocksmith/FileLocksmithCLI/pch.cpp | Precompiled header source file |
| src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj | New project configuration for the CLI console application |
| src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj.filters | Project filter organization for the CLI |
| src/modules/FileLocksmith/FileLocksmithCLI/packages.config | NuGet package configuration for CppWinRT dependency |
| src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj | Updated to add FILELOCKSMITH_LIB_STATIC preprocessor definition and include FileLocksmithLibInterop source files directly |
| src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj.filters | Added filters for newly included FileLocksmithLibInterop source files |
| src/modules/FileLocksmith/FileLocksmithLib/pch.h | Removed precompiled header file (creates build issue) |
| src/modules/FileLocksmith/FileLocksmithLib/Settings.cpp | Added missing <filesystem> include |
| src/modules/FileLocksmith/FileLocksmithLib/FileLocksmith.h | New header exposing process-finding functions for CLI usage |
| src/modules/FileLocksmith/FileLocksmithLib/ProcessResult.h | New header defining ProcessResult structure for returning process information |
| src/modules/FileLocksmith/FileLocksmithLibInterop/pch.h | Modified to conditionally exclude WinRT headers when FILELOCKSMITH_LIB_STATIC is defined |
| PowerToys.slnx | Added FileLocksmithCLI project to the solution |
| .pipelines/ESRPSigning_core.json | Added FileLocksmithCLI.exe to the signing configuration |
| #ifndef PCH_H | ||
| #pragma once |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The precompiled header has duplicate #ifndef PCH_H guards at lines 3 and 6, and duplicate #endif // PCH_H at lines 25 and 28. Remove the redundant outer guards to follow standard practice and avoid confusion.
|
|
||
|
|
||
| #endif // PCH_H | ||
|
|
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the extra blank lines and the trailing #endif // PCH_H at line 28. There should only be one header guard closing at the end of the file.
| ss << L"Waiting for files to be unlocked..." << std::endl; | ||
| while (true) | ||
| { | ||
| auto results = find_processes_recursive(paths); | ||
| if (results.empty()) | ||
| { | ||
| ss << L"Files unlocked." << std::endl; | ||
| break; | ||
| } | ||
| Sleep(1000); | ||
| } |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The --wait flag implements a busy wait loop with Sleep(1000), checking every second indefinitely with no timeout or escape mechanism. This can cause the CLI to hang indefinitely if files never unlock. Consider adding a timeout option or at least documenting this behavior in the help text.
| #include "pch.h" | ||
| #include "FileLocksmithLib/FileLocksmith.h" | ||
| #include <common/utils/json.h> | ||
| #include <iostream> | ||
| #include <sstream> | ||
|
|
||
| struct CommandResult | ||
| { | ||
| int exit_code; | ||
| std::wstring output; | ||
| }; | ||
|
|
||
| std::wstring get_usage() | ||
| { | ||
| std::wstringstream ss; | ||
| ss << L"Usage: FileLocksmithCLI.exe [options] <path1> [path2] ...\n" | ||
| << L"Options:\n" | ||
| << L" --kill Kill processes locking the files\n" | ||
| << L" --json Output results in JSON format\n" | ||
| << L" --wait Wait for files to be unlocked\n" | ||
| << L" --help Show this help message\n"; | ||
| return ss.str(); | ||
| } | ||
|
|
||
| std::wstring get_json(const std::vector<ProcessResult>& results) | ||
| { | ||
| json::JsonObject root; | ||
| json::JsonArray processes; | ||
|
|
||
| for (const auto& result : results) | ||
| { | ||
| json::JsonObject process; | ||
| process.SetNamedValue(L"pid", json::JsonValue::CreateNumberValue(result.pid)); | ||
| process.SetNamedValue(L"name", json::JsonValue::CreateStringValue(result.name)); | ||
| process.SetNamedValue(L"user", json::JsonValue::CreateStringValue(result.user)); | ||
|
|
||
| json::JsonArray files; | ||
| for (const auto& file : result.files) | ||
| { | ||
| files.Append(json::JsonValue::CreateStringValue(file)); | ||
| } | ||
| process.SetNamedValue(L"files", files); | ||
|
|
||
| processes.Append(process); | ||
| } | ||
|
|
||
| root.SetNamedValue(L"processes", processes); | ||
| return root.Stringify().c_str(); | ||
| } | ||
|
|
||
| std::wstring get_text(const std::vector<ProcessResult>& results) | ||
| { | ||
| std::wstringstream ss; | ||
| if (results.empty()) | ||
| { | ||
| ss << L"No processes found locking the file(s)." << std::endl; | ||
| return ss.str(); | ||
| } | ||
|
|
||
| ss << L"PID\tUser\tProcess" << std::endl; | ||
| for (const auto& result : results) | ||
| { | ||
| ss << result.pid << L"\t" | ||
| << result.user << L"\t" | ||
| << result.name << std::endl; | ||
| } | ||
| return ss.str(); | ||
| } | ||
|
|
||
| std::wstring kill_processes(const std::vector<ProcessResult>& results) | ||
| { | ||
| std::wstringstream ss; | ||
| for (const auto& result : results) | ||
| { | ||
| HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, result.pid); | ||
| if (hProcess) | ||
| { | ||
| if (TerminateProcess(hProcess, 0)) | ||
| { | ||
| ss << L"Terminated process " << result.pid << L" (" << result.name << L")" << std::endl; | ||
| } | ||
| else | ||
| { | ||
| ss << L"Failed to terminate process " << result.pid << L" (" << result.name << L")" << std::endl; | ||
| } | ||
| CloseHandle(hProcess); | ||
| } | ||
| else | ||
| { | ||
| ss << L"Failed to open process " << result.pid << L" (" << result.name << L")" << std::endl; | ||
| } | ||
| } | ||
| return ss.str(); | ||
| } | ||
|
|
||
| CommandResult run_command(int argc, wchar_t* argv[]) | ||
| { | ||
| if (argc < 2) | ||
| { | ||
| return { 1, get_usage() }; | ||
| } | ||
|
|
||
| bool json_output = false; | ||
| bool kill = false; | ||
| bool wait = false; | ||
| std::vector<std::wstring> paths; | ||
|
|
||
| for (int i = 1; i < argc; ++i) | ||
| { | ||
| std::wstring arg = argv[i]; | ||
| if (arg == L"--json") | ||
| { | ||
| json_output = true; | ||
| } | ||
| else if (arg == L"--kill") | ||
| { | ||
| kill = true; | ||
| } | ||
| else if (arg == L"--wait") | ||
| { | ||
| wait = true; | ||
| } | ||
| else if (arg == L"--help") | ||
| { | ||
| return { 0, get_usage() }; | ||
| } | ||
| else | ||
| { | ||
| paths.push_back(arg); | ||
| } | ||
| } | ||
|
|
||
| if (paths.empty()) | ||
| { | ||
| return { 1, L"Error: No paths specified.\n" }; | ||
| } | ||
|
|
||
| if (wait) | ||
| { | ||
| std::wstringstream ss; | ||
| if (json_output) | ||
| { | ||
| ss << L"Warning: --wait is incompatible with --json. Ignoring --json." << std::endl; | ||
| json_output = false; | ||
| } | ||
|
|
||
| ss << L"Waiting for files to be unlocked..." << std::endl; | ||
| while (true) | ||
| { | ||
| auto results = find_processes_recursive(paths); | ||
| if (results.empty()) | ||
| { | ||
| ss << L"Files unlocked." << std::endl; | ||
| break; | ||
| } | ||
| Sleep(1000); | ||
| } | ||
| return { 0, ss.str() }; | ||
| } | ||
|
|
||
| auto results = find_processes_recursive(paths); | ||
| std::wstringstream output_ss; | ||
|
|
||
| if (kill) | ||
| { | ||
| output_ss << kill_processes(results); | ||
| // Re-check after killing | ||
| results = find_processes_recursive(paths); | ||
| } | ||
|
|
||
| if (json_output) | ||
| { | ||
| output_ss << get_json(results) << std::endl; | ||
| } | ||
| else | ||
| { | ||
| output_ss << get_text(results); | ||
| } | ||
|
|
||
| return { 0, output_ss.str() }; | ||
| } | ||
|
|
||
| int wmain(int argc, wchar_t* argv[]) | ||
| { | ||
| winrt::init_apartment(); | ||
| auto result = run_command(argc, argv); | ||
| std::wcout << result.output; | ||
| return result.exit_code; | ||
| } |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new CLI functionality in main.cpp lacks unit test coverage. Other C++ modules in this repository have comprehensive unit tests (FancyZones, KeyboardManager, PowerRename, Workspaces). Consider adding tests for command-line parsing logic, the different execution modes (kill, wait, json), and edge cases like invalid paths or permission failures.
| "WinUI3Apps\\PowerToys.FileLocksmithUI.dll", | ||
| "WinUI3Apps\\PowerToys.FileLocksmithContextMenu.dll", | ||
| "FileLocksmithContextMenuPackage.msix", | ||
| "FileLocksmithCLI.exe", |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FileLocksmithCLI.exe binary is signed in ESRPSigning_core.json but is not yet included in the installer configuration. According to the PR checklist, you need to add the new binary to the WXS installer files (likely in installer/PowerToysSetupVNext/BaseApplications.wxs or a similar component file) so the CLI executable is deployed with PowerToys installations.
| <ClCompile Include="..\FileLocksmithLibInterop\NtdllBase.cpp" /> | ||
| <ClCompile Include="..\FileLocksmithLibInterop\NtdllExtensions.cpp" /> | ||
| <ClCompile Include="pch.cpp"> | ||
| <PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader> |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pch.h file has been removed from FileLocksmithLib, but pch.cpp is still being compiled with precompiled header creation enabled. The pch.cpp file contains #include "pch.h" which will fail to compile since pch.h no longer exists. Either restore the pch.h file or remove the precompiled header usage from the project configuration.
| <PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader> | |
| <PrecompiledHeader>NotUsing</PrecompiledHeader> | |
| <ExcludedFromBuild>true</ExcludedFromBuild> |
lei9444
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also add some logging for this project.
Summary of the Pull Request
This pull request introduces a new command-line interface (CLI) project for the File Locksmith module, enabling users to interact with File Locksmith functionality directly from the command line. The changes include project and build configuration, CLI implementation, and supporting code to integrate with the existing FileLocksmith library.
Commands and Options
<path>--kill--json--wait--helpUsage Examples
1. Basic check (Human-readable output)
Check which processes are locking a specific file:
2. Check multiple files and output JSON
Check multiple files and get the output in JSON format for parsing:
3. Wait for a file to be unlocked
Block script execution until a file is released (useful in build scripts):
4. Force unlock a file
Kill all processes that are locking a specific file:
PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed