chore: import upstream snapshot with attribution
Spell checking / Check Spelling (push) Has been cancelled
Spell checking / Update PR (push) Has been cancelled
Publish Dev Docs Website / build (push) Failing after 1s
Publish Dev Docs Website / deploy (push) Has been skipped
Spell checking / Report (Push) (push) Has been cancelled
Spell checking / Report (PR) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:16:02 +08:00
commit 79031da543
8235 changed files with 1365916 additions and 0 deletions
@@ -0,0 +1,249 @@
#include "pch.h"
#include "CLILogic.h"
#include <common/utils/json.h>
#include <iostream>
#include <sstream>
#include <chrono>
#include "resource.h"
#include <common/logger/logger.h>
#include <common/utils/logger_helper.h>
#include <type_traits>
template<typename T>
DWORD_PTR ToDwordPtr(T val)
{
if constexpr (std::is_pointer_v<T>)
{
return reinterpret_cast<DWORD_PTR>(val);
}
else
{
return static_cast<DWORD_PTR>(val);
}
}
template<typename... Args>
std::wstring FormatString(IStringProvider& strings, UINT id, Args... args)
{
std::wstring format = strings.GetString(id);
if (format.empty()) return L"";
DWORD_PTR arguments[] = { ToDwordPtr(args)..., 0 };
LPWSTR buffer = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
format.c_str(),
0,
0,
reinterpret_cast<LPWSTR>(&buffer),
0,
reinterpret_cast<va_list*>(arguments));
if (buffer)
{
std::wstring result(buffer);
LocalFree(buffer);
return result;
}
return L"";
}
std::wstring get_usage(IStringProvider& strings)
{
return strings.GetString(IDS_USAGE);
}
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, IStringProvider& strings)
{
std::wstringstream ss;
if (results.empty())
{
ss << strings.GetString(IDS_NO_PROCESSES);
return ss.str();
}
ss << strings.GetString(IDS_HEADER);
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, IProcessTerminator& terminator, IStringProvider& strings)
{
std::wstringstream ss;
for (const auto& result : results)
{
if (terminator.terminate(result.pid))
{
ss << FormatString(strings, IDS_TERMINATED, result.pid, result.name.c_str());
}
else
{
ss << FormatString(strings, IDS_FAILED_TERMINATE, result.pid, result.name.c_str());
}
}
return ss.str();
}
CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IProcessTerminator& terminator, IStringProvider& strings)
{
Logger::info("Parsing arguments");
if (argc < 2)
{
Logger::warn("No arguments provided");
return { 1, get_usage(strings), L"help" };
}
bool json_output = false;
bool kill = false;
bool wait = false;
int timeout_ms = -1;
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"--timeout")
{
if (i + 1 < argc)
{
try
{
timeout_ms = std::stoi(argv[++i]);
}
catch (...)
{
Logger::error("Invalid timeout value");
return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT), L"query-wait" };
}
}
else
{
Logger::error("Timeout argument missing");
return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG), L"query-wait" };
}
}
else if (arg == L"--help")
{
return { 0, get_usage(strings), L"help" };
}
else
{
paths.push_back(arg);
}
}
if (paths.empty())
{
Logger::error("No paths specified");
return { 1, strings.GetString(IDS_ERROR_NO_PATHS), L"query" };
}
Logger::info("Processing {} paths", paths.size());
if (wait)
{
std::wstringstream ss;
if (json_output)
{
Logger::warn("Wait is incompatible with JSON output");
ss << strings.GetString(IDS_WARN_JSON_WAIT);
json_output = false;
}
ss << strings.GetString(IDS_WAITING);
auto start_time = std::chrono::steady_clock::now();
while (true)
{
auto results = finder.find(paths);
if (results.empty())
{
Logger::info("Files unlocked");
ss << strings.GetString(IDS_UNLOCKED);
break;
}
if (timeout_ms >= 0)
{
auto current_time = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - start_time).count();
if (elapsed > timeout_ms)
{
Logger::warn("Timeout waiting for files to be unlocked");
ss << strings.GetString(IDS_TIMEOUT);
return { 1, ss.str(), L"query-wait" };
}
}
Sleep(200);
}
return { 0, ss.str(), L"query-wait" };
}
auto results = finder.find(paths);
Logger::info("Found {} processes locking the files", results.size());
std::wstringstream output_ss;
if (kill)
{
Logger::info("Killing processes");
output_ss << kill_processes(results, terminator, strings);
// Re-check after killing
results = finder.find(paths);
Logger::info("Remaining processes: {}", results.size());
}
if (json_output)
{
output_ss << get_json(results) << std::endl;
}
else
{
output_ss << get_text(results, strings);
}
std::wstring cmd_name = kill ? L"kill" : (json_output ? L"query-json" : L"query");
return { 0, output_ss.str(), cmd_name };
}
@@ -0,0 +1,32 @@
#pragma once
#include <vector>
#include <string>
#include "FileLocksmithLib/FileLocksmith.h"
#include <Windows.h>
struct CommandResult
{
int exit_code;
std::wstring output;
std::wstring command_name;
};
struct IProcessFinder
{
virtual std::vector<ProcessResult> find(const std::vector<std::wstring>& paths) = 0;
virtual ~IProcessFinder() = default;
};
struct IProcessTerminator
{
virtual bool terminate(DWORD pid) = 0;
virtual ~IProcessTerminator() = default;
};
struct IStringProvider
{
virtual std::wstring GetString(UINT id) = 0;
virtual ~IStringProvider() = default;
};
CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IProcessTerminator& terminator, IStringProvider& strings);
@@ -0,0 +1,62 @@
#include "resource.h"
#include <windows.h>
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", "File Locksmith CLI"
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", "FileLocksmithCLI.exe"
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", "FileLocksmithCLI.exe"
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
STRINGTABLE
BEGIN
IDS_USAGE "Usage: FileLocksmithCLI.exe [options] <path1> [path2] ...\nOptions:\n --kill Kill processes locking the files\n --json Output results in JSON format\n --wait Wait for files to be unlocked\n --timeout Timeout in milliseconds for --wait\n --help Show this help message\n"
IDS_NO_PROCESSES "No processes found locking the file(s).\n"
IDS_HEADER "PID\tUser\tProcess\n"
IDS_TERMINATED "Terminated process %1!d! (%2)\n"
IDS_FAILED_TERMINATE "Failed to terminate process %1!d! (%2)\n"
IDS_FAILED_OPEN "Failed to open process %1!d! (%2)\n"
IDS_ERROR_NO_PATHS "Error: No paths specified.\n"
IDS_WARN_JSON_WAIT "Warning: --wait is incompatible with --json. Ignoring --json.\n"
IDS_WAITING "Waiting for files to be unlocked...\n"
IDS_UNLOCKED "Files unlocked.\n"
IDS_TIMEOUT "Timeout waiting for files to be unlocked.\n"
IDS_ERROR_INVALID_TIMEOUT "Error: Invalid timeout value.\n"
IDS_ERROR_TIMEOUT_ARG "Error: --timeout requires an argument.\n"
END
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{49D456D3-F485-45AF-8875-45B44F193DDC}</ProjectGuid>
<RootNamespace>FileLocksmithCLI</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>FileLocksmithCLI</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)..\..\..;$(ProjectDir)..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RunCodeAnalysis>false</RunCodeAnalysis>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>false</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..;$(ProjectDir)..\..\..;$(ProjectDir)..\..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RunCodeAnalysis>false</RunCodeAnalysis>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="CLILogic.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CLILogic.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="FileLocksmithCLI.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FileLocksmithLib\FileLocksmithLib.vcxproj">
<Project>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\version\version.vcxproj">
<Project>{1248566c-272a-43c0-88d6-e6675d569a09}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="CLILogic.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CLILogic.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>
@@ -0,0 +1,76 @@
#include "pch.h"
#include "CLILogic.h"
#include "FileLocksmithLib/FileLocksmith.h"
#include "FileLocksmithLib/Trace.h"
#include <iostream>
#include "resource.h"
#include <common/logger/logger.h>
#include <common/utils/logger_helper.h>
struct RealProcessFinder : IProcessFinder
{
std::vector<ProcessResult> find(const std::vector<std::wstring>& paths) override
{
return find_processes_recursive(paths);
}
};
struct RealProcessTerminator : IProcessTerminator
{
bool terminate(DWORD pid) override
{
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (hProcess)
{
bool result = TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
return result;
}
return false;
}
};
struct RealStringProvider : IStringProvider
{
std::wstring GetString(UINT id) override
{
wchar_t buffer[4096];
int len = LoadStringW(GetModuleHandle(NULL), id, buffer, ARRAYSIZE(buffer));
if (len > 0)
{
return std::wstring(buffer, len);
}
return L"";
}
};
#ifndef UNIT_TEST
int wmain(int argc, wchar_t* argv[])
{
winrt::init_apartment();
Trace::RegisterProvider();
LoggerHelpers::init_logger(L"FileLocksmithCLI", L"", LogSettings::fileLocksmithLoggerName);
Logger::info("FileLocksmithCLI started");
RealProcessFinder finder;
RealProcessTerminator terminator;
RealStringProvider strings;
auto result = run_command(argc, argv, finder, terminator, strings);
if (result.exit_code != 0)
{
Logger::error("Command failed with exit code {}", result.exit_code);
}
else
{
Logger::info("Command succeeded");
}
Trace::CLICommand(result.command_name.c_str(), result.exit_code == 0);
std::wcout << result.output;
Trace::UnregisterProvider();
return result.exit_code;
}
#endif
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
</packages>
@@ -0,0 +1 @@
#include "pch.h"
@@ -0,0 +1,22 @@
#pragma once
#ifndef PCH_H
#define PCH_H
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <winternl.h>
#include <Psapi.h>
#include <shellapi.h>
#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include <winrt/base.h>
#endif // PCH_H
@@ -0,0 +1,16 @@
// resource.h
#pragma once
#define IDS_USAGE 101
#define IDS_NO_PROCESSES 102
#define IDS_HEADER 103
#define IDS_TERMINATED 104
#define IDS_FAILED_TERMINATE 105
#define IDS_FAILED_OPEN 106
#define IDS_ERROR_NO_PATHS 107
#define IDS_WARN_JSON_WAIT 108
#define IDS_WAITING 109
#define IDS_UNLOCKED 110
#define IDS_TIMEOUT 111
#define IDS_ERROR_INVALID_TIMEOUT 112
#define IDS_ERROR_TIMEOUT_ARG 113
@@ -0,0 +1,136 @@
#include "pch.h"
#include "CppUnitTest.h"
#include "../CLILogic.h"
#include <map>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FileLocksmithCLIUnitTests
{
struct MockProcessFinder : IProcessFinder
{
std::vector<ProcessResult> results;
std::vector<ProcessResult> find(const std::vector<std::wstring>& paths) override
{
(void)paths;
return results;
}
};
struct MockProcessTerminator : IProcessTerminator
{
bool shouldSucceed = true;
std::vector<DWORD> terminatedPids;
bool terminate(DWORD pid) override
{
terminatedPids.push_back(pid);
return shouldSucceed;
}
};
struct MockStringProvider : IStringProvider
{
std::map<UINT, std::wstring> strings;
std::wstring GetString(UINT id) override
{
if (strings.count(id)) return strings[id];
return L"String_" + std::to_wstring(id);
}
};
TEST_CLASS(CLITests)
{
public:
TEST_METHOD(TestNoArgs)
{
MockProcessFinder finder;
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe" };
auto result = run_command(1, argv, finder, terminator, strings);
Assert::AreEqual(1, result.exit_code);
Assert::AreEqual(std::wstring(L"help"), result.command_name);
}
TEST_METHOD(TestHelp)
{
MockProcessFinder finder;
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"--help" };
auto result = run_command(2, argv, finder, terminator, strings);
Assert::AreEqual(0, result.exit_code);
Assert::AreEqual(std::wstring(L"help"), result.command_name);
}
TEST_METHOD(TestFindProcesses)
{
MockProcessFinder finder;
finder.results = { { L"process", 123, L"user", { L"file1" } } };
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1" };
auto result = run_command(2, argv, finder, terminator, strings);
Assert::AreEqual(0, result.exit_code);
Assert::AreEqual(std::wstring(L"query"), result.command_name);
Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
Assert::IsTrue(result.output.find(L"process") != std::wstring::npos);
}
TEST_METHOD(TestJsonOutput)
{
MockProcessFinder finder;
finder.results = { { L"process", 123, L"user", { L"file1" } } };
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--json" };
auto result = run_command(3, argv, finder, terminator, strings);
Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(result.output.c_str());
Assert::AreEqual(0, result.exit_code);
Assert::AreEqual(std::wstring(L"query-json"), result.command_name);
Assert::IsTrue(result.output.find(L"\"pid\"") != std::wstring::npos);
Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
}
TEST_METHOD(TestKill)
{
MockProcessFinder finder;
finder.results = { { L"process", 123, L"user", { L"file1" } } };
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--kill" };
auto result = run_command(3, argv, finder, terminator, strings);
Assert::AreEqual(0, result.exit_code);
Assert::AreEqual(std::wstring(L"kill"), result.command_name);
Assert::AreEqual((size_t)1, terminator.terminatedPids.size());
Assert::AreEqual((DWORD)123, terminator.terminatedPids[0]);
}
TEST_METHOD(TestTimeout)
{
MockProcessFinder finder;
// Always return results so it waits
finder.results = { { L"process", 123, L"user", { L"file1" } } };
MockProcessTerminator terminator;
MockStringProvider strings;
wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--wait", (wchar_t*)L"--timeout", (wchar_t*)L"100" };
auto result = run_command(5, argv, finder, terminator, strings);
Assert::AreEqual(1, result.exit_code);
Assert::AreEqual(std::wstring(L"query-wait"), result.command_name);
}
};
}
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<ProjectGuid>{A1B2C3D4-E5F6-7890-1234-567890ABCDEF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>FileLocksmithCLIUnitTests</RootNamespace>
<ProjectName>FileLocksmithCLI.UnitTests</ProjectName>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\tests\FileLocksmithCLI\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\;..\..\;$(RepoRoot)src\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;UNIT_TEST;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<DisableSpecificWarnings>26466;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>shlwapi.lib;ntdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="FileLocksmithCLITests.cpp" />
<ClCompile Include="..\CLILogic.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FileLocksmithLib\FileLocksmithLib.vcxproj">
<Project>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\version\version.vcxproj">
<Project>{1248566c-272a-43c0-88d6-e6675d569a09}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
</packages>
@@ -0,0 +1 @@
#include "pch.h"
@@ -0,0 +1,9 @@
#pragma once
#include <winrt/base.h>
#include <Windows.h>
#include <shellapi.h>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include "CppUnitTest.h"
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
xmlns:desktop5="http://schemas.microsoft.com/appx/manifest/desktop/windows10/5"
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" IgnorableNamespaces="uap uap2 uap3 rescap desktop desktop4 desktop5 uap10 com">
<Identity Name="Microsoft.PowerToys.FileLocksmithContextMenu" ProcessorArchitecture="neutral" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.0.0.0" />
<Properties>
<DisplayName>PowerToys FileLocksmith Context Menu</DisplayName>
<PublisherDisplayName>Microsoft</PublisherDisplayName>
<Logo>Assets\FileLocksmith\storelogo.png</Logo>
<uap10:AllowExternalContent>true</uap10:AllowExternalContent>
</Properties>
<Resources>
<Resource Language="en-us" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.18950.0" MaxVersionTested="10.0.19000.0" />
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
<rescap:Capability Name="unvirtualizedResources"/>
</Capabilities>
<Applications>
<Application Id="Microsoft.PowerToys.FileLocksmithContextMenu" Executable="FileLocksmithUI.exe" uap10:TrustLevel="mediumIL" uap10:RuntimeBehavior="win32App">
<uap:VisualElements AppListEntry="none" DisplayName="PowerToys FileLocksmith Context Menu" Description="FileLocksmithContextMenu" BackgroundColor="transparent" Square150x150Logo="Assets\FileLocksmith\Square150x150Logo.png" Square44x44Logo="Assets\FileLocksmith\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\FileLocksmith\Wide310x150Logo.png" Square310x310Logo="Assets\FileLocksmith\LargeTile.png" Square71x71Logo="Assets\FileLocksmith\SmallTile.png"></uap:DefaultTile>
<uap:SplashScreen Image="Assets\FileLocksmith\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type="Directory">
<desktop5:Verb Id="FileLocksmithCommand" Clsid="AAF1E27D-4976-49C2-8895-AAFA743C0A7E" />
</desktop5:ItemType>
<desktop5:ItemType Type="*">
<desktop5:Verb Id="FileLocksmithCommand" Clsid="AAF1E27D-4976-49C2-8895-AAFA743C0A7E" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
<com:Extension Category="windows.comServer" uap10:RuntimeBehavior="packagedClassicApp">
<com:ComServer>
<com:SurrogateServer DisplayName="Context menu verb handler">
<com:Class Id="AAF1E27D-4976-49C2-8895-AAFA743C0A7E" Path="PowerToys.FileLocksmithContextMenu.dll" ThreadingModel="STA"/>
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
</Package>
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,50 @@
#include <windows.h>
#include "Generated Files/resource.h"
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec LogStandardErrorAsError="false" Command="powershell -NonInteractive -executionpolicy Unrestricted -NoProfile $(RepoRoot)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h FileLocksmithContextMenu.base.rc FileLocksmithContextMenu.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{799a50d8-de89-4ed1-8ff8-ad5a9ed8c0ca}</ProjectGuid>
<RootNamespace>FileLocksmithContextMenu</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<TargetName>PowerToys.FileLocksmithContextMenu</TargetName>
<!-- Needs a different int dir to avoid conflicts in msix creation. -->
<IntDir>$(SolutionDir)$(Platform)\$(Configuration)\TemporaryBuild\obj\$(ProjectName)\</IntDir>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;FILELOCKSMITHCONTEXTMENU_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..;../../..;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>runtimeobject.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ModuleDefinitionFile>Source.def</ModuleDefinitionFile>
</Link>
<PreBuildEvent>
<Command>del $(OutDir)\FileLocksmithContextMenuPackage.msix /q
MakeAppx.exe pack /d . /p $(OutDir)FileLocksmithContextMenuPackage.msix /nv</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;FILELOCKSMITHCONTEXTMENU_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..;../../..;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>runtimeobject.lib;%(AdditionalDependencies)</AdditionalDependencies>
<ModuleDefinitionFile>Source.def</ModuleDefinitionFile>
</Link>
<PreBuildEvent>
<Command>del $(OutDir)\FileLocksmithContextMenuPackage.msix /q
MakeAppx.exe pack /d . /p $(OutDir)FileLocksmithContextMenuPackage.msix /nv</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="framework.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.base.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="Resources.resx" />
</ItemGroup>
<ItemGroup>
<None Include="Assets\FileLocksmith\FileLocksmith.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\LargeTile.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\SmallTile.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\SplashScreen.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\Square150x150Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\Square44x44Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\storelogo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Assets\FileLocksmith\Wide310x150Logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>
<ProjectReference Include="..\FileLocksmithLib\FileLocksmithLib.vcxproj">
<Project>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="FileLocksmithContextMenu.base.rc" />
</ItemGroup>
<Import Project="$(RepoRoot)deps\spdlog.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="framework.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.base.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="Resources.resx">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\FileLocksmith.ico">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\LargeTile.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\SmallTile.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\SplashScreen.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\Square44x44Logo.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\Square150x150Logo.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\storelogo.png">
<Filter>Resource Files</Filter>
</None>
<None Include="Assets\FileLocksmith\Wide310x150Logo.png">
<Filter>Resource Files</Filter>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="FileLocksmithContextMenu.base.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root"
xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="File_Locksmith_Context_Menu_Entry" xml:space="preserve">
<value>Unlock with File Locksmith</value>
<comment>This text will be shown when the user opens the context menu (right clicks) a file. File Locksmith is the product name, do not loc.</comment>
</data>
<data name="File_Locksmith_App_Name" xml:space="preserve">
<value>File Locksmith</value>
</data>
</root>
@@ -0,0 +1,5 @@
LIBRARY
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllGetActivationFactory PRIVATE
@@ -0,0 +1,204 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <common/telemetry/EtwTrace/EtwTrace.h>
#include <common/utils/process_path.h>
#include <common/utils/resources.h>
#include <common/utils/elevation.h>
#include "FileLocksmithLib/IPC.h"
#include "FileLocksmithLib/Settings.h"
#include "FileLocksmithLib/Trace.h"
#include <Shlwapi.h>
#include <shobjidl_core.h>
#include <string>
#include <wrl/module.h>
#include "Generated Files/resource.h"
using namespace Microsoft::WRL;
HINSTANCE g_hInst = 0;
Shared::Trace::ETWTrace trace(L"FileLocksmithContextMenu");
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hInst = hModule;
Trace::RegisterProvider();
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
class __declspec(uuid("AAF1E27D-4976-49C2-8895-AAFA743C0A7E")) FileLocksmithContextMenuCommand final : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand, IObjectWithSite>
{
public:
virtual const wchar_t* Title() { return L"File Locksmith"; }
virtual const EXPCMDFLAGS Flags() { return ECF_DEFAULT; }
virtual const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) { return ECS_ENABLED; }
// IExplorerCommand
IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name)
{
return SHStrDup(context_menu_caption.c_str(), name);
}
IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* icon)
{
std::wstring iconResourcePath = get_module_folderpath(g_hInst);
iconResourcePath += L"\\Assets\\FileLocksmith\\";
iconResourcePath += L"FileLocksmith.ico";
return SHStrDup(iconResourcePath.c_str(), icon);
}
IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip)
{
*infoTip = nullptr;
return E_NOTIMPL;
}
IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName)
{
*guidCommandName = __uuidof(this);
return S_OK;
}
IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* cmdState)
{
*cmdState = ECS_ENABLED;
if (!FileLocksmithSettingsInstance().GetEnabled())
{
*cmdState = ECS_HIDDEN;
}
if (FileLocksmithSettingsInstance().GetShowInExtendedContextMenu())
{
*cmdState = ECS_HIDDEN;
}
return S_OK;
}
IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept
{
trace.UpdateState(true);
Trace::Invoked();
ipc::Writer writer;
if (selection == nullptr)
{
return S_OK;
}
if (HRESULT result = writer.start(); FAILED(result))
{
Trace::InvokedRet(result);
trace.Flush();
trace.UpdateState(false);
return result;
}
std::wstring path = get_module_folderpath(g_hInst);
path = path + L"\\PowerToys.FileLocksmithUI.exe";
HRESULT result;
if (!RunNonElevatedEx(path.c_str(), L"", get_module_folderpath(g_hInst)))
{
result = E_FAIL;
Trace::InvokedRet(result);
trace.Flush();
trace.UpdateState(false);
return result;
}
DWORD num_items;
selection->GetCount(&num_items);
for (DWORD i = 0; i < num_items; i++)
{
IShellItem* item;
result = selection->GetItemAt(i, &item);
if (SUCCEEDED(result))
{
LPWSTR file_path;
result = item->GetDisplayName(SIGDN_FILESYSPATH, &file_path);
if (SUCCEEDED(result))
{
// TODO Aggregate items and send to UI
writer.add_path(file_path);
CoTaskMemFree(file_path);
}
item->Release();
}
}
Trace::InvokedRet(S_OK);
trace.Flush();
trace.UpdateState(false);
return S_OK;
}
IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* flags)
{
*flags = Flags();
return S_OK;
}
IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands)
{
*enumCommands = nullptr;
return E_NOTIMPL;
}
// IObjectWithSite
IFACEMETHODIMP SetSite(_In_ IUnknown* site) noexcept
{
m_site = site;
return S_OK;
}
IFACEMETHODIMP GetSite(_In_ REFIID riid, _COM_Outptr_ void** site) noexcept { return m_site.CopyTo(riid, site); }
protected:
ComPtr<IUnknown> m_site;
private:
std::wstring context_menu_caption = GET_RESOURCE_STRING_FALLBACK(IDS_FILE_LOCKSMITH_CONTEXT_MENU_ENTRY, L"Unlock with File Locksmith");
};
CoCreatableClass(FileLocksmithContextMenuCommand)
CoCreatableClassWrlCreatorMapInclude(FileLocksmithContextMenuCommand)
STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory)
{
return Module<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory);
}
STDAPI DllCanUnloadNow()
{
return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
}
STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ void** instance)
{
return Module<InProc>::GetModule().GetClassObject(rclsid, riid, instance);
}
@@ -0,0 +1,5 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>
@@ -0,0 +1,5 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
@@ -0,0 +1,15 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
#include <atlbase.h>
// add headers that you want to pre-compile here
#include "framework.h"
#endif //PCH_H
@@ -0,0 +1,13 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by ImageResizerContextMenu.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys File Locksmith Context Menu"
#define INTERNAL_NAME "PowerToys.FileLocksmithContextMenu.dll"
#define ORIGINAL_FILENAME "PowerToys.FileLocksmithContextMenu.dll"
// Non-localizable
//////////////////////////////
@@ -0,0 +1,80 @@
#include "pch.h"
#include "ClassFactory.h"
#include "ExplorerCommand.h"
#include "dllmain.h"
// Class ctor/dtors
ClassFactory::ClassFactory(_In_ REFCLSID clsid) :
m_ref_count(1),
m_clsid(clsid)
{
++globals::ref_count;
}
ClassFactory::~ClassFactory()
{
--globals::ref_count;
}
// Implementations of inherited IUnknown methods
IFACEMETHODIMP ClassFactory::QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] = {
QITABENT(ClassFactory, IClassFactory),
{ 0, 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) ClassFactory::AddRef()
{
return ++m_ref_count;
}
IFACEMETHODIMP_(ULONG) ClassFactory::Release()
{
auto result = --m_ref_count;
if (result == 0)
{
delete this;
}
return result;
}
// Implementations of inherited IClassFactory methods
IFACEMETHODIMP ClassFactory::CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject)
{
*ppvObject = NULL;
HRESULT hr;
if (pUnkOuter)
{
hr = CLASS_E_NOAGGREGATION;
}
else if (m_clsid == __uuidof(ExplorerCommand))
{
hr = ExplorerCommand::s_CreateInstance(pUnkOuter, riid, ppvObject);
}
else
{
hr = CLASS_E_CLASSNOTAVAILABLE;
}
return hr;
}
IFACEMETHODIMP ClassFactory::LockServer(BOOL fLock)
{
if (fLock)
{
++globals::ref_count;
}
else
{
--globals::ref_count;
}
return S_OK;
}
@@ -0,0 +1,22 @@
#pragma once
#include "pch.h"
class ClassFactory : public IClassFactory
{
public:
ClassFactory(_In_ REFCLSID clsid);
~ClassFactory();
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
IFACEMETHODIMP_(ULONG) AddRef() override;
IFACEMETHODIMP_(ULONG) Release() override;
// IClassFactory
IFACEMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) override;
IFACEMETHODIMP LockServer(BOOL fLock) override;
private:
std::atomic<ULONG> m_ref_count;
IID m_clsid;
};
@@ -0,0 +1,309 @@
#include "pch.h"
#include "ExplorerCommand.h"
#include "dllmain.h"
#include "Generated Files/resource.h"
#include "FileLocksmithLib/Constants.h"
#include "FileLocksmithLib/Settings.h"
#include "FileLocksmithLib/Trace.h"
#include <common/themes/icon_helpers.h>
#include <common/utils/process_path.h>
#include <common/utils/resources.h>
// Implementations of inherited IUnknown methods
IFACEMETHODIMP ExplorerCommand::QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] = {
QITABENT(ExplorerCommand, IExplorerCommand),
QITABENT(ExplorerCommand, IShellExtInit),
QITABENT(ExplorerCommand, IContextMenu),
{ 0, 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) ExplorerCommand::AddRef()
{
return ++m_ref_count;
}
IFACEMETHODIMP_(ULONG) ExplorerCommand::Release()
{
auto result = --m_ref_count;
if (result == 0)
{
delete this;
}
return result;
}
// Implementations of inherited IExplorerCommand methods
IFACEMETHODIMP ExplorerCommand::GetTitle(IShellItemArray* psiItemArray, LPWSTR* ppszName)
{
return SHStrDup(context_menu_caption.c_str(), ppszName);
}
IFACEMETHODIMP ExplorerCommand::GetIcon(IShellItemArray* psiItemArray, LPWSTR* ppszIcon)
{
std::wstring iconResourcePath = get_module_filename();
iconResourcePath += L",-";
iconResourcePath += std::to_wstring(IDI_FILELOCKSMITH);
return SHStrDup(iconResourcePath.c_str(), ppszIcon);
}
IFACEMETHODIMP ExplorerCommand::GetToolTip(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip)
{
// No tooltip for now
return E_NOTIMPL;
}
IFACEMETHODIMP ExplorerCommand::GetCanonicalName(GUID* pguidCommandName)
{
*pguidCommandName = __uuidof(this);
return S_OK;
}
IFACEMETHODIMP ExplorerCommand::GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState)
{
*pCmdState = FileLocksmithSettingsInstance().GetEnabled() ? ECS_ENABLED : ECS_HIDDEN;
return S_OK;
}
IFACEMETHODIMP ExplorerCommand::Invoke(IShellItemArray* psiItemArray, IBindCtx* pbc)
{
return S_OK;
}
IFACEMETHODIMP ExplorerCommand::GetFlags(EXPCMDFLAGS* pFlags)
{
*pFlags = ECF_DEFAULT;
return S_OK;
}
IFACEMETHODIMP ExplorerCommand::EnumSubCommands(IEnumExplorerCommand** ppEnum)
{
*ppEnum = NULL;
return E_NOTIMPL;
}
// Implementations of inherited IShellExtInit methods
IFACEMETHODIMP ExplorerCommand::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID)
{
m_data_obj = NULL;
if (!FileLocksmithSettingsInstance().GetEnabled())
{
return E_FAIL;
}
if (pdtobj)
{
m_data_obj = pdtobj;
}
return S_OK;
}
// Implementations of inherited IContextMenu methods
IFACEMETHODIMP ExplorerCommand::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
// Skip if disabled
if (!FileLocksmithSettingsInstance().GetEnabled())
{
return E_FAIL;
}
if (FileLocksmithSettingsInstance().GetShowInExtendedContextMenu() && !(uFlags & CMF_EXTENDEDVERBS))
{
return E_FAIL;
}
HRESULT hr = E_UNEXPECTED;
if (m_data_obj && !(uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY | CMF_OPTIMIZEFORINVOKE)))
{
wchar_t menuName[128] = { 0 };
wcscpy_s(menuName, ARRAYSIZE(menuName), context_menu_caption.c_str());
MENUITEMINFO mii;
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_STATE;
mii.wID = idCmdFirst++;
mii.fType = MFT_STRING;
mii.dwTypeData = (PWSTR)menuName;
mii.fState = MFS_ENABLED;
// icon from file
HICON hIcon = static_cast<HICON>(LoadImage(globals::instance, MAKEINTRESOURCE(IDI_FILELOCKSMITH), IMAGE_ICON, 16, 16, 0));
if (hIcon)
{
mii.fMask |= MIIM_BITMAP;
if (m_hbmpIcon == NULL)
{
m_hbmpIcon = CreateBitmapFromIcon(hIcon);
}
mii.hbmpItem = m_hbmpIcon;
DestroyIcon(hIcon);
}
if (!InsertMenuItem(hmenu, indexMenu, TRUE, &mii))
{
m_etwTrace.UpdateState(true);
hr = HRESULT_FROM_WIN32(GetLastError());
Trace::QueryContextMenuError(hr);
m_etwTrace.Flush();
m_etwTrace.UpdateState(false);
}
else
{
hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
}
}
return hr;
}
IFACEMETHODIMP ExplorerCommand::InvokeCommand(CMINVOKECOMMANDINFO* pici)
{
m_etwTrace.UpdateState(true);
HRESULT hr = E_FAIL;
if (FileLocksmithSettingsInstance().GetEnabled() &&
pici && (IS_INTRESOURCE(pici->lpVerb)) &&
(LOWORD(pici->lpVerb) == 0))
{
Trace::Invoked();
ipc::Writer writer;
if (HRESULT result = writer.start(); FAILED(result))
{
Trace::InvokedRet(result);
m_etwTrace.Flush();
m_etwTrace.UpdateState(false);
return result;
}
if (HRESULT result = LaunchUI(pici, &writer); FAILED(result))
{
Trace::InvokedRet(result);
m_etwTrace.Flush();
m_etwTrace.UpdateState(false);
return result;
}
IShellItemArray* shell_item_array;
hr = SHCreateShellItemArrayFromDataObject(m_data_obj, __uuidof(IShellItemArray), reinterpret_cast<void**>(&shell_item_array));
if (SUCCEEDED(hr))
{
DWORD num_items;
shell_item_array->GetCount(&num_items);
for (DWORD i = 0; i < num_items; i++)
{
IShellItem* item;
hr = shell_item_array->GetItemAt(i, &item);
if (SUCCEEDED(hr))
{
LPWSTR file_path;
hr = item->GetDisplayName(SIGDN_FILESYSPATH, &file_path);
if (SUCCEEDED(hr))
{
// TODO Aggregate items and send to UI
writer.add_path(file_path);
CoTaskMemFree(file_path);
}
item->Release();
}
}
shell_item_array->Release();
}
}
Trace::InvokedRet(hr);
m_etwTrace.Flush();
m_etwTrace.UpdateState(false);
return hr;
}
IFACEMETHODIMP ExplorerCommand::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, CHAR* pszName, UINT cchMax)
{
return E_NOTIMPL;
}
HRESULT ExplorerCommand::s_CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject)
{
*ppvObject = NULL;
HRESULT hr = E_OUTOFMEMORY;
ExplorerCommand* pNew = new (std::nothrow) ExplorerCommand;
if (pNew)
{
hr = pNew->QueryInterface(riid, ppvObject);
pNew->Release();
}
return hr;
}
ExplorerCommand::ExplorerCommand()
{
++globals::ref_count;
context_menu_caption = GET_RESOURCE_STRING_FALLBACK(IDS_FILELOCKSMITH_CONTEXT_MENU_ENTRY, L"Unlock with File Locksmith");
}
ExplorerCommand::~ExplorerCommand()
{
--globals::ref_count;
}
HRESULT ExplorerCommand::LaunchUI(CMINVOKECOMMANDINFO* pici, ipc::Writer* writer)
{
// Compute exe path
std::wstring exe_path = get_module_folderpath(globals::instance);
exe_path += L'\\';
exe_path += constants::nonlocalizable::FileNameUIExe;
STARTUPINFO startupInfo;
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.dwFlags = STARTF_USESHOWWINDOW;
if (pici)
{
startupInfo.wShowWindow = pici->nShow;
}
else
{
startupInfo.wShowWindow = SW_SHOWNORMAL;
}
PROCESS_INFORMATION processInformation;
std::wstring command_line = L"\"";
command_line += exe_path;
command_line += L"\"\0";
CreateProcessW(
NULL,
command_line.data(),
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&startupInfo,
&processInformation);
// Discard handles
CloseHandle(processInformation.hProcess);
CloseHandle(processInformation.hThread);
return S_OK;
}
@@ -0,0 +1,57 @@
#pragma once
#include "pch.h"
#include "FileLocksmithLib/IPC.h"
#include <common/Telemetry/EtwTrace/EtwTrace.h>
#define EXPLORER_COMMAND_UUID_STR "84d68575-e186-46ad-b0cb-baeb45ee29c0"
class __declspec(uuid(EXPLORER_COMMAND_UUID_STR)) ExplorerCommand : public IExplorerCommand, public IShellExtInit, public IContextMenu
{
public:
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) override;
IFACEMETHODIMP_(ULONG) AddRef() override;
IFACEMETHODIMP_(ULONG) Release() override;
// IExplorerCommand
IFACEMETHODIMP GetTitle(IShellItemArray* psiItemArray, LPWSTR* ppszName) override;
IFACEMETHODIMP GetIcon(IShellItemArray* psiItemArray, LPWSTR* ppszIcon) override;
IFACEMETHODIMP GetToolTip(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip) override;
IFACEMETHODIMP GetCanonicalName(GUID* pguidCommandName) override;
IFACEMETHODIMP GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) override;
IFACEMETHODIMP Invoke(IShellItemArray* psiItemArray, IBindCtx* pbc) override;
IFACEMETHODIMP GetFlags(EXPCMDFLAGS* pFlags) override;
IFACEMETHODIMP EnumSubCommands(IEnumExplorerCommand** ppEnum) override;
// IShellExtInit
IFACEMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) override;
// IContextMenu
IFACEMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) override;
IFACEMETHODIMP InvokeCommand(CMINVOKECOMMANDINFO* pici) override;
IFACEMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, CHAR* pszName, UINT cchMax) override;
// Static member to create an instance
static HRESULT s_CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject);
// Constructor
ExplorerCommand();
// Destructor
~ExplorerCommand();
private:
HBITMAP m_hbmpIcon = nullptr;
// Helpers
HRESULT LaunchUI(CMINVOKECOMMANDINFO* pici, ipc::Writer* writer);
std::atomic<ULONG> m_ref_count = 1;
IDataObject* m_data_obj = NULL;
std::wstring context_menu_caption;
Shared::Trace::ETWTrace m_etwTrace{ L"FileExplorerExt" };
};
@@ -0,0 +1,57 @@
#include <windows.h>
#include "Generated Files/resource.h"
#include "../../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
// Non-localizable
//////////////////////////////
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_FILELOCKSMITH ICON "..\\FileLocksmithUI\\Assets\\FileLocksmith\\Icon.ico"
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec LogStandardErrorAsError="false" Command="powershell -NonInteractive -executionpolicy Unrestricted -NoProfile $(RepoRoot)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h FileLocksmithExt.base.rc FileLocksmithExt.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{57175ec7-92a5-4c1e-8244-e3fbca2a81de}</ProjectGuid>
<RootNamespace>FileLocksmithExt</RootNamespace>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
<TargetName>PowerToys.FileLocksmithExt</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;FILELOCKSMITHEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..;../../;../../../;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;FILELOCKSMITHEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..;../../;../../../;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>dll.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="ClassFactory.h" />
<ClInclude Include="dllmain.h" />
<ClInclude Include="ExplorerCommand.h" />
<ClInclude Include="RuntimeRegistration.h" />
<ClInclude Include="pch.h" />
<None Include="packages.config" />
<None Include="resource.base.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files/FileLocksmithExt.rc" />
<None Include="FileLocksmithExt.base.rc" />
</ItemGroup>
<ItemGroup>
<None Include="Resources.resx" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ClassFactory.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="ExplorerCommand.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="PowerToysModule.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="dll.def" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\Telemetry\EtwTrace\EtwTrace.vcxproj">
<Project>{8f021b46-362b-485c-bfba-ccf83e820cbd}</Project>
</ProjectReference>
<ProjectReference Include="$(RepoRoot)src\common\Themes\Themes.vcxproj">
<Project>{98537082-0fdb-40de-abd8-0dc5a4269bab}</Project>
</ProjectReference>
<ProjectReference Include="..\FileLocksmithLib\FileLocksmithLib.vcxproj">
<Project>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="$(RepoRoot)deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ExplorerCommand.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ClassFactory.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dllmain.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RuntimeRegistration.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ExplorerCommand.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ClassFactory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PowerToysModule.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="dll.def">
<Filter>Source Files</Filter>
</None>
<None Include="resource.base.h">
<Filter>Header Files</Filter>
</None>
<None Include="FileLocksmithExt.base.rc">
<Filter>Resource Files</Filter>
</None>
<None Include="Resources.resx">
<Filter>Resource Files</Filter>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files/FileLocksmithExt.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
@@ -0,0 +1,171 @@
#include "pch.h"
#include <interface/powertoy_module_interface.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/logger/logger.h>
#include <common/logger/logger_settings.h>
#include <common/utils/logger_helper.h>
#include <common/utils/package.h>
#include <common/utils/process_path.h>
#include <optional>
#include "FileLocksmithLib/Constants.h"
#include "FileLocksmithLib/Settings.h"
#include "FileLocksmithLib/Trace.h"
#include "RuntimeRegistration.h"
#include "dllmain.h"
#include "Generated Files/resource.h"
class FileLocksmithModule : public PowertoyModuleIface
{
private:
// Update registration based on enabled state
void UpdateRegistration(bool enabled)
{
if (enabled)
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::EnsureRegistered();
Logger::info(L"File Locksmith context menu registered");
#endif
}
else
{
#if defined(ENABLE_REGISTRATION) || defined(NDEBUG)
FileLocksmithRuntimeRegistration::Unregister();
Logger::info(L"File Locksmith context menu unregistered");
#endif
}
}
public:
FileLocksmithModule()
{
LoggerHelpers::init_logger(constants::nonlocalizable::PowerToyName, L"ModuleInterface", LogSettings::fileLocksmithLoggerName);
init_settings();
}
virtual const wchar_t* get_name() override
{
static WCHAR buffer[128];
LoadStringW(globals::instance, IDS_FILELOCKSMITH_POWERTOYNAME, buffer, ARRAYSIZE(buffer));
return buffer;
}
virtual const wchar_t* get_key() override
{
return constants::nonlocalizable::PowerToyKey;
}
// Return the configured status for the gpo policy for the module
virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override
{
return powertoys_gpo::getConfiguredFileLocksmithEnabledValue();
}
// Return JSON with the configuration options.
// These are the settings shown on the settings page along with their current values.
virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
return settings.serialize_to_buffer(buffer, buffer_size);
}
// Passes JSON with the configuration settings for the powertoy.
// This is called when the user hits Save on the settings page.
virtual void set_config(PCWSTR config) override
{
try
{
// Parse the input JSON string.
PowerToysSettings::PowerToyValues values =
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
toggle_extended_only(values.get_bool_value(L"bool_show_extended_menu").value());
save_settings();
}
catch (std::exception& e)
{
Logger::error("Configuration parsing failed: {}", std::string{ e.what() });
}
}
virtual void enable() override
{
Logger::info(L"File Locksmith enabled");
if (package::IsWin11OrGreater())
{
std::wstring path = get_module_folderpath(globals::instance);
std::wstring packageUri = path + L"\\FileLocksmithContextMenuPackage.msix";
if (!package::IsPackageRegisteredWithPowerToysVersion(constants::nonlocalizable::ContextMenuPackageName))
{
package::RegisterSparsePackage(path, packageUri);
}
}
m_enabled = true;
UpdateRegistration(m_enabled);
}
virtual void disable() override
{
Logger::info(L"File Locksmith disabled");
m_enabled = false;
UpdateRegistration(m_enabled);
}
virtual bool is_enabled() override
{
return m_enabled;
}
virtual void toggle_extended_only(bool extended_only)
{
Logger::info(L"File Locksmith toggle extended only");
m_extended_only = extended_only;
save_settings();
}
virtual bool is_extended_only()
{
return m_extended_only;
}
virtual void destroy() override
{
delete this;
}
private:
bool m_enabled = false;
bool m_extended_only;
void init_settings()
{
m_enabled = FileLocksmithSettingsInstance().GetEnabled();
m_extended_only = FileLocksmithSettingsInstance().GetShowInExtendedContextMenu();
UpdateRegistration(m_enabled);
Trace::EnableFileLocksmith(m_enabled);
}
void save_settings()
{
auto& settings = FileLocksmithSettingsInstance();
m_enabled = FileLocksmithSettingsInstance().GetEnabled();
settings.SetExtendedContextMenuOnly(m_extended_only);
settings.Save();
Trace::EnableFileLocksmith(m_enabled);
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new FileLocksmithModule();
}
@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="FileLocksmith_Context_Menu_Entry" xml:space="preserve">
<value>Unlock with File Locksmith</value>
<comment>This text will be shown when the user opens the context menu (right clicks) a file. File Locksmith is the product name, do not loc.</comment>
</data>
<data name="FileLocksmith_PowerToyName" xml:space="preserve">
<value>File Locksmith</value>
<comment>Localized name of the PowerToy.</comment>
</data>
</root>
@@ -0,0 +1,36 @@
// Header-only runtime registration for FileLocksmith context menu extension.
#pragma once
#include <common/utils/shell_ext_registration.h>
namespace globals { extern HMODULE instance; }
namespace FileLocksmithRuntimeRegistration
{
namespace
{
inline runtime_shell_ext::Spec BuildSpec()
{
runtime_shell_ext::Spec spec;
spec.clsid = L"{84D68575-E186-46AD-B0CB-BAEB45EE29C0}";
spec.sentinelKey = L"Software\\Microsoft\\PowerToys\\FileLocksmith";
spec.sentinelValue = L"ContextMenuRegistered";
spec.dllFileCandidates = { L"PowerToys.FileLocksmithExt.dll" };
spec.contextMenuHandlerKeyPaths = {
L"Software\\Classes\\AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt",
L"Software\\Classes\\Drive\\ShellEx\\ContextMenuHandlers\\FileLocksmithExt" };
spec.friendlyName = L"File Locksmith Shell Extension";
return spec;
}
}
inline bool EnsureRegistered()
{
return runtime_shell_ext::EnsureRegistered(BuildSpec(), globals::instance);
}
inline void Unregister()
{
runtime_shell_ext::Unregister(BuildSpec());
}
}
@@ -0,0 +1,5 @@
EXPORTS
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
@@ -0,0 +1,62 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
// Additional libraries to link
#pragma comment(lib, "shlwapi")
#include "ClassFactory.h"
#include "FileLocksmithLib/Trace.h"
namespace globals
{
HMODULE instance;
std::atomic<ULONG> ref_count;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
globals::instance = hModule;
Trace::RegisterProvider();
break;
case DLL_PROCESS_DETACH:
Trace::UnregisterProvider();
break;
}
return TRUE;
}
STDAPI DllRegisterServer()
{
return S_OK;
}
STDAPI DllUnregisterServer()
{
return S_OK;
}
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void** ppv)
{
HRESULT result = E_FAIL;
*ppv = NULL;
ClassFactory* class_factory = new (std::nothrow) ClassFactory(clsid);
if (class_factory)
{
result = class_factory->QueryInterface(riid, ppv);
class_factory->Release();
}
return result;
}
STDAPI DllCanUnloadNow(void)
{
return globals::ref_count == 0 ? S_OK : S_FALSE;
}
@@ -0,0 +1,10 @@
#pragma once
#include "pch.h"
namespace globals
{
extern HMODULE instance;
extern std::atomic<ULONG> ref_count;
extern std::atomic<bool> enabled;
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
</packages>
@@ -0,0 +1,5 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
@@ -0,0 +1,16 @@
#pragma once
// add headers that you want to pre-compile here
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <Windows.h>
#include <Shlwapi.h>
#include <ShlObj_core.h>
#include <atlbase.h>
#include <commctrl.h>
// C++ Standard library
#include <atomic>
#include <string>
#include <filesystem>
#include <fstream>
@@ -0,0 +1,14 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by FileLocksmithLib.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys File Locksmith Static Library"
#define INTERNAL_NAME "PowerToys.FileLocksmithLib.lib"
#define ORIGINAL_FILENAME "PowerToys.FileLocksmithLib.lib"
#define IDI_FILELOCKSMITH 1001
// Non-localizable
//////////////////////////////
@@ -0,0 +1,39 @@
#pragma once
#include "pch.h"
// Non-localizable constants
namespace constants::nonlocalizable
{
// Description of the registry key
constexpr WCHAR RegistryKeyDescription[] = L"File Locksmith Shell Extension";
// File name of the UI executable
constexpr WCHAR FileNameUIExe[] = L"PowerToys.FileLocksmithUI.exe";
// String key used by PowerToys
constexpr WCHAR PowerToyKey[] = L"File Locksmith";
// Nonlocalized name of this PowerToy, for logs, etc.
constexpr WCHAR PowerToyName[] = L"File Locksmith";
// JSON key used to store whether the module is enabled
constexpr WCHAR JsonKeyEnabled[] = L"Enabled";
// JSON key used to store extended menu enabled
constexpr WCHAR JsonKeyShowInExtendedContextMenu[] = L"showInExtendedContextMenu";
// Path of the JSON file used to store settings
constexpr WCHAR DataFilePath[] = L"\\file-locksmith-settings.json";
// Name of the file where the list of files to checked will be stored
constexpr WCHAR LastRunPath[] = L"\\last-run.log";
// Name of the tier 1 context menu package
constexpr WCHAR ContextMenuPackageName[] = L"FileLocksmithContextMenu";
}
// Macros, non-localizable
// Description of the registry key
#define REGISTRY_CONTEXT_MENU_KEY L"FileLocksmithExt"
@@ -0,0 +1,9 @@
#pragma once
#include "ProcessResult.h"
// Second version, checks handles towards files and all subfiles and folders of given dirs, if any.
std::vector<ProcessResult> find_processes_recursive(const std::vector<std::wstring>& paths);
// Gives the full path of the executable, given the process id
std::wstring pid_to_full_path(DWORD pid);
@@ -0,0 +1,10 @@
// FileLocksmithLib.cpp : Defines the functions for the static library.
//
#include "pch.h"
#include "framework.h"
// TODO: This is an example of a library function
void fnFileLocksmithLib()
{
}
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{9d52fd25-ef90-4f9a-a015-91efc5daf54f}</ProjectGuid>
<RootNamespace>FileLocksmithLib</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;FILELOCKSMITH_LIB_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..\FileLocksmithLibInterop;../../..;../..;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;FILELOCKSMITH_LIB_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..\FileLocksmithLibInterop;../../..;../..;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Constants.h" />
<ClInclude Include="IPC.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="Trace.h" />
<ClInclude Include="framework.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="IPC.cpp" />
<ClCompile Include="Settings.cpp" />
<ClCompile Include="Trace.cpp" />
<ClCompile Include="FileLocksmithLib.cpp" />
<ClCompile Include="..\FileLocksmithLibInterop\FileLocksmith.cpp" />
<ClCompile Include="..\FileLocksmithLibInterop\NtdllBase.cpp" />
<ClCompile Include="..\FileLocksmithLibInterop\NtdllExtensions.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="framework.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Settings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="IPC.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Constants.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="FileLocksmithLib.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileLocksmith.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="NtdllBase.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="NtdllExtensions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="IPC.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
</Project>
@@ -0,0 +1,67 @@
#include "pch.h"
#include "IPC.h"
#include "Constants.h"
#include <common/SettingsAPI/settings_helpers.h>
constexpr DWORD DefaultPipeBufferSize = 8192;
constexpr DWORD DefaultPipeTimeoutMillis = 200;
namespace ipc
{
Writer::Writer()
{
start();
}
Writer::~Writer()
{
finish();
}
HRESULT Writer::start()
{
std::wstring path = PTSettingsHelper::get_module_save_folder_location(constants::nonlocalizable::PowerToyName);
path += L"\\";
path += constants::nonlocalizable::LastRunPath;
try
{
m_stream = std::ofstream(path, std::ios::binary);
if (!m_stream.is_open())
{
return E_FAIL;
}
return S_OK;
}
catch (...)
{
return E_FAIL;
}
}
HRESULT Writer::add_path(LPCWSTR path)
{
int length = lstrlenW(path);
if (!m_stream.write(reinterpret_cast<const char*>(path), length * sizeof(WCHAR)))
{
return E_FAIL;
}
WCHAR line_break = L'\n';
if (!m_stream.write(reinterpret_cast<const char*>(&line_break), sizeof(WCHAR)))
{
return E_FAIL;
}
return S_OK;
}
void Writer::finish()
{
add_path(L"");
m_stream.close();
}
}
@@ -0,0 +1,22 @@
#pragma once
#include "pch.h"
#include <fstream>
namespace ipc
{
class Writer
{
public:
Writer();
~Writer();
HRESULT start();
HRESULT add_path(LPCWSTR path);
void finish();
HANDLE get_read_handle();
private:
std::ofstream m_stream;
};
}
@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <vector>
#include <Windows.h>
struct ProcessResult
{
std::wstring name;
DWORD pid;
std::wstring user;
std::vector<std::wstring> files;
};
@@ -0,0 +1,114 @@
#include "pch.h"
#include "Settings.h"
#include "Constants.h"
#include <filesystem>
#include <common/utils/json.h>
#include <common/SettingsAPI/settings_helpers.h>
static bool LastModifiedTime(const std::wstring& filePath, FILETIME* lpFileTime)
{
WIN32_FILE_ATTRIBUTE_DATA attr{};
if (GetFileAttributesExW(filePath.c_str(), GetFileExInfoStandard, &attr))
{
*lpFileTime = attr.ftLastWriteTime;
return true;
}
return false;
}
FileLocksmithSettings::FileLocksmithSettings()
{
generalJsonFilePath = PTSettingsHelper::get_powertoys_general_save_file_location();
std::wstring savePath = PTSettingsHelper::get_module_save_folder_location(constants::nonlocalizable::PowerToyKey);
std::error_code ec;
jsonFilePath = savePath + constants::nonlocalizable::DataFilePath;
RefreshEnabledState();
Load();
}
void FileLocksmithSettings::Save()
{
json::JsonObject jsonData;
jsonData.SetNamedValue(constants::nonlocalizable::JsonKeyShowInExtendedContextMenu, json::value(settings.showInExtendedContextMenu));
json::to_file(jsonFilePath, jsonData);
GetSystemTimeAsFileTime(&lastLoadedTime);
}
void FileLocksmithSettings::Load()
{
if (!std::filesystem::exists(jsonFilePath))
{
Save();
}
else
{
ParseJson();
}
}
void FileLocksmithSettings::RefreshEnabledState()
{
// Load json settings from data file if it is modified in the meantime.
FILETIME lastModifiedTime{};
if (!(LastModifiedTime(generalJsonFilePath, &lastModifiedTime) &&
CompareFileTime(&lastModifiedTime, &lastLoadedGeneralSettingsTime) == 1))
return;
lastLoadedGeneralSettingsTime = lastModifiedTime;
auto json = json::from_file(generalJsonFilePath);
if (!json)
return;
const json::JsonObject& jsonSettings = json.value();
try
{
json::JsonObject modulesEnabledState;
json::get(jsonSettings, L"enabled", modulesEnabledState, json::JsonObject{});
json::get(modulesEnabledState, L"File Locksmith", settings.enabled, true);
}
catch (const winrt::hresult_error&)
{
}
}
void FileLocksmithSettings::Reload()
{
// Load json settings from data file if it is modified in the meantime.
FILETIME lastModifiedTime{};
if (LastModifiedTime(jsonFilePath, &lastModifiedTime) &&
CompareFileTime(&lastModifiedTime, &lastLoadedTime) == 1)
{
Load();
}
}
void FileLocksmithSettings::ParseJson()
{
auto json = json::from_file(jsonFilePath);
if (json)
{
const json::JsonObject& jsonSettings = json.value();
try
{
if (json::has(jsonSettings, constants::nonlocalizable::JsonKeyShowInExtendedContextMenu, json::JsonValueType::Boolean))
{
settings.showInExtendedContextMenu = jsonSettings.GetNamedBoolean(constants::nonlocalizable::JsonKeyShowInExtendedContextMenu);
}
}
catch (const winrt::hresult_error&)
{
}
}
GetSystemTimeAsFileTime(&lastLoadedTime);
}
FileLocksmithSettings& FileLocksmithSettingsInstance()
{
static FileLocksmithSettings instance;
return instance;
}
@@ -0,0 +1,54 @@
#pragma once
#include "pch.h"
#include <common/utils/gpo.h>
class FileLocksmithSettings
{
public:
FileLocksmithSettings();
inline bool GetEnabled()
{
auto gpoSetting = powertoys_gpo::getConfiguredFileLocksmithEnabledValue();
if (gpoSetting == powertoys_gpo::gpo_rule_configured_enabled)
return true;
if (gpoSetting == powertoys_gpo::gpo_rule_configured_disabled)
return false;
Reload();
RefreshEnabledState();
return settings.enabled;
}
inline bool GetShowInExtendedContextMenu() const
{
return settings.showInExtendedContextMenu;
}
inline void SetExtendedContextMenuOnly(bool extendedOnly)
{
settings.showInExtendedContextMenu = extendedOnly;
}
void Save();
void Load();
private:
struct Settings
{
bool enabled{ true };
bool showInExtendedContextMenu{ false };
};
void RefreshEnabledState();
void Reload();
void ParseJson();
Settings settings;
std::wstring generalJsonFilePath;
std::wstring jsonFilePath;
FILETIME lastLoadedTime{};
FILETIME lastLoadedGeneralSettingsTime{};
};
FileLocksmithSettings& FileLocksmithSettingsInstance();
@@ -0,0 +1,62 @@
#include "pch.h"
#include "Trace.h"
#include <common/Telemetry/TraceBase.h>
TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
// {38e8889b-9731-53f5-e901-e8a7c1753074}
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
TraceLoggingOptionProjectTelemetry());
void Trace::EnableFileLocksmith(_In_ bool enabled) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"FileLocksmith_EnableFileLocksmith",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
void Trace::Invoked() noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"FileLocksmith_Invoked",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::InvokedRet(_In_ HRESULT hr) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"FileLocksmith_InvokedRet",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingHResult(hr),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::QueryContextMenuError(_In_ HRESULT hr) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"FileLocksmith_QueryContextMenuError",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingHResult(hr),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
void Trace::CLICommand(_In_ PCWSTR commandName, _In_ bool successful) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"FileLocksmith_CLICommand",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(commandName, "CommandName"),
TraceLoggingBoolean(successful, "Successful"));
}
@@ -0,0 +1,15 @@
#pragma once
#include "pch.h"
#include <common/Telemetry/TraceBase.h>
class Trace : public telemetry::TraceBase
{
public:
static void EnableFileLocksmith(_In_ bool enabled) noexcept;
static void Invoked() noexcept;
static void InvokedRet(_In_ HRESULT hr) noexcept;
static void QueryContextMenuError(_In_ HRESULT hr) noexcept;
static void CLICommand(_In_ PCWSTR commandName, _In_ bool successful) noexcept;
};
@@ -0,0 +1,15 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <Windows.h>
#include <Shlwapi.h>
#include <ShlObj_core.h>
#include <atlbase.h>
#include <commctrl.h>
// C++ Standard library
#include <atomic>
#include <string>
#include <filesystem>
#include <fstream>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
</packages>
@@ -0,0 +1,5 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
@@ -0,0 +1,129 @@
#include "pch.h"
#include "FileLocksmith.h"
#include "NtdllExtensions.h"
static bool is_directory(const std::wstring path)
{
DWORD attributes = GetFileAttributesW(path.c_str());
return attributes != INVALID_FILE_ATTRIBUTES && attributes & FILE_ATTRIBUTE_DIRECTORY;
}
// C++20 method
static constexpr bool starts_with(std::wstring_view whole, std::wstring_view part)
{
return whole.size() >= part.size() && whole.substr(0, part.size()) == part;
}
std::vector<ProcessResult> find_processes_recursive(const std::vector<std::wstring>& paths)
{
NtdllExtensions nt_ext;
// TODO use a trie!
// This maps kernel names of files within `paths` to their normal paths.
std::map<std::wstring, std::wstring> kernel_names_files;
// This maps kernel names of directories within `paths` to their normal paths.
std::map<std::wstring, std::wstring> kernel_names_dirs;
for (const auto& path : paths)
{
auto kernel_path = nt_ext.path_to_kernel_name(path.c_str());
if (!kernel_path.empty())
{
(is_directory(path) ? kernel_names_dirs : kernel_names_files)[kernel_path] = path;
}
}
std::map<ULONG_PTR, std::set<std::wstring>> pid_files;
// Returns a normal path of the file specified by kernel_name, if it matches
// the search criteria. Otherwise, return an empty string.
auto kernel_paths_contain = [&](const std::wstring& kernel_name) -> std::wstring
{
// Normal equivalence
if (auto it = kernel_names_files.find(kernel_name); it != kernel_names_files.end())
{
return it->second;
}
if (auto it = kernel_names_dirs.find(kernel_name); it != kernel_names_dirs.end())
{
return it->second;
}
for (const auto& [dir_kernel_name, dir_path] : kernel_names_dirs)
{
if (starts_with(kernel_name, dir_kernel_name + (dir_kernel_name.length()>0&&dir_kernel_name[dir_kernel_name.length()-1]!=L'\\' ? L"\\" : L"")))
{
return dir_path + kernel_name.substr(dir_kernel_name.size());
}
}
return {};
};
for (const auto& handle_info : nt_ext.handles())
{
if (handle_info.type_name == L"File")
{
auto path = kernel_paths_contain(handle_info.kernel_file_name);
if (!path.empty())
{
pid_files[handle_info.pid].insert(std::move(path));
}
}
}
// Check all modules used by processes
auto processes = nt_ext.processes();
for (const auto& process : processes)
{
for (const auto& path : process.modules)
{
auto kernel_name = nt_ext.path_to_kernel_name(path.c_str());
auto found_path = kernel_paths_contain(kernel_name);
if (!found_path.empty())
{
pid_files[process.pid].insert(std::move(found_path));
}
}
}
std::vector<ProcessResult> result;
for (const auto& process_info : processes)
{
if (auto it = pid_files.find(process_info.pid); it != pid_files.end())
{
result.push_back(ProcessResult
{
process_info.name,
process_info.pid,
process_info.user,
std::vector(it->second.begin(), it->second.end())
});
}
}
return result;
}
constexpr size_t LongMaxPathSize = 65536;
std::wstring pid_to_full_path(DWORD pid)
{
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
std::wstring result(LongMaxPathSize, L'\0');
// Returns zero on failure, so it's okay to resize to zero.
auto length = GetModuleFileNameExW(process, NULL, result.data(), static_cast<DWORD>(result.size()));
result.resize(length);
CloseHandle(process);
return result;
}
@@ -0,0 +1,17 @@
#pragma once
#include "pch.h"
struct ProcessResult
{
std::wstring name;
DWORD pid;
std::wstring user;
std::vector<std::wstring> files;
};
// Second version, checks handles towards files and all subfiles and folders of given dirs, if any.
std::vector<ProcessResult> find_processes_recursive(const std::vector<std::wstring>& paths);
// Gives the full path of the executable, given the process id
std::wstring pid_to_full_path(DWORD pid);
@@ -0,0 +1,40 @@
#include <windows.h>
#include "resource.h"
#include "../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END
@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{c604b37e-9d0e-4484-8778-e8b31b0e1b3a}</ProjectGuid>
<ProjectName>PowerToys.FileLocksmithLib.Interop</ProjectName>
<RootNamespace>PowerToys.FileLocksmithLib.Interop</RootNamespace>
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
<OutDir>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\</OutDir>
<TargetName>PowerToys.FileLocksmithLib.Interop</TargetName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>FILELOCKSMITHLIBINTEROP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalOptions>%(AdditionalOptions) /bigobj /Zc:twoPhase-</AdditionalOptions>
<AdditionalIncludeDirectories>.;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>PowerToys.FileLocksmithLibInterop.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<PropertyGroup>
<CopyCppRuntimeToOutputDir>true</CopyCppRuntimeToOutputDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemGroup>
<ClCompile Include="FileLocksmith.cpp" />
<ClCompile Include="NativeMethods.cpp">
<DependentUpon>NativeMethods.idl</DependentUpon>
</ClCompile>
<ClCompile Include="NtdllBase.cpp" />
<ClCompile Include="NtdllExtensions.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ProcessResult.cpp">
<DependentUpon>ProcessResult.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="FileLocksmith.h" />
<ClInclude Include="NativeMethods.h">
<DependentUpon>NativeMethods.idl</DependentUpon>
</ClInclude>
<ClInclude Include="NtdllBase.h" />
<ClInclude Include="NtdllExtensions.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="ProcessResult.h">
<DependentUpon>ProcessResult.idl</DependentUpon>
</ClInclude>
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\common\interop\PowerToys.Interop.vcxproj">
<Project>{f055103b-f80b-4d0c-bf48-057c55620033}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="FileLocksmithLibInterop.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="PowerToys.Interop.def" />
</ItemGroup>
<ItemGroup>
<Midl Include="NativeMethods.idl" />
<Midl Include="ProcessResult.idl" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(RepoRoot)packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileLocksmith.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="NtdllBase.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="NtdllExtensions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ProcessResult.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="NativeMethods.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FileLocksmith.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="NtdllBase.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="NtdllExtensions.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ProcessResult.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="NativeMethods.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="FileLocksmithLibInterop.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Midl Include="ProcessResult.idl">
<Filter>Source Files</Filter>
</Midl>
<Midl Include="NativeMethods.idl">
<Filter>Source Files</Filter>
</Midl>
</ItemGroup>
</Project>
@@ -0,0 +1,208 @@
#include "pch.h"
#include "NativeMethods.h"
#include "FileLocksmith.h"
#include "../FileLocksmithLib/Constants.h"
namespace winrt::PowerToys::FileLocksmithLib::Interop::implementation
{
#pragma region HelperMethods
std::wstring executable_path()
{
return pid_to_full_path(GetCurrentProcessId());
}
std::wstring paths_file()
{
std::wstring path{ PowerToys::Interop::Constants::AppDataPath() };
path += L"\\";
path += constants::nonlocalizable::PowerToyName;
path += L"\\";
path += constants::nonlocalizable::LastRunPath;
return path;
}
#pragma endregion
com_array<winrt::PowerToys::FileLocksmithLib::Interop::ProcessResult> NativeMethods::FindProcessesRecursive(array_view<hstring const> paths)
{
std::vector<std::wstring> paths_cpp{ paths.begin(), paths.end() };
auto result_cpp = find_processes_recursive(paths_cpp);
const auto result_size = static_cast<int>(result_cpp.size());
std::vector<ProcessResult> result;
if (result_size == 0)
{
return com_array<ProcessResult>();
}
for (int i = 0; i < result_size; i++)
{
result.push_back(ProcessResult
{
hstring { result_cpp[i].name },
result_cpp[i].pid,
hstring{ result_cpp[i].user },
winrt::com_array<hstring>
{
result_cpp[i].files.begin(), result_cpp[i].files.end()
}
});
}
return com_array<ProcessResult>{ result.begin(), result.end() };
}
hstring NativeMethods::PidToFullPath(uint32_t pid)
{
return hstring{ pid_to_full_path(pid) };
}
com_array<hstring> NativeMethods::ReadPathsFromFile()
{
std::ifstream stream(paths_file(), std::ios::binary);
std::vector<std::wstring> result_cpp;
std::wstring line;
bool finished = false;
while (!finished)
{
WCHAR ch{};
// We have to read data like this
if (!stream.read(reinterpret_cast<char*>(&ch), 2))
{
finished = true;
}
else if (ch == L'\n')
{
if (line.empty())
{
finished = true;
}
else
{
result_cpp.push_back(line);
line = {};
}
}
else
{
line += ch;
}
}
return com_array<hstring>{ result_cpp.begin(), result_cpp.end() };
}
bool NativeMethods::StartAsElevated(array_view<hstring const> paths)
{
std::ofstream stream(paths_file(), std::ios::binary);
const WCHAR newline = L'\n';
for (uint32_t i = 0; i < paths.size(); i++)
{
std::wstring path_cpp{ paths[i] };
stream.write(reinterpret_cast<const char*>(path_cpp.c_str()), path_cpp.size() * sizeof(WCHAR));
stream.write(reinterpret_cast<const char*>(&newline), sizeof(WCHAR));
}
stream.write(reinterpret_cast<const char*>(&newline), sizeof(WCHAR));
if (!stream)
{
return false;
}
stream.close();
auto exec_path = executable_path();
SHELLEXECUTEINFOW exec_info{};
exec_info.cbSize = sizeof(exec_info);
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
exec_info.hwnd = NULL;
exec_info.lpVerb = L"runas";
exec_info.lpFile = exec_path.c_str();
exec_info.lpParameters = L"--elevated";
exec_info.lpDirectory = NULL;
exec_info.nShow = SW_SHOW;
exec_info.hInstApp = NULL;
if (ShellExecuteExW(&exec_info))
{
CloseHandle(exec_info.hProcess);
return true;
}
return false;
}
/* Adapted from "https://learn.microsoft.com/windows/win32/secauthz/enabling-and-disabling-privileges-in-c--" */
bool NativeMethods::SetDebugPrivilege()
{
HANDLE hToken;
TOKEN_PRIVILEGES tp{};
LUID luid;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken) != 0)
{
if (!LookupPrivilegeValue(
NULL, // lookup privilege on local system
SE_DEBUG_NAME, // privilege to lookup
&luid)) // receives LUID of privilege
{
CloseHandle(hToken);
return false;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
NULL,
NULL))
{
CloseHandle(hToken);
return false;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
CloseHandle(hToken);
return false;
}
CloseHandle(hToken);
return true;
}
return false;
}
// adapted from common/utils/elevation.h. No need to bring all dependencies to this project, though.
// TODO: Make elevation.h lighter so that this function can be used without bringing dependencies like spdlog in.
bool NativeMethods::IsProcessElevated()
{
HANDLE token = nullptr;
bool elevated = false;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
{
TOKEN_ELEVATION elevation{};
DWORD size;
if (GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &size))
{
elevated = (elevation.TokenIsElevated != 0);
}
}
if (token)
{
CloseHandle(token);
}
return elevated;
}
}
@@ -0,0 +1,23 @@
#pragma once
#include "NativeMethods.g.h"
namespace winrt::PowerToys::FileLocksmithLib::Interop::implementation
{
struct NativeMethods : NativeMethodsT<NativeMethods>
{
NativeMethods() = default;
static com_array<winrt::PowerToys::FileLocksmithLib::Interop::ProcessResult> FindProcessesRecursive(array_view<hstring const> paths);
static hstring PidToFullPath(uint32_t pid);
static com_array<hstring> ReadPathsFromFile();
static bool StartAsElevated(array_view<hstring const> paths);
static bool SetDebugPrivilege();
static bool IsProcessElevated();
};
}
namespace winrt::PowerToys::FileLocksmithLib::Interop::factory_implementation
{
struct NativeMethods : NativeMethodsT<NativeMethods, implementation::NativeMethods>
{
};
}
@@ -0,0 +1,19 @@
import "ProcessResult.idl";
namespace PowerToys
{
namespace FileLocksmithLib
{
namespace Interop
{
[default_interface] static runtimeclass NativeMethods {
static PowerToys.FileLocksmithLib.Interop.ProcessResult[] FindProcessesRecursive(String[] paths);
static String PidToFullPath(UInt32 pid);
static String[] ReadPathsFromFile();
static Boolean StartAsElevated(String[] paths);
static Boolean SetDebugPrivilege();
static Boolean IsProcessElevated();
}
}
}
}
@@ -0,0 +1,61 @@
#include "pch.h"
#include "NtdllBase.h"
Ntdll::Ntdll()
{
m_module = GetModuleHandleW(L"ntdll.dll");
if (m_module == 0)
{
throw std::runtime_error{ "GetModuleHandleW returned null" };
}
m_NtQuerySystemInformation = reinterpret_cast<NtQuerySystemInformation_t>(GetProcAddress(m_module, "NtQuerySystemInformation"));
if (m_NtQuerySystemInformation == 0)
{
throw std::runtime_error{ "GetProcAddress returned null for NtQuerySystemInformation" };
}
m_NtDuplicateObject = reinterpret_cast<NtDuplicateObject_t>(GetProcAddress(m_module, "NtDuplicateObject"));
if (m_NtDuplicateObject == 0)
{
throw std::runtime_error{ "GetProcAddress returned null for NtDuplicateObject" };
}
m_NtQueryObject = reinterpret_cast<NtQueryObject_t>(GetProcAddress(m_module, "NtQueryObject"));
if (m_NtQueryObject == 0)
{
throw std::runtime_error{ "GetProcAddress returned null for NtQueryObject" };
}
}
NTSTATUS Ntdll::NtQuerySystemInformation(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength)
{
return m_NtQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
}
NTSTATUS Ntdll::NtDuplicateObject(
HANDLE SourceProcessHandle,
HANDLE SourceHandle,
HANDLE TargetProcessHandle,
PHANDLE TargetHandle,
ACCESS_MASK DesiredAccess,
ULONG Attributes,
ULONG Options)
{
return m_NtDuplicateObject(SourceProcessHandle, SourceHandle, TargetProcessHandle, TargetHandle, DesiredAccess, Attributes, Options);
}
NTSTATUS Ntdll::NtQueryObject(
HANDLE ObjectHandle,
ULONG ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength)
{
return m_NtQueryObject(ObjectHandle, ObjectInformationClass, ObjectInformation, ObjectInformationLength, ReturnLength);
}
@@ -0,0 +1,101 @@
#pragma once
#include "pch.h"
#define DECLARE_NTDLL_FUNCTION(name, ...) \
private: \
typedef NTSTATUS(NTAPI* name ## _t)( \
__VA_ARGS__ \
); \
name ## _t m_ ## name; \
public: \
NTSTATUS name(__VA_ARGS__);
class Ntdll
{
private:
HMODULE m_module;
public:
struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
{
PVOID Object;
ULONG_PTR UniqueProcessId;
ULONG_PTR HandleValue;
ULONG GrantedAccess;
USHORT CreatorBackTraceIndex;
USHORT ObjectTypeIndex;
ULONG HandleAttributes;
ULONG Reserved;
};
struct SYSTEM_HANDLE_INFORMATION_EX
{
ULONG_PTR NumberOfHandles;
ULONG_PTR Reserved;
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
};
enum POOL_TYPE
{
NonPagedPool,
PagedPool,
NonPagedPoolMustSucceed,
DontUseThisType,
NonPagedPoolCacheAligned,
PagedPoolCacheAligned,
NonPagedPoolCacheAlignedMustS
};
struct OBJECT_TYPE_INFORMATION
{
UNICODE_STRING Name;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccess;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
USHORT MaintainTypeList;
POOL_TYPE PoolType;
ULONG PagedPoolUsage;
ULONG NonPagedPoolUsage;
};
Ntdll();
DECLARE_NTDLL_FUNCTION(NtQuerySystemInformation,
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
)
DECLARE_NTDLL_FUNCTION(NtDuplicateObject,
HANDLE SourceProcessHandle,
HANDLE SourceHandle,
HANDLE TargetProcessHandle,
PHANDLE TargetHandle,
ACCESS_MASK DesiredAccess,
ULONG Attributes,
ULONG Options
)
DECLARE_NTDLL_FUNCTION(NtQueryObject,
HANDLE ObjectHandle,
ULONG ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength
);
};
@@ -0,0 +1,369 @@
#include "pch.h"
#include "NtdllExtensions.h"
#include <thread>
#include <atomic>
#define STATUS_INFO_LENGTH_MISMATCH ((LONG)0xC0000004)
// Calls NtQuerySystemInformation and returns a buffer containing the result.
namespace
{
std::wstring_view unicode_to_view(UNICODE_STRING unicode_str)
{
return std::wstring_view(unicode_str.Buffer, unicode_str.Length / sizeof(WCHAR));
}
std::wstring unicode_to_str(UNICODE_STRING unicode_str)
{
return std::wstring(unicode_str.Buffer, unicode_str.Length / sizeof(WCHAR));
}
// Implementation adapted from src/common/utils
inline std::wstring get_module_name(HANDLE process, HMODULE mod)
{
wchar_t buffer[MAX_PATH + 1];
DWORD actual_length = GetModuleFileNameExW(process, mod, buffer, MAX_PATH + 1);
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
const DWORD long_path_length = 0xFFFF; // should be always enough
std::wstring long_filename(long_path_length, L'\0');
actual_length = GetModuleFileNameW(mod, long_filename.data(), long_path_length);
long_filename.resize(std::wcslen(long_filename.data()));
long_filename.shrink_to_fit();
return long_filename;
}
return { buffer, static_cast<UINT>(lstrlenW(buffer)) };
}
constexpr size_t DefaultModulesResultSize = 512;
std::vector<std::wstring> process_modules(DWORD pid)
{
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
if (!process)
{
return {};
}
std::vector<std::wstring> result;
bool completed = false;
std::vector<HMODULE> modules(DefaultModulesResultSize);
while (!completed)
{
DWORD needed;
auto status = EnumProcessModules(process, modules.data(), static_cast<DWORD>(modules.size() * sizeof(HMODULE)), &needed);
if (!status)
{
// Give up
return {};
}
if (needed > modules.size() * sizeof(HMODULE))
{
// Array is too small
modules.resize(needed / sizeof(HMODULE));
continue;
}
// Okay
modules.resize(needed / sizeof(HMODULE));
for (auto mod : modules)
{
result.push_back(get_module_name(process, mod));
}
completed = true;
}
CloseHandle(process);
return result;
}
}
NtdllExtensions::MemoryLoopResult NtdllExtensions::NtQuerySystemInformationMemoryLoop(ULONG SystemInformationClass)
{
MemoryLoopResult result;
result.memory.resize(DefaultResultBufferSize);
while (result.memory.size() <= MaxResultBufferSize)
{
ULONG result_len;
result.status = NtQuerySystemInformation(SystemInformationClass, result.memory.data(), static_cast<ULONG>(result.memory.size()), &result_len);
if (result.status == STATUS_INFO_LENGTH_MISMATCH)
{
result.memory.resize(result.memory.size() * 2);
continue;
}
if (NT_ERROR(result.status))
{
result.memory.clear();
}
return result;
}
result.status = STATUS_INFO_LENGTH_MISMATCH;
result.memory.clear();
return result;
}
std::wstring NtdllExtensions::file_handle_to_kernel_name(HANDLE file_handle, std::vector<BYTE>& buffer)
{
if (GetFileType(file_handle) != FILE_TYPE_DISK)
{
return L"";
}
ULONG return_length;
auto status = NtQueryObject(file_handle, ObjectNameInformation, buffer.data(), static_cast<ULONG>(buffer.size()), &return_length);
if (NT_SUCCESS(status))
{
auto object_name_info = reinterpret_cast<UNICODE_STRING*>(buffer.data());
return unicode_to_str(*object_name_info);
}
return L"";
}
std::wstring NtdllExtensions::file_handle_to_kernel_name(HANDLE file_handle)
{
std::vector<BYTE> buffer(DefaultResultBufferSize);
return file_handle_to_kernel_name(file_handle, buffer);
}
std::wstring NtdllExtensions::path_to_kernel_name(LPCWSTR path)
{
HANDLE file_handle = CreateFileW(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (file_handle == INVALID_HANDLE_VALUE)
{
return {};
}
auto kernel_name = file_handle_to_kernel_name(file_handle);
CloseHandle(file_handle);
return kernel_name;
}
std::vector<NtdllExtensions::HandleInfo> NtdllExtensions::handles() noexcept
{
auto get_info_result = NtQuerySystemInformationMemoryLoop(SystemExtendedHandleInformation);
if (NT_ERROR(get_info_result.status))
{
return {};
}
auto info_ptr = reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(get_info_result.memory.data());
std::map<ULONG_PTR, HANDLE> pid_to_handle;
std::vector<HandleInfo> result;
std::vector<BYTE> object_info_buffer(DefaultResultBufferSize);
std::atomic<ULONG> i = 0;
std::atomic<ULONG_PTR> handle_count = info_ptr->NumberOfHandles;
std::atomic<HANDLE> process_handle = NULL;
std::atomic<HANDLE> handle_copy = NULL;
ULONG previous_i;
while (i < handle_count)
{
previous_i = i;
// The system calls we use in this block were reported to hang on some machines.
// We need to offload the cycle to another thread and keep track of progress to terminate and resume when needed.
// Unfortunately, there are no alternative APIs to what we're using that accept timeouts. (NtQueryObject and GetFileType)
auto offload_function = std::thread([&] {
for (; i < handle_count; i++)
{
process_handle = NULL;
handle_copy = NULL;
auto handle_info = info_ptr->Handles + i;
auto pid = handle_info->UniqueProcessId;
auto iter = pid_to_handle.find(pid);
if (iter != pid_to_handle.end())
{
process_handle = iter->second;
}
else
{
process_handle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, static_cast<DWORD>(pid));
if (!process_handle)
{
continue;
}
pid_to_handle[pid] = process_handle;
}
// According to this:
// https://stackoverflow.com/questions/46384048/enumerate-handles
// NtQueryObject could hang
// TODO uncomment and investigate
// if (handle_info->GrantedAccess == 0x0012019f) {
// continue;
// }
HANDLE local_handle_copy;
auto dh_result = DuplicateHandle(process_handle, reinterpret_cast<HANDLE>(handle_info->HandleValue), GetCurrentProcess(), &local_handle_copy, 0, 0, DUPLICATE_SAME_ACCESS);
if (dh_result == 0)
{
// Ignore this handle.
continue;
}
handle_copy = local_handle_copy;
ULONG return_length;
auto status = NtQueryObject(handle_copy, ObjectTypeInformation, object_info_buffer.data(), static_cast<ULONG>(object_info_buffer.size()), &return_length);
if (NT_ERROR(status))
{
// Ignore this handle.
CloseHandle(handle_copy);
handle_copy = NULL;
continue;
}
auto object_type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>(object_info_buffer.data());
auto type_name = unicode_to_str(object_type_info->Name);
std::wstring file_name;
if (type_name == L"File")
{
file_name = file_handle_to_kernel_name(handle_copy, object_info_buffer);
result.push_back(HandleInfo{ pid, handle_info->HandleValue, type_name, file_name });
}
CloseHandle(handle_copy);
handle_copy = NULL;
}
});
offload_function.detach();
do
{
Sleep(200); // Timeout in milliseconds for detecting that the system hang on getting information for a handle.
if (i >= handle_count)
{
// We're done.
break;
}
if (previous_i >= i)
{
// The thread looks like it's hanging on some handle. Let's kill it and resume.
// HACK: This is unsafe and may leak something, but looks like there's no way to properly clean up a thread when it's hanging on a system call.
TerminateThread(offload_function.native_handle(), 1);
// Close Handles that might be lingering.
if (handle_copy!=NULL)
{
CloseHandle(handle_copy);
}
i++;
break;
}
previous_i = i;
} while (1);
}
for (auto [pid, handle] : pid_to_handle)
{
CloseHandle(handle);
}
return result;
}
// Returns the list of all processes.
// On failure, returns an empty vector.
std::wstring NtdllExtensions::pid_to_user(DWORD pid)
{
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
std::wstring user;
std::wstring domain;
if (process == nullptr)
{
return user;
}
HANDLE token = nullptr;
if (!OpenProcessToken(process, TOKEN_QUERY, &token))
{
return user;
}
DWORD token_size = 0;
const bool ok = GetTokenInformation(token, TokenUser, nullptr, 0, &token_size);
if ((!ok && GetLastError() != ERROR_INSUFFICIENT_BUFFER) || !token_size)
{
return user;
}
std::vector<BYTE> token_buffer(token_size);
GetTokenInformation(token, TokenUser, token_buffer.data(), token_size, &token_size);
TOKEN_USER* user_ptr = reinterpret_cast<TOKEN_USER*>(token_buffer.data());
PSID psid = user_ptr->User.Sid;
DWORD user_buf_size = 0;
DWORD domain_buf_size = 0;
SID_NAME_USE sid_name;
LookupAccountSidW(nullptr, psid, nullptr, &user_buf_size, nullptr, &domain_buf_size, &sid_name);
if (!user_buf_size || !domain_buf_size)
{
return user;
}
user.resize(user_buf_size);
domain.resize(domain_buf_size);
LookupAccountSidW(nullptr, psid, user.data(), &user_buf_size, domain.data(), &domain_buf_size, &sid_name);
user.resize(user.size() - 1);
domain.resize(domain.size() - 1);
CloseHandle(token);
CloseHandle(process);
return user;
}
std::vector<NtdllExtensions::ProcessInfo> NtdllExtensions::processes() noexcept
{
auto get_info_result = NtQuerySystemInformationMemoryLoop(SystemProcessInformation);
if (NT_ERROR(get_info_result.status))
{
return {};
}
std::vector<ProcessInfo> result;
auto info_ptr = reinterpret_cast<PSYSTEM_PROCESS_INFORMATION>(get_info_result.memory.data());
while (info_ptr->NextEntryOffset)
{
info_ptr = reinterpret_cast<decltype(info_ptr)>(reinterpret_cast<LPBYTE>(info_ptr) + info_ptr->NextEntryOffset);
ProcessInfo item;
item.name = unicode_to_str(info_ptr->ImageName);
item.pid = static_cast<DWORD>(reinterpret_cast<uintptr_t>(info_ptr->UniqueProcessId));
item.modules = process_modules(item.pid);
item.user = pid_to_user(item.pid);
result.push_back(item);
}
return result;
}
@@ -0,0 +1,56 @@
#pragma once
#include "pch.h"
#include "NtdllBase.h"
class NtdllExtensions : protected Ntdll
{
private:
constexpr static size_t DefaultResultBufferSize = 64 * 1024;
constexpr static size_t MaxResultBufferSize = 1024 * 1024 * 1024;
constexpr static int ObjectNameInformation = 1;
constexpr static int SystemExtendedHandleInformation = 64;
struct MemoryLoopResult
{
NTSTATUS status = 0;
std::vector<BYTE> memory;
};
// Calls NtQuerySystemInformation and returns a buffer containing the result.
MemoryLoopResult NtQuerySystemInformationMemoryLoop(ULONG SystemInformationClass);
std::wstring file_handle_to_kernel_name(HANDLE file_handle, std::vector<BYTE>& buffer);
public:
struct ProcessInfo
{
DWORD pid = 0;
std::wstring name;
std::wstring user;
std::vector<std::wstring> modules;
};
struct HandleInfo
{
ULONG_PTR pid;
ULONG_PTR handle;
std::wstring type_name;
std::wstring kernel_file_name;
};
std::wstring file_handle_to_kernel_name(HANDLE file_handle);
std::wstring path_to_kernel_name(LPCWSTR path);
// Gives the user name of the account running this process
std::wstring pid_to_user(DWORD pid);
std::vector<HandleInfo> handles() noexcept;
// Returns the list of all processes.
// On failure, returns an empty vector.
std::vector<ProcessInfo> processes() noexcept;
};
@@ -0,0 +1,3 @@
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
@@ -0,0 +1,29 @@
#include "pch.h"
#include "ProcessResult.h"
namespace winrt::PowerToys::FileLocksmithLib::Interop::implementation
{
ProcessResult::ProcessResult(hstring const& name, uint32_t pid, hstring const& user, array_view<hstring const> files)
{
_name = name;
_pid = pid;
_user = user;
_files = { files.begin(), files.end() };
}
hstring ProcessResult::name()
{
return _name;
}
uint32_t ProcessResult::pid()
{
return _pid;
}
hstring ProcessResult::user()
{
return _user;
}
com_array<hstring> ProcessResult::files()
{
return winrt::com_array<hstring>{ _files.begin(), _files.end() };
}
}
@@ -0,0 +1,28 @@
#pragma once
#include "ProcessResult.g.h"
namespace winrt::PowerToys::FileLocksmithLib::Interop::implementation
{
struct ProcessResult : ProcessResultT<ProcessResult>
{
ProcessResult() = default;
ProcessResult(hstring const& name, uint32_t pid, hstring const& user, array_view<hstring const> files);
hstring name();
uint32_t pid();
hstring user();
com_array<hstring> files();
private:
hstring _name;
uint32_t _pid;
hstring _user;
com_array<hstring> _files;
};
}
namespace winrt::PowerToys::FileLocksmithLib::Interop::factory_implementation
{
struct ProcessResult : ProcessResultT<ProcessResult, implementation::ProcessResult>
{
};
}
@@ -0,0 +1,16 @@
namespace PowerToys
{
namespace FileLocksmithLib
{
namespace Interop
{
[default_interface] runtimeclass ProcessResult {
ProcessResult(String name, UInt32 pid, String user, String[] files);
String name{ get; };
UInt32 pid{ get; };
String user{ get; };
String[] files{ get; };
};
}
}
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
</packages>
@@ -0,0 +1 @@
#include "pch.h"
@@ -0,0 +1,23 @@
#pragma once
// System headers
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <winternl.h>
#include <Psapi.h>
#include <shellapi.h>
// C++ standard library headers
#include <stdexcept>
#include <vector>
#include <map>
#include <iostream>
#include <string>
#include <set>
#include <algorithm>
#include <fstream>
#ifndef FILELOCKSMITH_LIB_STATIC
#include <winrt/PowerToys.Interop.h>
#endif
@@ -0,0 +1,13 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by FileLocksmithLibInterop.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys FileLocksmithLib Interop"
#define INTERNAL_NAME "PowerToys.FileLocksmithLib.Interop"
#define ORIGINAL_FILENAME "PowerToys.FileLocksmithLib.Interop.dll"
// Non-localizable
//////////////////////////////
Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

@@ -0,0 +1,25 @@
// 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.
using System;
using Microsoft.UI.Xaml.Data;
namespace PowerToys.FileLocksmithUI.Converters
{
public sealed partial class FileCountConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
#pragma warning disable CA1305 // Specify IFormatProvider
return ((string[])value).Length.ToString();
#pragma warning restore CA1305 // Specify IFormatProvider
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
}
@@ -0,0 +1,49 @@
// 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.
using System;
using System.IO;
using Microsoft.UI.Xaml.Data;
namespace PowerToys.FileLocksmithUI.Converters
{
public sealed partial class FileListToDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var paths = (string[])value;
if (paths.Length == 0)
{
return string.Empty;
}
string firstPath = paths[0];
firstPath = Path.GetFileName(paths[0]);
if (string.IsNullOrEmpty(firstPath))
{
firstPath = Path.GetDirectoryName(paths[0]);
}
if (string.IsNullOrEmpty(firstPath))
{
firstPath = Path.GetPathRoot(paths[0]);
}
if (paths.Length == 1)
{
return firstPath;
}
else
{
return firstPath + "; +" + (paths.Length - 1);
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
}
@@ -0,0 +1,64 @@
// 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.
using System;
using System.Drawing;
using System.IO;
using ManagedCommon;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media.Imaging;
namespace PowerToys.FileLocksmithUI.Converters
{
public sealed partial class PidToIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var y = PowerToys.FileLocksmithLib.Interop.NativeMethods.PidToFullPath((uint)value);
Icon icon = null;
if (!string.IsNullOrEmpty(y))
{
try
{
icon = Icon.ExtractAssociatedIcon(y);
}
catch (Exception ex)
{
// The process image path can be non-empty but no longer exist on disk
// (e.g. self-updating software that deletes its old versioned directory while
// the old process is still running). ExtractAssociatedIcon then throws and,
// because this converter runs per-row during ListView virtualization, the
// exception would otherwise reach App_UnhandledException and fast-fail the app.
// Fall through to the placeholder icon instead of crashing.
Logger.LogWarning($"Couldn't extract the icon for '{y}'. {ex}");
}
}
if (icon != null)
{
Bitmap bitmap = icon.ToBitmap();
BitmapImage bitmapImage = new BitmapImage();
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
stream.Position = 0;
bitmapImage.SetSource(stream.AsRandomAccessStream());
}
return bitmapImage;
}
else
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
}
@@ -0,0 +1,32 @@
// 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.
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace PowerToys.FileLocksmithUI.Converters
{
public sealed partial class UserToSystemWarningVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
string user = ((string)value).ToUpperInvariant().Trim();
if (user.Equals("SYSTEM", StringComparison.Ordinal) || user.Equals("LOCALSYSTEM", StringComparison.Ordinal))
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
}
@@ -0,0 +1,89 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
<PropertyGroup>
<AssemblyTitle>PowerToys.FileLocksmith</AssemblyTitle>
<AssemblyDescription>PowerToys File Locksmith</AssemblyDescription>
<OutputType>WinExe</OutputType>
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
<RootNamespace>PowerToys.FileLocksmithUI</RootNamespace>
<AssemblyName>PowerToys.FileLocksmithUI</AssemblyName>
<ApplicationManifest>app.manifest</ApplicationManifest>
<UseWinUI>true</UseWinUI>
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<ApplicationIcon>Assets/FileLocksmith/Icon.ico</ApplicationIcon>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.FileLocksmithUI.pri</ProjectPriFileName>
</PropertyGroup>
<ItemGroup>
<Page Remove="FileLocksmithXAML\App.xaml" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="FileLocksmithXAML\App.xaml" />
</ItemGroup>
<PropertyGroup>
<NoWarn>0436</NoWarn>
</PropertyGroup>
<ItemGroup>
<Content Include="Assets\FileLocksmith\Icon.ico" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\FileLocksmith\AppList.scale-100.png" />
<None Remove="Assets\FileLocksmith\AppList.scale-125.png" />
<None Remove="Assets\FileLocksmith\AppList.scale-150.png" />
<None Remove="Assets\FileLocksmith\AppList.scale-200.png" />
<None Remove="Assets\FileLocksmith\AppList.scale-400.png" />
</ItemGroup>
<!-- See https://learn.microsoft.com/windows/apps/develop/platform/csharp-winrt/net-projection-from-cppwinrt-component for more info -->
<PropertyGroup>
<CsWinRTIncludes>PowerToys.GPOWrapper;PowerToys.FileLocksmithLib.Interop</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="WinUIEx" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
<PackageReference Include="Microsoft.Web.WebView2" />
<!-- HACK: CmdPal uses CommunityToolkit.Common directly. Align the version. -->
<PackageReference Include="CommunityToolkit.Common" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored -->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<!-- HACK: Common.UI is referenced, even if it is not used, to force dll versions to be the same as in other projects that use it. It's still unclear why this is the case, but this is need for flattening the install directory. -->
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,14 @@
<Application
x:Class="FileLocksmithUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FileLocksmithUI">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
@@ -0,0 +1,74 @@
// 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.
using System;
using System.Linq;
using ManagedCommon;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
namespace FileLocksmithUI
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application
{
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
string appLanguage = LanguageHelper.LoadLanguage();
if (!string.IsNullOrEmpty(appLanguage))
{
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
}
Logger.InitializeLogger("\\File Locksmith\\FileLocksmithUI\\Logs");
this.InitializeComponent();
UnhandledException += App_UnhandledException;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="args">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredFileLocksmithEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
{
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
Environment.Exit(0); // Current.Exit won't work until there's a window opened.
return;
}
bool isElevated = PowerToys.FileLocksmithLib.Interop.NativeMethods.IsProcessElevated();
if (isElevated)
{
if (!PowerToys.FileLocksmithLib.Interop.NativeMethods.SetDebugPrivilege())
{
Logger.LogWarning("Couldn't set debug privileges to see system processes.");
}
}
_window = new MainWindow(isElevated);
_window.Activate();
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{
Logger.LogError("Unhandled exception", e.Exception);
}
private Window _window;
}
}

Some files were not shown because too many files have changed in this diff Show More