diff --git a/src/AppInstallerCLICore/Commands/TestCommand.cpp b/src/AppInstallerCLICore/Commands/TestCommand.cpp index 2063b0d3cf..ebcbcf0bf4 100644 --- a/src/AppInstallerCLICore/Commands/TestCommand.cpp +++ b/src/AppInstallerCLICore/Commands/TestCommand.cpp @@ -31,7 +31,7 @@ namespace AppInstaller::CLI HRESULT WaitForShutdown(Execution::Context& context) { LogAndReport(context, "Waiting for app shutdown event"); - if (!ShutdownMonitoring::TerminationSignalHandler::Instance()->WaitForAppShutdownEvent()) + if (!ShutdownMonitoring::ServerShutdownSynchronization::WaitForShutdown(300000)) { LogAndReport(context, "Failed getting app shutdown event"); return APPINSTALLER_CLI_ERROR_INTERNAL_ERROR; @@ -82,6 +82,21 @@ namespace AppInstaller::CLI return hr; } + void AppShutdownTestSystemBlockNewWork(CancelReason reason) + { + AICLI_LOG(CLI, Info, << "AppShutdownTestSystemBlockNewWork :: " << reason); + } + + void AppShutdownTestSystemBeginShutdown(CancelReason reason) + { + AICLI_LOG(CLI, Info, << "AppShutdownTestSystemBeginShutdown :: " << reason); + } + + void AppShutdownTestSystemWait() + { + AICLI_LOG(CLI, Info, << "AppShutdownTestSystemWait"); + } + void EnsureDSCv3Processor(Execution::Context& context) { auto& configurationSet = context.Get().Set(); @@ -354,6 +369,13 @@ namespace AppInstaller::CLI { HRESULT hr = E_FAIL; + ShutdownMonitoring::ServerShutdownSynchronization::ComponentSystem appShutdownTestSystem{}; + appShutdownTestSystem.BlockNewWork = AppShutdownTestSystemBlockNewWork; + appShutdownTestSystem.BeginShutdown = AppShutdownTestSystemBeginShutdown; + appShutdownTestSystem.Wait = AppShutdownTestSystemWait; + + ShutdownMonitoring::ServerShutdownSynchronization::AddComponent(appShutdownTestSystem); + // Only package context and admin won't create the window message. if (!Runtime::IsRunningInPackagedContext() || !Runtime::IsRunningAsAdmin()) { diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h index 3bda124a46..339245d0d5 100644 --- a/src/AppInstallerCLICore/ExecutionContext.h +++ b/src/AppInstallerCLICore/ExecutionContext.h @@ -80,12 +80,6 @@ namespace AppInstaller::CLI::Execution DEFINE_ENUM_FLAG_OPERATORS(ContextFlag); -#ifndef AICLI_DISABLE_TEST_HOOKS - HWND GetWindowHandle(); - - bool WaitForAppShutdownEvent(); -#endif - // Callback to log data actions. void ContextEnumBasedVariantMapActionCallback(const void* map, Data data, EnumBasedVariantMapAction action); diff --git a/src/AppInstallerCLICore/Public/ShutdownMonitoring.h b/src/AppInstallerCLICore/Public/ShutdownMonitoring.h index 77599c3021..912903f0f8 100644 --- a/src/AppInstallerCLICore/Public/ShutdownMonitoring.h +++ b/src/AppInstallerCLICore/Public/ShutdownMonitoring.h @@ -3,10 +3,10 @@ #pragma once #include #include -#include #include #include #include +#include namespace AppInstaller::ShutdownMonitoring { @@ -32,9 +32,6 @@ namespace AppInstaller::ShutdownMonitoring #ifndef AICLI_DISABLE_TEST_HOOKS // Gets the window handle for the message window. HWND GetWindowHandle() const; - - // Waits for the shutdown event. - bool WaitForAppShutdownEvent() const; #endif private: @@ -52,17 +49,11 @@ namespace AppInstaller::ShutdownMonitoring void CreateWindowAndStartMessageLoop(); -#ifndef AICLI_DISABLE_TEST_HOOKS - wil::unique_event m_appShutdownEvent; -#endif - std::mutex m_listenersLock; std::vector m_listeners; wil::unique_event m_messageQueueReady; wil::unique_hwnd m_windowHandle; std::thread m_windowThread; - winrt::Windows::ApplicationModel::PackageCatalog m_catalog = nullptr; - decltype(winrt::Windows::ApplicationModel::PackageCatalog{ nullptr }.PackageUpdating(winrt::auto_revoke, nullptr)) m_updatingEvent; }; // Coordinates shutdown across server components @@ -91,7 +82,7 @@ namespace AppInstaller::ShutdownMonitoring static void AddComponent(const ComponentSystem& component); // Waits for the shutdown to complete. - static void WaitForShutdown(); + static bool WaitForShutdown(std::optional timeout = std::nullopt); // Listens for a termination signal. void Cancel(CancelReason reason, bool force) override; diff --git a/src/AppInstallerCLICore/ShutdownMonitoring.cpp b/src/AppInstallerCLICore/ShutdownMonitoring.cpp index c7122bf66f..bd03ab1f3a 100644 --- a/src/AppInstallerCLICore/ShutdownMonitoring.cpp +++ b/src/AppInstallerCLICore/ShutdownMonitoring.cpp @@ -63,30 +63,10 @@ namespace AppInstaller::ShutdownMonitoring { return m_windowHandle.get(); } - - bool TerminationSignalHandler::WaitForAppShutdownEvent() const - { - return m_appShutdownEvent.wait(60000); - } #endif TerminationSignalHandler::TerminationSignalHandler() { -#ifndef AICLI_DISABLE_TEST_HOOKS - m_appShutdownEvent.create(); -#endif - - if (Runtime::IsRunningInPackagedContext()) - { - // Create package update listener - m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentPackage(); - m_updatingEvent = m_catalog.PackageUpdating( - winrt::auto_revoke, [this](winrt::Windows::ApplicationModel::PackageCatalog, winrt::Windows::ApplicationModel::PackageUpdatingEventArgs) - { - this->StartAppShutdown(); - }); - } - // Create message only window. m_messageQueueReady.create(); m_windowThread = std::thread(&TerminationSignalHandler::CreateWindowAndStartMessageLoop, this); @@ -120,10 +100,6 @@ namespace AppInstaller::ShutdownMonitoring { AICLI_LOG(CLI, Info, << "Initiating shutdown procedure"); -#ifndef AICLI_DISABLE_TEST_HOOKS - m_appShutdownEvent.SetEvent(); -#endif - // Lifetime manager sends CTRL-C after the WM_QUERYENDSESSION is processed. // If we disable the CTRL-C handler, the default handler will kill us. InformListeners(CancelReason::AppShutdown, true); @@ -306,20 +282,27 @@ namespace AppInstaller::ShutdownMonitoring instance.m_components.push_back(component); } - void ServerShutdownSynchronization::WaitForShutdown() + bool ServerShutdownSynchronization::WaitForShutdown(std::optional timeout) { ServerShutdownSynchronization& instance = Instance(); + if (timeout) + { + return instance.m_shutdownComplete.wait(timeout.value()); + } + else { - std::lock_guard lock{ instance.m_threadLock }; - if (!instance.m_shutdownThread.joinable()) { - AICLI_LOG(Core, Warning, << "Attempt to wait for shutdown when shutdown has not been initiated."); - return; + std::lock_guard lock{ instance.m_threadLock }; + if (!instance.m_shutdownThread.joinable()) + { + AICLI_LOG(Core, Warning, << "Attempt to wait for shutdown when shutdown has not been initiated."); + return false; + } } - } - instance.m_shutdownComplete.wait(); + return instance.m_shutdownComplete.wait(); + } } void ServerShutdownSynchronization::Cancel(CancelReason reason, bool) @@ -362,6 +345,7 @@ namespace AppInstaller::ShutdownMonitoring components = m_components; } + AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: BlockNewWork"); for (const auto& component : components) { if (component.BlockNewWork) @@ -370,6 +354,7 @@ namespace AppInstaller::ShutdownMonitoring } } + AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: BeginShutdown"); for (const auto& component : components) { if (component.BeginShutdown) @@ -378,6 +363,7 @@ namespace AppInstaller::ShutdownMonitoring } } + AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: Wait"); for (const auto& component : components) { if (component.Wait) @@ -386,6 +372,7 @@ namespace AppInstaller::ShutdownMonitoring } } + AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: ShutdownCompleteCallback"); ShutdownCompleteCallback callback = m_callback; if (callback) { diff --git a/src/AppInstallerCLIE2ETests/AppShutdownTests.cs b/src/AppInstallerCLIE2ETests/AppShutdownTests.cs index 2864c4e991..7c83c13549 100644 --- a/src/AppInstallerCLIE2ETests/AppShutdownTests.cs +++ b/src/AppInstallerCLIE2ETests/AppShutdownTests.cs @@ -24,6 +24,7 @@ public class AppShutdownTests /// Runs winget test appshutdown and register the application to force a WM_QUERYENDSESSION message. /// [Test] + [Ignore("This test relied on a signal to terminate that was determined to be problematic. We may need OS fixes to test it when elevated.")] public void RegisterApplicationTest() { if (!TestSetup.Parameters.PackagedContext) @@ -95,9 +96,6 @@ public void RegisterApplicationTest() Task.WaitAll(new Task[] { testCmdTask, registerTask }, 360000); - // Assert.True(registerTask.Result); - TestContext.Out.Write(testCmdTask.Result.StdOut); - // The ctrl-c command terminates the batch file before the exit code file gets created. // Look for the output. Assert.True(testCmdTask.Result.StdOut.Contains("Succeeded waiting for app shutdown event")); diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 7bbd210747..970d0a628f 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -1053,6 +1053,12 @@ true + + true + + + true + diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index 24659ea796..94d3523d70 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -73,47 +73,79 @@ Manifest::Manifest MakeDefaultManifest(std::string_view version = "1.0"sv) return result; } -struct TestPackageHelper +struct TestManifestHelper { - TestPackageHelper(bool isInstalled, std::shared_ptr source = {}) : - m_isInstalled(isInstalled), m_manifest(MakeDefaultManifest()), m_source(source) {} + TestManifestHelper() : m_manifest(MakeDefaultManifest()) {} - TestPackageHelper& WithId(const std::string& id) + TestManifestHelper& WithId(const std::string& id) { m_manifest.Id = id; return *this; } - TestPackageHelper& WithVersion(std::string_view version) + TestManifestHelper& WithVersion(std::string_view version) { m_manifest.Version = version; return *this; } - TestPackageHelper& WithChannel(const std::string& channel) + TestManifestHelper& WithChannel(const std::string& channel) { m_manifest.Channel = channel; return *this; } - TestPackageHelper& WithDefaultName(std::string_view name) + TestManifestHelper& WithDefaultName(std::string_view name) { m_manifest.DefaultLocalization.Add(std::string{ name }); return *this; } - TestPackageHelper& WithPFN(const std::string& pfn) + TestManifestHelper& WithPFN(const std::string& pfn) { m_manifest.Installers[0].PackageFamilyName = pfn; return *this; } - TestPackageHelper& WithPC(const std::string& pc) + TestManifestHelper& WithPC(const std::string& pc) { m_manifest.Installers[0].ProductCode = pc; return *this; } + TestManifestHelper& WithType(Manifest::InstallerTypeEnum type) + { + m_manifest.Installers[0].BaseInstallerType = type; + return *this; + } + + TestManifestHelper& WithDisplayVersion(std::string_view version) + { + if (m_manifest.Installers[0].AppsAndFeaturesEntries.empty()) + { + m_manifest.Installers[0].AppsAndFeaturesEntries.emplace_back(); + } + m_manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = version; + return *this; + } + + operator const Manifest::Manifest& () const + { + return m_manifest; + } + +private: + Manifest::Manifest m_manifest; +}; + +struct TestPackageHelper +{ + TestPackageHelper(bool isInstalled, std::shared_ptr source = {}) : + m_isInstalled(isInstalled), m_source(source) + { + m_manifestHelpers.emplace_back(); + } + TestPackageHelper& HideSRS(bool value = true) { m_hideSystemReferenceStrings = value; @@ -126,11 +158,18 @@ struct TestPackageHelper { if (m_isInstalled) { - m_package = TestCompositePackage::Make(m_manifest, TestCompositePackage::MetadataMap{}, std::vector(), m_source); + m_package = TestCompositePackage::Make(this->operator const Manifest::Manifest&(), m_metadata, std::vector(), m_source); } else { - m_package = TestCompositePackage::Make(std::vector{ m_manifest }, m_source, m_hideSystemReferenceStrings); + std::vector manifests; + + for (const auto& helper : m_manifestHelpers) + { + manifests.emplace_back(helper.operator const AppInstaller::Manifest::Manifest &()); + } + + m_package = TestCompositePackage::Make(manifests, m_source, m_hideSystemReferenceStrings); } } @@ -142,17 +181,89 @@ struct TestPackageHelper return ToPackage(); } + TestPackageHelper& WithId(const std::string& id) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithId(id); + return *this; + } + + TestPackageHelper& WithVersion(std::string_view version) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithVersion(version); + return *this; + } + + TestPackageHelper& WithChannel(const std::string& channel) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithChannel(channel); + return *this; + } + + TestPackageHelper& WithDefaultName(std::string_view name) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithDefaultName(name); + return *this; + } + + TestPackageHelper& WithPFN(const std::string& pfn) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithPFN(pfn); + return *this; + } + + TestPackageHelper& WithPC(const std::string& pc) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithPC(pc); + return *this; + } + + TestPackageHelper& WithType(Manifest::InstallerTypeEnum type) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithType(type); + return *this; + } + + TestPackageHelper& WithDisplayVersion(std::string_view version) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithDisplayVersion(version); + return *this; + } + + TestPackageHelper& WithMetadata(PackageVersionMetadata metadata, const std::string& value) + { + THROW_HR_IF(E_UNEXPECTED, !m_isInstalled); + m_metadata[metadata] = value; + return *this; + } + operator const Manifest::Manifest& () const { - return m_manifest; + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + return m_manifestHelpers[0]; + } + + TestManifestHelper& MakeManifest() + { + THROW_HR_IF(E_UNEXPECTED, m_isInstalled); + m_manifestHelpers.emplace_back(); + return m_manifestHelpers.back(); } private: bool m_isInstalled; - Manifest::Manifest m_manifest; + std::vector m_manifestHelpers; std::shared_ptr m_source; std::shared_ptr m_package; bool m_hideSystemReferenceStrings = false; + TestCompositePackage::MetadataMap m_metadata; }; // A helper to create the sources used by the majority of tests in this file. @@ -1831,3 +1942,43 @@ TEST_CASE("CompositeSource_SxS_Available_TwoVersions_SameAvailable", "[Composite REQUIRE(availablePackages.size() == 1); REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); } + +TEST_CASE("CompositeSource_MappedVersions_ProperSorting", "[CompositeSource]") +{ + std::string installedID = "Installed.Id"; + std::string availableID = "Available.Id"; + auto type = Manifest::InstallerTypeEnum::Exe; + std::string pfn = "MY_PFN"; + std::string version1 = "1000.0"; + std::string version2 = "2000.0"; + std::string versionMapped1 = "1.0"; + std::string versionMapped2 = "2.0"; + + CompositeTestSetup setup; + + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(installedID).WithPFN(pfn).WithVersion(version1).WithMetadata(PackageVersionMetadata::InstalledType, "exe"), Criteria()); + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(installedID).WithPFN(pfn).WithVersion(version2).WithMetadata(PackageVersionMetadata::InstalledType, "exe"), Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + auto package = setup.MakeAvailable(); + package.WithId(availableID).WithType(type).WithPFN(pfn).WithVersion(versionMapped1).WithDisplayVersion(version1); + package.MakeManifest().WithId(availableID).WithType(type).WithPFN(pfn).WithVersion(versionMapped2).WithDisplayVersion(version2); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + SearchResult result = setup.Search(true); + + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + auto installedPackage = package->GetInstalled(); + REQUIRE(installedPackage); + auto installedVersions = installedPackage->GetVersionKeys(); + REQUIRE(installedVersions.size() == 2); + REQUIRE(installedVersions[0].Version == versionMapped2); + REQUIRE(installedVersions[1].Version == versionMapped1); +} diff --git a/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-AppsAndFeatures.yaml b/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-AppsAndFeatures.yaml new file mode 100644 index 0000000000..4a0503d09f --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-AppsAndFeatures.yaml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.12.0.schema.json + +PackageIdentifier: Microsoft.ArchiveContainingMSIX +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: Archive with nested MSIX +Description: A manifest containing an archive with a nested MSIX installer +InstallerLocale: en-US +InstallerType: exe +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +AppsAndFeaturesEntries: + - DisplayName: DisplayName + InstallerType: msix +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + +ManifestType: singleton +ManifestVersion: 1.12.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-Archive.yaml b/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-Archive.yaml new file mode 100644 index 0000000000..02f5072321 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-Archive.yaml @@ -0,0 +1,34 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.12.0.schema.json + +PackageIdentifier: Microsoft.ArchiveContainingMSIX +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: Archive with nested MSIX +Description: A manifest containing an archive with a nested MSIX installer +InstallerLocale: en-US +InstallerType: zip +NestedInstallerType: msix +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + +ManifestType: singleton +ManifestVersion: 1.12.0 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index ce1b23b757..fac1eee06e 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -2085,3 +2085,22 @@ TEST_CASE("ShadowManifest_NotVerifiedPublisher", "[ShadowManifest]") TempFile mergedManifestFile{ "merged.yaml" }; REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile), ManifestException, ManifestExceptionMatcher("Field usage requires verified publishers. [Icons]")); } + +TEST_CASE("Manifest_PackageFamilyNameInheritance", "[ManifestValidation]") +{ + std::filesystem::path testManifest; + + SECTION("MSIX inside Archive") + { + testManifest = "Manifest-MSIX-in-Archive.yaml"; + } + SECTION("MSIX in AppsAndFeatures") + { + testManifest = "Manifest-MSIX-in-AppsAndFeatures.yaml"; + } + + auto manifest = YamlParser::CreateFromPath(TestDataFile(testManifest), GetTestManifestValidateOption()); + + REQUIRE(!manifest.Installers.empty()); + REQUIRE(!manifest.Installers[0].PackageFamilyName.empty()); +} diff --git a/src/AppInstallerCommonCore/MSStore.cpp b/src/AppInstallerCommonCore/MSStore.cpp index 81b3b21db1..6b8f458072 100644 --- a/src/AppInstallerCommonCore/MSStore.cpp +++ b/src/AppInstallerCommonCore/MSStore.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace AppInstaller::MSStore { @@ -176,6 +177,133 @@ namespace AppInstaller::MSStore return S_FALSE; } + + // Used to detect a signal that a package update is being requested so that we can early out + // on an attempt to update ourself. This is only needed for elevated processes because the + // standard shutdown signals are not sent to elevated processes in the same manner. + struct PackageUpdateMonitor + { + PackageUpdateMonitor() + { + if (Runtime::IsRunningAsAdmin() && Runtime::IsRunningInPackagedContext()) + { + m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentPackage(); + m_updatingEvent = m_catalog.PackageUpdating( + winrt::auto_revoke, [this](winrt::Windows::ApplicationModel::PackageCatalog, winrt::Windows::ApplicationModel::PackageUpdatingEventArgs args) + { + // Deployment always sends a value of 0 before doing any work and a value of 100 when completely done. + constexpr double minProgress = 0; + auto progress = args.Progress(); + if (progress > minProgress) + { + m_isUpdating = true; + } + }); + } + } + + bool IsUpdating() const + { + return m_isUpdating; + } + + private: + winrt::Windows::ApplicationModel::PackageCatalog m_catalog = nullptr; + decltype(winrt::Windows::ApplicationModel::PackageCatalog{ nullptr }.PackageUpdating(winrt::auto_revoke, nullptr)) m_updatingEvent; + std::atomic_bool m_isUpdating = false; + }; + + HRESULT WaitForOperation(const std::wstring& productId, bool isSilentMode, IVectorView& installItems, IProgressCallback& progress, const PackageUpdateMonitor& monitor) + { + auto cancelIfOperationFailed = wil::scope_exit( + [&]() + { + try + { + AppInstallManager installManager; + installManager.Cancel(productId); + } + CATCH_LOG(); + }); + + for (auto const& installItem : installItems) + { + AICLI_LOG(Core, Info, << + "Started MSStore package execution. ProductId: " << Utility::ConvertToUTF8(installItem.ProductId()) << + " PackageFamilyName: " << Utility::ConvertToUTF8(installItem.PackageFamilyName())); + + if (isSilentMode) + { + installItem.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + installItem.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + } + } + + HRESULT errorCode = S_OK; + + // We are aggregating all AppInstallItem progresses into one. + // Averaging every progress for now until we have a better way to find overall progress. + uint64_t overallProgressMax = 100 * static_cast(installItems.Size()); + uint64_t currentProgress = 0; + + while (currentProgress < overallProgressMax) + { + currentProgress = 0; + + for (auto const& installItem : installItems) + { + const auto& status = installItem.GetCurrentStatus(); + currentProgress += static_cast(status.PercentComplete()); + + errorCode = status.ErrorCode(); + + if (!SUCCEEDED(errorCode)) + { + return errorCode; + } + } + + // It may take a while for Store client to pick up the install request. + // So we show indefinite progress here to avoid a progress bar stuck at 0. + if (currentProgress > 0) + { + progress.OnProgress(currentProgress, overallProgressMax, ProgressType::Percent); + } + + if (progress.IsCancelledBy(CancelReason::User)) + { + for (auto const& installItem : installItems) + { + installItem.Cancel(); + } + } + + // If app shutdown then we have 30s to keep installing, keep going and hope for the best. + else if (progress.IsCancelledBy(CancelReason::AppShutdown) || monitor.IsUpdating()) + { + for (auto const& installItem : installItems) + { + // Insert spiderman meme. + if (installItem.ProductId() == std::wstring{ s_AppInstallerProductId }) + { + AICLI_LOG(Core, Info, << "Asked to shutdown while installing AppInstaller."); + progress.OnProgress(overallProgressMax, overallProgressMax, ProgressType::Percent); + cancelIfOperationFailed.release(); + return S_OK; + } + } + } + + Sleep(100); + } + + if (SUCCEEDED(errorCode)) + { + cancelIfOperationFailed.release(); + } + + return errorCode; + } } HRESULT MSStoreOperation::StartAndWaitForOperation(IProgressCallback& progress) @@ -195,6 +323,8 @@ namespace AppInstaller::MSStore HRESULT MSStoreOperation::InstallPackage(IProgressCallback& progress) { + PackageUpdateMonitor monitor; + AppInstallManager installManager; AppInstallOptions installOptions; @@ -248,11 +378,13 @@ namespace AppInstaller::MSStore installOptions).get(); } - return WaitForOperation(installItems, progress); + return WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); } HRESULT MSStoreOperation::UpdatePackage(IProgressCallback& progress) { + PackageUpdateMonitor monitor; + AppInstallManager installManager; AppUpdateOptions updateOptions; updateOptions.AllowForcedAppRestart(m_force); @@ -300,98 +432,6 @@ namespace AppInstaller::MSStore installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView(); } - return WaitForOperation(installItems, progress); - } - - HRESULT MSStoreOperation::WaitForOperation(IVectorView& installItems, IProgressCallback& progress) - { - auto cancelIfOperationFailed = wil::scope_exit( - [&]() - { - try - { - AppInstallManager installManager; - installManager.Cancel(m_productId); - } - CATCH_LOG(); - }); - - for (auto const& installItem : installItems) - { - AICLI_LOG(Core, Info, << - "Started MSStore package execution. ProductId: " << Utility::ConvertToUTF8(installItem.ProductId()) << - " PackageFamilyName: " << Utility::ConvertToUTF8(installItem.PackageFamilyName())); - - if (m_isSilentMode) - { - installItem.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - installItem.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - } - } - - HRESULT errorCode = S_OK; - - // We are aggregating all AppInstallItem progresses into one. - // Averaging every progress for now until we have a better way to find overall progress. - uint64_t overallProgressMax = 100 * static_cast(installItems.Size()); - uint64_t currentProgress = 0; - - while (currentProgress < overallProgressMax) - { - currentProgress = 0; - - for (auto const& installItem : installItems) - { - const auto& status = installItem.GetCurrentStatus(); - currentProgress += static_cast(status.PercentComplete()); - - errorCode = status.ErrorCode(); - - if (!SUCCEEDED(errorCode)) - { - return errorCode; - } - } - - // It may take a while for Store client to pick up the install request. - // So we show indefinite progress here to avoid a progress bar stuck at 0. - if (currentProgress > 0) - { - progress.OnProgress(currentProgress, overallProgressMax, ProgressType::Percent); - } - - if (progress.IsCancelledBy(CancelReason::User)) - { - for (auto const& installItem : installItems) - { - installItem.Cancel(); - } - } - - // If app shutdown then we have 30s to keep installing, keep going and hope for the best. - else if (progress.IsCancelledBy(CancelReason::AppShutdown)) - { - for (auto const& installItem : installItems) - { - // Insert spiderman meme. - if (installItem.ProductId() == std::wstring{ s_AppInstallerProductId }) - { - AICLI_LOG(Core, Info, << "Asked to shutdown while installing AppInstaller."); - progress.OnProgress(overallProgressMax, overallProgressMax, ProgressType::Percent); - cancelIfOperationFailed.release(); - return S_OK; - } - } - } - - Sleep(100); - } - - if (SUCCEEDED(errorCode)) - { - cancelIfOperationFailed.release(); - } - - return errorCode; + return WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); } } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp index 1fdddaec90..544c82e36c 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -885,6 +885,19 @@ namespace AppInstaller::Manifest return (installerType == InstallerTypeEnum::Msix || installerType == InstallerTypeEnum::MSStore); } + bool DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(const std::vector& entries) + { + for (const AppsAndFeaturesEntry& entry : entries) + { + if (DoesInstallerTypeUsePackageFamilyName(entry.InstallerType)) + { + return true; + } + } + + return false; + } + bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType) { return ( @@ -919,7 +932,8 @@ namespace AppInstaller::Manifest installerType == InstallerTypeEnum::Msi || installerType == InstallerTypeEnum::Nullsoft || installerType == InstallerTypeEnum::Wix || - installerType == InstallerTypeEnum::Burn + installerType == InstallerTypeEnum::Burn || + installerType == InstallerTypeEnum::Msix ); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index 586d8b7deb..c9f87c516a 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -197,7 +197,7 @@ namespace AppInstaller::Manifest // Validate system reference strings if they are set at the installer level // Allow PackageFamilyName to be declared with non msix installers to support nested installer scenarios. But still report as warning to notify user of this uncommon case. - if (!installer.PackageFamilyName.empty() && !DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType())) + if (!installer.PackageFamilyName.empty() && !(DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType()) || DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(installer.AppsAndFeaturesEntries))) { resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "InstallerType", std::string{ InstallerTypeToString(installer.EffectiveInstallerType()) }, ValidationError::Level::Warning); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 2d46f60403..aef14781b6 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -1140,22 +1140,7 @@ namespace AppInstaller::Manifest auto errors = ValidateAndProcessFields(entry, InstallerFieldInfos, VariantManifestPtr(&installer)); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - // Copy in system reference strings from the root if not set in the installer and appropriate - if (installer.PackageFamilyName.empty() && DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType())) - { - installer.PackageFamilyName = m_manifest.get().DefaultInstallerInfo.PackageFamilyName; - } - - if (installer.ProductCode.empty() && DoesInstallerTypeUseProductCode(installer.EffectiveInstallerType())) - { - installer.ProductCode = m_manifest.get().DefaultInstallerInfo.ProductCode; - } - - if (installer.AppsAndFeaturesEntries.empty() && DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.EffectiveInstallerType())) - { - installer.AppsAndFeaturesEntries = m_manifest.get().DefaultInstallerInfo.AppsAndFeaturesEntries; - } - + // Set installer type back before attempting to use it in any of the EffectiveInstallerType calls below if (IsArchiveType(installer.BaseInstallerType)) { if (installer.NestedInstallerFiles.empty()) @@ -1169,6 +1154,24 @@ namespace AppInstaller::Manifest } } + // Copy in system reference strings from the root if not set in the installer and appropriate + if (installer.AppsAndFeaturesEntries.empty() && DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.EffectiveInstallerType())) + { + installer.AppsAndFeaturesEntries = m_manifest.get().DefaultInstallerInfo.AppsAndFeaturesEntries; + } + + if (installer.PackageFamilyName.empty() && + (DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType()) || + DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(installer.AppsAndFeaturesEntries))) + { + installer.PackageFamilyName = m_manifest.get().DefaultInstallerInfo.PackageFamilyName; + } + + if (installer.ProductCode.empty() && DoesInstallerTypeUseProductCode(installer.EffectiveInstallerType())) + { + installer.ProductCode = m_manifest.get().DefaultInstallerInfo.ProductCode; + } + // If there are no dependencies on installer use default ones if (!installer.Dependencies.HasAny()) { diff --git a/src/AppInstallerCommonCore/Public/winget/MSStore.h b/src/AppInstallerCommonCore/Public/winget/MSStore.h index 55540b28bc..8dd67491a4 100644 --- a/src/AppInstallerCommonCore/Public/winget/MSStore.h +++ b/src/AppInstallerCommonCore/Public/winget/MSStore.h @@ -39,7 +39,6 @@ namespace AppInstaller::MSStore private: HRESULT InstallPackage(IProgressCallback& progress); HRESULT UpdatePackage(IProgressCallback& progress); - HRESULT WaitForOperation(winrt::Windows::Foundation::Collections::IVectorView& installItems, IProgressCallback& progress); MSStoreOperationType m_type; std::wstring m_productId; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 39c6ff9685..f30f4ae8d6 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -434,6 +434,9 @@ namespace AppInstaller::Manifest // Gets a value indicating whether the given installer uses the PackageFamilyName system reference. bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); + // Gets a value indicating whether any of the ARP entries uses the PackageFamilyName system reference. + bool DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(const std::vector& entries); + // Gets a value indicating whether the given installer uses the ProductCode system reference. bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType); diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index d8f81eb923..78c0ad5e22 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -550,6 +550,7 @@ namespace AppInstaller::Repository if (Manifest::DoesInstallerTypeSupportArpVersionRange(key.InstalledType)) { key.Version = GetMappedInstalledVersion(key.InstalledVersion->GetProperty(PackageVersionProperty::Version), availablePackage); + key.VersionAndChannel = Utility::VersionAndChannel{ key.Version, key.Channel }; } } }