PredefinedInstalledSourceFactory.cpp (21624B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Microsoft/ARPHelper.h" 5 #include "Microsoft/PredefinedInstalledSourceFactory.h" 6 #include "Microsoft/SQLiteIndex.h" 7 #include "Microsoft/SQLiteIndexSource.h" 8 #include <winget/ManifestInstaller.h> 9 #include <winget/COMStaticStorage.h> 10 #include <winget/Registry.h> 11 #include <AppInstallerArchitecture.h> 12 #include <winget/ExperimentalFeature.h> 13 14 using namespace std::string_literals; 15 using namespace std::string_view_literals; 16 17 namespace AppInstaller::Repository::Microsoft 18 { 19 namespace 20 { 21 std::optional<std::string> GetCachedMSIXName(const Utility::NormalizedString& id, const Utility::Version& version, SQLiteIndex& cacheData) 22 { 23 SearchRequest searchRequest; 24 searchRequest.Inclusions.emplace_back(PackageMatchField::Id, MatchType::Exact, id); 25 26 SQLiteIndex::SearchResult searchResult = cacheData.Search(searchRequest); 27 28 if (searchResult.Matches.empty()) 29 { 30 return std::nullopt; 31 } 32 33 if (searchResult.Matches.size() != 1) 34 { 35 // This is very unexpected, but just log it and carry on 36 AICLI_LOG(Repo, Warning, << "Found multiple (" << searchResult.Matches.size() << ") cache entries for: " << id); 37 return std::nullopt; 38 } 39 40 auto versionKeys = cacheData.GetVersionKeysById(searchResult.Matches[0].first); 41 const SQLiteIndex::VersionKey* versionKey = nullptr; 42 43 for (const auto& key : versionKeys) 44 { 45 if (key.VersionAndChannel.GetVersion() == version) 46 { 47 versionKey = &key; 48 break; 49 } 50 } 51 52 if (!versionKey) 53 { 54 return std::nullopt; 55 } 56 57 return cacheData.GetPropertyByPrimaryId(versionKey->ManifestId, PackageVersionProperty::Name); 58 } 59 60 // Populates the index with the entries from MSIX. 61 void PopulateIndexFromMSIX(SQLiteIndex& index, Manifest::ScopeEnum scope, SQLiteIndex* cacheData = nullptr) 62 { 63 using namespace winrt::Windows::ApplicationModel; 64 using namespace winrt::Windows::Management::Deployment; 65 using namespace winrt::Windows::Foundation::Collections; 66 67 AICLI_LOG(Repo, Verbose, << "Examining MSIX entries for " << ScopeToString(scope)); 68 69 IIterable<Package> packages; 70 PackageManager packageManager; 71 72 if (scope == Manifest::ScopeEnum::Machine) 73 { 74 // May not be present on our oldest supported systems; simply ignore for the time being. 75 IPackageManager9 packageManager9 = packageManager.try_as<IPackageManager9>(); 76 if (packageManager9) 77 { 78 try 79 { 80 packages = packageManager.FindProvisionedPackages(); 81 } 82 catch (const winrt::hresult_error& hre) 83 { 84 // Historically this API has not been accessible unelevated; if it fails, try to carry on 85 AICLI_LOG(Repo, Warning, << "FindProvisionedPackages failed, bypassing provisioned packages: 0x" << Logging::SetHRFormat << hre.code()); 86 } 87 } 88 else 89 { 90 AICLI_LOG(Repo, Warning, << "FindProvisionedPackages is not available on this version of Windows"); 91 } 92 } 93 else 94 { 95 // TODO: Consider if Optional packages should also be enumerated 96 for (PackageTypes types : { PackageTypes::Main | PackageTypes::Framework, PackageTypes::Main, PackageTypes::Framework }) 97 { 98 try 99 { 100 packages = packageManager.FindPackagesForUserWithPackageTypes({}, types); 101 break; 102 } 103 catch (const winrt::hresult_error& hre) 104 { 105 if (hre.code() == E_NOT_SET) 106 { 107 // This OS issue occurs frequently enough that we will attempt to work around it by enumerating progressively fewer packages 108 AICLI_LOG(Repo, Warning, << "FindPackagesForUserWithPackageTypes returned E_NOT_SET for types: " << ToIntegral(types)); 109 } 110 else 111 { 112 throw; 113 } 114 } 115 } 116 } 117 118 // Failed to retrieve even an empty package list; make sure that these cases have a log to indicate why. 119 if (!packages) 120 { 121 AICLI_LOG(Repo, Warning, << "MSIX package list not populated"); 122 return; 123 } 124 125 // Reuse the same manifest object, as we will be setting the same values every time. 126 Manifest::Manifest manifest; 127 // Add one installer for storing the package family name. 128 manifest.Installers.emplace_back(); 129 // Every package will have the same tags currently. 130 manifest.DefaultLocalization.Add<Manifest::Localization::Tags>({ "msix" }); 131 132 // Fields in the index but not populated: 133 // AppMoniker - Not sure what we would put. 134 // Channel - We don't know this information here. 135 // Commands - We could open the manifest and look for these eventually. 136 // Tags - Not sure what else we could put in here. 137 for (const auto& package : packages) 138 { 139 // System packages are part of the OS, and cannot be managed by the user. 140 // Filter them out as there is no point in showing them in a package manager. 141 auto signatureKind = package.SignatureKind(); 142 if (signatureKind == PackageSignatureKind::System) 143 { 144 continue; 145 } 146 147 auto packageId = package.Id(); 148 Utility::NormalizedString fullName = Utility::ConvertToUTF8(packageId.FullName()); 149 Utility::NormalizedString familyName = Utility::ConvertToUTF8(packageId.FamilyName()); 150 151 manifest.Id = "MSIX\\" + fullName; 152 153 // Get version 154 std::ostringstream strstr; 155 auto packageVersion = packageId.Version(); 156 strstr << packageVersion.Major << '.' << packageVersion.Minor << '.' << packageVersion.Build << '.' << packageVersion.Revision; 157 158 manifest.Version = strstr.str(); 159 160 // Determine package name 161 bool isPackageNameSet = false; 162 163 // Look for the name in the cache data first 164 if (cacheData) 165 { 166 std::optional<std::string> cachedName = GetCachedMSIXName(manifest.Id, manifest.Version, *cacheData); 167 168 if (cachedName) 169 { 170 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>(cachedName.value()); 171 isPackageNameSet = true; 172 } 173 } 174 175 // Attempt to get the DisplayName. Since this will retrieve the localized value, it has a chance to fail. 176 // Rather than completely skip this package in that case, we will simply fall back to using the package name below. 177 if (!isPackageNameSet && !Runtime::IsRunningAsSystem()) 178 { 179 try 180 { 181 auto displayName = Utility::ConvertToUTF8(package.DisplayName()); 182 if (!displayName.empty()) 183 { 184 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>(displayName); 185 isPackageNameSet = true; 186 } 187 } 188 catch (const winrt::hresult_error& hre) 189 { 190 AICLI_LOG(Repo, Warning, << "winrt::hresult_error[0x" << Logging::SetHRFormat << hre.code() << ": " << 191 Utility::ConvertToUTF8(hre.message()) << "] exception thrown when getting DisplayName for " << fullName); 192 } 193 catch (...) 194 { 195 AICLI_LOG(Repo, Warning, << "Unknown exception thrown when getting DisplayName for " << fullName); 196 } 197 } 198 199 if (!isPackageNameSet) 200 { 201 manifest.DefaultLocalization.Add<Manifest::Localization::PackageName>(Utility::ConvertToUTF8(packageId.Name())); 202 } 203 204 manifest.Installers[0].PackageFamilyName = familyName; 205 206 // Use the full name as a unique key for the path 207 auto manifestId = index.AddManifest(manifest); 208 209 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, 210 Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msix)); 211 212 auto architecture = Utility::ConvertToArchitectureEnum(packageId.Architecture()); 213 if (architecture) 214 { 215 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, 216 ToString(architecture.value())); 217 } 218 219 // May not be present on our oldest supported systems; simply ignore for the time being. 220 IPackage8 package8 = package.try_as<IPackage8>(); 221 if (package8) 222 { 223 index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledLocation, 224 Utility::ConvertToUTF8(package8.InstalledPath())); 225 } 226 else 227 { 228 AICLI_LOG(Repo, Warning, << "Windows::ApplicationModel::Package::InstalledPath is not available on this version of Windows"); 229 } 230 } 231 } 232 233 SQLiteIndex CreateAndPopulateIndex(PredefinedInstalledSourceFactory::Filter filter) 234 { 235 AICLI_LOG(Repo, Verbose, << "Creating PredefinedInstalledSource with filter [" << PredefinedInstalledSourceFactory::FilterToString(filter) << ']'); 236 237 // Create an in memory index 238 SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless); 239 240 // Put installed packages into the index 241 if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::ARP || 242 filter == PredefinedInstalledSourceFactory::Filter::User || filter == PredefinedInstalledSourceFactory::Filter::Machine) 243 { 244 ARPHelper arpHelper; 245 if (filter != PredefinedInstalledSourceFactory::Filter::User) 246 { 247 arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::Machine); 248 } 249 if (filter != PredefinedInstalledSourceFactory::Filter::Machine) 250 { 251 arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::User); 252 } 253 } 254 255 if (filter == PredefinedInstalledSourceFactory::Filter::None || 256 filter == PredefinedInstalledSourceFactory::Filter::MSIX || 257 filter == PredefinedInstalledSourceFactory::Filter::User) 258 { 259 PopulateIndexFromMSIX(index, Manifest::ScopeEnum::User); 260 } 261 else if (filter == PredefinedInstalledSourceFactory::Filter::Machine) 262 { 263 PopulateIndexFromMSIX(index, Manifest::ScopeEnum::Machine); 264 } 265 266 AICLI_LOG(Repo, Verbose, << " ... finished creating PredefinedInstalledSource"); 267 268 return index; 269 } 270 271 struct CachedInstalledIndex 272 { 273 struct Singleton : public WinRT::COMStaticStorageBase<CachedInstalledIndex> 274 { 275 Singleton() : COMStaticStorageBase(L"WindowsPackageManager.CachedInstalledIndex") {} 276 }; 277 278 CachedInstalledIndex() 279 { 280 ARPHelper arpHelper; 281 m_registryWatchers = arpHelper.CreateRegistryWatchers(Manifest::ScopeEnum::Unknown, 282 [this](Manifest::ScopeEnum, Utility::Architecture, wil::RegistryChangeKind) { ForceNextUpdate(); }); 283 284 m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentUser(); 285 m_eventRevoker = m_catalog.PackageStatusChanged(winrt::auto_revoke, [this](auto...) { ForceNextUpdate(); }); 286 } 287 288 void UpdateIndexIfNeeded() 289 { 290 auto sharedLock = m_lock.lock_shared(); 291 if (CheckForUpdate()) 292 { 293 // Upgrade to exclusive 294 sharedLock.reset(); 295 auto exclusiveLock = m_lock.lock_exclusive(); 296 297 if (CheckForUpdate()) 298 { 299 // TODO: To support servicing, the initial implementation of update will simply leverage 300 // some data from the existing index to speed up the MSIX populate function. 301 // In a larger update, we may want to make it possible to actually update the cache directly. 302 // We may even persist the cache to disk to speed things up further. 303 304 // Set the update indicator to false before we start reading so that an external change can 305 // reindicate a need to update in the middle. But in the event that we error here, set it back to true 306 // to prevent an error from blocking further attempts. 307 m_forceNextUpdate = false; 308 auto scopeExit = wil::scope_exit([&]() { m_forceNextUpdate = true; }); 309 310 // Populate from ARP using standard mechanism. 311 SQLiteIndex update = CreateAndPopulateIndex(PredefinedInstalledSourceFactory::Filter::ARP); 312 313 // Populate from MSIX, using localization data from the existing index if applicable. 314 PopulateIndexFromMSIX(update, Manifest::ScopeEnum::User, m_index.get()); 315 316 m_index = std::make_unique<SQLiteIndex>(std::move(update)); 317 scopeExit.release(); 318 } 319 } 320 } 321 322 SQLiteIndex GetCopy() 323 { 324 auto lock = m_lock.lock_shared(); 325 THROW_HR_IF(E_POINTER, !m_index); 326 return SQLiteIndex::CopyFrom(SQLITE_MEMORY_DB_CONNECTION_TARGET, *m_index); 327 } 328 329 void ForceNextUpdate() 330 { 331 m_forceNextUpdate = true; 332 } 333 334 private: 335 bool CheckForUpdate() 336 { 337 return (!m_index || m_forceNextUpdate.load()); 338 } 339 340 wil::srwlock m_lock; 341 std::atomic_bool m_forceNextUpdate{ false }; 342 std::unique_ptr<SQLiteIndex> m_index; 343 std::vector<wil::unique_registry_watcher> m_registryWatchers; 344 winrt::Windows::ApplicationModel::PackageCatalog m_catalog = nullptr; 345 decltype(winrt::Windows::ApplicationModel::PackageCatalog{ nullptr }.PackageStatusChanged(winrt::auto_revoke, nullptr)) m_eventRevoker; 346 }; 347 348 struct PredefinedInstalledSourceReference : public ISourceReference 349 { 350 PredefinedInstalledSourceReference(const SourceDetails& details) : m_details(details) 351 { 352 m_details.Identifier = "*PredefinedInstalledSource"; 353 354 if (PredefinedInstalledSourceFactory::StringToFilter(m_details.Arg) == PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate) 355 { 356 GetCachedInstalledIndex()->ForceNextUpdate(); 357 } 358 } 359 360 std::string GetIdentifier() override { return m_details.Identifier; } 361 362 SourceDetails& GetDetails() override { return m_details; }; 363 364 std::shared_ptr<ISource> Open(IProgressCallback& progress) override 365 { 366 // TODO: Maybe we do need to use it? 367 UNREFERENCED_PARAMETER(progress); 368 369 // Determine the filter 370 PredefinedInstalledSourceFactory::Filter filter = PredefinedInstalledSourceFactory::StringToFilter(m_details.Arg); 371 372 // Only cache for the unfiltered install data 373 if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate) 374 { 375 std::shared_ptr<CachedInstalledIndex> cachedIndex = GetCachedInstalledIndex(); 376 cachedIndex->UpdateIndexIfNeeded(); 377 return std::make_shared<SQLiteIndexSource>(m_details, cachedIndex->GetCopy(), true); 378 } 379 else 380 { 381 return std::make_shared<SQLiteIndexSource>(m_details, CreateAndPopulateIndex(filter), true); 382 } 383 } 384 385 private: 386 std::shared_ptr<CachedInstalledIndex> GetCachedInstalledIndex() 387 { 388 static CachedInstalledIndex::Singleton s_installedIndex; 389 return s_installedIndex.Get(); 390 } 391 392 SourceDetails m_details; 393 }; 394 395 // The factory for the predefined installed source. 396 struct Factory : public ISourceFactory 397 { 398 std::string_view TypeName() const override final 399 { 400 return PredefinedInstalledSourceFactory::Type(); 401 } 402 403 std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final 404 { 405 THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedInstalledSourceFactory::Type()); 406 407 return std::make_shared<PredefinedInstalledSourceReference>(details); 408 } 409 410 bool Add(SourceDetails&, IProgressCallback&) override final 411 { 412 // Add should never be needed, as this is predefined. 413 THROW_HR(E_NOTIMPL); 414 } 415 416 bool Update(const SourceDetails&, IProgressCallback&) override final 417 { 418 // Update could be used later, but not for now. 419 THROW_HR(E_NOTIMPL); 420 } 421 422 bool Remove(const SourceDetails&, IProgressCallback&) override final 423 { 424 // Similar to add, remove should never be needed. 425 THROW_HR(E_NOTIMPL); 426 } 427 }; 428 } 429 430 std::string_view PredefinedInstalledSourceFactory::FilterToString(Filter filter) 431 { 432 switch (filter) 433 { 434 case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::None: 435 return "None"sv; 436 case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::ARP: 437 return "ARP"sv; 438 case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX: 439 return "MSIX"sv; 440 case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::User: 441 return "User"sv; 442 case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::Machine: 443 return "Machine"sv; 444 case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate: 445 return "NoneWithForcedCacheUpdate"sv; 446 default: 447 return "Unknown"sv; 448 } 449 } 450 451 PredefinedInstalledSourceFactory::Filter PredefinedInstalledSourceFactory::StringToFilter(std::string_view filter) 452 { 453 if (filter == FilterToString(Filter::ARP)) 454 { 455 return Filter::ARP; 456 } 457 else if (filter == FilterToString(Filter::MSIX)) 458 { 459 return Filter::MSIX; 460 } 461 else if (filter == FilterToString(Filter::User)) 462 { 463 return Filter::User; 464 } 465 else if (filter == FilterToString(Filter::Machine)) 466 { 467 return Filter::Machine; 468 } 469 else if (filter == FilterToString(Filter::NoneWithForcedCacheUpdate)) 470 { 471 return Filter::NoneWithForcedCacheUpdate; 472 } 473 else 474 { 475 return Filter::None; 476 } 477 } 478 479 std::unique_ptr<ISourceFactory> PredefinedInstalledSourceFactory::Create() 480 { 481 return std::make_unique<Factory>(); 482 } 483 }