winget-cli

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

commit 85869fe56ddc8241bd00481c77fc9e6df04194f7
parent a23bfb791f0531671befefc065c2f09ef0d7066e
Author: sreadingMSFT <74242768+sreadingMSFT@users.noreply.github.com>
Date:   Wed, 18 Aug 2021 12:29:57 -0700

Add an install queue and an ISource for querying Installing packages. (#1342)

* Add installing queue

* Fix spelling\contract version

* Fix progress callback to suspended processes.

* Try to fix some line endings.

* Code review comment updates.

* Differentiate installing packages by source.

* Change api to use PackageCatalogInfo

* Fix typo for spell check.

* Updates for Id creation.

* Change pointer types from pr feedback.

* Update src/AppInstallerCLICore/ContextOrchestrator.cpp

Co-authored-by: JohnMcPMS <johnmcp@microsoft.com>
Diffstat:
M.github/actions/spelling/allow.txt | 1+
M.github/actions/spelling/expect.txt | 1+
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj | 2++
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters | 6++++++
Msrc/AppInstallerCLICore/COMContext.cpp | 34++++++++++++++++++++++++++++------
Msrc/AppInstallerCLICore/COMContext.h | 16++++++++++------
Asrc/AppInstallerCLICore/ContextOrchestrator.cpp | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/ContextOrchestrator.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/ExecutionContext.cpp | 14+++++---------
Msrc/AppInstallerCLICore/ExecutionContext.h | 17++++++++++++++++-
Msrc/AppInstallerCommonCore/AppInstallerTelemetry.cpp | 10+++++-----
Msrc/AppInstallerCommonCore/Public/AppInstallerTelemetry.h | 6+++---
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj | 2++
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters | 6++++++
Asrc/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h | 36++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp | 15+++++++++++++++
Msrc/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h | 13+++++++++++++
Msrc/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h | 7-------
Msrc/AppInstallerRepositoryCore/Public/AppInstallerRepositorySource.h | 14+++++++++-----
Msrc/AppInstallerRepositoryCore/RepositorySource.cpp | 12++++++++++++
Msrc/Microsoft.Management.Deployment/Converters.cpp | 5-----
Msrc/Microsoft.Management.Deployment/Converters.h | 1-
Msrc/Microsoft.Management.Deployment/Helpers.h | 14--------------
Msrc/Microsoft.Management.Deployment/PackageCatalog.cpp | 4++--
Msrc/Microsoft.Management.Deployment/PackageManager.cpp | 507+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/Microsoft.Management.Deployment/PackageManager.h | 3++-
Msrc/Microsoft.Management.Deployment/PackageManager.idl | 9++++++++-
28 files changed, 845 insertions(+), 242 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -87,6 +87,7 @@ delstore Demitrius denelon depersist +deque Deserialize deserializer deserializing diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -140,6 +140,7 @@ IID IInstalled IISOn img +IMutable IName inet inor diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -249,6 +249,7 @@ <ClInclude Include="Commands\ValidateCommand.h" /> <ClInclude Include="Commands\SettingsCommand.h" /> <ClInclude Include="CompletionData.h" /> + <ClInclude Include="ContextOrchestrator.h" /> <ClInclude Include="Workflows\DependenciesFlow.h" /> <ClInclude Include="ExecutionArgs.h" /> <ClInclude Include="ExecutionContextData.h" /> @@ -279,6 +280,7 @@ <ClCompile Include="COMContext.cpp" /> <ClCompile Include="Commands\COMInstallCommand.cpp" /> <ClCompile Include="Commands\ImportCommand.cpp" /> + <ClCompile Include="ContextOrchestrator.cpp" /> <ClCompile Include="Workflows\DependenciesFlow.cpp" /> <ClCompile Include="PackageCollection.cpp" /> <ClCompile Include="Argument.cpp" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -155,6 +155,9 @@ <ClInclude Include="COMContext.h"> <Filter>Public</Filter> </ClInclude> + <ClInclude Include="ContextOrchestrator.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="Workflows\DependenciesFlow.h"> <Filter>Workflows</Filter> </ClInclude> @@ -283,6 +286,9 @@ <ClCompile Include="COMContext.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="ContextOrchestrator.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="Workflows\DependenciesFlow.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/src/AppInstallerCLICore/COMContext.cpp b/src/AppInstallerCLICore/COMContext.cpp @@ -13,34 +13,56 @@ namespace AppInstaller m_nullIn.reset(new std::istream(&m_nullStreamBuf)); } + void COMContext::AddProgressCallbackFunction(ProgressCallBackFunction&& f) + { + std::lock_guard<std::mutex> lock{ m_callbackLock }; + m_comProgressCallbacks.push_back(std::move(f)); + } + + void COMContext::FireCallbacks(::AppInstaller::ReportType reportType, uint64_t current, uint64_t maximum, ProgressType progressType, ::AppInstaller::CLI::Workflow::ExecutionStage executionPhase) + { + // Lock around iterating through the list. Callbacks should not do long running tasks. + std::lock_guard<std::mutex> lock{ m_callbackLock }; + for (auto& callback : m_comProgressCallbacks) + { + callback(reportType, current, maximum, progressType, executionPhase); + } + }; + void COMContext::BeginProgress() { - m_comProgressCallback(ReportType::BeginProgress, 0, 0, ProgressType::None, m_executionStage); + FireCallbacks(ReportType::BeginProgress, 0, 0, ProgressType::None, m_executionStage); }; void COMContext::OnProgress(uint64_t current, uint64_t maximum, ProgressType progressType) { - m_comProgressCallback(ReportType::Progressing, current, maximum, progressType, m_executionStage); + FireCallbacks(ReportType::Progressing, current, maximum, progressType, m_executionStage); } void COMContext::EndProgress(bool) { - m_comProgressCallback(ReportType::EndProgress, 0, 0, ProgressType::None, m_executionStage); + FireCallbacks(ReportType::EndProgress, 0, 0, ProgressType::None, m_executionStage); }; void COMContext::SetExecutionStage(CLI::Workflow::ExecutionStage executionStage, bool) { m_executionStage = executionStage; - m_comProgressCallback(ReportType::ExecutionPhaseUpdate, 0, 0, ProgressType::None, m_executionStage); + FireCallbacks(ReportType::ExecutionPhaseUpdate, 0, 0, ProgressType::None, m_executionStage); Logging::SetExecutionStage(static_cast<uint32_t>(m_executionStage)); } - void COMContext::SetLoggerContext(const std::wstring_view telemetryCorelationJson, const std::string& caller) + void COMContext::SetLoggerContext(const std::wstring_view telemetryCorrelationJson, const std::string& caller) { - Logging::Telemetry().SetTelemetryCorelationJson(telemetryCorelationJson); + m_correlationData = telemetryCorrelationJson; + Logging::Telemetry().SetTelemetryCorrelationJson(telemetryCorrelationJson); Logging::Telemetry().SetCaller(caller); Logging::Telemetry().LogStartup(true); } + + std::wstring_view COMContext::GetCorrelationJson() + { + return m_correlationData; + } void COMContext::SetLoggers() { diff --git a/src/AppInstallerCLICore/COMContext.h b/src/AppInstallerCLICore/COMContext.h @@ -58,21 +58,25 @@ namespace AppInstaller CLI::Workflow::ExecutionStage GetExecutionStage() const { return m_executionStage; } - void SetProgressCallbackFunction(ProgressCallBackFunction&& f) - { - m_comProgressCallback = std::move(f); - } + void AddProgressCallbackFunction(ProgressCallBackFunction&& f); // Set COM call context for diagnostic and telemetry loggers // This should be called for every COMContext object instance - void SetLoggerContext(const std::wstring_view telemetryCorelationJson, const std::string& caller); + void SetLoggerContext(const std::wstring_view telemetryCorrelationJson, const std::string& caller); + + std::wstring_view GetCorrelationJson(); // Set Diagnostic and Telemetry loggers, Wil failure callback // This should be called only once per COM Server instance static void SetLoggers(); private: + void FireCallbacks(::AppInstaller::ReportType reportType, uint64_t current, uint64_t maximum, ProgressType progressType, ::AppInstaller::CLI::Workflow::ExecutionStage executionPhase); + std::vector<ProgressCallBackFunction> GetCallbacks(); + CLI::Workflow::ExecutionStage m_executionStage = CLI::Workflow::ExecutionStage::Initial; - ProgressCallBackFunction m_comProgressCallback; + std::vector<ProgressCallBackFunction> m_comProgressCallbacks; + std::wstring m_correlationData = L""; + std::mutex m_callbackLock; }; } diff --git a/src/AppInstallerCLICore/ContextOrchestrator.cpp b/src/AppInstallerCLICore/ContextOrchestrator.cpp @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ExecutionContext.h" +#include "ContextOrchestrator.h" +#include "COMContext.h" +#include "Commands/COMInstallCommand.h" +#include "winget/UserSettings.h" +#include <Commands/RootCommand.h> + +namespace AppInstaller::CLI::Execution +{ + ContextOrchestrator& ContextOrchestrator::Instance() + { + static ContextOrchestrator s_instance; + return s_instance; + } + + ContextOrchestrator::ContextOrchestrator() + { + ::AppInstaller::ProgressCallback progress; + std::shared_ptr<::AppInstaller::Repository::ISource> installingSource = ::AppInstaller::Repository::OpenPredefinedSource(::AppInstaller::Repository::PredefinedSource::Installing, progress); + m_installingWriteableSource = std::dynamic_pointer_cast<::AppInstaller::Repository::IMutablePackageSource>(installingSource); + } + + _Requires_lock_held_(m_queueLock) + std::deque<std::shared_ptr<OrchestratorQueueItem>>::iterator ContextOrchestrator::FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId) + { + return std::find_if(m_queueItems.begin(), m_queueItems.end(), [&comparisonQueueItemId](const std::shared_ptr<OrchestratorQueueItem>& item) {return (item->GetId().IsSame(comparisonQueueItemId)); }); + + } + _Requires_lock_held_(m_queueLock) + std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::FindById(const OrchestratorQueueItemId& comparisonQueueItemId) + { + auto itr = FindIteratorById(comparisonQueueItemId); + if (itr != m_queueItems.end()) + { + return *itr; + } + return {}; + } + + void ContextOrchestrator::EnqueueItem(std::shared_ptr<OrchestratorQueueItem> item) + { + std::lock_guard<std::mutex> lock{ m_queueLock }; + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INSTALL_ALREADY_RUNNING), FindById(item->GetId())); + m_queueItems.push_back(item); + + // Add the package to the Installing source so that it can be queried using the ISource interface. + const auto& manifest = item->GetContext().Get<Execution::Data::Manifest>(); + m_installingWriteableSource->AddPackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + } + + void ContextOrchestrator::EnqueueAndRunItem(std::shared_ptr<OrchestratorQueueItem> item) + { + EnqueueItem(item); + + std::thread runnerThread(&ContextOrchestrator::RunItems, this); + runnerThread.detach(); + } + + std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::GetNextItem() + { + std::lock_guard<std::mutex> lock{ m_queueLock }; + + if (m_queueItems.empty()) + { + return {}; + } + + std::shared_ptr<OrchestratorQueueItem> item = m_queueItems.front(); + + // Check if item can be dequeued. + // Since only one item can be installed at a time currently the logic is very simple, + // and can just check if the first item is already running. This logic will need to become + // more complicated if multiple operation types (e.g. Download & Install) are added that can + // run simultaneously. + if (item->GetState() == OrchestratorQueueItemState::Running) + { + return {}; + } + + // Running state must be set inside the queueLock so that multiple threads don't try to run the same item. + item->SetState(OrchestratorQueueItemState::Running); + return item; + } + + void ContextOrchestrator::RunItems() + { + std::shared_ptr<OrchestratorQueueItem> item = GetNextItem(); + while(item != nullptr) + { + HRESULT terminationHR = S_OK; + try + { + ::AppInstaller::CLI::RootCommand rootCommand; + std::unique_ptr<::AppInstaller::CLI::Command> command = std::make_unique<::AppInstaller::CLI::COMInstallCommand>(rootCommand.Name()); + ::AppInstaller::Logging::Telemetry().LogCommand(command->FullName()); + command->ValidateArguments(item->GetContext().Args); + + item->GetContext().EnableCtrlHandler(); + + terminationHR = ::AppInstaller::CLI::Execute(item->GetContext(), command); + } + WINGET_CATCH_STORE(terminationHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + + if (FAILED(terminationHR)) + { + // ::Execute sometimes catches exceptions and returns hresults based on those exceptions without the context + // being updated with that hresult. This sets the termination hr directly so that the context always + // has the result of the operation no matter how it failed. + item->GetContext().SetTerminationHR(terminationHR); + } + + RemoveItemInState(*item, OrchestratorQueueItemState::Running); + + item = GetNextItem(); + } + } + + void ContextOrchestrator::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state) + { + std::lock_guard<std::mutex> lock{ m_queueLock }; + + // Look for the item. It's ok if the item is not found since multiple listeners may try to remove the same item. + //auto itr = std::find(m_queueItems.begin(), m_queueItems.end(), item); + auto itr = FindIteratorById(item.GetId()); + if (itr != m_queueItems.end() && (*itr)->GetState() == state) + { + m_queueItems.erase(itr); + + const auto& manifest = item.GetContext().Get<Execution::Data::Manifest>(); + m_installingWriteableSource->RemovePackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + + item.GetCompletedEvent().SetEvent(); + } + } + void ContextOrchestrator::CancelQueueItem(const OrchestratorQueueItem& item) + { + // Always cancel the item, even if it isn't running yet, to get the terminationHR set correctly. + item.GetContext().Cancel(false, true); + + RemoveItemInState(item, OrchestratorQueueItemState::Queued); + } + + std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::GetQueueItem(const OrchestratorQueueItemId& queueItemId) + { + std::lock_guard<std::mutex> lock{ m_queueLock }; + + return FindById(queueItemId); + } + + bool OrchestratorQueueItemId::IsSame(const OrchestratorQueueItemId& comparedId) const + { + return ((GetPackageId() == comparedId.GetPackageId()) && + (GetSourceId() == comparedId.GetSourceId())); + } + + std::unique_ptr<OrchestratorQueueItem> OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr<COMContext> context) + { + return std::make_unique<OrchestratorQueueItem>(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context)); + } + +} diff --git a/src/AppInstallerCLICore/ContextOrchestrator.h b/src/AppInstallerCLICore/ContextOrchestrator.h @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <AppInstallerLogging.h> +#include "ExecutionReporter.h" +#include "ExecutionArgs.h" +#include "ExecutionContextData.h" +#include "CompletionData.h" +#include "COMContext.h" + +#include <string_view> + +namespace AppInstaller::CLI::Execution +{ + enum class OrchestratorQueueItemState + { + Queued, + Running + }; + + struct OrchestratorQueueItemId + { + OrchestratorQueueItemId(std::wstring packageId, std::wstring sourceId) : m_packageId(std::move(packageId)), m_sourceId(std::move(sourceId)) {} + std::wstring_view GetPackageId() const { return m_packageId; } + std::wstring_view GetSourceId() const { return m_sourceId; } + + bool IsSame(const OrchestratorQueueItemId& comparisonQueueItemId) const; + private: + std::wstring m_packageId; + std::wstring m_sourceId; + }; + + struct OrchestratorQueueItem + { + OrchestratorQueueItem(OrchestratorQueueItemId id, std::unique_ptr<COMContext> context) : m_id(std::move(id)), m_context(std::move(context)) {} + + OrchestratorQueueItemState GetState() const { return m_state; } + void SetState(OrchestratorQueueItemState state) { m_state = state; } + COMContext& GetContext() const { return *m_context; } + const wil::unique_event& GetCompletedEvent() const { return m_completedEvent; } + const OrchestratorQueueItemId& GetId() const { return m_id; } + private: + OrchestratorQueueItemState m_state = OrchestratorQueueItemState::Queued; + std::unique_ptr<COMContext> m_context; + wil::unique_event m_completedEvent{ wil::EventOptions::ManualReset }; + OrchestratorQueueItemId m_id; + }; + + struct OrchestratorQueueItemFactory + { + static std::unique_ptr<OrchestratorQueueItem> CreateItemForInstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr<COMContext> context); + }; + + struct ContextOrchestrator + { + ContextOrchestrator(); + static ContextOrchestrator& Instance(); + + void EnqueueAndRunItem(std::shared_ptr<OrchestratorQueueItem> queueItem); + void CancelQueueItem(const OrchestratorQueueItem& item); + + std::shared_ptr<OrchestratorQueueItem> GetQueueItem(const OrchestratorQueueItemId& queueItemId); + + private: + std::mutex m_queueLock; + void RunItems(); + std::shared_ptr<OrchestratorQueueItem> GetNextItem(); + void EnqueueItem(std::shared_ptr<OrchestratorQueueItem> item); + void RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state); + + _Requires_lock_held_(m_queueLock) + std::deque<std::shared_ptr<OrchestratorQueueItem>>::iterator FindIteratorById(const OrchestratorQueueItemId& queueItemId); + _Requires_lock_held_(m_queueLock) + std::shared_ptr<OrchestratorQueueItem> FindById(const OrchestratorQueueItemId& queueItemId); + + std::shared_ptr<::AppInstaller::Repository::IMutablePackageSource> m_installingWriteableSource = nullptr; + std::deque<std::shared_ptr<OrchestratorQueueItem>> m_queueItems; + }; +} diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -24,15 +24,6 @@ namespace AppInstaller::CLI::Execution { std::lock_guard<std::mutex> lock{ m_contextsLock }; - // TODO: COMContexts are currently only used specifically for install operations, which Windows does not reliably support concurrently. - // As a temporary fix, this location which already has locking and is tracking the contexts is convenient to prevent those - // installs from happening concurrently. Future work will provide a more robust synchronization mechanism which can queue those requests - // rather than failing. - for (auto& existingContext : m_contexts) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INSTALL_ALREADY_RUNNING), (dynamic_cast<COMContext*>(existingContext) != 0)); - } - auto itr = std::find(m_contexts.begin(), m_contexts.end(), context); THROW_HR_IF(E_NOT_VALID_STATE, itr != m_contexts.end()); m_contexts.push_back(context); @@ -189,6 +180,11 @@ namespace AppInstaller::CLI::Execution m_terminationHR = hr; } + void Context::SetTerminationHR(HRESULT hr) + { + m_terminationHR = hr; + } + void Context::Cancel(bool exitIfStuck, bool bypassUser) { Terminate(exitIfStuck ? APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED : E_ABORT); diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h @@ -9,6 +9,19 @@ #include <string_view> +#define WINGET_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) catch (const wil::ResultException& re) { exceptionHR = re.GetErrorCode(); } +#define WINGET_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) catch (const winrt::hresult_error& hre) { exceptionHR = hre.code(); } +#define WINGET_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) catch (const ::AppInstaller::CLI::CommandException&) { exceptionHR = APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS; } +#define WINGET_CATCH_POLICY_EXCEPTION_STORE(exceptionHR) catch (const ::AppInstaller::Settings::GroupPolicyException&) { exceptionHR = APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY; } +#define WINGET_CATCH_STD_EXCEPTION_STORE(exceptionHR, genericHR) catch (const std::exception&) { exceptionHR = genericHR; } +#define WINGET_CATCH_ALL_EXCEPTION_STORE(exceptionHR, genericHR) catch (...) { exceptionHR = genericHR; } +#define WINGET_CATCH_STORE(exceptionHR, genericHR) \ + WINGET_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) \ + WINGET_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) \ + WINGET_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) \ + WINGET_CATCH_POLICY_EXCEPTION_STORE(exceptionHR) \ + WINGET_CATCH_STD_EXCEPTION_STORE(exceptionHR, genericHR) \ + WINGET_CATCH_ALL_EXCEPTION_STORE(exceptionHR, genericHR) // Terminates the Context with some logging to indicate the location. // Also returns from the current function. @@ -67,7 +80,6 @@ namespace AppInstaller::CLI::Execution virtual std::unique_ptr<Context> Clone(); // Enables reception of CTRL signals. - // Only one context can be enabled to handle CTRL signals at a time. void EnableCtrlHandler(bool enabled = true); // Applies changes based on the parsed args. @@ -82,6 +94,9 @@ namespace AppInstaller::CLI::Execution // Set the context to the terminated state. void Terminate(HRESULT hr, std::string_view file = {}, size_t line = {}); + // Set the termination hr of the context. + void SetTerminationHR(HRESULT hr); + // Cancel the context; this terminates it as well as informing any in progress task to stop cooperatively. // Multiple attempts with exitIfStuck == true may cause the process to simply exit. // The bypassUser indicates whether the user should be asked for cancellation (does not currently have any effect). diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -17,7 +17,7 @@ _eventName_,\ GetActivityId(false),\ nullptr,\ TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast<ULONG>(m_caller.size()), "Caller"),\ -TraceLoggingPackedFieldEx(m_telemetryCorelationJsonW.c_str(), static_cast<ULONG>((m_telemetryCorelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"),\ +TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast<ULONG>((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"),\ __VA_ARGS__) // Helper to print a GUID @@ -109,7 +109,7 @@ namespace AppInstaller::Logging m_isRuntimeEnabled = true; } - void TelemetryTraceLogger::SetTelemetryCorelationJson(const std::wstring_view jsonStr_view) noexcept + void TelemetryTraceLogger::SetTelemetryCorrelationJson(const std::wstring_view jsonStr_view) noexcept { // Check if passed in string is a valid Json formatted before returning the value // If invalid, return empty Json @@ -127,12 +127,12 @@ namespace AppInstaller::Logging if (result) { - m_telemetryCorelationJsonW = jsonStrW; - AICLI_LOG(Core, Info, << "Passed in Corelation Vector Json is valid: " << jsonStr); + m_telemetryCorrelationJsonW = jsonStrW; + AICLI_LOG(Core, Info, << "Passed in Correlation Vector Json is valid: " << jsonStr); } else { - AICLI_LOG(Core, Error, << "Passed in Corelation Vector Json is invalid: " << jsonStr << "; Error: " << errors); + AICLI_LOG(Core, Error, << "Passed in Correlation Vector Json is invalid: " << jsonStr << "; Error: " << errors); } } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h b/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h @@ -33,8 +33,8 @@ namespace AppInstaller::Logging // Store the passed in name of the Caller for COM calls void SetCaller(const std::string& caller); - // Store the passed in Telemetry Corelation Json for COM calls - void SetTelemetryCorelationJson(const std::wstring_view jsonStr_view) noexcept; + // Store the passed in Telemetry Correlation Json for COM calls + void SetTelemetryCorrelationJson(const std::wstring_view jsonStr_view) noexcept; // Logs the failure info. void LogFailure(const wil::FailureInfo& failure) const noexcept; @@ -135,7 +135,7 @@ namespace AppInstaller::Logging bool m_isSettingEnabled = true; std::atomic_bool m_isRuntimeEnabled{ true }; - std::wstring m_telemetryCorelationJsonW = L"{}"; + std::wstring m_telemetryCorrelationJsonW = L"{}"; std::string m_caller; // Data that is needed by AnonymizeString diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -228,6 +228,7 @@ <ClInclude Include="ICU\SQLiteICU.h" /> <ClInclude Include="Microsoft\ARPHelper.h" /> <ClInclude Include="Microsoft\PredefinedInstalledSourceFactory.h" /> + <ClInclude Include="Microsoft\PredefinedWriteableSourceFactory.h" /> <ClInclude Include="Microsoft\PreIndexedPackageSourceFactory.h" /> <ClInclude Include="Microsoft\Schema\1_0\ChannelTable.h" /> <ClInclude Include="Microsoft\Schema\1_0\CommandsTable.h" /> @@ -294,6 +295,7 @@ </ClCompile> <ClCompile Include="Microsoft\ARPHelper.cpp" /> <ClCompile Include="Microsoft\PredefinedInstalledSourceFactory.cpp" /> + <ClCompile Include="Microsoft\PredefinedWriteableSourceFactory.cpp" /> <ClCompile Include="Microsoft\PreIndexedPackageSourceFactory.cpp" /> <ClCompile Include="Microsoft\Schema\1_0\Interface_1_0.cpp" /> <ClCompile Include="Microsoft\Schema\1_0\ManifestTable.cpp" /> diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -216,6 +216,9 @@ <ClInclude Include="Microsoft\Schema\1_3\HashVirtualTable.h"> <Filter>Microsoft\Schema\1_3</Filter> </ClInclude> + <ClInclude Include="Microsoft\PredefinedWriteableSourceFactory.h"> + <Filter>Microsoft</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -329,6 +332,9 @@ <ClCompile Include="Microsoft\Schema\1_3\Interface_1_3.cpp"> <Filter>Microsoft\Schema\1_3</Filter> </ClCompile> + <ClCompile Include="Microsoft\PredefinedWriteableSourceFactory.cpp"> + <Filter>Microsoft</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "pch.h" +#include "Microsoft/ARPHelper.h" +#include "Microsoft/PredefinedWriteableSourceFactory.h" +#include "Microsoft/SQLiteIndex.h" +#include "Microsoft/SQLiteIndexSource.h" +#include <winget/ManifestInstaller.h> + +#include <winget/Registry.h> +#include <AppInstallerArchitecture.h> + +using namespace std::string_literals; +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Microsoft +{ + // The factory for the predefined installing source. + struct PredefinedWriteableSourceFactoryImpl : public ISourceFactory + { + std::shared_ptr<ISource> Create(const SourceDetails& details, IProgressCallback& progress) override final; + + bool Add(SourceDetails&, IProgressCallback&) override final + { + // Add should never be needed, as this is predefined. + THROW_HR(E_NOTIMPL); + } + + bool Update(const SourceDetails&, IProgressCallback&) override final + { + // Update could be used later, but not for now. + THROW_HR(E_NOTIMPL); + } + + bool Remove(const SourceDetails&, IProgressCallback&) override final + { + // Similar to add, remove should never be needed. + THROW_HR(E_NOTIMPL); + } + + private: + static std::shared_ptr<ISource> g_sharedSource; + static std::once_flag g_InstallingSourceOnceFlag; + }; + + std::shared_ptr<ISource> PredefinedWriteableSourceFactoryImpl::g_sharedSource = nullptr; + std::once_flag PredefinedWriteableSourceFactoryImpl::g_InstallingSourceOnceFlag; + + std::shared_ptr<ISource> PredefinedWriteableSourceFactoryImpl::Create(const SourceDetails& details, IProgressCallback&) + { + THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedWriteableSourceFactory::Type()); + + std::string lockName = "WriteableSource_"; + + // Create an in memory index + SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, Schema::Version::Latest()); + + // Installing is the only type right now so just return the Installing source to all callers. + // Since the source is writeable, it must be shared by all callers that try to open it + // since queries on one instance would not see what was written on another instance. + std::call_once(g_InstallingSourceOnceFlag, + [&]() + { + // Create an in memory index + SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, Schema::Version::Latest()); + + g_sharedSource = std::make_shared<SQLiteIndexWriteableSource>(details, "*PredefinedWriteableSource", std::move(index), Synchronization::CrossProcessReaderWriteLock{}, true); + }); + return g_sharedSource; + } + + std::string_view PredefinedWriteableSourceFactory::TypeToString(WriteableType type) + { + switch (type) + { + case AppInstaller::Repository::Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing: + return "Installing"sv; + default: + return "Unknown"sv; + } + } + + std::unique_ptr<ISourceFactory> PredefinedWriteableSourceFactory::Create() + { + return std::make_unique<PredefinedWriteableSourceFactoryImpl>(); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Public/AppInstallerRepositorySource.h" +#include "SourceFactory.h" + +#include <string_view> + +namespace AppInstaller::Repository::Microsoft +{ + using namespace std::string_view_literals; + + // A source of installing packages on the local system. + // Arg :: A value indicating the type of writeable source + // Data :: Not used. + struct PredefinedWriteableSourceFactory + { + // Get the type string for this source. + static constexpr std::string_view Type() + { + return "Microsoft.Predefined.Writeable"sv; + } + + // The type for the source. + enum class WriteableType + { + Installing + }; + + // Converts a type to its string. + static std::string_view TypeToString(WriteableType type); + + // Creates a source factory for this type. + static std::unique_ptr<ISourceFactory> Create(); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -391,4 +391,19 @@ namespace AppInstaller::Repository::Microsoft { return (other && GetIdentifier() == other->GetIdentifier()); } + + SQLiteIndexWriteableSource::SQLiteIndexWriteableSource(const SourceDetails& details, std::string identifier, SQLiteIndex&& index, Synchronization::CrossProcessReaderWriteLock&& lock, bool isInstalledSource) : + SQLiteIndexSource(details, identifier, std::move(index), std::move(lock), isInstalledSource) + { + } + + void SQLiteIndexWriteableSource::AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + m_index.AddManifest(manifest, relativePath); + } + + void SQLiteIndexWriteableSource::RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + m_index.RemoveManifest(manifest, relativePath); + } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h @@ -44,6 +44,19 @@ namespace AppInstaller::Repository::Microsoft SourceDetails m_details; Synchronization::CrossProcessReaderWriteLock m_lock; bool m_isInstalled; + protected: SQLiteIndex m_index; }; + + // A source that holds a SQLiteIndex and lock. + struct SQLiteIndexWriteableSource : public SQLiteIndexSource, public IMutablePackageSource + { + SQLiteIndexWriteableSource(const SourceDetails& details, std::string identifier, SQLiteIndex&& index, Synchronization::CrossProcessReaderWriteLock&& lock = {}, bool isInstalledSource = false); + + // Adds a package version to the source. + void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + // Removes a package version from the source. + void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + }; } diff --git a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h @@ -187,13 +187,6 @@ namespace AppInstaller::Repository virtual Metadata GetMetadata() const = 0; }; - // An installed package version. - struct IInstalledPackageVersion : public IPackageVersion - { - // Sets metadata on the installed version. - virtual void SetMetadata(PackageVersionMetadata metadata, std::string_view value) = 0; - }; - // A key to identify a package version within a package. struct PackageVersionKey { diff --git a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySource.h b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySource.h @@ -89,13 +89,16 @@ namespace AppInstaller::Repository virtual SearchResult Search(const SearchRequest& request) const = 0; }; - // Interface extension to ISource for locally installed packages. - struct IInstalledPackageSource : public ISource + // Interface extension to ISource for databases that can be updated after creation, like InstallingPackages + struct IMutablePackageSource { - virtual ~IInstalledPackageSource() = default; + virtual ~IMutablePackageSource() = default; - // Adds an installed package version to the source. - virtual std::shared_ptr<IInstalledPackageVersion> AddInstalledPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; + // Adds a package version to the source. + virtual void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; + + // Removes a package version from the source. + virtual void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; }; // Gets the details for all sources. @@ -130,6 +133,7 @@ namespace AppInstaller::Repository Installed, ARP, MSIX, + Installing, }; // A well known source. diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -6,6 +6,7 @@ #include "CompositeSource.h" #include "SourceFactory.h" #include "Microsoft/PredefinedInstalledSourceFactory.h" +#include "Microsoft/PredefinedWriteableSourceFactory.h" #include "Microsoft/PreIndexedPackageSourceFactory.h" #include "Rest/RestSourceFactory.h" @@ -587,6 +588,11 @@ namespace AppInstaller::Repository { return Microsoft::PredefinedInstalledSourceFactory::Create(); } + // Should always come from code, so no need for case insensitivity + else if (Microsoft::PredefinedWriteableSourceFactory::Type() == type) + { + return Microsoft::PredefinedWriteableSourceFactory::Create(); + } else if (Utility::CaseInsensitiveEquals(Rest::RestSourceFactory::Type(), type)) { return Rest::RestSourceFactory::Create(); @@ -1083,6 +1089,12 @@ namespace AppInstaller::Repository details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX); return details; + case PredefinedSource::Installing: + details.Type = Microsoft::PredefinedWriteableSourceFactory::Type(); + // As long as there is only one type this is not particularly needed, but Arg is exposed publicly + // so this is used here for consistency with other predefined sources. + details.Arg = Microsoft::PredefinedWriteableSourceFactory::TypeToString(Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing); + return details; } THROW_HR(E_UNEXPECTED); diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp @@ -265,9 +265,4 @@ namespace winrt::Microsoft::Management::Deployment::implementation } return resultStatus; } - - bool IsLocalPackageCatalog(winrt::Microsoft::Management::Deployment::PackageCatalogInfo info) - { - return (winrt::to_string(info.Type()).compare(::AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Type()) == 0); - } } \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h @@ -13,5 +13,4 @@ namespace winrt::Microsoft::Management::Deployment::implementation ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(winrt::Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField); winrt::Microsoft::Management::Deployment::InstallResultStatus GetInstallResultStatus(::AppInstaller::CLI::Workflow::ExecutionStage executionStage, winrt::hresult hresult); winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult); - bool IsLocalPackageCatalog(winrt::Microsoft::Management::Deployment::PackageCatalogInfo info); } \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/Helpers.h b/src/Microsoft.Management.Deployment/Helpers.h @@ -5,20 +5,6 @@ InternalWrlCreateCreatorMap(className##instance##_COM, clsid, nullptr, ::Microsoft::WRL::Details::CreateClassFactory<factory>, "minATL$__f") #define CoCreatableCppWinRtClassWithCLSID(className, instance, clsid) CoCreatableClassWithCLSIDWithFactory(className, instance, clsid, ::wil::wrl_factory_for_winrt_com_class<className>) -#define WINGET_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) catch (const wil::ResultException& re) { exceptionHR = re.GetErrorCode(); } -#define WINGET_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) catch (const winrt::hresult_error& hre) { exceptionHR = hre.code(); } -#define WINGET_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) catch (const ::AppInstaller::CLI::CommandException&) { exceptionHR = APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS; } -#define WINGET_CATCH_POLICY_EXCEPTION_STORE(exceptionHR) catch (const ::AppInstaller::Settings::GroupPolicyException&) { exceptionHR = APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS; } -#define WINGET_CATCH_STD_EXCEPTION_STORE(exceptionHR) catch (const std::exception&) { exceptionHR = APPINSTALLER_CLI_ERROR_COMMAND_FAILED; } -#define WINGET_CATCH_ALL_EXCEPTION_STORE(exceptionHR) catch (...) { exceptionHR = APPINSTALLER_CLI_ERROR_COMMAND_FAILED; } -#define WINGET_CATCH_STORE(exceptionHR) \ - WINGET_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_POLICY_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_STD_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_ALL_EXCEPTION_STORE(exceptionHR) - namespace winrt::Microsoft::Management::Deployment::implementation { enum class Capability diff --git a/src/Microsoft.Management.Deployment/PackageCatalog.cpp b/src/Microsoft.Management.Deployment/PackageCatalog.cpp @@ -12,7 +12,7 @@ #include "MatchResult.h" #include "CatalogPackage.h" #include "Commands/RootCommand.h" -#include "Helpers.h" +#include "ExecutionContext.h" #pragma warning( push ) #pragma warning ( disable : 4467 6388) // 6388 Allow CreateInstance. @@ -159,7 +159,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation } isTruncated = searchResult.Truncated; } - WINGET_CATCH_STORE(hr); + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); return GetFindPackagesResult(hr, isTruncated, matches); } diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -28,8 +28,10 @@ #include "Workflows/WorkflowBase.h" #include "Converters.h" #include "Helpers.h" +#include "ContextOrchestrator.h" using namespace std::literals::chrono_literals; +using namespace ::AppInstaller::CLI; const GUID PackageManagerCLSID1 = { 0xC53A4F16, 0x787E, 0x42A4, { 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 } }; //C53A4F16-787E-42A4-B304-29EFFB4BF597 const GUID PackageManagerCLSID2 = { 0xE65C7D5A, 0x95AF, 0x4A98, { 0xBE, 0x5F, 0xA7, 0x93, 0x02, 0x9C, 0xEB, 0x56 } }; //E65C7D5A-95AF-4A98-BE5F-A793029CEB56 @@ -72,17 +74,23 @@ namespace winrt::Microsoft::Management::Deployment::implementation } winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog) { - // InstalledPackages is the only one supported right now, so return early if it's not that. - if (localPackageCatalog != Microsoft::Management::Deployment::LocalPackageCatalog::InstalledPackages) + ::AppInstaller::Repository::SourceDetails sourceDetails; + switch (localPackageCatalog) { + case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstalledPackages: + sourceDetails = GetPredefinedSourceDetails(::AppInstaller::Repository::PredefinedSource::Installed); + break; + case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstallingPackages: + sourceDetails = GetPredefinedSourceDetails(::AppInstaller::Repository::PredefinedSource::Installing); + break; + default: throw hresult_invalid_argument(); } - ::AppInstaller::Repository::SourceDetails sourceDetails = GetPredefinedSourceDetails(::AppInstaller::Repository::PredefinedSource::Installed); auto packageCatalogInfo = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>>(); packageCatalogInfo->Initialize(sourceDetails); - auto packageCatalogImpl = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference>>(); - packageCatalogImpl->Initialize(*packageCatalogInfo); - return *packageCatalogImpl; + auto packageCatalogRef = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference>>(); + packageCatalogRef->Initialize(*packageCatalogInfo); + return *packageCatalogRef; } winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPackageCatalogByName(hstring const& catalogName) { @@ -101,23 +109,23 @@ namespace winrt::Microsoft::Management::Deployment::implementation return nullptr; } } - void AddPackageManifestToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo, ::AppInstaller::CLI::Execution::Context& context) + void AddPackageManifestToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo, ::AppInstaller::CLI::Execution::Context* context) { winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* packageVersionInfoImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>(packageVersionInfo); std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalPackageVersion = packageVersionInfoImpl->GetRepositoryPackageVersion(); ::AppInstaller::Manifest::Manifest manifest = internalPackageVersion->GetManifest(); std::string targetLocale; - if (context.Args.Contains(::AppInstaller::CLI::Execution::Args::Type::Locale)) + if (context->Args.Contains(::AppInstaller::CLI::Execution::Args::Type::Locale)) { - targetLocale = context.Args.GetArg(::AppInstaller::CLI::Execution::Args::Type::Locale); + targetLocale = context->Args.GetArg(::AppInstaller::CLI::Execution::Args::Type::Locale); } manifest.ApplyLocale(targetLocale); - context.Add<::AppInstaller::CLI::Execution::Data::Manifest>(std::move(manifest)); - context.Add<::AppInstaller::CLI::Execution::Data::PackageVersion>(std::move(internalPackageVersion)); - ::AppInstaller::Logging::Telemetry().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get<::AppInstaller::Manifest::Localization::PackageName>(), manifest.Version); + + context->Add<::AppInstaller::CLI::Execution::Data::Manifest>(std::move(manifest)); + context->Add<::AppInstaller::CLI::Execution::Data::PackageVersion>(std::move(internalPackageVersion)); } winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options) { @@ -135,226 +143,373 @@ namespace winrt::Microsoft::Management::Deployment::implementation return *packageCatalogImpl; } - Windows::Foundation::IAsyncOperation<winrt::hresult> ExecuteInstallAsync(::AppInstaller::CLI::Execution::Context& context, std::unique_ptr<::AppInstaller::CLI::Command>& command) - { - co_await winrt::resume_background(); - winrt::hresult result = ::AppInstaller::CLI::Execute(context, command); - return result; - } - winrt::Microsoft::Management::Deployment::InstallResult GetInstallResult(::AppInstaller::CLI::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, winrt::hstring correlationData, bool rebootRequired) + winrt::Microsoft::Management::Deployment::InstallResult GetInstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, winrt::hstring correlationData, bool rebootRequired) { winrt::Microsoft::Management::Deployment::InstallResultStatus installResultStatus = GetInstallResultStatus(executionStage, terminationHR); auto installResult = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::InstallResult>>(); installResult->Initialize(installResultStatus, terminationHR, correlationData, rebootRequired); return *installResult; } - winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Microsoft::Management::Deployment::InstallResult, winrt::Microsoft::Management::Deployment::InstallProgress> PackageManager::InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) - { - winrt::hresult terminationHR = S_OK; - ::AppInstaller::CLI::Workflow::ExecutionStage executionStage = ::AppInstaller::CLI::Workflow::ExecutionStage::Initial; - try + std::optional<winrt::Microsoft::Management::Deployment::InstallProgress> GetProgress( + ::AppInstaller::ReportType reportType, + uint64_t current, + uint64_t maximum, + ::AppInstaller::ProgressType progressType, + ::Workflow::ExecutionStage executionPhase) + { + bool reportProgress = false; + PackageInstallProgressState progressState = PackageInstallProgressState::Queued; + double downloadProgress = 0; + double installProgress = 0; + uint64_t downloadBytesDownloaded = 0; + uint64_t downloadBytesRequired = 0; + switch (executionPhase) { - std::optional<DWORD> callerProcessId = GetCallerProcessId(); - if (!callerProcessId.has_value()) + case ::Workflow::ExecutionStage::Initial: + case ::Workflow::ExecutionStage::ParseArgs: + case ::Workflow::ExecutionStage::Discovery: + // We already reported queued progress up front. + break; + case ::Workflow::ExecutionStage::Download: + progressState = PackageInstallProgressState::Downloading; + if (reportType == ::AppInstaller::ReportType::BeginProgress) { - co_return GetInstallResult(executionStage, E_ACCESSDENIED, options.CorrelationData(), false); + reportProgress = true; } - if (FAILED(terminationHR = EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId.value()))) + else if (progressType == ::AppInstaller::ProgressType::Bytes) + { + downloadBytesDownloaded = current; + downloadBytesRequired = maximum; + if (maximum > 0 && maximum >= current) + { + reportProgress = true; + downloadProgress = static_cast<double>(current) / static_cast<double>(maximum); + } + } + break; + case ::Workflow::ExecutionStage::PreExecution: + // Wait until installer starts to report Installing. + break; + case ::Workflow::ExecutionStage::Execution: + progressState = PackageInstallProgressState::Installing; + downloadProgress = 1; + if (reportType == ::AppInstaller::ReportType::ExecutionPhaseUpdate) + { + // Install is starting. Send progress so callers know the AsyncOperation can't be cancelled. + reportProgress = true; + } + else if (reportType == ::AppInstaller::ReportType::EndProgress) + { + // Install is "finished". May not have succeeded. + reportProgress = true; + installProgress = 1; + } + else if (progressType == ::AppInstaller::ProgressType::Percent) + { + if (maximum > 0 && maximum >= current) + { + // Install is progressing + reportProgress = true; + installProgress = static_cast<double>(current) / static_cast<double>(maximum); + } + } + break; + case ::Workflow::ExecutionStage::PostExecution: + if (reportType == ::AppInstaller::ReportType::ExecutionPhaseUpdate) { - co_return GetInstallResult(executionStage, terminationHR, options.CorrelationData(), false); + // Send PostInstall progress when it switches to PostExecution phase. + reportProgress = true; + progressState = PackageInstallProgressState::PostInstall; + downloadProgress = 1; + installProgress = 1; } - std::wstring callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId.value()); + break; + } + if (reportProgress) + { + winrt::Microsoft::Management::Deployment::InstallProgress contextProgress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress, installProgress }; + return contextProgress; + } + else + { + return {}; + } + } + + Microsoft::Management::Deployment::PackageVersionInfo GetPackageVersionInfo(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) + { + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; - auto report_progress{ co_await winrt::get_progress_token() }; - auto cancellationToken{ co_await winrt::get_cancellation_token() }; + winrt::Microsoft::Management::Deployment::PackageVersionId versionId = (options) ? options.PackageVersionId() : nullptr; + // If the version of the package is specified use that, otherwise use the default. + if (versionId) + { + packageVersionInfo = package.GetPackageVersionInfo(versionId); + } + else + { + packageVersionInfo = package.DefaultInstallVersion(); + } + // If the specified version wasn't found then return a failure. This is unusual, since all packages that came from a non-local catalog have a default version, + // and the versionId is strongly typed and comes from the CatalogPackage.GetAvailableVersions. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND, !packageVersionInfo); + return packageVersionInfo; + } - InstallProgress queuedProgress{ PackageInstallProgressState::Queued, 0, 0, 0 }; - report_progress(queuedProgress); + std::unique_ptr<::AppInstaller::COMContext> CreateContextFromInstallOptions( + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::InstallOptions options, + std::wstring callerProcessInfoString) + { + std::unique_ptr<::AppInstaller::COMContext> context = std::make_unique<::AppInstaller::COMContext>(); + hstring correlationData = (options) ? options.CorrelationData() : L""; + context->SetLoggerContext(correlationData, ::AppInstaller::Utility::ConvertToUTF8(callerProcessInfoString)); - Microsoft::Management::Deployment::PackageVersionId versionId{ nullptr }; - if (options) + // Convert the options to arguments for the installer. + if (options) + { + if (!options.LogOutputPath().empty()) + { + context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); + context->Args.AddArg(Execution::Args::Type::VerboseLogs); + } + if (options.AllowHashMismatch()) { - versionId = options.PackageVersionId(); + context->Args.AddArg(Execution::Args::Type::HashOverride); } - // If the version of the package is specified use that, otherwise use the default. - Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; - if (versionId) + // If the PackageInstallScope is anything other than ::Any then set it as a requirement. + if (options.PackageInstallScope() == PackageInstallScope::System) { - packageVersionInfo = package.GetPackageVersionInfo(versionId); + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(::AppInstaller::Manifest::ScopeEnum::Machine)); } - else + else if (options.PackageInstallScope() == PackageInstallScope::User) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(::AppInstaller::Manifest::ScopeEnum::User)); + } + + if (options.PackageInstallMode() == PackageInstallMode::Interactive) { - packageVersionInfo = package.DefaultInstallVersion(); + context->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (options.PackageInstallMode() == PackageInstallMode::Silent) + { + context->Args.AddArg(Execution::Args::Type::Silent); + } + + if (!options.PreferredInstallLocation().empty()) + { + context->Args.AddArg(Execution::Args::Type::InstallLocation, ::AppInstaller::Utility::ConvertToUTF8(options.PreferredInstallLocation())); } - if (!packageVersionInfo) + if (!options.ReplacementInstallerArguments().empty()) { - // If no package version was found on the catalog then return a failure. This is unexpected, a catalog with no latest version should not be in the catalog. - co_return GetInstallResult(executionStage, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER, options.CorrelationData(), false); + context->Args.AddArg(Execution::Args::Type::Override, ::AppInstaller::Utility::ConvertToUTF8(options.ReplacementInstallerArguments())); } + } - // Handle the progress from the installer - ::AppInstaller::COMContext context; + // If the version of the package is specified use that, otherwise use the default. + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); + AddPackageManifestToContext(packageVersionInfo, context.get()); - context.SetLoggerContext(options.CorrelationData(), ::AppInstaller::Utility::ConvertToUTF8(callerProcessInfoString)); + // Note: AdditionalPackageCatalogArguments is not needed during install since the manifest is already known so no additional calls to the source are needed. The property is deprecated. + return context; + } - // Convert the options to arguments for the installer. - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::Exact); - if (options) + std::shared_ptr<Execution::OrchestratorQueueItem> GetExistingQueueItemForPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + std::shared_ptr<Execution::OrchestratorQueueItem> queueItem = nullptr; + std::unique_ptr<::AppInstaller::COMContext> context = std::make_unique<::AppInstaller::COMContext>(); + if (catalogInfo) + { + // If the caller has passed in the catalog they expect the package to have come from, then only look for an install from that catalog. + // Fail if they've used a catalog that doesn't have an Id. This can currently happen for Info objects that come from PackageCatalogReference objects for REST catalogs. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, catalogInfo.Id().empty()); + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ catalogInfo.Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + return queueItem; + } + + // If the caller has not specified the catalog, then check InstalledVersion. When the package comes from the Installing catalog the PackageCatalog + // of the InstalledVersion will be set to the original catalog that the install was from, so checking the InstalledVersion first is most likely to + // find a result. + Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo = package.InstalledVersion(); + if (installedVersionInfo) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ installedVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) { - if (!options.LogOutputPath().empty()) - { - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::VerboseLogs); - } - if (options.AllowHashMismatch()) - { - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::HashOverride); - } + return queueItem; + } + } - // If the PackageInstallScope is anything other than ::Any then set it as a requirement. - if (options.PackageInstallScope() == PackageInstallScope::System) - { - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::InstallScope, ScopeToString(::AppInstaller::Manifest::ScopeEnum::Machine)); - } - else if (options.PackageInstallScope() == PackageInstallScope::User) - { - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::InstallScope, ScopeToString(::AppInstaller::Manifest::ScopeEnum::User)); - } + // If InstalledVersion was not found, check DefaultInstallVersion + Microsoft::Management::Deployment::PackageVersionInfo defaultInstallVersionInfo = package.DefaultInstallVersion(); + if (defaultInstallVersionInfo) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ defaultInstallVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) + { + return queueItem; + } + } - if (options.PackageInstallMode() == PackageInstallMode::Interactive) - { - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::Interactive); - } - else if (options.PackageInstallMode() == PackageInstallMode::Silent) - { - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::Silent); - } + // Finally check all catalogs in AvailableVersions. + for (Microsoft::Management::Deployment::PackageVersionId versionId : package.AvailableVersions()) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ package.GetPackageVersionInfo(versionId).PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) + { + return queueItem; + } + } + return nullptr; + } - if (!options.PreferredInstallLocation().empty()) - { - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::InstallLocation, ::AppInstaller::Utility::ConvertToUTF8(options.PreferredInstallLocation())); - } + winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Microsoft::Management::Deployment::InstallResult, winrt::Microsoft::Management::Deployment::InstallProgress> GetInstallOperation( + bool addToQueue, + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::InstallOptions options, + winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + winrt::hresult terminationHR = S_OK; + hstring correlationData = (options) ? options.CorrelationData() : L""; + ::Workflow::ExecutionStage executionStage = ::Workflow::ExecutionStage::Initial; - if (!options.ReplacementInstallerArguments().empty()) - { - context.Args.AddArg(::AppInstaller::CLI::Execution::Args::Type::Override, ::AppInstaller::Utility::ConvertToUTF8(options.ReplacementInstallerArguments())); - } + #define WINGET_RETURN_INSTALL_RESULT_IF(installResult, boolVal) { if(boolVal) { co_return installResult; }} + #define WINGET_RETURN_INSTALL_RESULT_HR(hr) { WINGET_RETURN_INSTALL_RESULT_IF(GetInstallResult(executionStage, hr, correlationData, false), true) } + #define WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { WINGET_RETURN_INSTALL_RESULT_HR(hr) }} + #define WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, FAILED(hr)) } + + // options and catalog can both be null, package must be set. + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + try + { + auto report_progress{ co_await winrt::get_progress_token() }; + auto cancellationToken{ co_await winrt::get_cancellation_token() }; + + wil::unique_event progressEvent{ wil::EventOptions::None }; + + std::shared_ptr<Execution::OrchestratorQueueItem> queueItem = nullptr; + if (addToQueue) + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + std::optional<DWORD> callerProcessId = GetCallerProcessId(); + WINGET_RETURN_INSTALL_RESULT_HR_IF(E_ACCESSDENIED, !callerProcessId.has_value()); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId.value())); + std::wstring callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId.value()); + + // co_await does not guarantee that it's on a background thread, so do so explicitly. + co_await winrt::resume_background(); + + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); + std::unique_ptr<::AppInstaller::COMContext> comContext = CreateContextFromInstallOptions(package, options, callerProcessInfoString); + queueItem = Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext)); + Execution::ContextOrchestrator::Instance().EnqueueAndRunItem(queueItem); + + InstallProgress queuedProgress{ PackageInstallProgressState::Queued, 0, 0, 0 }; + report_progress(queuedProgress); } + else + { + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); - AddPackageManifestToContext(packageVersionInfo, context); + queueItem = GetExistingQueueItemForPackage(package, catalogInfo); + WINGET_RETURN_INSTALL_RESULT_IF(nullptr, queueItem == nullptr); - // TODO: AdditionalPackageCatalogArguments is not currently supported by the underlying implementation. - ::AppInstaller::CLI::RootCommand rootCommand; - std::unique_ptr<::AppInstaller::CLI::Command> command = std::make_unique<::AppInstaller::CLI::COMInstallCommand>(rootCommand.Name()); - rootCommand.ValidateArguments(context.Args); + // correlation data is not passed in when retrieving an existing queue item, so get it from the existing context. + correlationData = hstring(queueItem->GetContext().GetCorrelationJson()); - ::AppInstaller::Logging::Telemetry().LogCommand(command->FullName()); + // co_await does not guarantee that it's on a background thread, so do so explicitly. + co_await winrt::resume_background(); + } - context.SetProgressCallbackFunction([=]( + std::atomic<winrt::Microsoft::Management::Deployment::InstallProgress> installProgress; + queueItem->GetContext().AddProgressCallbackFunction([&installProgress, &progressEvent]( ::AppInstaller::ReportType reportType, uint64_t current, uint64_t maximum, ::AppInstaller::ProgressType progressType, - ::AppInstaller::CLI::Workflow::ExecutionStage executionPhase) + ::Workflow::ExecutionStage executionPhase) { - bool reportProgress = false; - PackageInstallProgressState progressState = PackageInstallProgressState::Queued; - double downloadProgress = 0; - double installProgress = 0; - uint64_t downloadBytesDownloaded = 0; - uint64_t downloadBytesRequired = 0; - switch (executionPhase) - { - case ::AppInstaller::CLI::Workflow::ExecutionStage::Initial: - case ::AppInstaller::CLI::Workflow::ExecutionStage::ParseArgs: - case ::AppInstaller::CLI::Workflow::ExecutionStage::Discovery: - // We already reported queued progress up front. - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::Download: - progressState = PackageInstallProgressState::Downloading; - if (reportType == ::AppInstaller::ReportType::BeginProgress) - { - reportProgress = true; - } - else if (progressType == ::AppInstaller::ProgressType::Bytes) - { - downloadBytesDownloaded = current; - downloadBytesRequired = maximum; - if (maximum > 0 && maximum >= current) - { - reportProgress = true; - downloadProgress = static_cast<double>(current) / static_cast<double>(maximum); - } - } - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::PreExecution: - // Wait until installer starts to report Installing. - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::Execution: - progressState = PackageInstallProgressState::Installing; - downloadProgress = 1; - if (reportType == ::AppInstaller::ReportType::ExecutionPhaseUpdate) - { - // Install is starting. Send progress so callers know the AsyncOperation can't be cancelled. - reportProgress = true; - } - else if (reportType == ::AppInstaller::ReportType::EndProgress) - { - // Install is "finished". May not have succeeded. - reportProgress = true; - installProgress = 1; - } - else if (progressType == ::AppInstaller::ProgressType::Percent) - { - if (maximum > 0 && maximum >= current) - { - // Install is progressing - reportProgress = true; - installProgress = static_cast<double>(current) / static_cast<double>(maximum); - } - } - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::PostExecution: - if (reportType == ::AppInstaller::ReportType::ExecutionPhaseUpdate) - { - // Send PostInstall progress when it switches to PostExecution phase. - reportProgress = true; - progressState = PackageInstallProgressState::PostInstall; - downloadProgress = 1; - installProgress = 1; - } - break; - } - if (reportProgress) + std::optional<winrt::Microsoft::Management::Deployment::InstallProgress> installProgressOptional = GetProgress(reportType, current, maximum, progressType, executionPhase); + if (installProgressOptional.has_value()) { - winrt::Microsoft::Management::Deployment::InstallProgress contextProgress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress, installProgress }; - report_progress(contextProgress); + installProgress = installProgressOptional.value(); + ::SetEvent(progressEvent.get()); } return; } ); - context.EnableCtrlHandler(); + cancellationToken.callback([&queueItem] + { + // The cancellation of the AsyncOperation on the client triggers Cancel which causes the Execute to end. + Execution::ContextOrchestrator::Instance().CancelQueueItem(*queueItem); + }); - Windows::Foundation::IAsyncOperation<winrt::hresult> executeOperation = ExecuteInstallAsync(context, command); + // Wait for completion or progress events. + // Waiting for both on the same thread ensures that progress is never reported after the async operation itself has completed. + bool completionEventFired = false; + HANDLE operationEvents[2]; + operationEvents[0] = progressEvent.get(); + operationEvents[1] = queueItem->GetCompletedEvent().get(); + while (!completionEventFired) + { + DWORD dwEvent = WaitForMultipleObjects( + 2 /* number of events */, + operationEvents /* event array */, + FALSE /* bWaitAll, FALSE to wake on any event */, + INFINITE /* wait until operation completion */); - cancellationToken.callback([&context] + switch (dwEvent) { - context.Cancel(false, true); - }); - // Wait for the execute operation to finish. - // The cancellation of the AsyncOperation triggers Cancel which causes the executeOperation to end. - terminationHR = co_await executeOperation; - executionStage = context.GetExecutionStage(); + // operationEvents[0] was signaled, progress + case WAIT_OBJECT_0 + 0: + // The report_progress call will hang when making callbacks to suspended processes so it's important that this is now on a background thread. + // Progress events are not queued - some will be missed if multiple progress events are fired from the ComContext to the callback + // while the report_progress call is hung\in progress. + // Duplicate progress events can be fired if another progress event comes from the ComContext to the callback after the listener + // has been awaked, but before it has gotten the installProgress. + report_progress(installProgress); + break; + + // operationEvents[1] was signaled, operation completed + case WAIT_OBJECT_0 + 1: + completionEventFired = true; + break; + + // Return value is invalid. + default: + THROW_LAST_ERROR(); + } + } + // The install command has finished, check for success/failure and how far it got. + terminationHR = queueItem->GetContext().GetTerminationHR(); + executionStage = queueItem->GetContext().GetExecutionStage(); } - WINGET_CATCH_STORE(terminationHR); + WINGET_CATCH_STORE(terminationHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); // TODO - RebootRequired not yet populated, msi arguments not returned from Execute. - co_return GetInstallResult(executionStage, terminationHR, options.CorrelationData(), false); + WINGET_RETURN_INSTALL_RESULT_HR(terminationHR); } + + winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Microsoft::Management::Deployment::InstallResult, winrt::Microsoft::Management::Deployment::InstallProgress> PackageManager::InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) + { + return GetInstallOperation(true, package, options, nullptr); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Microsoft::Management::Deployment::InstallResult, winrt::Microsoft::Management::Deployment::InstallProgress> PackageManager::GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + return GetInstallOperation(false, package, nullptr, catalogInfo); + } + CoCreatableCppWinRtClassWithCLSID(PackageManager, 1, &PackageManagerCLSID1); CoCreatableCppWinRtClassWithCLSID(PackageManager, 2, &PackageManagerCLSID2); } diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h @@ -17,8 +17,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::PackageCatalogReference CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options); winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Microsoft::Management::Deployment::InstallResult, winrt::Microsoft::Management::Deployment::InstallProgress> InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); + //Contract 2.0 winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Microsoft::Management::Deployment::InstallResult, winrt::Microsoft::Management::Deployment::InstallProgress> - GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package); + GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); }; } namespace winrt::Microsoft::Management::Deployment::factory_implementation diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment { - [contractversion(1)] + [contractversion(2)] apicontract WindowsPackageManagerContract{}; /// State of the install. @@ -409,6 +409,7 @@ namespace Microsoft.Management.Deployment enum LocalPackageCatalog { InstalledPackages, + InstallingPackages }; /// Options for creating a composite catalog. @@ -502,6 +503,12 @@ namespace Microsoft.Management.Deployment /// Install the specified package Windows.Foundation.IAsyncOperationWithProgress<InstallResult, InstallProgress> InstallPackageAsync(CatalogPackage package, InstallOptions options); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + { + /// Get install progress + Windows.Foundation.IAsyncOperationWithProgress<InstallResult, InstallProgress> GetInstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); + } } /// Force midl3 to generate vector marshalling info.