diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4404881dcd..5689b2f0e3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -52,6 +52,7 @@ body: - ColorPicker - FancyZones - FancyZones Editor + - File Locksmith - File Explorer: Preview Pane - File Explorer: Thumbnail preview - Image Resizer diff --git a/.github/ISSUE_TEMPLATE/translation_issue.yml b/.github/ISSUE_TEMPLATE/translation_issue.yml index 5753245066..f99dd63180 100644 --- a/.github/ISSUE_TEMPLATE/translation_issue.yml +++ b/.github/ISSUE_TEMPLATE/translation_issue.yml @@ -25,7 +25,8 @@ body: - Awake - ColorPicker - FancyZones - - FancyZones Editor + - FancyZones Editor + - File Locksmith - File Explorer: Preview Pane - File Explorer: Thumbnail preview - Image Resizer diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 237e48b409..c9b0c978be 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -71,6 +71,11 @@ "modules\\Hosts\\PowerToys.Hosts.dll", "modules\\Hosts\\PowerToys.Hosts.exe", + "modules\\FileLocksmith\\PowerToys.FileLocksmithLib.Interop.dll", + "modules\\FileLocksmith\\PowerToys.FileLocksmithExt.dll", + "modules\\FileLocksmith\\PowerToys.FileLocksmithUI.exe", + "modules\\FileLocksmith\\PowerToys.FileLocksmithUI.dll", + "modules\\ImageResizer\\PowerToys.ImageResizer.exe", "modules\\ImageResizer\\PowerToys.ImageResizer.dll", "modules\\ImageResizer\\PowerToys.ImageResizerExt.dll", @@ -207,6 +212,7 @@ "vcomp140_app.dll", "vcruntime140_1_app.dll", "vcruntime140_app.dll", + "modules\\FileLocksmith\\CommunityToolkit.Labs.WinUI.SettingsControls.dll", "modules\\PowerAccent\\Vanara.Core.dll", "modules\\PowerAccent\\Vanara.PInvoke.ComCtl32.dll", "modules\\PowerAccent\\Vanara.PInvoke.Cryptography.dll", diff --git a/.pipelines/release-nuget.config b/.pipelines/release-nuget.config index 88c571211d..c9debdad23 100644 --- a/.pipelines/release-nuget.config +++ b/.pipelines/release-nuget.config @@ -2,7 +2,7 @@ - + diff --git a/.pipelines/release.yml b/.pipelines/release.yml index f630815610..8a53d0eba3 100644 --- a/.pipelines/release.yml +++ b/.pipelines/release.yml @@ -295,6 +295,22 @@ jobs: configuration: $(BuildConfiguration) maximumCpuCount: true + - task: VSBuild@1 + displayName: Publish File Locksmith UI for Packaging + inputs: + solution: 'src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj' + vsVersion: 17.0 + # The arguments should be the same as the ones for Settings; make sure they are. + msbuildArgs: >- + /target:Publish + /p:Configuration=$(BuildConfiguration);Platform=$(BuildPlatform);AppxBundle=Never + /p:VCRTForwarders-IncludeDebugCRT=false + /p:PowerToysRoot=$(Build.SourcesDirectory) + /p:PublishProfile=InstallationPublishProfile.pubxml + platform: $(BuildPlatform) + configuration: $(BuildConfiguration) + maximumCpuCount: true + - task: VSBuild@1 displayName: Build PowerToysSetupCustomActions DLL # This dll needs to be build and signed before building the MSI. inputs: diff --git a/PowerToys.sln b/PowerToys.sln index 867ba3a6db..d1c141e423 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -458,6 +458,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hosts.Tests", "src\modules\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HostsModuleInterface", "src\modules\Hosts\HostsModuleInterface\HostsModuleInterface.vcxproj", "{B41B888C-7DB8-4747-B262-4062E05A230D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileLocksmith", "FileLocksmith", "{AB82E5DD-C32D-4F28-9746-2C780846188E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithExt", "src\modules\FileLocksmith\FileLocksmithExt\FileLocksmithExt.vcxproj", "{57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLocksmithUI", "src\modules\FileLocksmith\FileLocksmithUI\FileLocksmithUI.csproj", "{E69B044A-2F8A-45AA-AD0B-256C59421807}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLibInterop", "src\modules\FileLocksmith\FileLocksmithLibInterop\FileLocksmithLibInterop.vcxproj", "{C604B37E-9D0E-4484-8778-E8B31B0E1B3A}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GPOWrapper", "src\common\GPOWrapper\GPOWrapper.vcxproj", "{E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GPOWrapperProjection", "src\common\GPOWrapperProjection\GPOWrapperProjection.csproj", "{00EE9BA6-4E8F-43CA-960D-D4882F0FBB97}" @@ -1547,8 +1555,8 @@ Global {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|ARM64.Build.0 = Release|ARM64 {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x64.ActiveCfg = Release|x64 {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x64.Build.0 = Release|x64 - {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x86.ActiveCfg = Release|Any CPU - {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x86.Build.0 = Release|Any CPU + {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x86.ActiveCfg = Release|x64 + {04B193D7-3E21-46B8-A958-89B63A8A69DE}.Release|x86.Build.0 = Release|x64 {F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|ARM64.ActiveCfg = Debug|ARM64 {F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|ARM64.Build.0 = Debug|ARM64 {F44934A8-36F3-49B0-9465-3831BE041CDE}.Debug|x64.ActiveCfg = Debug|x64 @@ -1853,6 +1861,36 @@ Global {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x64.Build.0 = Release|x64 {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x86.ActiveCfg = Release|x64 {B41B888C-7DB8-4747-B262-4062E05A230D}.Release|x86.Build.0 = Release|x64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|ARM64.Build.0 = Debug|ARM64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|x64.ActiveCfg = Debug|x64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|x64.Build.0 = Debug|x64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Debug|x86.ActiveCfg = Debug|x64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|ARM64.ActiveCfg = Release|ARM64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|ARM64.Build.0 = Release|ARM64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|x64.ActiveCfg = Release|x64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|x64.Build.0 = Release|x64 + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE}.Release|x86.ActiveCfg = Release|x64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|ARM64.ActiveCfg = Debug|arm64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|ARM64.Build.0 = Debug|arm64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|x64.ActiveCfg = Debug|x64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|x64.Build.0 = Debug|x64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Debug|x86.ActiveCfg = Debug|x64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|ARM64.ActiveCfg = Release|arm64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|ARM64.Build.0 = Release|arm64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|x64.ActiveCfg = Release|x64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|x64.Build.0 = Release|x64 + {E69B044A-2F8A-45AA-AD0B-256C59421807}.Release|x86.ActiveCfg = Release|x64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|ARM64.Build.0 = Debug|ARM64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|x64.ActiveCfg = Debug|x64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|x64.Build.0 = Debug|x64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Debug|x86.ActiveCfg = Debug|x64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|ARM64.ActiveCfg = Release|ARM64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|ARM64.Build.0 = Release|ARM64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|x64.ActiveCfg = Release|x64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|x64.Build.0 = Release|x64 + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A}.Release|x86.ActiveCfg = Release|x64 {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Debug|ARM64.ActiveCfg = Debug|ARM64 {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Debug|ARM64.Build.0 = Debug|ARM64 {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788}.Debug|x64.ActiveCfg = Debug|x64 @@ -2033,6 +2071,10 @@ Global {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {E2D03E0F-7A75-4813-9F4B-D8763D43FD3A} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} {B41B888C-7DB8-4747-B262-4062E05A230D} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} + {AB82E5DD-C32D-4F28-9746-2C780846188E} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {57175EC7-92A5-4C1E-8244-E3FBCA2A81DE} = {AB82E5DD-C32D-4F28-9746-2C780846188E} + {E69B044A-2F8A-45AA-AD0B-256C59421807} = {AB82E5DD-C32D-4F28-9746-2C780846188E} + {C604B37E-9D0E-4484-8778-E8B31B0E1B3A} = {AB82E5DD-C32D-4F28-9746-2C780846188E} {E599C30B-9DC8-4E5A-BF27-93D4CCEDE788} = {1AFB6476-670D-4E80-A464-657E01DFF482} {00EE9BA6-4E8F-43CA-960D-D4882F0FBB97} = {1AFB6476-670D-4E80-A464-657E01DFF482} EndGlobalSection diff --git a/doc/gpo/assets/PowerToys.admx b/doc/gpo/assets/PowerToys.admx index 1ae32fcb79..03446af73d 100644 --- a/doc/gpo/assets/PowerToys.admx +++ b/doc/gpo/assets/PowerToys.admx @@ -57,6 +57,16 @@ + + + + + + + + + + diff --git a/doc/gpo/assets/en-US/PowerToys.adml b/doc/gpo/assets/en-US/PowerToys.adml index d53ee7c8e6..9a07cd42f9 100644 --- a/doc/gpo/assets/en-US/PowerToys.adml +++ b/doc/gpo/assets/en-US/PowerToys.adml @@ -32,6 +32,7 @@ If you don't configure this setting, users are able to disable or enable the uti Awake: Configure enabled state Color Picker: Configure enabled state FancyZones: Configure enabled state + File Locksmith: Configure enabled state SVG file preview: Configure enabled state Markdown file preview: Configure enabled state Source code file preview: Configure enabled state diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index dd5890b485..4c617e370a 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -8,6 +8,7 @@ + @@ -52,11 +53,11 @@ - + - + - + @@ -116,6 +117,8 @@ + + @@ -498,6 +501,8 @@ + + @@ -877,6 +882,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1241,6 +1270,10 @@ + + + + @@ -1649,7 +1682,7 @@ - + @@ -1975,9 +2008,16 @@ - - - + + + + + + + diff --git a/installer/PowerToysSetup/publish.cmd b/installer/PowerToysSetup/publish.cmd index daebe026d4..58c6ef48aa 100644 --- a/installer/PowerToysSetup/publish.cmd +++ b/installer/PowerToysSetup/publish.cmd @@ -20,3 +20,5 @@ msbuild !PTRoot!\src\modules\previewpane\SvgPreviewHandler\SvgPreviewHandler.csp msbuild !PTRoot!\src\modules\previewpane\SvgThumbnailProvider\SvgThumbnailProvider.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml msbuild !PTRoot!\src\modules\MeasureTool\MeasureToolUI\MeasureToolUI.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml + +msbuild !PTRoot!\src\modules\FileLocksmith\FileLocksmithUI\FileLocksmithUI.csproj -t:Publish -p:Configuration="Release" -p:Platform="!PlatformArg!" -p:AppxBundle=Never -p:PowerToysRoot=!PTRoot! -p:VCRTForwarders-IncludeDebugCRT=false -p:PublishProfile=InstallationPublishProfile.pubxml diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index b05a4bb216..7434fe3af7 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -1045,11 +1045,20 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall) return WcaFinalize(er); } +const std::wstring WinAppSDKConsumers[] = +{ + L"Settings", + L"modules\\PowerRename", + L"modules\\MeasureTool", + L"modules\\FileLocksmith", + L"modules\\Hosts", +}; + UINT __stdcall CreateWinAppSDKHardlinksCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; - std::wstring installationFolder, winAppSDKFilesSrcDir, settingsDir, powerRenameDir, measureToolDir, hostsFileEditorDir; + std::wstring installationFolder, winAppSDKFilesSrcDir; hr = WcaInitialize(hInstall, "CreateWinAppSDKHardlinksCA"); ExitOnFailure(hr, "Failed to initialize"); @@ -1058,25 +1067,21 @@ UINT __stdcall CreateWinAppSDKHardlinksCA(MSIHANDLE hInstall) ExitOnFailure(hr, "Failed to get installation folder"); winAppSDKFilesSrcDir = installationFolder + L"dll\\WinAppSDK\\"; - hostsFileEditorDir = installationFolder + L"modules\\Hosts\\"; - settingsDir = installationFolder + L"Settings\\"; - powerRenameDir = installationFolder + L"modules\\PowerRename\\"; - measureToolDir = installationFolder + L"modules\\MeasureTool\\"; for (auto file : winAppSdkFiles) { - std::error_code ec; - std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (hostsFileEditorDir + file).c_str(), ec); - std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (settingsDir + file).c_str(), ec); - std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (powerRenameDir + file).c_str(), ec); - std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (measureToolDir + file).c_str(), ec); - - if (ec.value() != S_OK) + for (auto consumer : WinAppSDKConsumers) { - std::wstring errorMessage{ L"Error creating hard link for: " }; - errorMessage += file; - errorMessage += L", error code: " + std::to_wstring(ec.value()); - Logger::error(errorMessage); + std::error_code ec; + std::filesystem::create_hard_link((winAppSDKFilesSrcDir + file).c_str(), (installationFolder + consumer + L"\\" + file).c_str(), ec); + + if (ec.value() != S_OK) + { + std::wstring errorMessage{ L"Error creating hard link for: " }; + errorMessage += file; + errorMessage += L", error code: " + std::to_wstring(ec.value()); + Logger::error(errorMessage); + } } } @@ -1085,12 +1090,26 @@ LExit: return WcaFinalize(er); } +const std::wstring PTInteropConsumers[] = +{ + L"modules\\ColorPicker", + L"modules\\PowerOCR", + L"modules\\launcher", + L"modules\\FancyZones", + L"modules\\ImageResizer", + L"Settings", + L"modules\\Awake", + L"modules\\MeasureTool", + L"modules\\PowerAccent", + L"modules\\FileLocksmith", + L"modules\\Hosts", +}; + UINT __stdcall CreatePTInteropHardlinksCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; - std::wstring installationFolder, interopFilesSrcDir, colorPickerDir, powerOCRDir, launcherDir, fancyZonesDir, - imageResizerDir, settingsDir, awakeDir, measureToolDir, powerAccentDir, hostsFileEditorDir; + std::wstring installationFolder, interopFilesSrcDir; hr = WcaInitialize(hInstall, "CreatePTInteropHardlinksCA"); ExitOnFailure(hr, "Failed to initialize"); @@ -1099,37 +1118,21 @@ UINT __stdcall CreatePTInteropHardlinksCA(MSIHANDLE hInstall) ExitOnFailure(hr, "Failed to get installation folder"); interopFilesSrcDir = installationFolder + L"dll\\Interop\\"; - colorPickerDir = installationFolder + L"modules\\ColorPicker\\"; - powerOCRDir = installationFolder + L"modules\\PowerOCR\\"; - launcherDir = installationFolder + L"modules\\launcher\\"; - fancyZonesDir = installationFolder + L"modules\\FancyZones\\"; - hostsFileEditorDir = installationFolder + L"modules\\Hosts\\"; - imageResizerDir = installationFolder + L"modules\\ImageResizer\\"; - settingsDir = installationFolder + L"Settings\\"; - awakeDir = installationFolder + L"modules\\Awake\\"; - measureToolDir = installationFolder + L"modules\\MeasureTool\\"; - powerAccentDir = installationFolder + L"modules\\PowerAccent\\"; for (auto file : powerToysInteropFiles) { - std::error_code ec; - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (colorPickerDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (powerOCRDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (launcherDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (fancyZonesDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (hostsFileEditorDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (imageResizerDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (settingsDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (awakeDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (measureToolDir + file).c_str(), ec); - std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (powerAccentDir + file).c_str(), ec); - - if (ec.value() != S_OK) + for (auto consumer : PTInteropConsumers) { - std::wstring errorMessage{ L"Error creating hard link for: " }; - errorMessage += file; - errorMessage += L", error code: " + std::to_wstring(ec.value()); - Logger::error(errorMessage); + std::error_code ec; + std::filesystem::create_hard_link((interopFilesSrcDir + file).c_str(), (installationFolder + consumer + L"\\" + file).c_str(), ec); + + if (ec.value() != S_OK) + { + std::wstring errorMessage{ L"Error creating hard link for: " }; + errorMessage += file; + errorMessage += L", error code: " + std::to_wstring(ec.value()); + Logger::error(errorMessage); + } } } @@ -1142,7 +1145,7 @@ UINT __stdcall DeleteWinAppSDKHardlinksCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; - std::wstring installationFolder, settingsDir, powerRenameDir, measureToolDir, hostsFileEditorDir; + std::wstring installationFolder; hr = WcaInitialize(hInstall, "DeleteWinAppSDKHardlinksCA"); ExitOnFailure(hr, "Failed to initialize"); @@ -1150,19 +1153,14 @@ UINT __stdcall DeleteWinAppSDKHardlinksCA(MSIHANDLE hInstall) hr = getInstallFolder(hInstall, installationFolder); ExitOnFailure(hr, "Failed to get installation folder"); - hostsFileEditorDir = installationFolder + L"modules\\Hosts\\"; - settingsDir = installationFolder + L"Settings\\"; - powerRenameDir = installationFolder + L"modules\\PowerRename\\"; - measureToolDir = installationFolder + L"modules\\MeasureTool\\"; - try { for (auto file : winAppSdkFiles) { - DeleteFile((hostsFileEditorDir + file).c_str()); - DeleteFile((settingsDir + file).c_str()); - DeleteFile((powerRenameDir + file).c_str()); - DeleteFile((measureToolDir + file).c_str()); + for (auto consumer : WinAppSDKConsumers) + { + DeleteFile((installationFolder + consumer + L"\\" + file).c_str()); + } } } catch (std::exception e) @@ -1183,8 +1181,7 @@ UINT __stdcall DeletePTInteropHardlinksCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; - std::wstring installationFolder, interopFilesSrcDir, colorPickerDir, powerOCRDir, launcherDir, fancyZonesDir, - imageResizerDir, settingsDir, awakeDir, measureToolDir, powerAccentDir, hostsFileEditorDir; + std::wstring installationFolder, interopFilesSrcDir; hr = WcaInitialize(hInstall, "DeletePTInteropHardlinksCA"); ExitOnFailure(hr, "Failed to initialize"); @@ -1192,31 +1189,14 @@ UINT __stdcall DeletePTInteropHardlinksCA(MSIHANDLE hInstall) hr = getInstallFolder(hInstall, installationFolder); ExitOnFailure(hr, "Failed to get installation folder"); - colorPickerDir = installationFolder + L"modules\\ColorPicker\\"; - powerOCRDir = installationFolder + L"modules\\PowerOCR\\"; - launcherDir = installationFolder + L"modules\\launcher\\"; - fancyZonesDir = installationFolder + L"modules\\FancyZones\\"; - hostsFileEditorDir = installationFolder + L"modules\\Hosts\\"; - imageResizerDir = installationFolder + L"modules\\ImageResizer\\"; - settingsDir = installationFolder + L"Settings\\"; - awakeDir = installationFolder + L"modules\\Awake\\"; - measureToolDir = installationFolder + L"modules\\MeasureTool\\"; - powerAccentDir = installationFolder + L"modules\\PowerAccent\\"; - try { for (auto file : powerToysInteropFiles) { - DeleteFile((colorPickerDir + file).c_str()); - DeleteFile((powerOCRDir + file).c_str()); - DeleteFile((launcherDir + file).c_str()); - DeleteFile((fancyZonesDir + file).c_str()); - DeleteFile((hostsFileEditorDir + file).c_str()); - DeleteFile((imageResizerDir + file).c_str()); - DeleteFile((settingsDir + file).c_str()); - DeleteFile((awakeDir + file).c_str()); - DeleteFile((measureToolDir + file).c_str()); - DeleteFile((powerAccentDir + file).c_str()); + for (auto consumer : PTInteropConsumers) + { + DeleteFile((installationFolder + consumer + L"\\" + file).c_str()); + } } } catch (std::exception e) @@ -1256,6 +1236,7 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) L"PowerToys.Awake.exe", L"PowerToys.FancyZones.exe", L"PowerToys.FancyZonesEditor.exe", + L"PowerToys.FileLocksmithUI.exe", L"PowerToys.ColorPickerUI.exe", L"PowerToys.AlwaysOnTop.exe", L"PowerToys.exe" diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 5a4b9c0fb9..0000000000 --- a/nuget.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index b3cd0be71e..4489c4ef2c 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -20,6 +20,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return (GpoRuleConfigured)powertoys_gpo::getConfiguredFancyZonesEnabledValue(); } + GpoRuleConfigured GPOWrapper::GetConfiguredFileLocksmithEnabledValue() + { + return (GpoRuleConfigured)powertoys_gpo::getConfiguredFileLocksmithEnabledValue(); + } GpoRuleConfigured GPOWrapper::GetConfiguredSvgPreviewEnabledValue() { return (GpoRuleConfigured)powertoys_gpo::getConfiguredSvgPreviewEnabledValue(); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index 47079ee781..25b8d87445 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -11,6 +11,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetConfiguredAwakeEnabledValue(); static GpoRuleConfigured GetConfiguredColorPickerEnabledValue(); static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue(); + static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue(); static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredMarkdownPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredMonacoPreviewEnabledValue(); diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index 2b25ba772f..f316b63fe1 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -15,6 +15,7 @@ namespace PowerToys static GpoRuleConfigured GetConfiguredAwakeEnabledValue(); static GpoRuleConfigured GetConfiguredColorPickerEnabledValue(); static GpoRuleConfigured GetConfiguredFancyZonesEnabledValue(); + static GpoRuleConfigured GetConfiguredFileLocksmithEnabledValue(); static GpoRuleConfigured GetConfiguredSvgPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredMarkdownPreviewEnabledValue(); static GpoRuleConfigured GetConfiguredMonacoPreviewEnabledValue(); diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index d5364d47f4..e342ba410b 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -34,6 +34,7 @@ struct LogSettings inline const static std::string powerRenameLoggerName = "powerrename"; inline const static std::string alwaysOnTopLoggerName = "always-on-top"; inline const static std::string powerOcrLoggerName = "TextExtractor"; + inline const static std::string fileLocksmithLoggerName = "FileLocksmith"; inline const static std::wstring alwaysOnTopLogPath = L"always-on-top-log.txt"; inline const static std::string hostsLoggerName = "hosts"; inline const static std::wstring hostsLogPath = L"Logs\\hosts-log.txt"; diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index 3bac6bb856..19b772d550 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -23,6 +23,7 @@ namespace powertoys_gpo { const std::wstring POLICY_CONFIGURE_ENABLED_AWAKE = L"ConfigureEnabledUtilityAwake"; const std::wstring POLICY_CONFIGURE_ENABLED_COLOR_PICKER = L"ConfigureEnabledUtilityColorPicker"; const std::wstring POLICY_CONFIGURE_ENABLED_FANCYZONES = L"ConfigureEnabledUtilityFancyZones"; + const std::wstring POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH = L"ConfigureEnabledUtilityFileLocksmith"; const std::wstring POLICY_CONFIGURE_ENABLED_SVG_PREVIEW = L"ConfigureEnabledUtilityFileExplorerSVGPreview"; const std::wstring POLICY_CONFIGURE_ENABLED_MARKDOWN_PREVIEW = L"ConfigureEnabledUtilityFileExplorerMarkdownPreview"; const std::wstring POLICY_CONFIGURE_ENABLED_MONACO_PREVIEW = L"ConfigureEnabledUtilityFileExplorerMonacoPreview"; @@ -119,6 +120,11 @@ namespace powertoys_gpo { return getConfiguredValue(POLICY_CONFIGURE_ENABLED_FANCYZONES); } + inline gpo_rule_configured_t getConfiguredFileLocksmithEnabledValue() + { + return getConfiguredValue(POLICY_CONFIGURE_ENABLED_FILE_LOCKSMITH); + } + inline gpo_rule_configured_t getConfiguredSvgPreviewEnabledValue() { return getConfiguredValue(POLICY_CONFIGURE_ENABLED_SVG_PREVIEW); diff --git a/src/modules/FileLocksmith/FileLocksmithExt/ClassFactory.cpp b/src/modules/FileLocksmith/FileLocksmithExt/ClassFactory.cpp new file mode 100644 index 0000000000..de9492161b --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/ClassFactory.cpp @@ -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; +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/ClassFactory.h b/src/modules/FileLocksmith/FileLocksmithExt/ClassFactory.h new file mode 100644 index 0000000000..5b2f8c9e32 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/ClassFactory.h @@ -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 m_ref_count; + IID m_clsid; +}; diff --git a/src/modules/FileLocksmith/FileLocksmithExt/Constants.h b/src/modules/FileLocksmith/FileLocksmithExt/Constants.h new file mode 100644 index 0000000000..e01a20d13d --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/Constants.h @@ -0,0 +1,33 @@ +#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"; + + // 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"; +} + +// Macros, non-localizable + +// Description of the registry key +#define REGISTRY_CONTEXT_MENU_KEY L"FileLocksmithExt" diff --git a/src/modules/FileLocksmith/FileLocksmithExt/ExplorerCommand.cpp b/src/modules/FileLocksmith/FileLocksmithExt/ExplorerCommand.cpp new file mode 100644 index 0000000000..6db892d2fb --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/ExplorerCommand.cpp @@ -0,0 +1,296 @@ +#include "pch.h" + +#include "ExplorerCommand.h" +#include "Constants.h" +#include "Settings.h" +#include "dllmain.h" +#include "Trace.h" +#include "Generated Files/resource.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) +{ + WCHAR buffer[128]; + LoadStringW(globals::instance, IDS_FILELOCKSMITH_COMMANDTITLE, buffer, ARRAYSIZE(buffer)); + return SHStrDupW(buffer, ppszName); +} + +IFACEMETHODIMP ExplorerCommand::GetIcon(IShellItemArray* psiItemArray, LPWSTR* ppszIcon) +{ + // Path to the icon should be computed relative to the path of this module + ppszIcon = NULL; + return E_NOTIMPL; +} + +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) +{ + if (globals::enabled) + { + *pCmdState = ECS_ENABLED; + } + else + { + *pCmdState = 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 = pdtobj; + m_data_obj->AddRef(); + 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 S_OK; + } + + HRESULT hr = E_UNEXPECTED; + if (m_data_obj && !(uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY | CMF_OPTIMIZEFORINVOKE))) + { + MENUITEMINFO mii; + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_STATE; + mii.wID = idCmdFirst++; + mii.fType = MFT_STRING; + + hr = GetTitle(NULL, &mii.dwTypeData); + if (FAILED(hr)) + { + return hr; + } + + mii.fState = MFS_ENABLED; + + // TODO icon from file + + if (!InsertMenuItem(hmenu, indexMenu, TRUE, &mii)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + Trace::QueryContextMenuError(hr); + } + else + { + hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1); + } + } + + return hr; +} + +IFACEMETHODIMP ExplorerCommand::InvokeCommand(CMINVOKECOMMANDINFO* pici) +{ + Trace::Invoked(); + ipc::Writer writer; + + if (HRESULT result = writer.start(); FAILED(result)) + { + Trace::InvokedRet(result); + return result; + } + + if (HRESULT result = LaunchUI(pici, &writer); FAILED(result)) + { + Trace::InvokedRet(result); + return result; + } + + IShellItemArray* shell_item_array; + HRESULT result = SHCreateShellItemArrayFromDataObject(m_data_obj, __uuidof(IShellItemArray), reinterpret_cast(&shell_item_array)); + if (SUCCEEDED(result)) + { + DWORD num_items; + shell_item_array->GetCount(&num_items); + for (DWORD i = 0; i < num_items; i++) + { + IShellItem* item; + result = shell_item_array->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(); + } + } + + shell_item_array->Release(); + } + + Trace::InvokedRet(S_OK); + return S_OK; +} + +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; +} + +ExplorerCommand::~ExplorerCommand() +{ + if (m_data_obj) + { + m_data_obj->Release(); + } + --globals::ref_count; +} + +// Implementation taken from src/common/utils +// TODO reference that function +inline std::wstring get_module_folderpath(HMODULE mod = nullptr, const bool removeFilename = true) +{ + wchar_t buffer[MAX_PATH + 1]; + DWORD actual_length = GetModuleFileNameW(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); + PathRemoveFileSpecW(long_filename.data()); + long_filename.resize(std::wcslen(long_filename.data())); + long_filename.shrink_to_fit(); + return long_filename; + } + + if (removeFilename) + { + PathRemoveFileSpecW(buffer); + } + return { buffer, (UINT)lstrlenW(buffer) }; +} + +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; +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/ExplorerCommand.h b/src/modules/FileLocksmith/FileLocksmithExt/ExplorerCommand.h new file mode 100644 index 0000000000..5d6e2aad40 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/ExplorerCommand.h @@ -0,0 +1,50 @@ +#pragma once + +#include "pch.h" + +#include "IPC.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: + // Helpers + HRESULT LaunchUI(CMINVOKECOMMANDINFO* pici, ipc::Writer* writer); + + std::atomic m_ref_count = 1; + IDataObject* m_data_obj = NULL; +}; diff --git a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.base.rc b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.base.rc new file mode 100644 index 0000000000..b55a2b37ad --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.base.rc @@ -0,0 +1,50 @@ +#include +#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 diff --git a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj new file mode 100644 index 0000000000..4e69eba166 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj @@ -0,0 +1,299 @@ + + + + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {57175ec7-92a5-4c1e-8244-e3fbca2a81de} + FileLocksmithExt + 10.0.19041.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithExt + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithExt + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithExt + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithExt + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithExt + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithExt + + + + Level3 + true + WIN32;_DEBUG;FILELOCKSMITHEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../;../../../;%(AdditionalIncludeDirectories) + + + Windows + true + false + dll.def + + + + + Level3 + true + true + true + WIN32;NDEBUG;FILELOCKSMITHEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../;../../../;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + false + dll.def + + + + + Level3 + true + _DEBUG;FILELOCKSMITHEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../;../../../;%(AdditionalIncludeDirectories) + + + Windows + true + false + dll.def + + + + + Level3 + true + _DEBUG;FILELOCKSMITHEXT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../;../../../;%(AdditionalIncludeDirectories) + + + Windows + true + false + dll.def + + + + + Level3 + true + true + true + NDEBUG;FILELOCKSMITHLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../;../../../;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + false + dll.def + + + + + Level3 + true + true + true + NDEBUG;FILELOCKSMITHLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../;../../../;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + false + dll.def + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} + + + + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters new file mode 100644 index 0000000000..d0a249143f --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/FileLocksmithExt.vcxproj.filters @@ -0,0 +1,92 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Header Files + + + Resource Files + + + Resource Files + + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithExt/IPC.cpp b/src/modules/FileLocksmith/FileLocksmithExt/IPC.cpp new file mode 100644 index 0000000000..c99a3c353a --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/IPC.cpp @@ -0,0 +1,62 @@ +#include "pch.h" + +#include "IPC.h" +#include "Constants.h" + +#include + +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); + return S_OK; + } + catch (...) + { + return E_FAIL; + } + } + + HRESULT Writer::add_path(LPCWSTR path) + { + int length = lstrlenW(path); + if (!m_stream.write(reinterpret_cast(path), length * sizeof(WCHAR))) + { + return E_FAIL; + } + + WCHAR line_break = L'\n'; + if (!m_stream.write(reinterpret_cast(&line_break), sizeof(WCHAR))) + { + return E_FAIL; + } + + return S_OK; + } + + void Writer::finish() + { + add_path(L""); + m_stream.close(); + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/IPC.h b/src/modules/FileLocksmith/FileLocksmithExt/IPC.h new file mode 100644 index 0000000000..6013c0fa4d --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/IPC.h @@ -0,0 +1,20 @@ +#pragma once + +#include "pch.h" + +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; + }; +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp b/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp new file mode 100644 index 0000000000..707c79e868 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/PowerToysModule.cpp @@ -0,0 +1,118 @@ +#include "pch.h" + +#include +#include +#include +#include +#include +#include + +#include "Constants.h" +#include "dllmain.h" +#include "Settings.h" +#include "Trace.h" +#include "Generated Files/resource.h" + +class FileLocksmithModule : public PowertoyModuleIface +{ +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(&__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()); + + // Currently, there are no settings, so we don't do anything. + } + catch (std::exception& e) + { + Logger::error("Configuration parsing failed: {}", std::string{ e.what() }); + } + } + + virtual void enable() override + { + Logger::info(L"File Locksmith enabled"); + m_enabled = true; + save_settings(); + } + + virtual void disable() override + { + Logger::info(L"File Locksmith disabled"); + m_enabled = false; + save_settings(); + } + + virtual bool is_enabled() override + { + return m_enabled; + } + + virtual void destroy() override + { + delete this; + } + +private: + bool m_enabled; + + void init_settings() + { + m_enabled = FileLocksmithSettingsInstance().GetEnabled(); + Trace::EnableFileLocksmith(m_enabled); + } + + void save_settings() + { + auto& settings = FileLocksmithSettingsInstance(); + settings.SetEnabled(m_enabled); + settings.Save(); + Trace::EnableFileLocksmith(m_enabled); + } +}; + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new FileLocksmithModule(); +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/Resources.resx b/src/modules/FileLocksmith/FileLocksmithExt/Resources.resx new file mode 100644 index 0000000000..64aa4f4b98 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/Resources.resx @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + What's using this file? + This text will be shown when the user opens the context menu (right clicks) a file. + + + File Locksmith + Localized name of the PowerToy. + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithExt/Settings.cpp b/src/modules/FileLocksmith/FileLocksmithExt/Settings.cpp new file mode 100644 index 0000000000..501e45dfc8 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/Settings.cpp @@ -0,0 +1,85 @@ +#include "pch.h" +#include "Settings.h" +#include "Constants.h" + +#include +#include + +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() +{ + std::wstring savePath = PTSettingsHelper::get_module_save_folder_location(constants::nonlocalizable::PowerToyKey); + std::error_code ec; + + jsonFilePath = savePath + constants::nonlocalizable::DataFilePath; + Load(); +} + +void FileLocksmithSettings::Save() +{ + json::JsonObject jsonData; + + jsonData.SetNamedValue(constants::nonlocalizable::JsonKeyEnabled, json::value(settings.enabled)); + + json::to_file(jsonFilePath, jsonData); + GetSystemTimeAsFileTime(&lastLoadedTime); +} + +void FileLocksmithSettings::Load() +{ + if (!std::filesystem::exists(jsonFilePath)) + { + Save(); + } + else + { + ParseJson(); + } +} + +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::JsonKeyEnabled, json::JsonValueType::Boolean)) + { + settings.enabled = jsonSettings.GetNamedBoolean(constants::nonlocalizable::JsonKeyEnabled); + } + } + catch (const winrt::hresult_error&) + { + } + } + GetSystemTimeAsFileTime(&lastLoadedTime); +} + +FileLocksmithSettings& FileLocksmithSettingsInstance() +{ + static FileLocksmithSettings instance; + return instance; +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/Settings.h b/src/modules/FileLocksmith/FileLocksmithExt/Settings.h new file mode 100644 index 0000000000..7dfd28fe14 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/Settings.h @@ -0,0 +1,45 @@ +#pragma once + +#include "pch.h" +#include + +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(); + return settings.enabled; + } + + inline void SetEnabled(bool enabled) + { + settings.enabled = enabled; + Save(); + } + + void Save(); + void Load(); + +private: + struct Settings + { + bool enabled{ true }; + }; + + void Reload(); + void ParseJson(); + + Settings settings; + std::wstring jsonFilePath; + FILETIME lastLoadedTime; +}; + +FileLocksmithSettings& FileLocksmithSettingsInstance(); diff --git a/src/modules/FileLocksmith/FileLocksmithExt/Trace.cpp b/src/modules/FileLocksmith/FileLocksmithExt/Trace.cpp new file mode 100644 index 0000000000..98b2f9985d --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/Trace.cpp @@ -0,0 +1,60 @@ +#include "pch.h" + +#include "Trace.h" +#include "../common/Telemetry/ProjectTelemetry.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::RegisterProvider() noexcept +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::UnregisterProvider() noexcept +{ + TraceLoggingUnregister(g_hProvider); +} + +void Trace::EnableFileLocksmith(_In_ bool enabled) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "FileLocksmith_EnableFileLocksmith", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} + +void Trace::Invoked() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "FileLocksmith_Invoked", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::InvokedRet(_In_ HRESULT hr) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "FileLocksmith_InvokedRet", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingHResult(hr), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::QueryContextMenuError(_In_ HRESULT hr) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "FileLocksmith_QueryContextMenuError", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingHResult(hr), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/Trace.h b/src/modules/FileLocksmith/FileLocksmithExt/Trace.h new file mode 100644 index 0000000000..a9516b5d5c --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/Trace.h @@ -0,0 +1,14 @@ +#pragma once + +#include "pch.h" + +class Trace +{ +public: + static void RegisterProvider() noexcept; + static void UnregisterProvider() noexcept; + 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; +}; diff --git a/src/modules/FileLocksmith/FileLocksmithExt/dll.def b/src/modules/FileLocksmith/FileLocksmithExt/dll.def new file mode 100644 index 0000000000..450007ee91 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/dll.def @@ -0,0 +1,5 @@ +EXPORTS + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/src/modules/FileLocksmith/FileLocksmithExt/dllmain.cpp b/src/modules/FileLocksmith/FileLocksmithExt/dllmain.cpp new file mode 100644 index 0000000000..6951081625 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/dllmain.cpp @@ -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 "Trace.h" + +namespace globals +{ + HMODULE instance; + std::atomic ref_count; + std::atomic enabled; +} + +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; +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/dllmain.h b/src/modules/FileLocksmith/FileLocksmithExt/dllmain.h new file mode 100644 index 0000000000..b309bf504f --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/dllmain.h @@ -0,0 +1,10 @@ +#pragma once + +#include "pch.h" + +namespace globals +{ + extern HMODULE instance; + extern std::atomic ref_count; + extern std::atomic enabled; +} diff --git a/src/modules/FileLocksmith/FileLocksmithExt/packages.config b/src/modules/FileLocksmith/FileLocksmithExt/packages.config new file mode 100644 index 0000000000..48319b8c95 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithExt/pch.cpp b/src/modules/FileLocksmith/FileLocksmithExt/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/pch.cpp @@ -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. diff --git a/src/modules/FileLocksmith/FileLocksmithExt/pch.h b/src/modules/FileLocksmith/FileLocksmithExt/pch.h new file mode 100644 index 0000000000..b083a02c0e --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/pch.h @@ -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 +#include +#include +#include +#include + +// C++ Standard library +#include +#include +#include +#include diff --git a/src/modules/FileLocksmith/FileLocksmithExt/resource.base.h b/src/modules/FileLocksmith/FileLocksmithExt/resource.base.h new file mode 100644 index 0000000000..1ffbbe0caa --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithExt/resource.base.h @@ -0,0 +1,13 @@ +//{{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" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmith.cpp b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmith.cpp new file mode 100644 index 0000000000..ebec97e423 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmith.cpp @@ -0,0 +1,172 @@ +#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 bool starts_with(std::wstring_view whole, std::wstring_view part) +{ + return whole.size() >= part.size() && whole.substr(0, part.size()) == part; +} + +std::vector find_processes_recursive(const std::vector& paths) +{ + NtdllExtensions nt_ext; + + // TODO use a trie! + + // This maps kernel names of files within `paths` to their normal paths. + std::map kernel_names_files; + + // This maps kernel names of directories within `paths` to their normal paths. + std::map 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> 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 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, + std::vector(it->second.begin(), it->second.end()) + }); + } + } + + return result; +} + +std::wstring pid_to_user(DWORD pid) +{ + HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + + if (process == NULL) + { + return {}; + } + + std::wstring user = L""; + std::wstring domain = L""; + + HANDLE token = NULL; + + if (OpenProcessToken(process, TOKEN_QUERY, &token)) + { + DWORD token_size = 0; + GetTokenInformation(token, TokenUser, NULL, 0, &token_size); + + if (token_size > 0) + { + std::vector token_buffer(token_size); + GetTokenInformation(token, TokenUser, token_buffer.data(), token_size, &token_size); + TOKEN_USER* user_ptr = (TOKEN_USER*)token_buffer.data(); + PSID psid = user_ptr->User.Sid; + DWORD user_size = 0; + DWORD domain_size = 0; + SID_NAME_USE sid_name; + LookupAccountSidW(NULL, psid, NULL, &user_size, NULL, &domain_size, &sid_name); + user.resize(user_size + 1); + domain.resize(domain_size + 1); + LookupAccountSidW(NULL, psid, user.data(), &user_size, domain.data(), &domain_size, &sid_name); + user[user_size] = L'\0'; + domain[domain_size] = L'\0'; + } + + CloseHandle(token); + } + + CloseHandle(process); + + return user; +} + +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(), (DWORD)result.size()); + result.resize(length); + + CloseHandle(process); + return result; +} diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmith.h b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmith.h new file mode 100644 index 0000000000..ee959bc744 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmith.h @@ -0,0 +1,19 @@ +#pragma once + +#include "pch.h" + +struct ProcessResult +{ + std::wstring name; + DWORD pid; + std::vector files; +}; + +// Second version, checks handles towards files and all subfiles and folders of given dirs, if any. +std::vector find_processes_recursive(const std::vector& paths); + +// Gives the user name of the account running this process +std::wstring pid_to_user(DWORD pid); + +// Gives the full path of the executable, given the process id +std::wstring pid_to_full_path(DWORD pid); diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.rc b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.rc new file mode 100644 index 0000000000..86f1165619 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.rc @@ -0,0 +1,40 @@ +#include +#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 diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj new file mode 100644 index 0000000000..b0c9affc8e --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj @@ -0,0 +1,290 @@ + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {c604b37e-9d0e-4484-8778-e8b31b0e1b3a} + FileLocksmithLibInterop + 10.0.19041.0 + net6.0-windows + + + + DynamicLibrary + true + v143 + Unicode + NetCore + + + DynamicLibrary + false + v143 + true + Unicode + NetCore + + + DynamicLibrary + true + v143 + Unicode + NetCore + + + DynamicLibrary + true + v143 + Unicode + NetCore + + + DynamicLibrary + false + v143 + true + Unicode + NetCore + + + DynamicLibrary + false + v143 + true + Unicode + NetCore + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithLib.Interop + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithLib.Interop + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithLib.Interop + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithLib.Interop + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithLib.Interop + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileLocksmith\ + PowerToys.FileLocksmithLib.Interop + + + + Level3 + true + WIN32;_DEBUG;FILELOCKSMITHLIBINTEROP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + NetCore + stdcpp17 + MultiThreadedDebugDLL + /Zc:twoPhase- + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;FILELOCKSMITHLIBINTEROP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + NetCore + stdcpp17 + /Zc:twoPhase- + MultiThreadedDLL + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;FILELOCKSMITHLIBINTEROP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + NetCore + stdcpp17 + MultiThreadedDebugDLL + /Zc:twoPhase- + + + Windows + true + false + + + + + Level3 + true + _DEBUG;FILELOCKSMITHLIBINTEROP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + NetCore + stdcpp17 + MultiThreadedDebugDLL + /Zc:twoPhase- + + + Windows + true + false + + + + + Level3 + true + true + true + NDEBUG;FILELOCKSMITHLIBINTEROP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + NetCore + stdcpp17 + /Zc:twoPhase- + MultiThreadedDLL + + + Windows + true + true + true + false + + + + + Level3 + true + true + true + NDEBUG;FILELOCKSMITHLIBINTEROP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + NetCore + stdcpp17 + /Zc:twoPhase- + MultiThreadedDLL + + + Windows + true + true + true + false + + + + + + Create + Create + Create + Create + Create + Create + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + {f055103b-f80b-4d0c-bf48-057c55620033} + + + + + + + + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj.filters b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj.filters new file mode 100644 index 0000000000..14ec0e55f0 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/FileLocksmithLibInterop.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllBase.cpp b/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllBase.cpp new file mode 100644 index 0000000000..571ea153bc --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllBase.cpp @@ -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 = (NtQuerySystemInformation_t)GetProcAddress(m_module, "NtQuerySystemInformation"); + if (m_NtQuerySystemInformation == 0) + { + throw std::runtime_error{ "GetProcAddress returned null for NtQuerySystemInformation" }; + } + + m_NtDuplicateObject = (NtDuplicateObject_t)GetProcAddress(m_module, "NtDuplicateObject"); + if (m_NtDuplicateObject == 0) + { + throw std::runtime_error{ "GetProcAddress returned null for NtDuplicateObject" }; + } + + m_NtQueryObject = (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); +} diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllBase.h b/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllBase.h new file mode 100644 index 0000000000..fcc7724636 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllBase.h @@ -0,0 +1,98 @@ +#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 + { + ULONG ProcessId; + BYTE ObjectTypeNumber; + BYTE Flags; + USHORT Handle; + PVOID Object; + ACCESS_MASK GrantedAccess; + }; + + struct SYSTEM_HANDLE_INFORMATION + { + ULONG HandleCount; + SYSTEM_HANDLE 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 + ); +}; diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllExtensions.cpp b/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllExtensions.cpp new file mode 100644 index 0000000000..17d2ec25b8 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllExtensions.cpp @@ -0,0 +1,266 @@ +#include "pch.h" + +#include "NtdllExtensions.h" + +#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, (UINT)lstrlenW(buffer) }; + } + + constexpr size_t DefaultModulesResultSize = 512; + + std::vector process_modules(DWORD pid) + { + HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (!process) + { + return {}; + } + + std::vector result; + + bool completed = false; + std::vector modules(DefaultModulesResultSize); + while (!completed) + { + DWORD needed; + auto status = EnumProcessModules(process, modules.data(), static_cast(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(), (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& buffer) +{ + if (GetFileType(file_handle) != FILE_TYPE_DISK) + { + return L""; + } + + ULONG return_length; + auto status = NtQueryObject(file_handle, ObjectNameInformation, buffer.data(), (ULONG)buffer.size(), &return_length); + if (NT_SUCCESS(status)) + { + auto object_name_info = (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 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::handles() noexcept +{ + auto get_info_result = NtQuerySystemInformationMemoryLoop(SystemHandleInformation); + if (NT_ERROR(get_info_result.status)) + { + return {}; + } + + auto info_ptr = (SYSTEM_HANDLE_INFORMATION*)get_info_result.memory.data(); + + std::map pid_to_handle; + std::vector result; + + std::vector object_info_buffer(DefaultResultBufferSize); + + for (ULONG i = 0; i < info_ptr->HandleCount; i++) + { + auto handle_info = info_ptr->Handles + i; + DWORD pid = handle_info->ProcessId; + + HANDLE process_handle = NULL; + 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, 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 handle_copy; + + auto dh_result = DuplicateHandle(process_handle, (HANDLE)handle_info->Handle, GetCurrentProcess(), &handle_copy, 0, 0, DUPLICATE_SAME_ACCESS); + if (dh_result == 0) + { + // Ignore this handle. + continue; + } + + ULONG return_length; + auto status = NtQueryObject(handle_copy, ObjectTypeInformation, object_info_buffer.data(), (ULONG)object_info_buffer.size(), &return_length); + if (NT_ERROR(status)) + { + // Ignore this handle. + CloseHandle(handle_copy); + continue; + } + + auto object_type_info = (OBJECT_TYPE_INFORMATION*)object_info_buffer.data(); + auto type_name = unicode_to_str(object_type_info->Name); + + std::wstring file_name = file_handle_to_kernel_name(handle_copy, object_info_buffer); + + if (type_name == L"File") + { + file_name = file_handle_to_kernel_name(handle_copy, object_info_buffer); + } + + result.push_back(HandleInfo{ pid, handle_info->Handle, type_name, file_name }); + CloseHandle(handle_copy); + } + + for (auto [pid, handle] : pid_to_handle) + { + CloseHandle(handle); + } + + return result; +} + +// Returns the list of all processes. +// On failure, returns an empty vector. + +std::vector NtdllExtensions::processes() noexcept +{ + auto get_info_result = NtQuerySystemInformationMemoryLoop(SystemProcessInformation); + + if (NT_ERROR(get_info_result.status)) + { + return {}; + } + + std::vector result; + auto info_ptr = (PSYSTEM_PROCESS_INFORMATION)get_info_result.memory.data(); + + while (info_ptr->NextEntryOffset) + { + info_ptr = decltype(info_ptr)((LPBYTE)info_ptr + info_ptr->NextEntryOffset); + + ProcessInfo item; + item.name = unicode_to_str(info_ptr->ImageName); + item.pid = (DWORD)(uintptr_t)info_ptr->UniqueProcessId; + item.modules = process_modules(item.pid); + + result.push_back(item); + } + + return result; +} diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllExtensions.h b/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllExtensions.h new file mode 100644 index 0000000000..1d465161dc --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/NtdllExtensions.h @@ -0,0 +1,52 @@ +#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 SystemHandleInformation = 16; + + struct MemoryLoopResult + { + NTSTATUS status = 0; + std::vector 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& buffer); + +public: + struct ProcessInfo + { + DWORD pid; + std::wstring name; + std::vector modules; + }; + + struct HandleInfo + { + DWORD pid; + USHORT 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); + + std::vector handles() noexcept; + + // Returns the list of all processes. + // On failure, returns an empty vector. + std::vector processes() noexcept; +}; diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/interop.cpp b/src/modules/FileLocksmith/FileLocksmithLibInterop/interop.cpp new file mode 100644 index 0000000000..c3dfaa2d46 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/interop.cpp @@ -0,0 +1,186 @@ +#include "pch.h" + +#include "FileLocksmith.h" + +#include "../FileLocksmithExt/Constants.h" + +namespace FileLocksmith::Interop +{ + public ref struct ProcessResult + { + System::String^ name; + System::UInt32 pid; + array^ files; + System::Boolean isExpanded; // For helping in the UI + }; + + System::String^ from_wstring_view(std::wstring_view str) + { + return gcnew System::String(str.data(), 0, static_cast(str.size())); + } + + std::wstring from_system_string(System::String^ str) + { + // TODO use some built-in method + auto chars = str->ToCharArray(); + + std::wstring result(chars->Length, 0); + for (int i = 0; i < chars->Length; i++) + { + result[i] = chars[i]; + } + + return result; + } + + std::wstring paths_file() + { + std::wstring path = from_system_string(interop::Constants::AppDataPath()); + path += L"\\"; + path += constants::nonlocalizable::PowerToyName; + path += L"\\"; + path += constants::nonlocalizable::LastRunPath; + return path; + } + + std::wstring executable_path() + { + return pid_to_full_path(GetCurrentProcessId()); + } + + public ref struct NativeMethods + { + static array ^ FindProcessesRecursive(array^ paths) + { + const int n = paths->Length; + + std::vector paths_cpp(n); + for (int i = 0; i < n; i++) + { + paths_cpp[i] = from_system_string(paths[i]); + } + + auto result_cpp = find_processes_recursive(paths_cpp); + const auto result_size = static_cast(result_cpp.size()); + + auto result = gcnew array(result_size); + for (int i = 0; i < result_size; i++) + { + auto item = gcnew ProcessResult; + + item->name = from_wstring_view(result_cpp[i].name); + item->pid = result_cpp[i].pid; + + const int n_files = static_cast(result_cpp[i].files.size()); + item->files = gcnew array(n_files); + for (int j = 0; j < n_files; j++) + { + item->files[j] = from_wstring_view(result_cpp[i].files[j]); + } + item->isExpanded = false; + + result[i] = item; + } + + return result; + } + + static System::String^ PidToUser(System::UInt32 pid) + { + auto user_cpp = pid_to_user(pid); + return from_wstring_view(user_cpp); + } + + static System::String^ PidToFullPath(System::UInt32 pid) + { + auto path_cpp = pid_to_full_path(pid); + return from_wstring_view(path_cpp); + } + + static array^ ReadPathsFromFile() + { + std::ifstream stream(paths_file()); + + std::vector result_cpp; + std::wstring line; + + bool finished = false; + + while (!finished) + { + WCHAR ch; + // We have to read data like this + if (!stream.read(reinterpret_cast(&ch), 2)) + { + finished = true; + } + else if (ch == L'\n') + { + if (line.empty()) + { + finished = true; + } + else + { + result_cpp.push_back(line); + line = {}; + } + } + else + { + line += ch; + } + } + + auto result = gcnew array(static_cast(result_cpp.size())); + for (int i = 0; i < result->Length; i++) + { + result[i] = from_wstring_view(result_cpp[i]); + } + + return result; + } + + static System::Boolean StartAsElevated(array ^ paths) + { + std::ofstream stream(paths_file()); + const WCHAR newline = L'\n'; + for (int i = 0; i < paths->Length; i++) + { + auto path_cpp = from_system_string(paths[i]); + stream.write(reinterpret_cast(path_cpp.c_str()), path_cpp.size() * sizeof(WCHAR)); + stream.write(reinterpret_cast(&newline), sizeof(WCHAR)); + } + + stream.write(reinterpret_cast(&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; + } + }; +} diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.cpp b/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.cpp new file mode 100644 index 0000000000..1d9f38c57d --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.h b/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.h new file mode 100644 index 0000000000..5bfe0c9082 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.h @@ -0,0 +1,19 @@ +#pragma once + +// System headers +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +// C++ standard library headers +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/resource.h b/src/modules/FileLocksmith/FileLocksmithLibInterop/resource.h new file mode 100644 index 0000000000..3e5caf34cb --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/resource.h @@ -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 +////////////////////////////// diff --git a/src/modules/FileLocksmith/FileLocksmithUI/App.xaml b/src/modules/FileLocksmith/FileLocksmithUI/App.xaml new file mode 100644 index 0000000000..3c59df7866 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/App.xaml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/FileLocksmith/FileLocksmithUI/App.xaml.cs b/src/modules/FileLocksmith/FileLocksmithUI/App.xaml.cs new file mode 100644 index 0000000000..6ad1997a91 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/App.xaml.cs @@ -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.Linq; +using FileLocksmithUI.Helpers; +using ManagedCommon; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; + +namespace FileLocksmithUI +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application + { + /// + /// Initializes a new instance of the 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(). + /// + public App() + { + this.InitializeComponent(); + } + + /// + /// 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. + /// + /// Details about the launch request and process. + 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; + } + + _window = new MainWindow(Environment.GetCommandLineArgs().Contains("--elevated")); + _window.Activate(); + } + + private Window _window; + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Converters/FileCountConverter.cs b/src/modules/FileLocksmith/FileLocksmithUI/Converters/FileCountConverter.cs new file mode 100644 index 0000000000..737a6cf34f --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Converters/FileCountConverter.cs @@ -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. + +namespace PowerToys.FileLocksmithUI.Converters +{ + using System; + using FileLocksmith.Interop; + using Microsoft.UI.Xaml.Data; + + public sealed 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; + } + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Converters/FileListToDescriptionConverter.cs b/src/modules/FileLocksmith/FileLocksmithUI/Converters/FileListToDescriptionConverter.cs new file mode 100644 index 0000000000..4ea7687b13 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Converters/FileListToDescriptionConverter.cs @@ -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. + +namespace PowerToys.FileLocksmithUI.Converters +{ + using System; + using System.IO; + using FileLocksmith.Interop; + using Microsoft.UI.Xaml.Data; + + public sealed 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; + } + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Converters/PidToIconConverter.cs b/src/modules/FileLocksmith/FileLocksmithUI/Converters/PidToIconConverter.cs new file mode 100644 index 0000000000..875ad4241f --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Converters/PidToIconConverter.cs @@ -0,0 +1,51 @@ +// 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. + +namespace PowerToys.FileLocksmithUI.Converters +{ + using System; + using System.Drawing; + using System.IO; + using CommunityToolkit.WinUI.UI; + using Microsoft.UI.Xaml.Data; + using Microsoft.UI.Xaml.Media.Imaging; + using Windows.Storage; + + public sealed class PidToIconConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + var y = FileLocksmith.Interop.NativeMethods.PidToFullPath((uint)value); + Icon icon = null; + + if (!string.IsNullOrEmpty(y)) + { + icon = Icon.ExtractAssociatedIcon(y); + } + + 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; + } + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Converters/PidToUserConverter.cs b/src/modules/FileLocksmith/FileLocksmithUI/Converters/PidToUserConverter.cs new file mode 100644 index 0000000000..181035325d --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Converters/PidToUserConverter.cs @@ -0,0 +1,23 @@ +// 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. + +namespace PowerToys.FileLocksmithUI.Converters +{ + using System; + using FileLocksmith.Interop; + using Microsoft.UI.Xaml.Data; + + public sealed class PidToUserConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + return NativeMethods.PidToUser((uint)value); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return value; + } + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj b/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj new file mode 100644 index 0000000000..ea03825d70 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/FileLocksmithUI.csproj @@ -0,0 +1,82 @@ + + + + + PowerToys.FileLocksmith + PowerToys File Locksmith + WinExe + net6.0-windows10.0.19041.0 + 10.0.19041.0 + ..\..\..\..\$(Platform)\$(Configuration)\modules\FileLocksmith + PowerToys.FileLocksmithUI + PowerToys.FileLocksmithUI + app.manifest + win10-x64;win10-arm64 + true + true + false + false + true + None + true + 10.0.19041.0 + icon.ico + + + + 0436 + + + + + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + + https://pkgs.dev.azure.com/dotnet/CommunityToolkit/_packaging/CommunityToolkit-Labs/nuget/v3/index.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Helpers/Logger.cs b/src/modules/FileLocksmith/FileLocksmithUI/Helpers/Logger.cs new file mode 100644 index 0000000000..2ec357c3cf --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Helpers/Logger.cs @@ -0,0 +1,76 @@ +// 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.Diagnostics; +using System.Globalization; +using System.IO; + +namespace FileLocksmithUI.Helpers +{ + public static class Logger + { + private static readonly string ApplicationLogPath = Path.Combine(interop.Constants.AppDataPath(), "File Locksmith\\FileLocksmithUI\\Logs"); + + static Logger() + { + if (!Directory.Exists(ApplicationLogPath)) + { + Directory.CreateDirectory(ApplicationLogPath); + } + + // Using InvariantCulture since this is used for a log file name + var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt"); + + Trace.Listeners.Add(new TextWriterTraceListener(logFilePath)); + + Trace.AutoFlush = true; + } + + public static void LogError(string message) + { + Log(message, "ERROR"); + } + + public static void LogError(string message, Exception ex) + { + Log( + message + Environment.NewLine + + ex?.Message + Environment.NewLine + + "Inner exception: " + Environment.NewLine + + ex?.InnerException?.Message + Environment.NewLine + + "Stack trace: " + Environment.NewLine + + ex?.StackTrace, + "ERROR"); + } + + public static void LogWarning(string message) + { + Log(message, "WARNING"); + } + + public static void LogInfo(string message) + { + Log(message, "INFO"); + } + + private static void Log(string message, string type) + { + Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay); + Trace.Indent(); + Trace.WriteLine(GetCallerInfo()); + Trace.WriteLine(message); + Trace.Unindent(); + } + + private static string GetCallerInfo() + { + StackTrace stackTrace = new StackTrace(); + + var methodName = stackTrace.GetFrame(3)?.GetMethod(); + var className = methodName?.DeclaringType.Name; + return "[Method]: " + methodName?.Name + " [Class]: " + className; + } + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/MainWindow.xaml b/src/modules/FileLocksmith/FileLocksmithUI/MainWindow.xaml new file mode 100644 index 0000000000..99541f6cee --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/MainWindow.xaml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/FileLocksmith/FileLocksmithUI/MainWindow.xaml.cs b/src/modules/FileLocksmith/FileLocksmithUI/MainWindow.xaml.cs new file mode 100644 index 0000000000..895a53989a --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/MainWindow.xaml.cs @@ -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 ManagedCommon; +using Microsoft.UI; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using WinUIEx; + +namespace FileLocksmithUI +{ + public sealed partial class MainWindow : WindowEx, IDisposable + { + public MainWindow(bool isElevated) + { + InitializeComponent(); + mainPage.ViewModel.IsElevated = isElevated; + SetTitleBar(); + } + + private void SetTitleBar() + { + if (AppWindowTitleBar.IsCustomizationSupported()) + { + AppWindow window = this.GetAppWindow(); + window.TitleBar.ExtendsContentIntoTitleBar = true; + window.TitleBar.ButtonBackgroundColor = Colors.Transparent; + SetTitleBar(titleBar); + } + else + { + var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + ThemeHelpers.SetImmersiveDarkMode(hWnd, ThemeHelpers.GetAppTheme() == AppTheme.Dark); + titleBar.Visibility = Visibility.Collapsed; + + // Set window icon + WindowId windowId = Win32Interop.GetWindowIdFromWindow(hWnd); + AppWindow appWindow = AppWindow.GetFromWindowId(windowId); + appWindow.SetIcon("icon.ico"); + } + } + + public void Dispose() + { + } + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Properties/PublishProfiles/InstallationPublishProfile.pubxml b/src/modules/FileLocksmith/FileLocksmithUI/Properties/PublishProfiles/InstallationPublishProfile.pubxml new file mode 100644 index 0000000000..0bb6a6e57e --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Properties/PublishProfiles/InstallationPublishProfile.pubxml @@ -0,0 +1,16 @@ + + + + + FileSystem + net6.0-windows10.0.19041.0 + $(PowerToysRoot)\$(Platform)\$(Configuration)\modules\FileLocksmith + win10-$(Platform) + false + False + False + false + + diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Strings/en-us/Resources.resw b/src/modules/FileLocksmith/FileLocksmithUI/Strings/en-us/Resources.resw new file mode 100644 index 0000000000..44efbce38e --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Strings/en-us/Resources.resw @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + File Locksmith + + + File Locksmith + + + No results + + + End task + + + Files + + + Click to see the entire list of paths. + Paths as in file paths that were selected for the utility to check. + + + Selected file paths + Paths as in file paths that were selected for the utility to check. + + + Close + As in, close a dialog prompt. + + + No files selected + + + Process ID + + + Reload + + + Restart as administrator + + + User + + \ No newline at end of file diff --git a/src/modules/FileLocksmith/FileLocksmithUI/ViewModels/MainViewModel.cs b/src/modules/FileLocksmith/FileLocksmithUI/ViewModels/MainViewModel.cs new file mode 100644 index 0000000000..1448a5b738 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/ViewModels/MainViewModel.cs @@ -0,0 +1,184 @@ +// 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. + +namespace PowerToys.FileLocksmithUI.ViewModels +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using CommunityToolkit.Mvvm.ComponentModel; + using CommunityToolkit.Mvvm.Input; + using FileLocksmith.Interop; + using global::FileLocksmithUI; + using global::FileLocksmithUI.Helpers; + using Microsoft.UI.Dispatching; + using Microsoft.UI.Xaml.Controls; + +#pragma warning disable CA1708 // Identifiers should differ by more than case + public partial class MainViewModel : ObservableObject, IDisposable +#pragma warning restore CA1708 // Identifiers should differ by more than case + { + public IAsyncRelayCommand LoadProcessesCommand { get; } + + private bool _isLoading; + private bool _isElevated; + private string[] paths; + private bool _disposed; + private CancellationTokenSource _cancelProcessWatching; + + public ObservableCollection Processes { get; } = new (); + + public bool IsLoading + { + get + { + return _isLoading; + } + + set + { + _isLoading = value; + OnPropertyChanged(nameof(IsLoading)); + } + } + + public bool IsElevated + { + get + { + return _isElevated; + } + + set + { + _isElevated = value; + OnPropertyChanged(nameof(IsElevated)); + } + } + + public string[] Paths + { + get => paths; + set + { + paths = value; + OnPropertyChanged(nameof(Paths)); + } + } + + public string PathsToString + { + get + { + return string.Join("\n", paths); + } + } + + public MainViewModel() + { + paths = NativeMethods.ReadPathsFromFile(); + Logger.LogInfo($"Starting FileLocksmith with {paths.Length} files selected."); + LoadProcessesCommand = new AsyncRelayCommand(LoadProcessesAsync); + } + + private async Task LoadProcessesAsync() + { + IsLoading = true; + Processes.Clear(); + + if (_cancelProcessWatching is not null) + { + _cancelProcessWatching.Cancel(); + } + + _cancelProcessWatching = new CancellationTokenSource(); + + foreach (ProcessResult p in await FindProcesses(paths)) + { + Processes.Add(p); + WatchProcess(p, _cancelProcessWatching.Token); + } + + IsLoading = false; + } + + private async Task> FindProcesses(string[] paths) + { + var results = new List(); + await Task.Run(() => + { + results = NativeMethods.FindProcessesRecursive(paths).ToList(); + }); + return results; + } + + private async void WatchProcess(ProcessResult process, CancellationToken token) + { + Process handle = Process.GetProcessById((int)process.pid); + try + { + await handle.WaitForExitAsync(token); + } + catch (TaskCanceledException) + { + // Nothing to do, normal operation + } + + if (handle.HasExited) + { + Processes.Remove(process); + } + } + + [RelayCommand] + public void EndTask(ProcessResult selectedProcess) + { + Process handle = Process.GetProcessById((int)selectedProcess.pid); + try + { + handle.Kill(); + } + catch (Exception) + { + Logger.LogError($"Couldn't kill process {selectedProcess.name} with PID {selectedProcess.pid}."); + } + } + + [RelayCommand] + public void RestartElevated() + { + if (NativeMethods.StartAsElevated(paths)) + { + // TODO gentler exit + Environment.Exit(0); + } + else + { + // TODO report error? + Logger.LogError($"Couldn't restart as elevated."); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _disposed = true; + } + } + } + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Views/MainPage.xaml b/src/modules/FileLocksmith/FileLocksmithUI/Views/MainPage.xaml new file mode 100644 index 0000000000..f186b209d6 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Views/MainPage.xaml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/FileLocksmith/FileLocksmithUI/Views/MainPage.xaml.cs b/src/modules/FileLocksmith/FileLocksmithUI/Views/MainPage.xaml.cs new file mode 100644 index 0000000000..61ae051548 --- /dev/null +++ b/src/modules/FileLocksmith/FileLocksmithUI/Views/MainPage.xaml.cs @@ -0,0 +1,27 @@ +// 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. + +namespace PowerToys.FileLocksmithUI.Views +{ + using System; + using Microsoft.UI.Xaml.Controls; + using PowerToys.FileLocksmithUI.ViewModels; + + public sealed partial class MainPage : Page + { + public MainViewModel ViewModel { get; private set; } + + public MainPage() + { + this.InitializeComponent(); + ViewModel = new MainViewModel(); + DataContext = ViewModel; + } + + private async void ShowSelectedPathsButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + { + await SelectedFilesListDialog.ShowAsync(); + } + } +} diff --git a/src/modules/FileLocksmith/FileLocksmithUI/icon.ico b/src/modules/FileLocksmith/FileLocksmithUI/icon.ico new file mode 100644 index 0000000000..6cbba351a1 Binary files /dev/null and b/src/modules/FileLocksmith/FileLocksmithUI/icon.ico differ diff --git a/src/modules/MeasureTool/MeasureToolUI/Strings/en-us/Resources.resw b/src/modules/MeasureTool/MeasureToolUI/Strings/en-us/Resources.resw new file mode 100644 index 0000000000..b959f3e2b5 --- /dev/null +++ b/src/modules/MeasureTool/MeasureToolUI/Strings/en-us/Resources.resw @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/runner/main.cpp b/src/runner/main.cpp index e38bc192e2..2203fdcb3d 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -155,6 +155,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow L"modules/MouseUtils/PowerToys.MousePointerCrosshairs.dll", L"modules/PowerAccent/PowerToys.PowerAccentModuleInterface.dll", L"modules/PowerOCR/PowerToys.PowerOCRModuleInterface.dll", + L"modules/FileLocksmith/PowerToys.FileLocksmithExt.dll", L"modules/MeasureTool/PowerToys.MeasureToolModuleInterface.dll", L"modules/Hosts/PowerToys.HostsModuleInterface.dll", }; diff --git a/src/settings-ui/Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Settings.UI.Library/EnabledModules.cs index cc34400f2f..de29ae6d35 100644 --- a/src/settings-ui/Settings.UI.Library/EnabledModules.cs +++ b/src/settings-ui/Settings.UI.Library/EnabledModules.cs @@ -303,6 +303,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } + private bool fileLocksmith = true; + + [JsonPropertyName("File Locksmith")] + public bool FileLocksmith + { + get => fileLocksmith; + set + { + if (fileLocksmith != value) + { + LogTelemetryEvent(value); + fileLocksmith = value; + } + } + } + public string ToJsonString() { return JsonSerializer.Serialize(this); diff --git a/src/settings-ui/Settings.UI/App.xaml.cs b/src/settings-ui/Settings.UI/App.xaml.cs index 73c8251609..7f10ecf0b1 100644 --- a/src/settings-ui/Settings.UI/App.xaml.cs +++ b/src/settings-ui/Settings.UI/App.xaml.cs @@ -118,6 +118,7 @@ namespace Microsoft.PowerToys.Settings.UI case "Awake": StartupPage = typeof(Views.AwakePage); break; case "ColorPicker": StartupPage = typeof(Views.ColorPickerPage); break; case "FancyZones": StartupPage = typeof(Views.FancyZonesPage); break; + case "FileLocksmith": StartupPage = typeof(Views.FileLocksmithPage); break; case "Run": StartupPage = typeof(Views.PowerLauncherPage); break; case "ImageResizer": StartupPage = typeof(Views.ImageResizerPage); break; case "KBM": StartupPage = typeof(Views.KeyboardManagerPage); break; diff --git a/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsFileLocksmith.png b/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsFileLocksmith.png new file mode 100644 index 0000000000..a7b0109afd Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsFileLocksmith.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Modules/FileLocksmith.png b/src/settings-ui/Settings.UI/Assets/Modules/FileLocksmith.png new file mode 100644 index 0000000000..336431d0e3 Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Modules/FileLocksmith.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Modules/OOBE/FileLocksmith.gif b/src/settings-ui/Settings.UI/Assets/Modules/OOBE/FileLocksmith.gif new file mode 100644 index 0000000000..27c9680a0d Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Modules/OOBE/FileLocksmith.gif differ diff --git a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs index 5039fe9b8c..148de19256 100644 --- a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs +++ b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs @@ -11,6 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums Awake, ColorPicker, FancyZones, + FileLocksmith, FileExplorer, ImageResizer, KBM, diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeFileLocksmith.xaml b/src/settings-ui/Settings.UI/OOBE/Views/OobeFileLocksmith.xaml new file mode 100644 index 0000000000..4f016e3ce8 --- /dev/null +++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeFileLocksmith.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + +