From e19590f25823ffdb742a384d4c6db5644f37efbc Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Thu, 5 Dec 2024 18:24:05 +0300 Subject: [PATCH] [Workspaces] PWA follow-up (#36217) --- .github/actions/spell-check/expect.txt | 3 +- .../WorkspacesLib/CommandLineArgsHelper.cpp | 41 ++ .../WorkspacesLib/CommandLineArgsHelper.h | 15 + .../Workspaces/WorkspacesLib/PwaHelper.cpp | 561 +++++++----------- .../Workspaces/WorkspacesLib/PwaHelper.h | 32 +- .../Workspaces/WorkspacesLib/StringUtils.h | 20 + .../Workspaces/WorkspacesLib/WbemHelper.cpp | 120 ++++ .../Workspaces/WorkspacesLib/WbemHelper.h | 24 + .../WorkspacesLib/WorkspacesLib.vcxproj | 5 + .../WorkspacesLib.vcxproj.filters | 18 + .../WorkspacesSnapshotTool/SnapshotUtils.cpp | 40 +- .../WindowArranger.cpp | 31 +- 12 files changed, 549 insertions(+), 361 deletions(-) create mode 100644 src/modules/Workspaces/WorkspacesLib/CommandLineArgsHelper.cpp create mode 100644 src/modules/Workspaces/WorkspacesLib/CommandLineArgsHelper.h create mode 100644 src/modules/Workspaces/WorkspacesLib/StringUtils.h create mode 100644 src/modules/Workspaces/WorkspacesLib/WbemHelper.cpp create mode 100644 src/modules/Workspaces/WorkspacesLib/WbemHelper.h diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index de32d2a324..dedbb36b59 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -59,6 +59,7 @@ Appium Applicationcan APPLICATIONFRAMEHOST appmanifest +appmodel APPNAME appref appsettings @@ -1135,7 +1136,7 @@ pdo pdto pdtobj pdw -peb +Peb pef PElems Pels diff --git a/src/modules/Workspaces/WorkspacesLib/CommandLineArgsHelper.cpp b/src/modules/Workspaces/WorkspacesLib/CommandLineArgsHelper.cpp new file mode 100644 index 0000000000..da65126feb --- /dev/null +++ b/src/modules/Workspaces/WorkspacesLib/CommandLineArgsHelper.cpp @@ -0,0 +1,41 @@ +#include "pch.h" +#include "CommandLineArgsHelper.h" + +#include + +CommandLineArgsHelper::CommandLineArgsHelper() : + m_wbemHelper(WbemHelper::Create()) +{ + if (!m_wbemHelper) + { + Logger::error(L"Failed to create WbemHelper"); + } +} + +std::wstring CommandLineArgsHelper::GetCommandLineArgs(DWORD processID) const +{ + if (!m_wbemHelper) + { + Logger::error(L"WbemHelper not initialized"); + return L""; + } + + std::wstring executablePath = m_wbemHelper->GetExecutablePath(processID); + std::wstring commandLineArgs = m_wbemHelper->GetCommandLineArgs(processID); + + if (!commandLineArgs.empty()) + { + auto pos = commandLineArgs.find(executablePath); + if (pos != std::wstring::npos) + { + commandLineArgs = commandLineArgs.substr(pos + executablePath.size()); + auto spacePos = commandLineArgs.find_first_of(' '); + if (spacePos != std::wstring::npos) + { + commandLineArgs = commandLineArgs.substr(spacePos + 1); + } + } + } + + return commandLineArgs; +} diff --git a/src/modules/Workspaces/WorkspacesLib/CommandLineArgsHelper.h b/src/modules/Workspaces/WorkspacesLib/CommandLineArgsHelper.h new file mode 100644 index 0000000000..d355e2461e --- /dev/null +++ b/src/modules/Workspaces/WorkspacesLib/CommandLineArgsHelper.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class CommandLineArgsHelper +{ +public: + CommandLineArgsHelper(); + ~CommandLineArgsHelper() = default; + + std::wstring GetCommandLineArgs(DWORD processID) const; + +private: + std::unique_ptr m_wbemHelper; +}; diff --git a/src/modules/Workspaces/WorkspacesLib/PwaHelper.cpp b/src/modules/Workspaces/WorkspacesLib/PwaHelper.cpp index 511ad51a20..41dfa9ef86 100644 --- a/src/modules/Workspaces/WorkspacesLib/PwaHelper.cpp +++ b/src/modules/Workspaces/WorkspacesLib/PwaHelper.cpp @@ -1,16 +1,24 @@ #include "pch.h" #include "PwaHelper.h" -#include "AppUtils.h" -#include -#include -#include -#include + #include -#include + +#include +#include +#include +#include +#include +#include +#include + +#include + #include #include -#include -#pragma comment(lib, "ntdll.lib") + +#include +#include +#include namespace Utils { @@ -22,197 +30,29 @@ namespace Utils const std::wstring EdgeBase = L"Microsoft\\Edge\\User Data\\Default\\Web Applications"; const std::wstring ChromeDirPrefix = L"_crx_"; const std::wstring EdgeDirPrefix = L"_crx__"; + const std::wstring IcoExtension = L".ico"; } - // {c8900b66-a973-584b-8cae-355b7f55341b} - DEFINE_GUID(CLSID_StartMenuCacheAndAppResolver, 0x660b90c8, 0x73a9, 0x4b58, 0x8c, 0xae, 0x35, 0x5b, 0x7f, 0x55, 0x34, 0x1b); - - // {46a6eeff-908e-4dc6-92a6-64be9177b41c} - DEFINE_GUID(IID_IAppResolver_7, 0x46a6eeff, 0x908e, 0x4dc6, 0x92, 0xa6, 0x64, 0xbe, 0x91, 0x77, 0xb4, 0x1c); - - // {de25675a-72de-44b4-9373-05170450c140} - DEFINE_GUID(IID_IAppResolver_8, 0xde25675a, 0x72de, 0x44b4, 0x93, 0x73, 0x05, 0x17, 0x04, 0x50, 0xc1, 0x40); - - struct IAppResolver_7 : public IUnknown + static const std::wstring& GetLocalAppDataFolder() { - public: - virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcut() = 0; - virtual HRESULT STDMETHODCALLTYPE GetAppIDForWindow(HWND hWnd, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0; - virtual HRESULT STDMETHODCALLTYPE GetAppIDForProcess(DWORD dwProcessId, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0; - }; + static std::wstring localFolder{}; - struct IAppResolver_8 : public IUnknown - { - public: - virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcut() = 0; - virtual HRESULT STDMETHODCALLTYPE GetAppIDForShortcutObject() = 0; - virtual HRESULT STDMETHODCALLTYPE GetAppIDForWindow(HWND hWnd, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0; - virtual HRESULT STDMETHODCALLTYPE GetAppIDForProcess(DWORD dwProcessId, WCHAR** pszAppId, void* pUnknown1, void* pUnknown2, void* pUnknown3) = 0; - }; - - std::optional PwaHelper::GetAppId_7(HWND hWnd) const - { - HRESULT hr; - std::optional result = std::nullopt; - - wil::com_ptr appResolver; - hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_7, reinterpret_cast(appResolver.put())); - if (SUCCEEDED(hr)) + if (localFolder.empty()) { - wil::unique_cotaskmem_string pszAppId; - hr = appResolver->GetAppIDForWindow(hWnd, &pszAppId, NULL, NULL, NULL); - if (SUCCEEDED(hr)) + wil::unique_cotaskmem_string folderPath; + HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &folderPath); + if (SUCCEEDED(hres)) { - result = std::wstring(pszAppId.get()); - } - - appResolver->Release(); - } - - return result; - } - - std::optional PwaHelper::GetAppId_8(HWND hWnd) const - { - HRESULT hr; - std::optional result = std::nullopt; - - wil::com_ptr appResolver; - hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, reinterpret_cast(appResolver.put())); - if (SUCCEEDED(hr)) - { - wil::unique_cotaskmem_string pszAppId; - hr = appResolver->GetAppIDForWindow(hWnd, &pszAppId, NULL, NULL, NULL); - if (SUCCEEDED(hr)) - { - result = std::wstring(pszAppId.get()); - } - - appResolver->Release(); - } - - return result; - } - - std::wstring PwaHelper::GetAppId(HWND hWnd) const - { - std::optional result = GetAppId_8(hWnd); - if (result == std::nullopt) - { - result = GetAppId_7(hWnd); - } - return result.has_value() ? result.value() : L""; - } - - std::optional PwaHelper::GetProcessId_7(DWORD dwProcessId) const - { - HRESULT hr; - std::optional result = std::nullopt; - - wil::com_ptr appResolver; - hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_7, reinterpret_cast(appResolver.put())); - if (SUCCEEDED(hr)) - { - wil::unique_cotaskmem_string pszAppId; - hr = appResolver->GetAppIDForProcess(dwProcessId, &pszAppId, NULL, NULL, NULL); - if (SUCCEEDED(hr)) - { - result = std::wstring(pszAppId.get()); - } - - appResolver->Release(); - } - - return result; - } - - std::optional PwaHelper::GetProcessId_8(DWORD dwProcessId) const - { - HRESULT hr; - std::optional result = std::nullopt; - - wil::com_ptr appResolver; - hr = CoCreateInstance(CLSID_StartMenuCacheAndAppResolver, NULL, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, IID_IAppResolver_8, reinterpret_cast(appResolver.put())); - if (SUCCEEDED(hr)) - { - wil::unique_cotaskmem_string pszAppId; - hr = appResolver->GetAppIDForProcess(dwProcessId, &pszAppId, NULL, NULL, NULL); - if (SUCCEEDED(hr)) - { - result = std::wstring(pszAppId.get()); - } - - appResolver->Release(); - } - - return result; - } - - std::wstring PwaHelper::GetProcessId(DWORD dwProcessId) const - { - std::optional result = GetProcessId_8(dwProcessId); - if (result == std::nullopt) - { - result = GetProcessId_7(dwProcessId); - } - return result.has_value() ? result.value() : L""; - } - - std::wstring GetProcCommandLine(DWORD pid) - { - std::wstring commandLine; - - // Open a handle to the process - const HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); - if (process == NULL) - { - Logger::error(L"Failed to open the process, error: {}", get_last_error_or_default(GetLastError())); - } - else - { - // Get the address of the ProcessEnvironmentBlock - PROCESS_BASIC_INFORMATION pbi = {}; - NTSTATUS status = NtQueryInformationProcess(process, ProcessBasicInformation, &pbi, sizeof(pbi), NULL); - if (status != STATUS_SUCCESS) - { - Logger::error(L"Failed to query the process, error: {}", status); + localFolder = folderPath.get(); } else { - // Get the address of the process parameters in the ProcessEnvironmentBlock - PEB processEnvironmentBlock = {}; - if (!ReadProcessMemory(process, pbi.PebBaseAddress, &processEnvironmentBlock, sizeof(processEnvironmentBlock), NULL)) - { - Logger::error(L"Failed to read the process ProcessEnvironmentBlock, error: {}", get_last_error_or_default(GetLastError())); - } - else - { - // Get the command line arguments from the process parameters - RTL_USER_PROCESS_PARAMETERS params = {}; - if (!ReadProcessMemory(process, processEnvironmentBlock.ProcessParameters, ¶ms, sizeof(params), NULL)) - { - Logger::error(L"Failed to read the process params, error: {}", get_last_error_or_default(GetLastError())); - } - else - { - UNICODE_STRING& commandLineArgs = params.CommandLine; - std::vector buffer(commandLineArgs.Length / sizeof(WCHAR)); - if (!ReadProcessMemory(process, commandLineArgs.Buffer, buffer.data(), commandLineArgs.Length, NULL)) - { - Logger::error(L"Failed to read the process command line, error: {}", get_last_error_or_default(GetLastError())); - } - else - { - commandLine.assign(buffer.data(), buffer.size()); - } - } - } + Logger::error(L"Failed to get the local app data folder path: {}", get_last_error_or_default(hres)); + localFolder = L""; // Ensure it is explicitly set to empty on failure } - - CloseHandle(process); } - - return commandLine; + + return localFolder; } // Finds all PwaHelper.exe processes with the specified parent process ID @@ -245,143 +85,116 @@ namespace Utils return pwaHelperProcessIds; } - void PwaHelper::InitAumidToAppId() + PwaHelper::PwaHelper() { - if (m_pwaAumidToAppId.size() > 0) + InitChromeAppIds(); + InitEdgeAppIds(); + } + + void PwaHelper::InitAppIds(const std::wstring& browserDataFolder, const std::wstring& browserDirPrefix, const std::function& addingAppIdCallback) + { + std::filesystem::path folderPath(GetLocalAppDataFolder()); + folderPath.append(browserDataFolder); + if (!std::filesystem::exists(folderPath)) { + Logger::info(L"Edge base path does not exist: {}", folderPath.wstring()); return; } - const auto pwaHelperProcessIds = FindPwaHelperProcessIds(); - Logger::info(L"Found {} edge Pwa helper processes", pwaHelperProcessIds.size()); - for (const auto subProcessID : pwaHelperProcessIds) + try { - std::wstring aumidID; - aumidID = GetProcessId(subProcessID); - std::wstring commandLineArg = GetProcCommandLine(subProcessID); - auto appIdIndexStart = commandLineArg.find(NonLocalizable::EdgeAppIdIdentifier); - if (appIdIndexStart != std::wstring::npos) + for (const auto& directory : std::filesystem::directory_iterator(folderPath)) { - commandLineArg = commandLineArg.substr(appIdIndexStart + NonLocalizable::EdgeAppIdIdentifier.size()); - auto appIdIndexEnd = commandLineArg.find(L" "); - if (appIdIndexEnd != std::wstring::npos) + if (!directory.is_directory()) { - commandLineArg = commandLineArg.substr(0, appIdIndexEnd); + continue; } - } - std::wstring appId{ commandLineArg }; - m_pwaAumidToAppId.insert(std::map::value_type(aumidID, appId)); - Logger::info(L"Found an edge Pwa helper process with AumidID {} and PwaAppId {}", aumidID, appId); - PWSTR path = NULL; - HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path); - if (SUCCEEDED(hres)) - { - std::filesystem::path fsPath(path); - fsPath /= NonLocalizable::EdgeBase; - for (const auto& directory : std::filesystem::directory_iterator(fsPath)) + const std::wstring directoryName = directory.path().filename(); + if (directoryName.find(browserDirPrefix) != 0) { - if (directory.is_directory()) + continue; + } + + const std::wstring appId = directoryName.substr(browserDirPrefix.length()); + if (addingAppIdCallback) + { + addingAppIdCallback(appId); + } + + for (const auto& filename : std::filesystem::directory_iterator(directory)) + { + if (!filename.is_directory()) { - const std::filesystem::path directoryName = directory.path().filename(); - if (directoryName.wstring().find(NonLocalizable::EdgeDirPrefix) == 0) + const std::filesystem::path filenameString = filename.path().filename(); + if (StringUtils::CaseInsensitiveEquals(filenameString.extension(), NonLocalizable::IcoExtension)) { - const std::wstring appIdDir = directoryName.wstring().substr(NonLocalizable::EdgeDirPrefix.size()); - if (appIdDir == appId) - { - for (const auto& filename : std::filesystem::directory_iterator(directory)) - { - if (!filename.is_directory()) - { - const std::filesystem::path filenameString = filename.path().filename(); - if (filenameString.extension().wstring() == L".ico") - { - m_pwaAppIdsToAppNames.insert(std::map::value_type(appId, filenameString.stem().wstring())); - Logger::info(L"Storing an edge Pwa app name {} for PwaAppId {}", filenameString.stem().wstring(), appId); - } - } - } - } + const auto stem = filenameString.stem().wstring(); + m_pwaAppIdsToAppNames.insert({ appId, stem }); + Logger::info(L"Found an installed Pwa app {} with PwaAppId {}", stem, appId); + break; } } } - CoTaskMemFree(path); } } + catch (std::exception& ex) + { + Logger::error("Failed to iterate over the directory: {}", ex.what()); + } } - std::optional PwaHelper::GetPwaAppId(const std::wstring& windowAumid) const + void PwaHelper::InitEdgeAppIds() { - const auto pwaIndex = m_pwaAumidToAppId.find(windowAumid); - if (pwaIndex != m_pwaAumidToAppId.end()) + if (!m_edgeAppIds.empty()) + { + // already initialized + return; + } + + CommandLineArgsHelper commandLineArgsHelper{}; + + const auto pwaHelperProcessIds = FindPwaHelperProcessIds(); + Logger::info(L"Found {} edge Pwa helper processes", pwaHelperProcessIds.size()); + + for (const auto subProcessID : pwaHelperProcessIds) + { + std::wstring aumidID = GetAUMIDFromProcessId(subProcessID); + std::wstring commandLineArg = commandLineArgsHelper.GetCommandLineArgs(subProcessID); + std::wstring appId = GetAppIdFromCommandLineArgs(commandLineArg); + + m_edgeAppIds.insert({ aumidID, appId }); + Logger::info(L"Found an edge Pwa helper process with AumidID {} and PwaAppId {}", aumidID, appId); + } + + InitAppIds(NonLocalizable::EdgeBase, NonLocalizable::EdgeDirPrefix, [&](const std::wstring&) {}); + } + + void PwaHelper::InitChromeAppIds() + { + if (!m_chromeAppIds.empty()) + { + // already initialized + return; + } + + InitAppIds(NonLocalizable::ChromeBase, NonLocalizable::ChromeDirPrefix, [&](const std::wstring& appId) { + m_chromeAppIds.push_back(appId); + }); + } + + std::optional PwaHelper::GetEdgeAppId(const std::wstring& windowAumid) const + { + const auto pwaIndex = m_edgeAppIds.find(windowAumid); + if (pwaIndex != m_edgeAppIds.end()) { return pwaIndex->second; } return std::nullopt; - ; } - - std::wstring PwaHelper::SearchPwaName(const std::wstring& pwaAppId, const std::wstring& windowAumid) const - { - const auto index = m_pwaAppIdsToAppNames.find(pwaAppId); - if (index != m_pwaAppIdsToAppNames.end()) - { - return index->second; - } - - std::wstring nameFromAumid{ windowAumid }; - const std::size_t delimiterPos = nameFromAumid.find(L"-"); - if (delimiterPos != std::string::npos) - { - return nameFromAumid.substr(0, delimiterPos); - } - - return nameFromAumid; - } - - void PwaHelper::InitChromeAppIds() - { - if (m_chromeAppIds.size() > 0) - { - return; - } - - PWSTR path = NULL; - HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path); - if (SUCCEEDED(hres)) - { - std::filesystem::path fsPath(path); - fsPath /= NonLocalizable::ChromeBase; - for (const auto& directory : std::filesystem::directory_iterator(fsPath)) - { - if (directory.is_directory()) - { - const std::filesystem::path directoryName = directory.path().filename(); - if (directoryName.wstring().find(NonLocalizable::ChromeDirPrefix) == 0) - { - const std::wstring appId = directoryName.wstring().substr(NonLocalizable::ChromeDirPrefix.size()); - m_chromeAppIds.push_back(appId); - for (const auto& filename : std::filesystem::directory_iterator(directory)) - { - if (!filename.is_directory()) - { - const std::filesystem::path filenameString = filename.path().filename(); - if (filenameString.extension().wstring() == L".ico") - { - m_pwaAppIdsToAppNames.insert(std::map::value_type(appId, filenameString.stem().wstring())); - Logger::info(L"Found an installed chrome Pwa app {} with PwaAppId {}", filenameString.stem().wstring(), appId); - } - } - } - } - } - } - CoTaskMemFree(path); - } - } - - std::optional PwaHelper::SearchPwaAppId(const std::wstring& windowAumid) const + + std::optional PwaHelper::GetChromeAppId(const std::wstring& windowAumid) const { const auto appIdIndexStart = windowAumid.find(NonLocalizable::ChromeAppIdIdentifier); if (appIdIndexStart != std::wstring::npos) @@ -406,48 +219,122 @@ namespace Utils return std::nullopt; } - void PwaHelper::UpdatePwaApp(Utils::Apps::AppData* appData, HWND window) + std::wstring PwaHelper::SearchPwaName(const std::wstring& pwaAppId, const std::wstring& windowAumid) const { - std::optional pwaAppId = std::nullopt; - std::wstring finalName = appData->name; - std::wstring pwaName = L""; - if (appData->IsEdge()) + const auto index = m_pwaAppIdsToAppNames.find(pwaAppId); + if (index != m_pwaAppIdsToAppNames.end()) { - InitAumidToAppId(); - - std::wstring windowAumid = GetAppId(window); - - Logger::info(L"Found an edge window with aumid {}", windowAumid); - - pwaAppId = GetPwaAppId(windowAumid); - if (pwaAppId.has_value()) - { - Logger::info(L"The found edge window is a PWA app with appId {}", pwaAppId.value()); - pwaName = SearchPwaName(pwaAppId.value(), windowAumid); - Logger::info(L"The found edge window is a PWA app with name {}", pwaName); - finalName = pwaName + L" (" + finalName + L")"; - } - else - { - Logger::info(L"The found edge window does not contain a PWA app"); - } - } - else if (appData->IsChrome()) - { - InitChromeAppIds(); - - std::wstring windowAumid = GetAppId(window); - Logger::info(L"Found a chrome window with aumid {}", windowAumid); - - pwaAppId = SearchPwaAppId(windowAumid); - if (pwaAppId.has_value()) - { - pwaName = SearchPwaName(pwaAppId.value(), windowAumid); - finalName = pwaName + L" (" + finalName + L")"; - } + return index->second; } - appData->name = finalName; - appData->pwaAppId = pwaAppId.has_value() ? pwaAppId.value() : L""; + std::wstring nameFromAumid{ windowAumid }; + const std::size_t delimiterPos = nameFromAumid.find(L"-"); + if (delimiterPos != std::string::npos) + { + return nameFromAumid.substr(0, delimiterPos); + } + + return nameFromAumid; + } + + std::wstring PwaHelper::GetAppIdFromCommandLineArgs(const std::wstring& commandLineArgs) const + { + auto result = commandLineArgs; + + // remove the prefix + if (result.find(NonLocalizable::EdgeAppIdIdentifier) == 0) + { + result.erase(0, NonLocalizable::EdgeAppIdIdentifier.length()); + } + + // remove the suffix + auto appIdIndexEnd = result.find(L" "); + if (appIdIndexEnd != std::wstring::npos) + { + result = result.substr(0, appIdIndexEnd); + } + + return result; + } + + std::wstring PwaHelper::GetAUMIDFromWindow(HWND hwnd) const + { + std::wstring result{}; + if (hwnd == NULL) + { + return result; + } + + Microsoft::WRL::ComPtr propertyStore; + HRESULT hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&propertyStore)); + if (FAILED(hr)) + { + return result; + } + + PROPVARIANT propVariant; + PropVariantInit(&propVariant); + + hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &propVariant); + if (SUCCEEDED(hr) && propVariant.vt == VT_LPWSTR && propVariant.pwszVal != nullptr) + { + result = propVariant.pwszVal; + } + + PropVariantClear(&propVariant); + + Logger::info(L"Found a window with aumid {}", result); + return result; + } + + std::wstring PwaHelper::GetAUMIDFromProcessId(DWORD processId) const + { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId); + if (hProcess == NULL) + { + Logger::error(L"Failed to open process handle. Error: {}", get_last_error_or_default(GetLastError())); + return {}; + } + + // Get the package full name for the process + UINT32 packageFullNameLength = 0; + LONG rc = GetPackageFullName(hProcess, &packageFullNameLength, nullptr); + if (rc != ERROR_INSUFFICIENT_BUFFER) + { + Logger::error(L"Failed to get package full name length. Error code: {}", rc); + CloseHandle(hProcess); + return {}; + } + + std::vector packageFullName(packageFullNameLength); + rc = GetPackageFullName(hProcess, &packageFullNameLength, packageFullName.data()); + if (rc != ERROR_SUCCESS) + { + Logger::error(L"Failed to get package full name. Error code: {}", rc); + CloseHandle(hProcess); + return {}; + } + + // Get the AUMID for the package + UINT32 appModelIdLength = 0; + rc = GetApplicationUserModelId(hProcess, &appModelIdLength, nullptr); + if (rc != ERROR_INSUFFICIENT_BUFFER) + { + Logger::error(L"Failed to get AppUserModelId length. Error code: {}", rc); + CloseHandle(hProcess); + return {}; + } + + std::vector appModelId(appModelIdLength); + rc = GetApplicationUserModelId(hProcess, &appModelIdLength, appModelId.data()); + if (rc != ERROR_SUCCESS) + { + Logger::error(L"Failed to get AppUserModelId. Error code: {}", rc); + CloseHandle(hProcess); + return {}; + } + + CloseHandle(hProcess); + return std::wstring(appModelId.data()); } } \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesLib/PwaHelper.h b/src/modules/Workspaces/WorkspacesLib/PwaHelper.h index e666774796..d304cac91d 100644 --- a/src/modules/Workspaces/WorkspacesLib/PwaHelper.h +++ b/src/modules/Workspaces/WorkspacesLib/PwaHelper.h @@ -1,5 +1,7 @@ #pragma once +#include + #include namespace Utils @@ -7,23 +9,25 @@ namespace Utils class PwaHelper { public: - void UpdatePwaApp(Apps::AppData* appData, HWND window); + PwaHelper(); + ~PwaHelper() = default; + std::wstring GetAUMIDFromWindow(HWND hWnd) const; + + std::optional GetEdgeAppId(const std::wstring& windowAumid) const; + std::optional GetChromeAppId(const std::wstring& windowAumid) const; + std::wstring SearchPwaName(const std::wstring& pwaAppId, const std::wstring& windowAumid) const; + private: - std::map m_pwaAumidToAppId; + void InitAppIds(const std::wstring& browserDataFolder, const std::wstring& browserDirPrefix, const std::function& addingAppIdCallback); + void InitEdgeAppIds(); + void InitChromeAppIds(); + + std::wstring GetAppIdFromCommandLineArgs(const std::wstring& commandLineArgs) const; + std::wstring GetAUMIDFromProcessId(DWORD processId) const; + + std::map m_edgeAppIds; std::vector m_chromeAppIds; std::map m_pwaAppIdsToAppNames; - - void InitAumidToAppId(); - void InitChromeAppIds(); - std::optional GetAppId_7(HWND hWnd) const; - std::optional GetAppId_8(HWND hWnd) const; - std::wstring GetAppId(HWND hWnd) const; - std::optional GetProcessId_7(DWORD dwProcessId) const; - std::optional GetProcessId_8(DWORD dwProcessId) const; - std::wstring GetProcessId(DWORD dwProcessId) const; - std::optional GetPwaAppId(const std::wstring& windowAumid) const; - std::wstring SearchPwaName(const std::wstring& pwaAppId, const std::wstring& windowAumid) const; - std::optional SearchPwaAppId(const std::wstring& windowAumid) const; }; } diff --git a/src/modules/Workspaces/WorkspacesLib/StringUtils.h b/src/modules/Workspaces/WorkspacesLib/StringUtils.h new file mode 100644 index 0000000000..ea53ed3f5b --- /dev/null +++ b/src/modules/Workspaces/WorkspacesLib/StringUtils.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +namespace StringUtils +{ + bool CaseInsensitiveEquals(const std::wstring& str1, const std::wstring& str2) + { + if (str1.size() != str2.size()) + { + return false; + } + + return std::equal(str1.begin(), str1.end(), str2.begin(), [](wchar_t ch1, wchar_t ch2) { + return towupper(ch1) == towupper(ch2); + }); + } +} diff --git a/src/modules/Workspaces/WorkspacesLib/WbemHelper.cpp b/src/modules/Workspaces/WorkspacesLib/WbemHelper.cpp new file mode 100644 index 0000000000..8ea720f588 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesLib/WbemHelper.cpp @@ -0,0 +1,120 @@ +#include "pch.h" +#include "WbemHelper.h" + +#include +#include + +#include +#include + +#pragma comment(lib, "wbemuuid.lib") + +std::unique_ptr WbemHelper::Create() +{ + auto instance = std::unique_ptr(new WbemHelper()); + if (instance->Initialize()) + { + return instance; + } + + return nullptr; +} + +WbemHelper::~WbemHelper() +{ + if (m_services) + { + m_services->Release(); + } + + if (m_locator) + { + m_locator->Release(); + } +} + +std::wstring WbemHelper::GetCommandLineArgs(DWORD processID) const +{ + static std::wstring property = L"CommandLine"; + std::wstring query = L"SELECT " + property + L" FROM Win32_Process WHERE ProcessId = " + std::to_wstring(processID); + return Query(query, property); +} + +std::wstring WbemHelper::GetExecutablePath(DWORD processID) const +{ + static std::wstring property = L"ExecutablePath"; + std::wstring query = L"SELECT " + property + L" FROM Win32_Process WHERE ProcessId = " + std::to_wstring(processID); + return Query(query, property); +} + +bool WbemHelper::Initialize() +{ + // Obtain the initial locator to WMI. + HRESULT hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast(&m_locator)); + if (FAILED(hres)) + { + Logger::error(L"Failed to create IWbemLocator object. Error: {}", get_last_error_or_default(hres)); + return false; + } + + // Connect to WMI through the IWbemLocator::ConnectServer method. + hres = m_locator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &m_services); + if (FAILED(hres)) + { + Logger::error(L"Could not connect to WMI. Error: {}", get_last_error_or_default(hres)); + return false; + } + + // Set security levels on the proxy. + hres = CoSetProxyBlanket(m_services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); + if (FAILED(hres)) + { + Logger::error(L"Could not set proxy blanket. Error: {}", get_last_error_or_default(hres)); + return false; + } + + return true; +} + +std::wstring WbemHelper::Query(const std::wstring& query, const std::wstring& propertyName) const +{ + if (!m_locator || !m_services) + { + return L""; + } + + IEnumWbemClassObject* pEnumerator = NULL; + + HRESULT hres = m_services->ExecQuery(bstr_t("WQL"), bstr_t(query.c_str()), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); + if (FAILED(hres)) + { + Logger::error(L"Query for process failed. Error: {}", get_last_error_or_default(hres)); + return L""; + } + + IWbemClassObject* pClassObject = NULL; + ULONG uReturn = 0; + std::wstring result = L""; + while (pEnumerator) + { + HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pClassObject, &uReturn); + if (uReturn == 0) + { + break; + } + + VARIANT vtProp; + hr = pClassObject->Get(propertyName.c_str(), 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) + { + result = vtProp.bstrVal; + } + VariantClear(&vtProp); + + pClassObject->Release(); + } + + pEnumerator->Release(); + + return result; +} diff --git a/src/modules/Workspaces/WorkspacesLib/WbemHelper.h b/src/modules/Workspaces/WorkspacesLib/WbemHelper.h new file mode 100644 index 0000000000..2d31978c38 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesLib/WbemHelper.h @@ -0,0 +1,24 @@ +#pragma once + +struct IWbemLocator; +struct IWbemServices; + +class WbemHelper +{ +public: + static std::unique_ptr Create(); + ~WbemHelper(); + + std::wstring GetCommandLineArgs(DWORD processID) const; + std::wstring GetExecutablePath(DWORD processID) const; + +private: + WbemHelper() = default; + + bool Initialize(); + + std::wstring Query(const std::wstring& query, const std::wstring& propertyName) const; + + IWbemLocator* m_locator = NULL; + IWbemServices* m_services = NULL; +}; diff --git a/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj b/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj index ed2ca53cd1..27394e29ee 100644 --- a/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj +++ b/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj @@ -33,6 +33,7 @@ + @@ -40,12 +41,15 @@ + + + @@ -54,6 +58,7 @@ + diff --git a/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj.filters b/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj.filters index 7774f9e6fc..b066c16a57 100644 --- a/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj.filters +++ b/src/modules/Workspaces/WorkspacesLib/WorkspacesLib.vcxproj.filters @@ -44,6 +44,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -73,8 +82,17 @@ Source Files + + Source Files + + + Source Files + + + + \ No newline at end of file diff --git a/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp b/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp index 2d6012be72..ddbf0f3cf7 100644 --- a/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp +++ b/src/modules/Workspaces/WorkspacesSnapshotTool/SnapshotUtils.cpp @@ -116,7 +116,33 @@ namespace SnapshotUtils continue; } - pwaHelper.UpdatePwaApp(&data.value(), window); + auto appData = data.value(); + + bool isEdge = appData.IsEdge(); + bool isChrome = appData.IsChrome(); + if (isEdge || isChrome) + { + auto windowAumid = pwaHelper.GetAUMIDFromWindow(window); + std::optional pwaAppId{}; + + if (isEdge) + { + pwaAppId = pwaHelper.GetEdgeAppId(windowAumid); + } + else if (isChrome) + { + pwaAppId = pwaHelper.GetChromeAppId(windowAumid); + } + + if (pwaAppId.has_value()) + { + auto pwaName = pwaHelper.SearchPwaName(pwaAppId.value(), windowAumid); + Logger::info(L"Found {} PWA app with name {}, appId: {}", (isEdge ? L"Edge" : (isChrome ? L"Chrome" : L"unknown")), pwaName, pwaAppId.value()); + + appData.pwaAppId = pwaAppId.value(); + appData.name = pwaName + L" (" + appData.name + L")"; + } + } bool isMinimized = WindowUtils::IsMinimized(window); unsigned int monitorNumber = getMonitorNumberFromWindowHandle(window); @@ -132,15 +158,15 @@ namespace SnapshotUtils } WorkspacesData::WorkspacesProject::Application app{ - .name = data.value().name, + .name = appData.name, .title = title, - .path = data.value().installPath, - .packageFullName = data.value().packageFullName, - .appUserModelId = data.value().appUserModelId, - .pwaAppId = data.value().pwaAppId, + .path = appData.installPath, + .packageFullName = appData.packageFullName, + .appUserModelId = appData.appUserModelId, + .pwaAppId = appData.pwaAppId, .commandLineArgs = L"", .isElevated = IsProcessElevated(pid), - .canLaunchElevated = data.value().canLaunchElevated, + .canLaunchElevated = appData.canLaunchElevated, .isMinimized = isMinimized, .isMaximized = WindowUtils::IsMaximized(window), .position = WorkspacesData::WorkspacesProject::Application::Position{ diff --git a/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp b/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp index 45cd47dcb4..579f01620e 100644 --- a/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp +++ b/src/modules/Workspaces/WorkspacesWindowArranger/WindowArranger.cpp @@ -178,9 +178,36 @@ std::optional WindowArranger::GetNearestWindow(const Workspa continue; } - pwaHelper.UpdatePwaApp(&data.value(), window); + auto appData = data.value(); - if ((app.name == data.value().name || app.path == data.value().installPath) && (app.pwaAppId == data.value().pwaAppId)) + // PWA apps + bool isEdge = appData.IsEdge(); + bool isChrome = appData.IsChrome(); + if (isEdge || isChrome) + { + auto windowAumid = pwaHelper.GetAUMIDFromWindow(window); + std::optional pwaAppId{}; + + if (isEdge) + { + pwaAppId = pwaHelper.GetEdgeAppId(windowAumid); + } + else if (isChrome) + { + pwaAppId = pwaHelper.GetChromeAppId(windowAumid); + } + + if (pwaAppId.has_value()) + { + auto pwaName = pwaHelper.SearchPwaName(pwaAppId.value(), windowAumid); + Logger::info(L"Found {} PWA app with name {}, appId: {}", (isEdge ? L"Edge" : (isChrome ? L"Chrome" : L"unknown")), pwaName, pwaAppId.value()); + + appData.pwaAppId = pwaAppId.value(); + appData.name = pwaName + L" (" + appData.name + L")"; + } + } + + if ((app.name == appData.name || app.path == appData.installPath) && (app.pwaAppId == appData.pwaAppId)) { if (!appDataNearest.has_value()) {