winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }