diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 7e9f524056..449671bb30 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -1105,4 +1105,4 @@ - \ No newline at end of file + diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index c87be7b62c..0e6b4d3fda 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -3,7 +3,10 @@ #include "pch.h" #include "WorkflowCommon.h" #include "TestHooks.h" +#include "AppInstallerRuntime.h" #include +#include +#include #include #include #include @@ -590,6 +593,41 @@ TEST_CASE("MSStoreInstallFlowWithTestManifest", "[InstallFlow][workflow]") REQUIRE(installResultStr.find("9WZDNCRFJ364") != std::string::npos); } +TEST_CASE("MSStoreInstallFlow_MachineScopeProvision", "[InstallFlow][MSStore]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin() || AppInstaller::Runtime::IsRunningAsSystem()) + { + WARN("Test requires running as admin but not SYSTEM. Skipped."); + return; + } + + TestHook::SetForceProvisionAfterInstall_Override forceProvisionOverride(true); + + AppInstaller::ProgressCallback progress; + AppInstaller::MSStore::MSStoreOperation installOperation( + AppInstaller::MSStore::MSStoreOperationType::Install, + L"9NVTPZWRC6KQ", + AppInstaller::Manifest::ScopeEnum::User, + true, + false); + + HRESULT hr = installOperation.StartAndWaitForOperation(progress); + REQUIRE(SUCCEEDED(hr)); + + // Verify the package is now provisioned. + winrt::Windows::Management::Deployment::PackageManager packageManager; + bool isProvisioned = false; + for (auto const& pkg : packageManager.FindProvisionedPackages()) + { + if (pkg.Id().FamilyName() == L"Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe") + { + isProvisioned = true; + break; + } + } + REQUIRE(isProvisioned); +} + TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow][workflow]") { TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); diff --git a/src/AppInstallerCLITests/TestHooks.h b/src/AppInstallerCLITests/TestHooks.h index 44c5b2304a..eac856bd48 100644 --- a/src/AppInstallerCLITests/TestHooks.h +++ b/src/AppInstallerCLITests/TestHooks.h @@ -100,6 +100,8 @@ namespace AppInstaller void SetSfsClientAppContents_Override(std::function(std::string_view)>* value); void SetLicensingHttpPipelineStage_Override(std::shared_ptr value); + + void TestHook_SetProvisionAfterInstall(bool* value); } namespace Utility::TestHooks @@ -320,6 +322,22 @@ namespace TestHook } }; + struct SetForceProvisionAfterInstall_Override + { + SetForceProvisionAfterInstall_Override(bool value) : m_value(value) + { + AppInstaller::MSStore::TestHooks::TestHook_SetProvisionAfterInstall(&m_value); + } + + ~SetForceProvisionAfterInstall_Override() + { + AppInstaller::MSStore::TestHooks::TestHook_SetProvisionAfterInstall(nullptr); + } + + private: + bool m_value; + }; + struct SetDownloadResult_Function_Override { SetDownloadResult_Function_Override(std::function& deployOperation, + IProgressCallback& callback, + bool throwOnError) + { + return WaitForDeployment(deployOperation, GetDeploymentOperationId(), callback, throwOnError); + } + void AddPackage( const winrt::Windows::Foundation::Uri& uri, const Options& options, diff --git a/src/AppInstallerCommonCore/MSStore.cpp b/src/AppInstallerCommonCore/MSStore.cpp index 6b8f458072..29e0d161b5 100644 --- a/src/AppInstallerCommonCore/MSStore.cpp +++ b/src/AppInstallerCommonCore/MSStore.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,7 @@ namespace AppInstaller::MSStore using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; + using namespace winrt::Windows::Management::Deployment; namespace { @@ -213,6 +215,50 @@ namespace AppInstaller::MSStore std::atomic_bool m_isUpdating = false; }; + // After a successful machine-scope Store install, the package may not be provisioned on older + // Windows versions (fixed in Windows 11 26100). This helper ensures the package is provisioned + // as a best-effort mitigation. + void EnsureProvisionedForMachineScope(const IVectorView& installItems, std::wstring_view productId, IProgressCallback& progress) try + { + PackageManager packageManager; + auto provisionedPackages = packageManager.FindProvisionedPackages(); + + for (auto const& installItem : installItems) + { + if (Utility::CaseInsensitiveEquals(installItem.ProductId(), productId)) + { + std::wstring familyName{ installItem.PackageFamilyName() }; + if (familyName.empty()) + { + continue; + } + + bool isProvisioned = false; + for (auto const& provisionedPkg : provisionedPackages) + { + if (provisionedPkg.Id().FamilyName() == familyName) + { + isProvisioned = true; + break; + } + } + + if (!isProvisioned) + { + AICLI_LOG(Core, Info, << "Package not provisioned after machine scope install, provisioning: " << Utility::ConvertToUTF8(familyName)); + try + { + auto operation = packageManager.ProvisionPackageForAllUsersAsync(familyName); + Deployment::WaitForDeployment(operation, progress); + AICLI_LOG(Core, Info, << "Successfully provisioned package: " << Utility::ConvertToUTF8(familyName)); + } + CATCH_LOG(); + } + } + } + } + CATCH_LOG(); + HRESULT WaitForOperation(const std::wstring& productId, bool isSilentMode, IVectorView& installItems, IProgressCallback& progress, const PackageUpdateMonitor& monitor) { auto cancelIfOperationFailed = wil::scope_exit( @@ -321,6 +367,18 @@ namespace AppInstaller::MSStore } } +#ifndef AICLI_DISABLE_TEST_HOOKS + namespace TestHooks + { + static bool* s_ProvisionAfterInstall = nullptr; + + void TestHook_SetProvisionAfterInstall(bool* value) + { + s_ProvisionAfterInstall = value; + } + } +#endif + HRESULT MSStoreOperation::InstallPackage(IProgressCallback& progress) { PackageUpdateMonitor monitor; @@ -378,7 +436,25 @@ namespace AppInstaller::MSStore installOptions).get(); } - return WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); + HRESULT hr = WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); + + bool shouldProvision = m_scope == Manifest::ScopeEnum::Machine; +#ifndef AICLI_DISABLE_TEST_HOOKS + if (TestHooks::s_ProvisionAfterInstall) + { + shouldProvision = *TestHooks::s_ProvisionAfterInstall; + } +#endif + + if (SUCCEEDED(hr) && shouldProvision) + { + // Mitigation: on older OS versions (fixed in Windows 11 26100) the Store install service + // may not provision the package even when InstallForAllUsers was set. + // Explicitly provision if not already provisioned. + EnsureProvisionedForMachineScope(installItems, m_productId, progress); + } + + return hr; } HRESULT MSStoreOperation::UpdatePackage(IProgressCallback& progress) diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h index 68f16b80e8..2216abf224 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -20,6 +20,12 @@ namespace AppInstaller::Deployment std::vector> ExpectedDigests; }; + // Waits for a deployment operation. + HRESULT WaitForDeployment( + winrt::Windows::Foundation::IAsyncOperationWithProgress& deployOperation, + IProgressCallback& callback, + bool throwOnError = true); + // Calls winrt::Windows::Management::Deployment::PackageManager::AddPackageAsync if skipSmartScreen is true, // Otherwise, calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync void AddPackage(