Skip to content

Conversation

@shuaiyuanxx
Copy link
Contributor

@shuaiyuanxx shuaiyuanxx commented Dec 3, 2025

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

Command / Option Alias Description
<path> N/A Required. One or more file or directory paths to check. You can specify multiple paths separated by spaces.
--kill N/A Terminates (kills) all processes that are currently locking the specified files.
--json N/A Outputs the results in structured JSON format instead of human-readable text. Useful for automation and scripts.
--wait N/A Blocks execution and waits until the specified files are released. The command will not exit until the files are unlocked.
--help N/A Displays the help message with usage instructions.

Usage Examples

1. Basic check (Human-readable output)

Check which processes are locking a specific file:

FileLocksmithCLI.exe "C:\Users\Docs\report.docx"

2. Check multiple files and output JSON

Check multiple files and get the output in JSON format for parsing:

FileLocksmithCLI.exe --json "C:\File1.txt" "C:\Folder\File2.dll"

3. Wait for a file to be unlocked

Block script execution until a file is released (useful in build scripts):

FileLocksmithCLI.exe --wait "C:\bin\output.exe"

4. Force unlock a file

Kill all processes that are locking a specific file:

FileLocksmithCLI.exe --kill "C:\LockedFile.dat"

PR Checklist

  • Closes: #xxx
  • Communication: I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
  • Tests: Added/updated and all pass
  • Localization: All end-user-facing strings can be localized
  • Dev docs: Added/updated
  • New binaries: Added on the required places
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
@shuaiyuanxx shuaiyuanxx marked this pull request as ready for review December 5, 2025 05:25
@shuaiyuanxx shuaiyuanxx requested a review from a team as a code owner December 5, 2025 05:25
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com>
@niels9001
Copy link
Collaborator

@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)
Copy link
Contributor

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"
Copy link
Contributor

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1s maybe too long

Copy link
Contributor

Copilot AI left a 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

Comment on lines +3 to +4
#ifndef PCH_H
#pragma once
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +29


#endif // PCH_H

Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +157
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);
}
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +189
#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;
}
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
"WinUI3Apps\\PowerToys.FileLocksmithUI.dll",
"WinUI3Apps\\PowerToys.FileLocksmithContextMenu.dll",
"FileLocksmithContextMenuPackage.msix",
"FileLocksmithCLI.exe",
Copy link

Copilot AI Jan 5, 2026

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.

Copilot generated this review using guidance from repository custom instructions.
<ClCompile Include="..\FileLocksmithLibInterop\NtdllBase.cpp" />
<ClCompile Include="..\FileLocksmithLibInterop\NtdllExtensions.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
Copy link

Copilot AI Jan 5, 2026

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.

Suggested change
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<ExcludedFromBuild>true</ExcludedFromBuild>

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@lei9444 lei9444 left a 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants