winget-cli

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

PreIndexedPackageSourceFactory.cpp (32841B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Microsoft/PreIndexedPackageSourceFactory.h"
      5 #include "Microsoft/SQLiteIndex.h"
      6 #include "Microsoft/SQLiteIndexSource.h"
      7 #include "SourceUpdateChecks.h"
      8 
      9 #include <AppInstallerDateTime.h>
     10 #include <AppInstallerDeployment.h>
     11 #include <AppInstallerDownloader.h>
     12 #include <AppInstallerMsixInfo.h>
     13 #include <winget/ManagedFile.h>
     14 #include <winget/ExperimentalFeature.h>
     15 
     16 using namespace std::string_literals;
     17 using namespace std::string_view_literals;
     18 
     19 namespace AppInstaller::Repository::Microsoft
     20 {
     21     namespace
     22     {
     23         static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageFileName = "source.msix"sv;
     24         static constexpr std::string_view s_PreIndexedPackageSourceFactory_V2_PackageFileName = "source2.msix"sv;
     25         static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageVersionHeader = "x-ms-meta-sourceversion"sv;
     26         static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFileName = "index.db"sv;
     27         // TODO: This being hard coded to force using the Public directory name is not ideal.
     28         static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFilePath = "Public\\index.db"sv;
     29 
     30         // Construct the package location from the given details.
     31         // Currently expects that the arg is an https uri pointing to the root of the data.
     32         std::string GetPackageLocation(const std::string& basePath, std::string_view fileName)
     33         {
     34             std::string result = basePath;
     35             if (result.back() != '/')
     36             {
     37                 result += '/';
     38             }
     39             result += fileName;
     40             return result;
     41         }
     42 
     43         // Gets the set of package locations that should be tried, in order.
     44         std::vector<std::string> GetPackageLocations(const SourceDetails& details)
     45         {
     46             THROW_HR_IF(E_INVALIDARG, details.Arg.empty());
     47 
     48             std::vector<std::string> result;
     49 
     50             result.emplace_back(GetPackageLocation(details.Arg, s_PreIndexedPackageSourceFactory_V2_PackageFileName));
     51             result.emplace_back(GetPackageLocation(details.Arg, s_PreIndexedPackageSourceFactory_PackageFileName));
     52 
     53             if (!details.AlternateArg.empty())
     54             {
     55                 result.emplace_back(GetPackageLocation(details.AlternateArg, s_PreIndexedPackageSourceFactory_V2_PackageFileName));
     56                 result.emplace_back(GetPackageLocation(details.AlternateArg, s_PreIndexedPackageSourceFactory_PackageFileName));
     57             }
     58 
     59             return result;
     60         }
     61 
     62         // Abstracts the fallback for package location when the MsixInfo is needed.
     63         struct PreIndexedPackageInfo
     64         {
     65             template <typename LocationCheck>
     66             PreIndexedPackageInfo(const SourceDetails& details, LocationCheck&& locationCheck)
     67             {
     68                 std::vector<std::string> potentialLocations = GetPackageLocations(details);
     69 
     70                 for (const auto& location : potentialLocations)
     71                 {
     72                     locationCheck(location);
     73                 }
     74 
     75                 std::exception_ptr primaryException;
     76 
     77                 for (const auto& location : potentialLocations)
     78                 {
     79                     try
     80                     {
     81                         m_msixInfo = std::make_unique<Msix::MsixInfo>(location);
     82                         m_packageLocation = location;
     83                         return;
     84                     }
     85                     catch (...)
     86                     {
     87                         LOG_CAUGHT_EXCEPTION_MSG("PreIndexedPackageInfo failed on location: %hs", location.c_str());
     88                         if (!primaryException)
     89                         {
     90                             primaryException = std::current_exception();
     91                         }
     92                     }
     93                 }
     94 
     95                 std::rethrow_exception(primaryException);
     96             }
     97 
     98             const std::string& PackageLocation() const { return m_packageLocation; }
     99             Msix::MsixInfo& MsixInfo() { return *m_msixInfo; }
    100 
    101         private:
    102             std::string m_packageLocation;
    103             std::unique_ptr<Msix::MsixInfo> m_msixInfo;
    104         };
    105 
    106         // Abstracts the fallback for package location when an update is being done.
    107         struct PreIndexedPackageUpdateCheck
    108         {
    109             PreIndexedPackageUpdateCheck(const SourceDetails& details)
    110             {
    111                 std::vector<std::string> potentialLocations = GetPackageLocations(details);
    112 
    113                 std::exception_ptr primaryException;
    114 
    115                 for (const auto& location : potentialLocations)
    116                 {
    117                     try
    118                     {
    119                         m_availableVersion = GetAvailableVersionFrom(location);
    120                         m_packageLocation = location;
    121                         return;
    122                     }
    123                     catch (...)
    124                     {
    125                         LOG_CAUGHT_EXCEPTION_MSG("PreIndexedPackageUpdateCheck failed on location: %hs", location.c_str());
    126                         if (!primaryException)
    127                         {
    128                             primaryException = std::current_exception();
    129                         }
    130                     }
    131                 }
    132 
    133                 std::rethrow_exception(primaryException);
    134             }
    135 
    136             const std::string& PackageLocation() const { return m_packageLocation; }
    137             const Msix::PackageVersion& AvailableVersion() const { return m_availableVersion; }
    138 
    139         private:
    140             std::string m_packageLocation;
    141             Msix::PackageVersion m_availableVersion;
    142 
    143             Msix::PackageVersion GetAvailableVersionFrom(const std::string& packageLocation)
    144             {
    145                 if (Utility::IsUrlRemote(packageLocation))
    146                 {
    147                     std::map<std::string, std::string> headers = Utility::GetHeaders(packageLocation);
    148                     auto itr = headers.find(std::string{ s_PreIndexedPackageSourceFactory_PackageVersionHeader });
    149                     if (itr != headers.end())
    150                     {
    151                         AICLI_LOG(Repo, Verbose, << "Header indicates version is: " << itr->second);
    152                         return { itr->second };
    153                     }
    154 
    155                     // We did not find the header we were looking for, log the ones we did find
    156                     AICLI_LOG(Repo, Verbose, << "Did not find " << s_PreIndexedPackageSourceFactory_PackageVersionHeader << " in:\n" << [&]()
    157                         {
    158                             std::ostringstream headerLog;
    159                             for (const auto& header : headers)
    160                             {
    161                                 headerLog << "  " << header.first << " : " << header.second << '\n';
    162                             }
    163                             return std::move(headerLog).str();
    164                         }());
    165                 }
    166 
    167                 AICLI_LOG(Repo, Verbose, << "Reading package data to determine version");
    168                 Msix::MsixInfo info{ packageLocation };
    169                 auto manifest = info.GetAppPackageManifests();
    170 
    171                 THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, manifest.size() > 1);
    172                 THROW_HR_IF(E_UNEXPECTED, manifest.size() == 0);
    173 
    174                 return manifest[0].GetIdentity().GetVersion();
    175             }
    176         };
    177 
    178         // Gets the package family name from the details.
    179         std::string GetPackageFamilyNameFromDetails(const SourceDetails& details)
    180         {
    181             THROW_HR_IF(E_UNEXPECTED, details.Data.empty());
    182             return details.Data;
    183         }
    184 
    185         // Creates a name for the cross process reader-writer lock given the details.
    186         std::string CreateNameForCPL(const SourceDetails& details)
    187         {
    188             // The only relevant data is the package family name
    189             return "PreIndexedSourceCPL_"s + GetPackageFamilyNameFromDetails(details);
    190         }
    191 
    192         // The base class for a package that comes from a preindexed packaged source.
    193         struct PreIndexedFactoryBase : public ISourceFactory
    194         {
    195             std::string_view TypeName() const override final
    196             {
    197                 return PreIndexedPackageSourceFactory::Type();
    198             }
    199 
    200             std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final
    201             {
    202                 // With more than one source implementation, we will probably need to probe first
    203                 THROW_HR_IF(E_INVALIDARG, !details.Type.empty() && details.Type != PreIndexedPackageSourceFactory::Type());
    204 
    205                 return CreateInternal(details);
    206             }
    207 
    208             virtual std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) = 0;
    209 
    210             bool Add(SourceDetails& details, IProgressCallback& progress) override final
    211             {
    212                 if (details.Type.empty())
    213                 {
    214                     // With more than one source implementation, we will probably need to probe first
    215                     details.Type = PreIndexedPackageSourceFactory::Type();
    216                     AICLI_LOG(Repo, Info, << "Initializing source type: " << details.Name << " => " << details.Type);
    217                 }
    218                 else
    219                 {
    220                     THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type());
    221                 }
    222 
    223                 PreIndexedPackageInfo packageInfo(details, [](const std::string& packageLocation)
    224                     {
    225                         THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, Utility::IsUrlRemote(packageLocation) && !Utility::IsUrlSecure(packageLocation));
    226                     });
    227 
    228                 AICLI_LOG(Repo, Info, << "Initializing source from: " << details.Name << " => " << packageInfo.PackageLocation());
    229 
    230                 THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, packageInfo.MsixInfo().GetIsBundle());
    231 
    232                 auto fullName = packageInfo.MsixInfo().GetPackageFullName();
    233                 AICLI_LOG(Repo, Info, << "Found package full name: " << details.Name << " => " << fullName);
    234 
    235                 details.Data = Msix::GetPackageFamilyNameFromFullName(fullName);
    236                 details.Identifier = Msix::GetPackageFamilyNameFromFullName(fullName);
    237 
    238                 auto lock = LockExclusive(details, progress);
    239                 if (!lock)
    240                 {
    241                     return false;
    242                 }
    243 
    244                 return UpdateInternal(packageInfo.PackageLocation(), details, progress);
    245             }
    246 
    247             bool Update(const SourceDetails& details, IProgressCallback& progress) override final
    248             {
    249                 return UpdateBase(details, false, progress);
    250             }
    251 
    252             bool BackgroundUpdate(const SourceDetails& details, IProgressCallback& progress) override final
    253             {
    254                 return UpdateBase(details, true, progress);
    255             }
    256 
    257             // Retrieves the currently cached version of the package.
    258             virtual std::optional<Msix::PackageVersion> GetCurrentVersion(const SourceDetails& details) = 0;
    259 
    260             virtual bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress) = 0;
    261 
    262             bool Remove(const SourceDetails& details, IProgressCallback& progress) override final
    263             {
    264                 THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type());
    265                 auto lock = LockExclusive(details, progress);
    266                 if (!lock)
    267                 {
    268                     return false;
    269                 }
    270 
    271                 return RemoveInternal(details, progress);
    272             }
    273 
    274             virtual bool RemoveInternal(const SourceDetails& details, IProgressCallback&) = 0;
    275 
    276         private:
    277             Synchronization::CrossProcessLock LockExclusive(const SourceDetails& details, IProgressCallback& progress, bool isBackground = false)
    278             {
    279                 Synchronization::CrossProcessLock result(CreateNameForCPL(details));
    280 
    281                 if (isBackground)
    282                 {
    283                     // If this is a background update, don't wait on the lock.
    284                     result.TryAcquireNoWait();
    285                 }
    286                 else
    287                 {
    288                     result.Acquire(progress);
    289                 }
    290 
    291                 return result;
    292             }
    293 
    294             bool UpdateBase(const SourceDetails& details, bool isBackground, IProgressCallback& progress)
    295             {
    296                 THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type());
    297 
    298                 std::optional<Msix::PackageVersion> currentVersion = GetCurrentVersion(details);
    299                 PreIndexedPackageUpdateCheck updateCheck(details);
    300 
    301                 if (currentVersion)
    302                 {
    303                     if (currentVersion.value() >= updateCheck.AvailableVersion())
    304                     {
    305                         AICLI_LOG(Repo, Verbose, << "Remote source data (" << updateCheck.AvailableVersion().ToString() <<
    306                             ") was not newer than existing (" << currentVersion.value().ToString() << "), no update needed");
    307                         return true;
    308                     }
    309                     else
    310                     {
    311                         AICLI_LOG(Repo, Verbose, << "Remote source data (" << updateCheck.AvailableVersion().ToString() <<
    312                             ") was newer than existing (" << currentVersion.value().ToString() << "), updating");
    313                     }
    314                 }
    315 
    316                 if (progress.IsCancelledBy(CancelReason::Any))
    317                 {
    318                     AICLI_LOG(Repo, Info, << "Cancelling update upon request");
    319                     return false;
    320                 }
    321 
    322                 auto lock = LockExclusive(details, progress, isBackground);
    323                 if (!lock)
    324                 {
    325                     return false;
    326                 }
    327 
    328                 return UpdateInternal(updateCheck.PackageLocation(), details, progress);
    329             }
    330         };
    331 
    332         // *Should only be called when under a CrossProcessReaderWriteLock*
    333         std::optional<Deployment::Extension> GetExtensionFromDetails(const SourceDetails& details)
    334         {
    335             Deployment::ExtensionCatalog catalog(Deployment::SourceExtensionName);
    336             return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId);
    337         }
    338 
    339         std::optional<Msix::PackageVersion> PackagedContextGetCurrentVersion(const SourceDetails& details)
    340         {
    341             auto extension = GetExtensionFromDetails(details);
    342 
    343             if (extension)
    344             {
    345                 auto version = extension->GetPackageVersion();
    346                 return Msix::PackageVersion{ version.Major, version.Minor, version.Build, version.Revision };
    347             }
    348             else
    349             {
    350                 return std::nullopt;
    351             }
    352         }
    353 
    354         // Constructs the location that we will write files to.
    355         std::filesystem::path GetStatePathFromDetails(const SourceDetails& details)
    356         {
    357             std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState);
    358             result /= PreIndexedPackageSourceFactory::Type();
    359             result /= GetPackageFamilyNameFromDetails(details);
    360             return result;
    361         }
    362 
    363         std::optional<Msix::PackageVersion> DesktopContextGetCurrentVersion(const SourceDetails& details)
    364         {
    365             std::filesystem::path packageState = GetStatePathFromDetails(details);
    366             std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName;
    367 
    368             if (std::filesystem::exists(packagePath))
    369             {
    370                 // If we already have a trusted index package, use it to determine if we need to update or not.
    371                 Msix::WriteLockedMsixFile indexPackage{ packagePath };
    372                 if (indexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin)))
    373                 {
    374                     Msix::MsixInfo msixInfo{ packagePath };
    375                     auto manifest = msixInfo.GetAppPackageManifests();
    376 
    377                     if (manifest.size() == 1)
    378                     {
    379                         return manifest[0].GetIdentity().GetVersion();
    380                     }
    381                 }
    382             }
    383 
    384             return std::nullopt;
    385         }
    386 
    387         bool CheckForUpdateBeforeOpen(const SourceDetails& details, std::optional<Msix::PackageVersion> currentVersion, const std::optional<TimeSpan>& requestedUpdateInterval)
    388         {
    389             // If we can't find a good package, then we have to update to operate
    390             if (!currentVersion)
    391             {
    392                 AICLI_LOG(Repo, Verbose, << "Source `" << details.Name << "` has no data");
    393                 return true;
    394             }
    395 
    396             using namespace std::chrono_literals;
    397             using clock = std::chrono::system_clock;
    398 
    399             // Attempt to convert the package version to a time_point
    400             clock::time_point versionTime = Utility::GetTimePointFromVersion(currentVersion.value());
    401 
    402             // Since we expect that the version time indicates creation time, don't let it be far in the future.
    403             auto now = clock::now();
    404             if (versionTime > now && versionTime - now > 24h)
    405             {
    406                 versionTime = clock::time_point::min();
    407             }
    408 
    409             // Use the later of the version and last update times
    410             clock::time_point timeToCheck = (versionTime > details.LastUpdateTime ? versionTime : details.LastUpdateTime);
    411 
    412             return IsAfterUpdateCheckTime(details.Name, timeToCheck, requestedUpdateInterval);
    413         }
    414 
    415         struct PackagedContextSourceReference : public ISourceReference
    416         {
    417             PackagedContextSourceReference(const SourceDetails& details) : m_details(details)
    418             {
    419                 if (!m_details.Data.empty())
    420                 {
    421                     m_details.Identifier = GetPackageFamilyNameFromDetails(details);
    422                 }
    423             }
    424 
    425             std::string GetIdentifier() override { return m_details.Identifier; }
    426 
    427             SourceDetails& GetDetails() override { return m_details; };
    428 
    429             bool ShouldUpdateBeforeOpen(const std::optional<TimeSpan>& requestedUpdateInterval) override
    430             {
    431                 return CheckForUpdateBeforeOpen(m_details, PackagedContextGetCurrentVersion(m_details), requestedUpdateInterval);
    432             }
    433 
    434             std::shared_ptr<ISource> Open(IProgressCallback& progress) override
    435             {
    436                 Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details));
    437                 if (!lock.Acquire(progress))
    438                 {
    439                     return {};
    440                 }
    441 
    442                 auto extension = GetExtensionFromDetails(m_details);
    443                 if (!extension)
    444                 {
    445                     AICLI_LOG(Repo, Info, << "Package not found " << m_details.Data);
    446                     THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING);
    447                 }
    448 
    449                 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NEEDS_REMEDIATION), !extension->VerifyContentIntegrity(progress));
    450 
    451                 // To work around an issue with accessing the public folder, we are temporarily
    452                 // constructing the location ourself.  This was already the case for the non-packaged
    453                 // runtime, and we can fix both in the future.  The only problem with this is that
    454                 // the directory in the extension *must* be Public, rather than one set by the creator.
    455                 std::filesystem::path indexLocation = extension->GetPackagePath();
    456                 indexLocation /= s_PreIndexedPackageSourceFactory_IndexFilePath;
    457 
    458                 SQLiteIndex index = SQLiteIndex::Open(indexLocation.u8string(), SQLiteIndex::OpenDisposition::Immutable);
    459 
    460                 // We didn't use to store the source identifier, so we compute it here in case it's
    461                 // missing from the details.
    462                 m_details.Identifier = GetPackageFamilyNameFromDetails(m_details);
    463                 return std::make_shared<SQLiteIndexSource>(m_details, std::move(index), false, true);
    464             }
    465 
    466         private:
    467             SourceDetails m_details;
    468         };
    469 
    470         // Source factory for running within a packaged context
    471         struct PackagedContextFactory : public PreIndexedFactoryBase
    472         {
    473             std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) override
    474             {
    475                 return std::make_shared<PackagedContextSourceReference>(details);
    476             }
    477 
    478             std::optional<Msix::PackageVersion> GetCurrentVersion(const SourceDetails& details) override
    479             {
    480                 return PackagedContextGetCurrentVersion(details);
    481             }
    482 
    483             bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress) override
    484             {
    485                 // Due to complications with deployment, download the file and deploy from
    486                 // a local source while we investigate further.
    487                 bool download = Utility::IsUrlRemote(packageLocation);
    488                 std::filesystem::path localFile;
    489 
    490                 if (download)
    491                 {
    492                     localFile = Runtime::GetPathTo(Runtime::PathName::Temp);
    493                     localFile /= GetPackageFamilyNameFromDetails(details) + ".msix";
    494 
    495                     Utility::Download(packageLocation, localFile, Utility::DownloadType::Index, progress);
    496                 }
    497                 else
    498                 {
    499                     localFile = Utility::ConvertToUTF16(packageLocation);
    500                 }
    501 
    502                 // Verify the local file
    503                 Msix::WriteLockedMsixFile fileLock{ localFile };
    504                 Msix::MsixInfo localMsixInfo{ localFile };
    505 
    506                 // The package should not be a bundle
    507                 THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, localMsixInfo.GetIsBundle());
    508 
    509                 // Ensure that family name has not changed
    510                 THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE,
    511                     GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(localMsixInfo.GetPackageFullName()));
    512 
    513                 if (!fileLock.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin)))
    514                 {
    515                     AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation.");
    516                     THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE);
    517                 }
    518 
    519                 winrt::Windows::Foundation::Uri uri = winrt::Windows::Foundation::Uri(localFile.c_str());
    520                 Deployment::AddPackage(
    521                     uri,
    522                     Deployment::Options{ WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted) },
    523                     progress);
    524 
    525                 if (download)
    526                 {
    527                     try
    528                     {
    529                         // If successful, delete the file
    530                         std::filesystem::remove(localFile);
    531                     }
    532                     CATCH_LOG();
    533                 }
    534 
    535                 return true;
    536             }
    537 
    538             bool RemoveInternal(const SourceDetails& details, IProgressCallback& callback) override
    539             {
    540                 auto fullName = Msix::GetPackageFullNameFromFamilyName(GetPackageFamilyNameFromDetails(details));
    541 
    542                 if (!fullName)
    543                 {
    544                     AICLI_LOG(Repo, Info, << "No full name found for family name: " << GetPackageFamilyNameFromDetails(details));
    545                 }
    546                 else
    547                 {
    548                     AICLI_LOG(Repo, Info, << "Removing package: " << *fullName);
    549                     Deployment::RemovePackage(*fullName, winrt::Windows::Management::Deployment::RemovalOptions::None, callback);
    550                 }
    551 
    552                 return true;
    553             }
    554         };
    555 
    556         struct DesktopContextSourceReference : public ISourceReference
    557         {
    558             DesktopContextSourceReference(const SourceDetails& details) : m_details(details)
    559             {
    560                 if (!m_details.Data.empty())
    561                 {
    562                     m_details.Identifier = GetPackageFamilyNameFromDetails(details);
    563                 }
    564             }
    565 
    566             std::string GetIdentifier() override { return m_details.Identifier; }
    567 
    568             SourceDetails& GetDetails() override { return m_details; };
    569 
    570             bool ShouldUpdateBeforeOpen(const std::optional<TimeSpan>& requestedUpdateInterval) override
    571             {
    572                 return CheckForUpdateBeforeOpen(m_details, DesktopContextGetCurrentVersion(m_details), requestedUpdateInterval);
    573             }
    574 
    575             std::shared_ptr<ISource> Open(IProgressCallback& progress) override
    576             {
    577                 Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details));
    578                 if (!lock.Acquire(progress))
    579                 {
    580                     return {};
    581                 }
    582 
    583                 std::filesystem::path packageLocation = GetStatePathFromDetails(m_details);
    584                 packageLocation /= s_PreIndexedPackageSourceFactory_PackageFileName;
    585 
    586                 if (!std::filesystem::exists(packageLocation))
    587                 {
    588                     AICLI_LOG(Repo, Info, << "Data not found at " << packageLocation);
    589                     THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING);
    590                 }
    591 
    592                 // Put a write exclusive lock on the index package.
    593                 Msix::WriteLockedMsixFile indexPackage{ packageLocation };
    594 
    595                 // Validate index package trust info.
    596                 THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, !indexPackage.ValidateTrustInfo(WI_IsFlagSet(m_details.TrustLevel, SourceTrustLevel::StoreOrigin)));
    597 
    598                 // Create a temp lock exclusive index file.
    599                 auto tempIndexFilePath = Runtime::GetNewTempFilePath();
    600                 auto tempIndexFile = Utility::ManagedFile::CreateWriteLockedFile(tempIndexFilePath, GENERIC_WRITE, true);
    601 
    602                 // Populate temp index file.
    603                 Msix::MsixInfo packageInfo(packageLocation);
    604                 packageInfo.WriteToFileHandle(s_PreIndexedPackageSourceFactory_IndexFilePath, tempIndexFile.GetFileHandle(), progress);
    605 
    606                 if (progress.IsCancelledBy(CancelReason::Any))
    607                 {
    608                     AICLI_LOG(Repo, Info, << "Cancelling open upon request");
    609                     return {};
    610                 }
    611 
    612                 SQLiteIndex index = SQLiteIndex::Open(tempIndexFile.GetFilePath().u8string(), SQLiteIndex::OpenDisposition::Immutable, std::move(tempIndexFile));
    613 
    614                 // We didn't use to store the source identifier, so we compute it here in case it's
    615                 // missing from the details.
    616                 m_details.Identifier = GetPackageFamilyNameFromDetails(m_details);
    617                 return std::make_shared<SQLiteIndexSource>(m_details, std::move(index), false, true);
    618             }
    619 
    620         private:
    621             SourceDetails m_details;
    622         };
    623 
    624         // Source factory for running outside of a package.
    625         struct DesktopContextFactory : public PreIndexedFactoryBase
    626         {
    627             std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) override
    628             {
    629                 return std::make_shared<DesktopContextSourceReference>(details);
    630             }
    631 
    632             std::optional<Msix::PackageVersion> GetCurrentVersion(const SourceDetails& details) override
    633             {
    634                 return DesktopContextGetCurrentVersion(details);
    635             }
    636 
    637             bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress) override
    638             {
    639                 // We will extract the manifest and index files directly to this location
    640                 std::filesystem::path packageState = GetStatePathFromDetails(details);
    641                 std::filesystem::create_directories(packageState);
    642 
    643                 std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName;
    644 
    645                 std::filesystem::path tempPackagePath = packagePath.u8string() + ".dnld.msix";
    646                 auto removeTempFileOnExit = wil::scope_exit([&]()
    647                     {
    648                         try
    649                         {
    650                             std::filesystem::remove(tempPackagePath);
    651                         }
    652                         catch (...)
    653                         {
    654                             AICLI_LOG(Repo, Info, << "Failed to remove temp index file at: " << tempPackagePath);
    655                         }
    656                     });
    657 
    658                 if (Utility::IsUrlRemote(packageLocation))
    659                 {
    660                     AppInstaller::Utility::Download(packageLocation, tempPackagePath, AppInstaller::Utility::DownloadType::Index, progress);
    661                 }
    662                 else
    663                 {
    664                     std::filesystem::copy(packageLocation, tempPackagePath);
    665                     progress.OnProgress(100, 100, ProgressType::Percent);
    666                 }
    667 
    668                 if (progress.IsCancelledBy(CancelReason::Any))
    669                 {
    670                     AICLI_LOG(Repo, Info, << "Cancelling update upon request");
    671                     return false;
    672                 }
    673 
    674                 {
    675                     // Extra scope to release the file lock right after trust validation.
    676                     Msix::WriteLockedMsixFile tempIndexPackage{ tempPackagePath };
    677                     Msix::MsixInfo tempMsixInfo{ tempPackagePath };
    678 
    679                     // The package should not be a bundle
    680                     THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, tempMsixInfo.GetIsBundle());
    681 
    682                     // Ensure that family name has not changed
    683                     THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE,
    684                         GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(tempMsixInfo.GetPackageFullName()));
    685 
    686                     if (!tempIndexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin)))
    687                     {
    688                         AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation.");
    689                         THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE);
    690                     }
    691                 }
    692 
    693                 std::filesystem::rename(tempPackagePath, packagePath);
    694                 AICLI_LOG(Repo, Info, << "Source update success.");
    695 
    696                 removeTempFileOnExit.release();
    697 
    698                 return true;
    699             }
    700 
    701             bool RemoveInternal(const SourceDetails& details, IProgressCallback&) override
    702             {
    703                 std::filesystem::path packageState = GetStatePathFromDetails(details);
    704 
    705                 if (!std::filesystem::exists(packageState))
    706                 {
    707                     AICLI_LOG(Repo, Info, << "No state found for source: " << packageState.u8string());
    708                 }
    709                 else
    710                 {
    711                     AICLI_LOG(Repo, Info, << "Removing state found for source: " << packageState.u8string());
    712                     std::filesystem::remove_all(packageState);
    713                 }
    714 
    715                 return true;
    716             }
    717         };
    718     }
    719 
    720     std::unique_ptr<ISourceFactory> PreIndexedPackageSourceFactory::Create()
    721     {
    722         if (Runtime::IsRunningInPackagedContext())
    723         {
    724             return std::make_unique<PackagedContextFactory>();
    725         }
    726         else
    727         {
    728             return std::make_unique<DesktopContextFactory>();
    729         }
    730     }
    731 }