From 222b7f3e7d8a969817a40ba3db3af5bc1c0caddb Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 4 Mar 2026 15:24:01 -0800 Subject: [PATCH 1/5] impl --- src/AppInstallerCommonCore/Deployment.cpp | 8 +++ src/AppInstallerCommonCore/MSStore.cpp | 56 ++++++++++++++++++- .../Public/AppInstallerDeployment.h | 6 ++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp index 3824d21644..e37480f208 100644 --- a/src/AppInstallerCommonCore/Deployment.cpp +++ b/src/AppInstallerCommonCore/Deployment.cpp @@ -150,6 +150,14 @@ namespace AppInstaller::Deployment return out; } + HRESULT WaitForDeployment( + IAsyncOperationWithProgress& 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..cdcc0676a0 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,49 @@ 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. 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( @@ -378,7 +423,16 @@ namespace AppInstaller::MSStore installOptions).get(); } - return WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); + HRESULT hr = WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); + + if (SUCCEEDED(hr) && m_scope == Manifest::ScopeEnum::Machine) + { + // Mitigation: on older OS versions 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( From 05b36f8200b0191d7d118f3b51da94c10456816d Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 4 Mar 2026 17:41:57 -0800 Subject: [PATCH 2/5] Test impl; needs review and validation --- .../AppInstallerCLITests.vcxproj | 3 ++ src/AppInstallerCLITests/InstallFlow.cpp | 37 +++++++++++++++++++ ...lowTest_MSStore_MachineScopeProvision.yaml | 12 ++++++ src/AppInstallerCLITests/TestHooks.h | 18 +++++++++ src/AppInstallerCommonCore/MSStore.cpp | 22 ++++++++++- 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore_MachineScopeProvision.yaml diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 7e9f524056..5491d2e7b3 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -360,6 +360,9 @@ true + + true + true diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index c87be7b62c..9cb3b7ae2d 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "WorkflowCommon.h" #include "TestHooks.h" +#include "AppInstallerRuntime.h" #include #include #include @@ -590,6 +591,42 @@ 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); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForMSStore(context, false); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_MSStore_MachineScopeProvision.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(!context.IsTerminated()); + + // 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-Preview_8wekyb3d8bbwe") + { + isProvisioned = true; + break; + } + } + REQUIRE(isProvisioned); +} + TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow][workflow]") { TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore_MachineScopeProvision.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore_MachineScopeProvision.yaml new file mode 100644 index 0000000000..95af2f8057 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore_MachineScopeProvision.yaml @@ -0,0 +1,12 @@ +Id: AppInstallerCliTest.TestMSStoreMachineScopeProvision +Version: Latest +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +License: Test +Installers: + - Arch: neutral + Url: https://ThisIsNotUsed + InstallerType: msstore + ProductId: 9PCX3HX4HZ0Z + PackageFamilyName: Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe +ManifestVersion: 0.2.0-msstore 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 Date: Fri, 1 May 2026 13:42:38 -0700 Subject: [PATCH 3/5] Change test --- src/AppInstallerCLITests/InstallFlow.cpp | 23 ++++++++++--------- ...lowTest_MSStore_MachineScopeProvision.yaml | 12 ---------- 2 files changed, 12 insertions(+), 23 deletions(-) delete mode 100644 src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore_MachineScopeProvision.yaml diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index 9cb3b7ae2d..0e6b4d3fda 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -5,6 +5,8 @@ #include "TestHooks.h" #include "AppInstallerRuntime.h" #include +#include +#include #include #include #include @@ -601,24 +603,23 @@ TEST_CASE("MSStoreInstallFlow_MachineScopeProvision", "[InstallFlow][MSStore]") TestHook::SetForceProvisionAfterInstall_Override forceProvisionOverride(true); - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForMSStore(context, false); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_MSStore_MachineScopeProvision.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); + AppInstaller::ProgressCallback progress; + AppInstaller::MSStore::MSStoreOperation installOperation( + AppInstaller::MSStore::MSStoreOperationType::Install, + L"9NVTPZWRC6KQ", + AppInstaller::Manifest::ScopeEnum::User, + true, + false); - REQUIRE(!context.IsTerminated()); + 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-Preview_8wekyb3d8bbwe") + if (pkg.Id().FamilyName() == L"Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe") { isProvisioned = true; break; diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore_MachineScopeProvision.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore_MachineScopeProvision.yaml deleted file mode 100644 index 95af2f8057..0000000000 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore_MachineScopeProvision.yaml +++ /dev/null @@ -1,12 +0,0 @@ -Id: AppInstallerCliTest.TestMSStoreMachineScopeProvision -Version: Latest -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -License: Test -Installers: - - Arch: neutral - Url: https://ThisIsNotUsed - InstallerType: msstore - ProductId: 9PCX3HX4HZ0Z - PackageFamilyName: Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe -ManifestVersion: 0.2.0-msstore From 44941541c1d59287e6adaedee8f67030b3d6f65a Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Fri, 1 May 2026 14:23:21 -0700 Subject: [PATCH 4/5] Apply forgotten project change --- src/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 5491d2e7b3..449671bb30 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -360,9 +360,6 @@ true - - true - true @@ -1108,4 +1105,4 @@ - \ No newline at end of file + From 41be1a2213fb2dc08e6c80a4f7ec9eefeef630fc Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Fri, 1 May 2026 15:08:57 -0700 Subject: [PATCH 5/5] PR feedback: note on OS fix --- src/AppInstallerCommonCore/MSStore.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerCommonCore/MSStore.cpp b/src/AppInstallerCommonCore/MSStore.cpp index 3671cbe0af..29e0d161b5 100644 --- a/src/AppInstallerCommonCore/MSStore.cpp +++ b/src/AppInstallerCommonCore/MSStore.cpp @@ -216,7 +216,8 @@ namespace AppInstaller::MSStore }; // After a successful machine-scope Store install, the package may not be provisioned on older - // Windows versions. This helper ensures the package is provisioned as a best-effort mitigation. + // 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; @@ -447,8 +448,9 @@ namespace AppInstaller::MSStore if (SUCCEEDED(hr) && shouldProvision) { - // Mitigation: on older OS versions the Store install service may not provision the package - // even when InstallForAllUsers was set. Explicitly provision if not already provisioned. + // 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); }