From ebe046a66be1567aa5d5ccfd5ba3cd00a98502ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Thu, 2 Jul 2026 22:41:38 +0200 Subject: [PATCH 1/7] Fallback source package storage for noninteractive packaged runs --- .../PreIndexedPackageSourceFactory.cpp | 283 +++++++++++------- 1 file changed, 169 insertions(+), 114 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index 53a73e1835..8c4d9aa68c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -384,6 +384,8 @@ namespace AppInstaller::Repository::Microsoft return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId); } + std::optional DesktopContextGetCurrentVersion(const SourceDetails& details); + std::optional PackagedContextGetCurrentVersion(const SourceDetails& details) { auto extension = GetExtensionFromDetails(details); @@ -395,7 +397,7 @@ namespace AppInstaller::Repository::Microsoft } else { - return std::nullopt; + return DesktopContextGetCurrentVersion(details); } } @@ -432,6 +434,105 @@ namespace AppInstaller::Repository::Microsoft return std::nullopt; } + bool UpdateDesktopContextPackage(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) + { + // We will extract the manifest and index files directly to this location + std::filesystem::path packageState = GetStatePathFromDetails(details); + std::filesystem::create_directories(packageState); + + std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; + + std::filesystem::path tempPackagePath = packagePath.u8string() + ".dnld.msix"; + auto removeTempFileOnExit = wil::scope_exit([&]() + { + try + { + std::filesystem::remove(tempPackagePath); + } + catch (...) + { + AICLI_LOG(Repo, Info, << "Failed to remove temp index file at: " << tempPackagePath); + } + }); + + if (Utility::IsUrlRemote(packageLocation)) + { + auto downloadResult = AppInstaller::Utility::Download(packageLocation, tempPackagePath, AppInstaller::Utility::DownloadType::Index, progress); + downloadedBytes = downloadResult.SizeInBytes; + } + else + { + std::filesystem::copy(packageLocation, tempPackagePath); + progress.OnProgress(100, 100, ProgressType::Percent); + } + + if (progress.IsCancelledBy(CancelReason::Any)) + { + AICLI_LOG(Repo, Info, << "Cancelling update upon request"); + return false; + } + + { + // Extra scope to release the file lock right after trust validation. + Msix::WriteLockedMsixFile tempIndexPackage{ tempPackagePath }; + Msix::MsixInfo tempMsixInfo{ tempPackagePath }; + + // The package should not be a bundle + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, tempMsixInfo.GetIsBundle()); + + // Ensure that family name has not changed + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, + GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(tempMsixInfo.GetPackageFullName())); + + if (!tempIndexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) + { + AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); + } + } + + std::filesystem::rename(tempPackagePath, packagePath); + AICLI_LOG(Repo, Info, << "Source update success."); + + removeTempFileOnExit.release(); + + return true; + } + + SQLiteIndex OpenDesktopContextIndex(const SourceDetails& details, IProgressCallback& progress) + { + std::filesystem::path packageLocation = GetStatePathFromDetails(details); + packageLocation /= s_PreIndexedPackageSourceFactory_PackageFileName; + + if (!std::filesystem::exists(packageLocation)) + { + AICLI_LOG(Repo, Info, << "Data not found at " << packageLocation); + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); + } + + // Put a write exclusive lock on the index package. + Msix::WriteLockedMsixFile indexPackage{ packageLocation }; + + // Validate index package trust info. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, !indexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))); + + // Create a temp lock exclusive index file. + auto tempIndexFilePath = Runtime::GetNewTempFilePath(); + auto tempIndexFile = Utility::ManagedFile::CreateWriteLockedFile(tempIndexFilePath, GENERIC_WRITE, true); + + // Populate temp index file. + Msix::MsixInfo packageInfo(packageLocation); + packageInfo.WriteToFileHandle(s_PreIndexedPackageSourceFactory_IndexFilePath, tempIndexFile.GetFileHandle(), progress); + + if (progress.IsCancelledBy(CancelReason::Any)) + { + AICLI_LOG(Repo, Info, << "Cancelling open upon request"); + THROW_HR(E_ABORT); + } + + return SQLiteIndex::Open(tempIndexFile.GetFilePath().u8string(), SQLiteIndex::OpenDisposition::Immutable, std::move(tempIndexFile)); + } + bool CheckForUpdateBeforeOpen(const SourceDetails& details, std::optional currentVersion, const std::optional& requestedUpdateInterval) { // If we can't find a good package, then we have to update to operate @@ -602,7 +703,20 @@ namespace AppInstaller::Repository::Microsoft return {}; } - index.emplace(OpenPackagedContextIndex(m_details, progress, openTimer)); + try + { + index.emplace(OpenPackagedContextIndex(m_details, progress, openTimer)); + } + catch (...) + { + if (progress.IsCancelledBy(CancelReason::Any)) + { + throw; + } + + LOG_CAUGHT_EXCEPTION_MSG("Locked packaged source open failed, falling back to local state for source: %hs", m_details.Name.c_str()); + index.emplace(OpenDesktopContextIndex(m_details, progress)); + } } return completeOpen(std::move(index.value())); @@ -645,37 +759,60 @@ namespace AppInstaller::Repository::Microsoft localFile = Utility::ConvertToUTF16(packageLocation); } + auto removeLocalFileOnExit = wil::scope_exit([&]() + { + if (download) + { + try + { + std::filesystem::remove(localFile); + } + CATCH_LOG(); + } + }); + // Verify the local file - Msix::WriteLockedMsixFile fileLock{ localFile }; - Msix::MsixInfo localMsixInfo{ localFile }; + { + Msix::WriteLockedMsixFile fileLock{ localFile }; + Msix::MsixInfo localMsixInfo{ localFile }; - // The package should not be a bundle - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, localMsixInfo.GetIsBundle()); + // The package should not be a bundle + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, localMsixInfo.GetIsBundle()); - // Ensure that family name has not changed - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, - GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(localMsixInfo.GetPackageFullName())); + // Ensure that family name has not changed + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, + GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(localMsixInfo.GetPackageFullName())); - if (!fileLock.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) - { - AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); + if (!fileLock.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) + { + AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); + } } winrt::Windows::Foundation::Uri uri = winrt::Windows::Foundation::Uri(localFile.c_str()); - Deployment::AddPackage( - uri, - Deployment::Options{ WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted) }, - progress); - - if (download) + try { - try + Deployment::AddPackage( + uri, + Deployment::Options{ WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted) }, + progress); + } + catch (...) + { + if (progress.IsCancelledBy(CancelReason::Any)) { - // If successful, delete the file - std::filesystem::remove(localFile); + throw; } - CATCH_LOG(); + + LOG_CAUGHT_EXCEPTION_MSG("Packaged source deployment failed, falling back to local state for source: %hs", details.Name.c_str()); + return UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); + } + + if (!GetExtensionFromDetails(details)) + { + AICLI_LOG(Repo, Warning, << "Packaged source deployment completed, but the extension was not found; falling back to local state for source: " << details.Name); + return UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); } return true; @@ -695,6 +832,13 @@ namespace AppInstaller::Repository::Microsoft Deployment::RemovePackage(*fullName, winrt::Windows::Management::Deployment::RemovalOptions::None, callback); } + std::filesystem::path packageState = GetStatePathFromDetails(details); + if (std::filesystem::exists(packageState)) + { + AICLI_LOG(Repo, Info, << "Removing local state found for source: " << packageState.u8string()); + std::filesystem::remove_all(packageState); + } + return true; } }; @@ -726,36 +870,7 @@ namespace AppInstaller::Repository::Microsoft return {}; } - std::filesystem::path packageLocation = GetStatePathFromDetails(m_details); - packageLocation /= s_PreIndexedPackageSourceFactory_PackageFileName; - - if (!std::filesystem::exists(packageLocation)) - { - AICLI_LOG(Repo, Info, << "Data not found at " << packageLocation); - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); - } - - // Put a write exclusive lock on the index package. - Msix::WriteLockedMsixFile indexPackage{ packageLocation }; - - // Validate index package trust info. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, !indexPackage.ValidateTrustInfo(WI_IsFlagSet(m_details.TrustLevel, SourceTrustLevel::StoreOrigin))); - - // Create a temp lock exclusive index file. - auto tempIndexFilePath = Runtime::GetNewTempFilePath(); - auto tempIndexFile = Utility::ManagedFile::CreateWriteLockedFile(tempIndexFilePath, GENERIC_WRITE, true); - - // Populate temp index file. - Msix::MsixInfo packageInfo(packageLocation); - packageInfo.WriteToFileHandle(s_PreIndexedPackageSourceFactory_IndexFilePath, tempIndexFile.GetFileHandle(), progress); - - if (progress.IsCancelledBy(CancelReason::Any)) - { - AICLI_LOG(Repo, Info, << "Cancelling open upon request"); - return {}; - } - - SQLiteIndex index = SQLiteIndex::Open(tempIndexFile.GetFilePath().u8string(), SQLiteIndex::OpenDisposition::Immutable, std::move(tempIndexFile)); + SQLiteIndex index = OpenDesktopContextIndex(m_details, progress); // We didn't use to store the source identifier, so we compute it here in case it's // missing from the details. @@ -782,67 +897,7 @@ namespace AppInstaller::Repository::Microsoft bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) override { - // We will extract the manifest and index files directly to this location - std::filesystem::path packageState = GetStatePathFromDetails(details); - std::filesystem::create_directories(packageState); - - std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; - - std::filesystem::path tempPackagePath = packagePath.u8string() + ".dnld.msix"; - auto removeTempFileOnExit = wil::scope_exit([&]() - { - try - { - std::filesystem::remove(tempPackagePath); - } - catch (...) - { - AICLI_LOG(Repo, Info, << "Failed to remove temp index file at: " << tempPackagePath); - } - }); - - if (Utility::IsUrlRemote(packageLocation)) - { - auto downloadResult = AppInstaller::Utility::Download(packageLocation, tempPackagePath, AppInstaller::Utility::DownloadType::Index, progress); - downloadedBytes = downloadResult.SizeInBytes; - } - else - { - std::filesystem::copy(packageLocation, tempPackagePath); - progress.OnProgress(100, 100, ProgressType::Percent); - } - - if (progress.IsCancelledBy(CancelReason::Any)) - { - AICLI_LOG(Repo, Info, << "Cancelling update upon request"); - return false; - } - - { - // Extra scope to release the file lock right after trust validation. - Msix::WriteLockedMsixFile tempIndexPackage{ tempPackagePath }; - Msix::MsixInfo tempMsixInfo{ tempPackagePath }; - - // The package should not be a bundle - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, tempMsixInfo.GetIsBundle()); - - // Ensure that family name has not changed - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, - GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(tempMsixInfo.GetPackageFullName())); - - if (!tempIndexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) - { - AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); - } - } - - std::filesystem::rename(tempPackagePath, packagePath); - AICLI_LOG(Repo, Info, << "Source update success."); - - removeTempFileOnExit.release(); - - return true; + return UpdateDesktopContextPackage(packageLocation, details, progress, downloadedBytes); } bool RemoveInternal(const SourceDetails& details, IProgressCallback&) override From f9eebd345175f6d2c31a09504e90a7466450296b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Thu, 2 Jul 2026 23:00:32 +0200 Subject: [PATCH 2/7] Narrow source package fallback for logged-off users --- .../PreIndexedPackageSourceFactory.cpp | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index 8c4d9aa68c..e6e5b82d3e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -434,6 +434,24 @@ namespace AppInstaller::Repository::Microsoft return std::nullopt; } + bool HasTrustedDesktopContextPackage(const SourceDetails& details) + { + try + { + return DesktopContextGetCurrentVersion(details).has_value(); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + return false; + } + } + + bool IsDeploymentBlockedByUserLogOff(HRESULT hr) + { + return hr == HRESULT_FROM_WIN32(ERROR_DEPLOYMENT_BLOCKED_BY_USER_LOG_OFF); + } + bool UpdateDesktopContextPackage(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) { // We will extract the manifest and index files directly to this location @@ -707,15 +725,22 @@ namespace AppInstaller::Repository::Microsoft { index.emplace(OpenPackagedContextIndex(m_details, progress, openTimer)); } - catch (...) + catch (const wil::ResultException& re) { if (progress.IsCancelledBy(CancelReason::Any)) { throw; } - LOG_CAUGHT_EXCEPTION_MSG("Locked packaged source open failed, falling back to local state for source: %hs", m_details.Name.c_str()); - index.emplace(OpenDesktopContextIndex(m_details, progress)); + if (re.GetErrorCode() == APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING && HasTrustedDesktopContextPackage(m_details)) + { + AICLI_LOG(Repo, Warning, << "Packaged source extension was not found; using trusted local state fallback for source: " << m_details.Name); + index.emplace(OpenDesktopContextIndex(m_details, progress)); + } + else + { + throw; + } } } @@ -798,21 +823,31 @@ namespace AppInstaller::Repository::Microsoft Deployment::Options{ WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted) }, progress); } - catch (...) + catch (const wil::ResultException& re) { if (progress.IsCancelledBy(CancelReason::Any)) { throw; } - LOG_CAUGHT_EXCEPTION_MSG("Packaged source deployment failed, falling back to local state for source: %hs", details.Name.c_str()); - return UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); + if (IsDeploymentBlockedByUserLogOff(re.GetErrorCode())) + { + AICLI_LOG(Repo, Warning, << "Packaged source deployment was blocked because the user is logged off; using local state fallback for source: " << details.Name); + return UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); + } + + throw; } if (!GetExtensionFromDetails(details)) { - AICLI_LOG(Repo, Warning, << "Packaged source deployment completed, but the extension was not found; falling back to local state for source: " << details.Name); - return UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); + if (HasTrustedDesktopContextPackage(details)) + { + AICLI_LOG(Repo, Warning, << "Packaged source deployment completed, but the extension was not found; refreshing local state fallback for source: " << details.Name); + return UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); + } + + THROW_HR_MSG(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING, "Packaged source deployment completed, but the extension was not found for source: %hs", details.Name.c_str()); } return true; From 6e992df458e0559497b47939115372bbb9e416b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Thu, 2 Jul 2026 23:22:51 +0200 Subject: [PATCH 3/7] Handle stale source package fallback state --- .../PreIndexedPackageSourceFactory.cpp | 69 +++++++++++++++---- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index e6e5b82d3e..bbe91c5c99 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -385,8 +385,9 @@ namespace AppInstaller::Repository::Microsoft } std::optional DesktopContextGetCurrentVersion(const SourceDetails& details); + std::optional TryGetDesktopContextCurrentVersion(const SourceDetails& details); - std::optional PackagedContextGetCurrentVersion(const SourceDetails& details) + std::optional PackagedContextGetExtensionVersion(const SourceDetails& details) { auto extension = GetExtensionFromDetails(details); @@ -395,10 +396,20 @@ namespace AppInstaller::Repository::Microsoft auto version = extension->GetPackageVersion(); return Msix::PackageVersion{ version.Major, version.Minor, version.Build, version.Revision }; } - else + + return std::nullopt; + } + + std::optional PackagedContextGetCurrentVersion(const SourceDetails& details) + { + auto extensionVersion = PackagedContextGetExtensionVersion(details); + + if (extensionVersion) { - return DesktopContextGetCurrentVersion(details); + return extensionVersion; } + + return TryGetDesktopContextCurrentVersion(details); } // Constructs the location that we will write files to. @@ -434,19 +445,29 @@ namespace AppInstaller::Repository::Microsoft return std::nullopt; } - bool HasTrustedDesktopContextPackage(const SourceDetails& details) + std::optional TryGetDesktopContextCurrentVersion(const SourceDetails& details) { try { - return DesktopContextGetCurrentVersion(details).has_value(); + return DesktopContextGetCurrentVersion(details); } catch (...) { - LOG_CAUGHT_EXCEPTION(); - return false; + LOG_CAUGHT_EXCEPTION_MSG("Failed to read local state source package version for source: %hs", details.Name.c_str()); + return std::nullopt; } } + bool HasTrustedDesktopContextPackage(const SourceDetails& details) + { + return TryGetDesktopContextCurrentVersion(details).has_value(); + } + + bool ShouldPreferDesktopContext(std::optional desktopVersion, std::optional packagedVersion) + { + return desktopVersion && (!packagedVersion || desktopVersion.value() > packagedVersion.value()); + } + bool IsDeploymentBlockedByUserLogOff(HRESULT hr) { return hr == HRESULT_FROM_WIN32(ERROR_DEPLOYMENT_BLOCKED_BY_USER_LOG_OFF); @@ -697,6 +718,27 @@ namespace AppInstaller::Repository::Microsoft std::optional index; bool retryUnderLock = false; + auto desktopVersion = TryGetDesktopContextCurrentVersion(m_details); + auto packagedVersion = PackagedContextGetExtensionVersion(m_details); + if (ShouldPreferDesktopContext(desktopVersion, packagedVersion)) + { + AICLI_LOG(Repo, Warning, << "Local state fallback is newer than packaged source extension; using fallback for source: " << m_details.Name); + try + { + index.emplace(OpenDesktopContextIndex(m_details, progress)); + return completeOpen(std::move(index.value())); + } + catch (...) + { + if (progress.IsCancelledBy(CancelReason::Any)) + { + throw; + } + + LOG_CAUGHT_EXCEPTION_MSG("Newer local state fallback failed to open, continuing with packaged source extension for source: %hs", m_details.Name.c_str()); + } + } + try { index.emplace(OpenPackagedContextIndex(m_details, progress, openTimer)); @@ -841,13 +883,14 @@ namespace AppInstaller::Repository::Microsoft if (!GetExtensionFromDetails(details)) { - if (HasTrustedDesktopContextPackage(details)) - { - AICLI_LOG(Repo, Warning, << "Packaged source deployment completed, but the extension was not found; refreshing local state fallback for source: " << details.Name); - return UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); - } + AICLI_LOG(Repo, Warning, << "Packaged source deployment completed, but the extension was not found; using local state fallback for source: " << details.Name); + return UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); + } - THROW_HR_MSG(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING, "Packaged source deployment completed, but the extension was not found for source: %hs", details.Name.c_str()); + if (HasTrustedDesktopContextPackage(details)) + { + AICLI_LOG(Repo, Info, << "Refreshing local state fallback after packaged source deployment for source: " << details.Name); + UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); } return true; From 18af6558ab8ab5d53791bfebd1f8daf1bca5e339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Thu, 2 Jul 2026 23:51:06 +0200 Subject: [PATCH 4/7] Fix packaged source fallback review issues --- .../Microsoft/PreIndexedPackageSourceFactory.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index bbe91c5c99..078cf89c06 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -386,6 +386,7 @@ namespace AppInstaller::Repository::Microsoft std::optional DesktopContextGetCurrentVersion(const SourceDetails& details); std::optional TryGetDesktopContextCurrentVersion(const SourceDetails& details); + bool ShouldPreferDesktopContext(std::optional desktopVersion, std::optional packagedVersion); std::optional PackagedContextGetExtensionVersion(const SourceDetails& details) { @@ -403,13 +404,14 @@ namespace AppInstaller::Repository::Microsoft std::optional PackagedContextGetCurrentVersion(const SourceDetails& details) { auto extensionVersion = PackagedContextGetExtensionVersion(details); + auto desktopVersion = TryGetDesktopContextCurrentVersion(details); - if (extensionVersion) + if (ShouldPreferDesktopContext(desktopVersion, extensionVersion)) { - return extensionVersion; + return desktopVersion; } - return TryGetDesktopContextCurrentVersion(details); + return extensionVersion; } // Constructs the location that we will write files to. @@ -725,6 +727,12 @@ namespace AppInstaller::Repository::Microsoft AICLI_LOG(Repo, Warning, << "Local state fallback is newer than packaged source extension; using fallback for source: " << m_details.Name); try { + Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); + if (!lock.Acquire(progress)) + { + return {}; + } + index.emplace(OpenDesktopContextIndex(m_details, progress)); return completeOpen(std::move(index.value())); } From 186bbe4726e736812afff911ef90a77d454e241e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Fri, 3 Jul 2026 00:27:29 +0200 Subject: [PATCH 5/7] Harden packaged source fallback refresh --- .../PreIndexedPackageSourceFactory.cpp | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index 078cf89c06..7251b120b9 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -720,30 +720,33 @@ namespace AppInstaller::Repository::Microsoft std::optional index; bool retryUnderLock = false; - auto desktopVersion = TryGetDesktopContextCurrentVersion(m_details); auto packagedVersion = PackagedContextGetExtensionVersion(m_details); - if (ShouldPreferDesktopContext(desktopVersion, packagedVersion)) + { - AICLI_LOG(Repo, Warning, << "Local state fallback is newer than packaged source extension; using fallback for source: " << m_details.Name); - try + Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); + if (!lock.Acquire(progress)) { - Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); - if (!lock.Acquire(progress)) - { - return {}; - } - - index.emplace(OpenDesktopContextIndex(m_details, progress)); - return completeOpen(std::move(index.value())); + return {}; } - catch (...) + + auto desktopVersion = TryGetDesktopContextCurrentVersion(m_details); + if (ShouldPreferDesktopContext(desktopVersion, packagedVersion)) { - if (progress.IsCancelledBy(CancelReason::Any)) + AICLI_LOG(Repo, Warning, << "Local state fallback is newer than packaged source extension; using fallback for source: " << m_details.Name); + try { - throw; + index.emplace(OpenDesktopContextIndex(m_details, progress)); + return completeOpen(std::move(index.value())); } + catch (...) + { + if (progress.IsCancelledBy(CancelReason::Any)) + { + throw; + } - LOG_CAUGHT_EXCEPTION_MSG("Newer local state fallback failed to open, continuing with packaged source extension for source: %hs", m_details.Name.c_str()); + LOG_CAUGHT_EXCEPTION_MSG("Newer local state fallback failed to open, continuing with packaged source extension for source: %hs", m_details.Name.c_str()); + } } } @@ -898,7 +901,19 @@ namespace AppInstaller::Repository::Microsoft if (HasTrustedDesktopContextPackage(details)) { AICLI_LOG(Repo, Info, << "Refreshing local state fallback after packaged source deployment for source: " << details.Name); - UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); + try + { + UpdateDesktopContextPackage(localFile.u8string(), details, progress, downloadedBytes); + } + catch (...) + { + if (progress.IsCancelledBy(CancelReason::Any)) + { + throw; + } + + LOG_CAUGHT_EXCEPTION_MSG("Failed to refresh local state fallback after packaged source deployment for source: %hs", details.Name.c_str()); + } } return true; From 84fad21214daa370e1ed95a1d3e0db82d1b6f445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Fri, 3 Jul 2026 00:57:19 +0200 Subject: [PATCH 6/7] Protect packaged fallback updates --- .../Microsoft/PreIndexedPackageSourceFactory.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index 7251b120b9..3b7afb25a8 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -404,7 +404,17 @@ namespace AppInstaller::Repository::Microsoft std::optional PackagedContextGetCurrentVersion(const SourceDetails& details) { auto extensionVersion = PackagedContextGetExtensionVersion(details); - auto desktopVersion = TryGetDesktopContextCurrentVersion(details); + std::optional desktopVersion; + + Synchronization::CrossProcessLock lock(CreateNameForCPL(details)); + if (lock.TryAcquireNoWait()) + { + desktopVersion = TryGetDesktopContextCurrentVersion(details); + } + else + { + AICLI_LOG(Repo, Verbose, << "Skipping local state fallback version probe because source lock is held for source: " << details.Name); + } if (ShouldPreferDesktopContext(desktopVersion, extensionVersion)) { @@ -496,6 +506,8 @@ namespace AppInstaller::Repository::Microsoft } }); + std::filesystem::remove(tempPackagePath); + if (Utility::IsUrlRemote(packageLocation)) { auto downloadResult = AppInstaller::Utility::Download(packageLocation, tempPackagePath, AppInstaller::Utility::DownloadType::Index, progress); @@ -503,7 +515,7 @@ namespace AppInstaller::Repository::Microsoft } else { - std::filesystem::copy(packageLocation, tempPackagePath); + std::filesystem::copy(packageLocation, tempPackagePath, std::filesystem::copy_options::overwrite_existing); progress.OnProgress(100, 100, ProgressType::Percent); } From 12ccebc78c76a7006dff06a564dfe68c72f401aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Fri, 3 Jul 2026 01:20:49 +0200 Subject: [PATCH 7/7] Keep packaged opens optimistic --- .../PreIndexedPackageSourceFactory.cpp | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index 3b7afb25a8..d2c3d3807e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -470,6 +470,18 @@ namespace AppInstaller::Repository::Microsoft } } + void RemoveDesktopContextPackage(const SourceDetails& details) + { + try + { + std::filesystem::remove(GetStatePathFromDetails(details) / s_PreIndexedPackageSourceFactory_PackageFileName); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION_MSG("Failed to remove unusable local state source package for source: %hs", details.Name.c_str()); + } + } + bool HasTrustedDesktopContextPackage(const SourceDetails& details) { return TryGetDesktopContextCurrentVersion(details).has_value(); @@ -732,34 +744,37 @@ namespace AppInstaller::Repository::Microsoft std::optional index; bool retryUnderLock = false; - auto packagedVersion = PackagedContextGetExtensionVersion(m_details); - { Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); - if (!lock.Acquire(progress)) + if (lock.TryAcquireNoWait()) { - return {}; - } + auto packagedVersion = PackagedContextGetExtensionVersion(m_details); + auto desktopVersion = TryGetDesktopContextCurrentVersion(m_details); - auto desktopVersion = TryGetDesktopContextCurrentVersion(m_details); - if (ShouldPreferDesktopContext(desktopVersion, packagedVersion)) - { - AICLI_LOG(Repo, Warning, << "Local state fallback is newer than packaged source extension; using fallback for source: " << m_details.Name); - try - { - index.emplace(OpenDesktopContextIndex(m_details, progress)); - return completeOpen(std::move(index.value())); - } - catch (...) + if (ShouldPreferDesktopContext(desktopVersion, packagedVersion)) { - if (progress.IsCancelledBy(CancelReason::Any)) + AICLI_LOG(Repo, Warning, << "Local state fallback is newer than packaged source extension; using fallback for source: " << m_details.Name); + try { - throw; + index.emplace(OpenDesktopContextIndex(m_details, progress)); + return completeOpen(std::move(index.value())); } + catch (...) + { + if (progress.IsCancelledBy(CancelReason::Any)) + { + throw; + } - LOG_CAUGHT_EXCEPTION_MSG("Newer local state fallback failed to open, continuing with packaged source extension for source: %hs", m_details.Name.c_str()); + LOG_CAUGHT_EXCEPTION_MSG("Newer local state fallback failed to open; removing it and continuing with packaged source extension for source: %hs", m_details.Name.c_str()); + RemoveDesktopContextPackage(m_details); + } } } + else + { + AICLI_LOG(Repo, Verbose, << "Skipping local state fallback probe because source lock is held for source: " << m_details.Name); + } } try