PwaHelper.cpp
1 #include "pch.h" 2 #include "PwaHelper.h" 3 #include "WindowUtils.h" 4 5 #include <filesystem> 6 7 #include <appmodel.h> 8 #include <shellapi.h> 9 #include <ShlObj.h> 10 #include <shobjidl.h> 11 #include <tlhelp32.h> 12 #include <wrl.h> 13 #include <propkey.h> 14 15 #include <wil/com.h> 16 17 #include <common/logger/logger.h> 18 #include <common/utils/winapi_error.h> 19 20 #include <WorkspacesLib/AppUtils.h> 21 #include <WorkspacesLib/CommandLineArgsHelper.h> 22 #include <WorkspacesLib/StringUtils.h> 23 24 namespace Utils 25 { 26 namespace NonLocalizable 27 { 28 const std::wstring EdgeAppIdIdentifier = L"--app-id="; 29 const std::wstring ChromeAppIdIdentifier = L"Chrome._crx_"; 30 const std::wstring ChromeBase = L"Google\\Chrome\\User Data\\Default\\Web Applications"; 31 const std::wstring EdgeBase = L"Microsoft\\Edge\\User Data\\Default\\Web Applications"; 32 const std::wstring ChromeDirPrefix = L"_crx_"; 33 const std::wstring EdgeDirPrefix = L"_crx__"; 34 const std::wstring IcoExtension = L".ico"; 35 } 36 37 static const std::wstring& GetLocalAppDataFolder() 38 { 39 static std::wstring localFolder{}; 40 41 if (localFolder.empty()) 42 { 43 wil::unique_cotaskmem_string folderPath; 44 HRESULT hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &folderPath); 45 if (SUCCEEDED(hres)) 46 { 47 localFolder = folderPath.get(); 48 } 49 else 50 { 51 Logger::error(L"Failed to get the local app data folder path: {}", get_last_error_or_default(hres)); 52 localFolder = L""; // Ensure it is explicitly set to empty on failure 53 } 54 } 55 56 return localFolder; 57 } 58 59 // Finds all PwaHelper.exe processes with the specified parent process ID 60 std::vector<DWORD> FindPwaHelperProcessIds() 61 { 62 std::vector<DWORD> pwaHelperProcessIds; 63 const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 64 if (hSnapshot == INVALID_HANDLE_VALUE) 65 { 66 Logger::info(L"Invalid handle when creating snapshot for the search for PwaHelper processes"); 67 return pwaHelperProcessIds; 68 } 69 70 PROCESSENTRY32 pe; 71 pe.dwSize = sizeof(PROCESSENTRY32); 72 73 if (Process32First(hSnapshot, &pe)) 74 { 75 do 76 { 77 if (_wcsicmp(pe.szExeFile, L"PwaHelper.exe") == 0) 78 { 79 Logger::info(L"Found a PWA process with id {}", pe.th32ProcessID); 80 pwaHelperProcessIds.push_back(pe.th32ProcessID); 81 } 82 } while (Process32Next(hSnapshot, &pe)); 83 } 84 85 CloseHandle(hSnapshot); 86 return pwaHelperProcessIds; 87 } 88 89 PwaHelper::PwaHelper() 90 { 91 InitChromeAppIds(); 92 InitEdgeAppIds(); 93 } 94 95 void PwaHelper::InitAppIds(const std::wstring& browserDataFolder, const std::wstring& browserDirPrefix, const std::function<void(const std::wstring&)>& addingAppIdCallback) 96 { 97 std::filesystem::path folderPath(GetLocalAppDataFolder()); 98 folderPath.append(browserDataFolder); 99 if (!std::filesystem::exists(folderPath)) 100 { 101 Logger::info(L"Edge base path does not exist: {}", folderPath.wstring()); 102 return; 103 } 104 105 try 106 { 107 for (const auto& directory : std::filesystem::directory_iterator(folderPath)) 108 { 109 if (!directory.is_directory()) 110 { 111 continue; 112 } 113 114 const std::wstring directoryName = directory.path().filename(); 115 if (directoryName.find(browserDirPrefix) != 0) 116 { 117 continue; 118 } 119 120 const std::wstring appId = directoryName.substr(browserDirPrefix.length()); 121 if (addingAppIdCallback) 122 { 123 addingAppIdCallback(appId); 124 } 125 126 for (const auto& filename : std::filesystem::directory_iterator(directory)) 127 { 128 if (!filename.is_directory()) 129 { 130 const std::filesystem::path filenameString = filename.path().filename(); 131 if (StringUtils::CaseInsensitiveEquals(filenameString.extension(), NonLocalizable::IcoExtension)) 132 { 133 const auto stem = filenameString.stem().wstring(); 134 m_pwaAppIdsToAppNames.insert({ appId, stem }); 135 Logger::info(L"Found an installed Pwa app {} with PwaAppId {}", stem, appId); 136 break; 137 } 138 } 139 } 140 } 141 } 142 catch (std::exception& ex) 143 { 144 Logger::error("Failed to iterate over the directory: {}", ex.what()); 145 } 146 } 147 148 void PwaHelper::InitEdgeAppIds() 149 { 150 if (!m_edgeAppIds.empty()) 151 { 152 // already initialized 153 return; 154 } 155 156 CommandLineArgsHelper commandLineArgsHelper{}; 157 158 const auto pwaHelperProcessIds = FindPwaHelperProcessIds(); 159 Logger::info(L"Found {} edge Pwa helper processes", pwaHelperProcessIds.size()); 160 161 for (const auto subProcessID : pwaHelperProcessIds) 162 { 163 std::wstring aumidID = GetAUMIDFromProcessId(subProcessID); 164 std::wstring commandLineArg = commandLineArgsHelper.GetCommandLineArgs(subProcessID); 165 std::wstring appId = GetAppIdFromCommandLineArgs(commandLineArg); 166 167 m_edgeAppIds.insert({ aumidID, appId }); 168 Logger::info(L"Found an edge Pwa helper process with AumidID {} and PwaAppId {}", aumidID, appId); 169 } 170 171 InitAppIds(NonLocalizable::EdgeBase, NonLocalizable::EdgeDirPrefix, [&](const std::wstring&) {}); 172 } 173 174 void PwaHelper::InitChromeAppIds() 175 { 176 if (!m_chromeAppIds.empty()) 177 { 178 // already initialized 179 return; 180 } 181 182 InitAppIds(NonLocalizable::ChromeBase, NonLocalizable::ChromeDirPrefix, [&](const std::wstring& appId) { 183 m_chromeAppIds.push_back(appId); 184 }); 185 } 186 187 std::optional<std::wstring> PwaHelper::GetEdgeAppId(const std::wstring& windowAumid) const 188 { 189 const auto pwaIndex = m_edgeAppIds.find(windowAumid); 190 if (pwaIndex != m_edgeAppIds.end()) 191 { 192 return pwaIndex->second; 193 } 194 195 return std::nullopt; 196 } 197 198 std::optional<std::wstring> PwaHelper::GetChromeAppId(const std::wstring& windowAumid) const 199 { 200 const auto appIdIndexStart = windowAumid.find(NonLocalizable::ChromeAppIdIdentifier); 201 if (appIdIndexStart != std::wstring::npos) 202 { 203 std::wstring windowAumidSub = windowAumid.substr(appIdIndexStart + NonLocalizable::ChromeAppIdIdentifier.size()); 204 const auto appIdIndexEnd = windowAumidSub.find(L" "); 205 if (appIdIndexEnd != std::wstring::npos) 206 { 207 windowAumidSub = windowAumidSub.substr(0, appIdIndexEnd); 208 } 209 210 const std::wstring windowAumidBegin = windowAumidSub.substr(0, 10); 211 for (const auto chromeAppId : m_chromeAppIds) 212 { 213 if (chromeAppId.find(windowAumidBegin) == 0) 214 { 215 return chromeAppId; 216 } 217 } 218 } 219 220 return std::nullopt; 221 } 222 223 std::wstring PwaHelper::SearchPwaName(const std::wstring& pwaAppId, const std::wstring& windowAumid) const 224 { 225 const auto index = m_pwaAppIdsToAppNames.find(pwaAppId); 226 if (index != m_pwaAppIdsToAppNames.end()) 227 { 228 return index->second; 229 } 230 231 std::wstring nameFromAumid{ windowAumid }; 232 const std::size_t delimiterPos = nameFromAumid.find(L"-"); 233 if (delimiterPos != std::string::npos) 234 { 235 return nameFromAumid.substr(0, delimiterPos); 236 } 237 238 return nameFromAumid; 239 } 240 241 std::wstring PwaHelper::GetAppIdFromCommandLineArgs(const std::wstring& commandLineArgs) const 242 { 243 auto result = commandLineArgs; 244 245 // remove the prefix 246 if (result.find(NonLocalizable::EdgeAppIdIdentifier) == 0) 247 { 248 result.erase(0, NonLocalizable::EdgeAppIdIdentifier.length()); 249 } 250 251 // remove the suffix 252 auto appIdIndexEnd = result.find(L" "); 253 if (appIdIndexEnd != std::wstring::npos) 254 { 255 result = result.substr(0, appIdIndexEnd); 256 } 257 258 return result; 259 } 260 }