winget-cli

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

ContextOrchestrator.cpp (23715B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "ExecutionContext.h"
      5 #include "ContextOrchestrator.h"
      6 #include "COMContext.h"
      7 #include "Commands/COMCommand.h"
      8 #include "Public/ShutdownMonitoring.h"
      9 #include "winget/UserSettings.h"
     10 #include <Commands/RootCommand.h>
     11 
     12 namespace AppInstaller::CLI::Execution
     13 {
     14     namespace
     15     {
     16         // Operation command queue used by install, uninstall and repair commands.
     17         constexpr static std::string_view OperationCommandQueueName = "operation"sv;
     18 
     19         // Callback function used by worker threads in the queue.
     20         // context must be a pointer to a queue item.
     21         void CALLBACK OrchestratorQueueWorkCallback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_WORK)
     22         {
     23             auto queueItem = reinterpret_cast<OrchestratorQueueItem*>(context);
     24             auto queue = queueItem->GetCurrentQueue();
     25             if (queue)
     26             {
     27                 queue->RunItem(queueItem->GetId());
     28             }
     29         }
     30 
     31         // Get command queue name based on command name.
     32         std::string_view GetCommandQueueName(std::string_view commandName)
     33         {
     34             if (commandName == COMInstallCommand::CommandName || commandName == COMUninstallCommand::CommandName || commandName == COMRepairCommand::CommandName)
     35             {
     36                 return OperationCommandQueueName;
     37             }
     38 
     39             return commandName;
     40         }
     41     }
     42 
     43     ContextOrchestrator& ContextOrchestrator::Instance()
     44     {
     45         static ContextOrchestrator s_instance;
     46         return s_instance;
     47     }
     48 
     49     ContextOrchestrator::ContextOrchestrator() : ContextOrchestrator(std::thread::hardware_concurrency()) {}
     50 
     51     ContextOrchestrator::ContextOrchestrator(unsigned int hardwareConcurrency)
     52     {
     53         ProgressCallback progress;
     54         m_installingWriteableSource = Repository::Source(Repository::PredefinedSource::Installing);
     55         m_installingWriteableSource.Open(progress);
     56 
     57         // Decide how many threads to use for each command.
     58         // We always allow only one install at a time.
     59         // For download, if we can find the number of supported concurrent threads,
     60         // use that as the maximum (up to 3); otherwise use a single thread.
     61         const UINT32 maxDownloadThreads = 3;
     62         const UINT32 operationThreads = 1;
     63         const UINT32 downloadThreads = std::min(hardwareConcurrency > 1 ? hardwareConcurrency - 1 : 1, maxDownloadThreads);
     64 
     65         AddCommandQueue(COMDownloadCommand::CommandName, downloadThreads);
     66         AddCommandQueue(OperationCommandQueueName, operationThreads);
     67     }
     68 
     69     void ContextOrchestrator::AddCommandQueue(std::string_view commandName, UINT32 allowedThreads)
     70     {
     71         std::lock_guard<std::mutex> lockQueue{ m_queueLock };
     72         m_commandQueues.emplace(commandName, std::make_unique<OrchestratorQueue>(*this, commandName, allowedThreads));
     73     }
     74 
     75     _Requires_lock_held_(m_queueLock)
     76     std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::FindById(const OrchestratorQueueItemId& comparisonQueueItemId)
     77     {
     78         for (const auto& queue : m_commandQueues)
     79         {
     80             auto item = queue.second->FindById(comparisonQueueItemId);
     81             if (item)
     82             {
     83                 return item;
     84             }
     85         }
     86 
     87         return {};
     88     }
     89 
     90     void ContextOrchestrator::EnqueueAndRunItem(const std::shared_ptr<OrchestratorQueueItem>& item)
     91     {
     92         std::lock_guard<std::mutex> lockQueue{ m_queueLock };
     93 
     94         if (item->IsOnFirstCommand())
     95         {
     96             // Directly error on attempting to enqueue first time
     97             THROW_HR_IF(ToHRESULT(m_disabledReason), !m_enabled);
     98 
     99             THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INSTALL_ALREADY_RUNNING), FindById(item->GetId()));
    100 
    101             // Log the beginning of the item
    102             item->GetContext().GetThreadGlobals().GetTelemetryLogger().LogCommand(item->GetItemCommandName());
    103         }
    104         else if (!m_enabled)
    105         {
    106             // On subsequent command enqueues, cancel and complete the item
    107             item->GetContext().Cancel(m_disabledReason, true);
    108             item->HandleItemCompletion(*this);
    109         }
    110 
    111         std::string commandQueueName{ GetCommandQueueName(item->GetNextCommand().Name()) };
    112         m_commandQueues.at(commandQueueName)->EnqueueAndRunItem(item);
    113     }
    114 
    115     void ContextOrchestrator::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state)
    116     {
    117         std::lock_guard<std::mutex> lockQueue{ m_queueLock };
    118         for (const auto& queue : m_commandQueues)
    119         {
    120             if (queue.second->RemoveItemInState(item, state, true))
    121             {
    122                 return;
    123             }
    124         }
    125     }
    126 
    127     void ContextOrchestrator::CancelQueueItem(const OrchestratorQueueItem& item)
    128     {
    129         // Always cancel the item, even if it isn't running yet, to get the terminationHR set correctly.
    130         item.GetContext().Cancel(CancelReason::Abort, true);
    131 
    132         RemoveItemInState(item, OrchestratorQueueItemState::Queued);
    133     }
    134 
    135     std::shared_ptr<OrchestratorQueueItem> ContextOrchestrator::GetQueueItem(const OrchestratorQueueItemId& queueItemId)
    136     {
    137         std::lock_guard<std::mutex> lock{ m_queueLock };
    138 
    139         return FindById(queueItemId);
    140     }
    141 
    142     void ContextOrchestrator::AddItemManifestToInstallingSource(const OrchestratorQueueItem& queueItem)
    143     {
    144         if (queueItem.IsApplicableForInstallingSource())
    145         {
    146             const auto& manifest = queueItem.GetContext().Get<Execution::Data::Manifest>();
    147             m_installingWriteableSource.AddPackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version });
    148         }
    149     }
    150 
    151     void ContextOrchestrator::RemoveItemManifestFromInstallingSource(const OrchestratorQueueItem& queueItem)
    152     {
    153         if (queueItem.IsApplicableForInstallingSource())
    154         {
    155             const auto& manifest = queueItem.GetContext().Get<Execution::Data::Manifest>();
    156             m_installingWriteableSource.RemovePackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version });
    157         }
    158     }
    159 
    160     void ContextOrchestrator::RegisterForShutdownSynchronization()
    161     {
    162         static std::once_flag registerComponentOnceFlag;
    163         std::call_once(registerComponentOnceFlag,
    164             [&]()
    165             {
    166                 using namespace ShutdownMonitoring;
    167 
    168                 ServerShutdownSynchronization::ComponentSystem component;
    169                 component.BlockNewWork = StaticDisable;
    170                 component.BeginShutdown = StaticCancelQueuedItems;
    171                 component.Wait = StaticWaitForRunningItems;
    172 
    173                 ServerShutdownSynchronization::AddComponent(component);
    174             });
    175     }
    176 
    177     void ContextOrchestrator::StaticDisable(CancelReason reason)
    178     {
    179         Instance().Disable(reason);
    180     }
    181 
    182     void ContextOrchestrator::StaticCancelQueuedItems(CancelReason reason)
    183     {
    184         Instance().CancelQueuedItems(reason);
    185     }
    186 
    187     void ContextOrchestrator::StaticWaitForRunningItems()
    188     {
    189         Instance().WaitForRunningItems();
    190     }
    191 
    192     void ContextOrchestrator::Disable(CancelReason reason)
    193     {
    194         std::lock_guard<std::mutex> lock{ m_queueLock };
    195         m_enabled = false;
    196         m_disabledReason = reason;
    197     }
    198 
    199     void ContextOrchestrator::CancelQueuedItems(CancelReason reason)
    200     {
    201         std::lock_guard<std::mutex> lock{ m_queueLock };
    202         for (const auto& queue : m_commandQueues)
    203         {
    204             queue.second->CancelAllItems(reason);
    205         }
    206     }
    207 
    208     void ContextOrchestrator::WaitForRunningItems()
    209     {
    210         std::lock_guard<std::mutex> lock{ m_queueLock };
    211         for (const auto& queue : m_commandQueues)
    212         {
    213             queue.second->WaitForEmptyQueue();
    214         }
    215     }
    216 
    217     bool ContextOrchestrator::WaitForRunningItems(DWORD timeoutMilliseconds)
    218     {
    219         std::lock_guard<std::mutex> lock{ m_queueLock };
    220         for (const auto& queue : m_commandQueues)
    221         {
    222             if (!queue.second->WaitForEmptyQueue(timeoutMilliseconds))
    223             {
    224                 return false;
    225             }
    226         }
    227 
    228         return true;
    229     }
    230 
    231     std::string ContextOrchestrator::GetStatusString()
    232     {
    233         std::ostringstream stream;
    234 
    235         std::lock_guard<std::mutex> lock{ m_queueLock };
    236 
    237         if (!m_enabled)
    238         {
    239             stream << "Disabled due to " << ToIntegral(m_disabledReason) << std::endl;
    240         }
    241 
    242         for (const auto& queue : m_commandQueues)
    243         {
    244             stream << queue.second->GetStatusString();
    245         }
    246 
    247         return stream.str();
    248     }
    249 
    250     _Requires_lock_held_(m_itemLock)
    251     std::deque<std::shared_ptr<OrchestratorQueueItem>>::iterator OrchestratorQueue::FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId)
    252     {
    253         return std::find_if(m_queueItems.begin(), m_queueItems.end(), [&comparisonQueueItemId](const std::shared_ptr<OrchestratorQueueItem>& item) {return (item->GetId().IsSame(comparisonQueueItemId)); });
    254     }
    255 
    256     _Requires_lock_held_(m_itemLock)
    257     std::shared_ptr<OrchestratorQueueItem> OrchestratorQueue::FindById(const OrchestratorQueueItemId& comparisonQueueItemId)
    258     {
    259         auto itr = FindIteratorById(comparisonQueueItemId);
    260         if (itr != m_queueItems.end())
    261         {
    262             return *itr;
    263         }
    264 
    265         return {};
    266     }
    267 
    268     void OrchestratorQueue::EnqueueItem(const std::shared_ptr<OrchestratorQueueItem>& item)
    269     {
    270         {
    271             std::lock_guard<std::mutex> lockQueue{ m_itemLock };
    272             m_queueItems.push_back(item);
    273             m_queueEmpty.ResetEvent();
    274         }
    275 
    276         // Add the package to the Installing source so that it can be queried using the Source interface.
    277         // Only do this the first time the item is queued.
    278         if (item->IsOnFirstCommand())
    279         {
    280             try
    281             {
    282                 m_orchestrator.AddItemManifestToInstallingSource(*item);
    283             }
    284             catch (...)
    285             {
    286                 std::lock_guard<std::mutex> lockQueue{ m_itemLock };
    287                 auto itr = FindIteratorById(item->GetId());
    288                 if (itr != m_queueItems.end())
    289                 {
    290                     m_queueItems.erase(itr);
    291 
    292                     if (m_queueItems.empty())
    293                     {
    294                         m_queueEmpty.SetEvent();
    295                     }
    296                 }
    297                 throw;
    298             }
    299         }
    300 
    301         {
    302             std::lock_guard<std::mutex> lockQueue{ m_itemLock };
    303             item->SetState(OrchestratorQueueItemState::Queued);
    304         }
    305     }
    306 
    307     OrchestratorQueue::OrchestratorQueue(ContextOrchestrator& orchestrator, std::string_view commandName, UINT32 allowedThreads) :
    308         m_orchestrator(orchestrator), m_commandName(commandName), m_allowedThreads(allowedThreads)
    309     {
    310         m_threadPool.reset(CreateThreadpool(nullptr));
    311         THROW_LAST_ERROR_IF_NULL(m_threadPool);
    312         m_threadPoolCleanupGroup.reset(CreateThreadpoolCleanupGroup());
    313         THROW_LAST_ERROR_IF_NULL(m_threadPoolCleanupGroup);
    314         InitializeThreadpoolEnvironment(&m_threadPoolCallbackEnviron);
    315         SetThreadpoolCallbackPool(&m_threadPoolCallbackEnviron, m_threadPool.get());
    316         SetThreadpoolCallbackCleanupGroup(&m_threadPoolCallbackEnviron, m_threadPoolCleanupGroup.get(), nullptr);
    317 
    318         SetThreadpoolThreadMaximum(m_threadPool.get(), m_allowedThreads);
    319         THROW_LAST_ERROR_IF(!SetThreadpoolThreadMinimum(m_threadPool.get(), 1));
    320     }
    321 
    322     OrchestratorQueue::~OrchestratorQueue()
    323     {
    324         CloseThreadpoolCleanupGroupMembers(m_threadPoolCleanupGroup.get(), false, nullptr);
    325     }
    326 
    327     void OrchestratorQueue::EnqueueAndRunItem(const std::shared_ptr<OrchestratorQueueItem>& item)
    328     {
    329         EnqueueItem(item);
    330 
    331         item->SetCurrentQueue(this);
    332         auto work = CreateThreadpoolWork(OrchestratorQueueWorkCallback, item.get(), &m_threadPoolCallbackEnviron);
    333         SubmitThreadpoolWork(work);
    334     }
    335 
    336     void OrchestratorQueue::RunItem(const OrchestratorQueueItemId& itemId)
    337     {
    338         try
    339         {
    340             std::shared_ptr<OrchestratorQueueItem> item;
    341             bool isCancelled = false;
    342 
    343             // Try to find the item in the queue.
    344             {
    345                 std::lock_guard<std::mutex> lockQueue{ m_itemLock };
    346                 item = FindById(itemId);
    347 
    348                 if (!item)
    349                 {
    350                     // Item should be in the queue; this shouldn't happen.
    351                     return;
    352                 }
    353 
    354                 // Only run if the item is queued and not cancelled.
    355                 if (item->GetState() == OrchestratorQueueItemState::Queued)
    356                 {
    357                     // Mark it as running so that it cannot be cancelled by other threads.
    358                     item->SetState(OrchestratorQueueItemState::Running);
    359                 }
    360                 else if (item->GetState() == OrchestratorQueueItemState::Cancelled)
    361                 {
    362                     isCancelled = true;
    363                 }
    364             }
    365 
    366             if (isCancelled)
    367             {
    368                 // Do this separate from above block as the Remove function needs to manage the lock.
    369                 RemoveItemInState(*item, OrchestratorQueueItemState::Cancelled, true);
    370             }
    371 
    372             // Get the item's command and execute it.
    373             HRESULT exceptionHR = S_OK;
    374             try
    375             {
    376                 std::unique_ptr<Command> command = item->PopNextCommand();
    377 
    378                 std::unique_ptr<AppInstaller::ThreadLocalStorage::PreviousThreadGlobals> setThreadGlobalsToPreviousState = item->GetContext().SetForCurrentThread();
    379 
    380                 command->ValidateArguments(item->GetContext().Args);
    381 
    382                 item->GetContext().EnableSignalTerminationHandler();
    383 
    384                 ::AppInstaller::CLI::ExecuteWithoutLoggingSuccess(item->GetContext(), command.get());
    385             }
    386             WINGET_CATCH_STORE(exceptionHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED);
    387 
    388             if (FAILED(exceptionHR))
    389             {
    390                 // Set the termination hr directly from any exception that escaped so that the context always 
    391                 // has the result of the operation no matter how it failed.
    392                 item->GetContext().SetTerminationHR(exceptionHR);
    393             }
    394 
    395             item->GetContext().EnableSignalTerminationHandler(false);
    396 
    397             if (FAILED(item->GetContext().GetTerminationHR()) || item->IsComplete())
    398             {
    399                 if (SUCCEEDED(item->GetContext().GetTerminationHR()))
    400                 {
    401                     item->GetContext().GetThreadGlobals().GetTelemetryLogger().LogCommandSuccess(item->GetItemCommandName());
    402                 }
    403 
    404                 RemoveItemInState(*item, OrchestratorQueueItemState::Running, true);
    405             }
    406             else
    407             {
    408                 // Remove item from this queue and add it to the queue for the next command.
    409                 RemoveItemInState(*item, OrchestratorQueueItemState::Running, false);
    410                 m_orchestrator.EnqueueAndRunItem(item);
    411             }
    412         }
    413         catch (...)
    414         {
    415         }
    416     }
    417 
    418     void OrchestratorQueue::CancelAllItems(CancelReason reason)
    419     {
    420         std::lock_guard<std::mutex> lockQueue{ m_itemLock };
    421 
    422         for (auto itr = m_queueItems.begin(); itr != m_queueItems.end(); itr++)
    423         {
    424             auto& item = *itr;
    425 
    426             item->GetContext().Cancel(reason, true);
    427 
    428             // This mimics ContextOrchestrator::CancelQueueItem, which speeds up the process of cancelling queued items
    429             if (item->GetState() == OrchestratorQueueItemState::Queued)
    430             {
    431                 item->SetState(OrchestratorQueueItemState::Cancelled);
    432                 item->HandleItemCompletion(m_orchestrator);
    433             }
    434         }
    435     }
    436 
    437     void OrchestratorQueue::WaitForEmptyQueue()
    438     {
    439         m_queueEmpty.wait();
    440     }
    441 
    442     bool OrchestratorQueue::WaitForEmptyQueue(DWORD timeoutMilliseconds)
    443     {
    444         return m_queueEmpty.wait(timeoutMilliseconds);
    445     }
    446 
    447     std::string OrchestratorQueue::GetStatusString()
    448     {
    449         std::ostringstream stream;
    450         stream << m_commandName << '[' << m_allowedThreads << "]\n";
    451 
    452         std::map<OrchestratorQueueItemState, size_t> stateCounts;
    453         stateCounts[OrchestratorQueueItemState::NotQueued] = 0;
    454         stateCounts[OrchestratorQueueItemState::Queued] = 0;
    455         stateCounts[OrchestratorQueueItemState::Running] = 0;
    456         stateCounts[OrchestratorQueueItemState::Cancelled] = 0;
    457 
    458         {
    459             std::lock_guard<std::mutex> lock{ m_itemLock };
    460 
    461             for (const auto& item : m_queueItems)
    462             {
    463                 stateCounts[item->GetState()] += 1;
    464             }
    465         }
    466 
    467         for (const auto& stateCount : stateCounts)
    468         {
    469             stream << "  " << ToString(stateCount.first) << " : " << stateCount.second << std::endl;
    470         }
    471 
    472         return stream.str();
    473     }
    474 
    475     bool OrchestratorQueue::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state, bool isGlobalRemove)
    476     {
    477         // OrchestratorQueueItemState::Running items should only be removed by the thread that ran the item.
    478         // Queued items can be removed by any thread.
    479         // NotQueued items should not be removed since, if found in the queue, they are in the process of being queued by another thread.
    480         bool foundItem = false;
    481 
    482         {
    483             std::lock_guard<std::mutex> lockQueue{ m_itemLock };
    484 
    485             // Look for the item. It's ok if the item is not found since multiple listeners may try to remove the same item.
    486             auto itr = FindIteratorById(item.GetId());
    487             if (itr != m_queueItems.end() && (*itr)->GetState() == state)
    488             {
    489                 foundItem = true;
    490 
    491                 // The item must only be removed from the queue by the thread that runs
    492                 // it, because the callback uses it. If any other thread tries to remove
    493                 // it, we simply mark it as cancelled.
    494                 if (state == OrchestratorQueueItemState::Running || state == OrchestratorQueueItemState::Cancelled)
    495                 {
    496                     (*itr)->SetCurrentQueue(nullptr);
    497                     m_queueItems.erase(itr);
    498 
    499                     if (m_queueItems.empty())
    500                     {
    501                         m_queueEmpty.SetEvent();
    502                     }
    503                 }
    504                 else if (state == OrchestratorQueueItemState::Queued)
    505                 {
    506                     (*itr)->SetState(OrchestratorQueueItemState::Cancelled);
    507                 }
    508             }
    509         }
    510 
    511         if (foundItem && isGlobalRemove)
    512         {
    513             item.HandleItemCompletion(m_orchestrator);
    514         }
    515 
    516         return foundItem;
    517     }
    518 
    519     bool OrchestratorQueueItemId::IsSame(const OrchestratorQueueItemId& comparedId) const
    520     {
    521         return ((GetPackageId() == comparedId.GetPackageId()) && 
    522                 (GetSourceId() == comparedId.GetSourceId()));
    523     }
    524 
    525     std::string_view OrchestratorQueueItem::GetItemCommandName() const
    526     {
    527         // The goal is that these should match the winget.exe commands for easy correlation.
    528         switch (m_operationType)
    529         {
    530         case PackageOperationType::Search: return "root:search"sv;
    531         case PackageOperationType::Install: return "root:install"sv;
    532         case PackageOperationType::Upgrade: return "root:upgrade"sv;
    533         case PackageOperationType::Uninstall: return "root:uninstall"sv;
    534         case PackageOperationType::Download: return "root:download"sv;
    535         case PackageOperationType::Repair: return "root:repair"sv;
    536         default: return "unknown";
    537         }
    538     }
    539 
    540     void OrchestratorQueueItem::HandleItemCompletion(ContextOrchestrator& orchestrator) const
    541     {
    542         orchestrator.RemoveItemManifestFromInstallingSource(*this);
    543         GetCompletedEvent().SetEvent();
    544     }
    545 
    546     std::unique_ptr<OrchestratorQueueItem> OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr<COMContext> context, bool isUpgrade)
    547     {
    548         std::unique_ptr<OrchestratorQueueItem> item = std::make_unique<OrchestratorQueueItem>(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), isUpgrade ? PackageOperationType::Upgrade : PackageOperationType::Install);
    549         item->AddCommand(std::make_unique<::AppInstaller::CLI::COMDownloadCommand>(RootCommand::CommandName));
    550         item->AddCommand(std::make_unique<::AppInstaller::CLI::COMInstallCommand>(RootCommand::CommandName));
    551         return item;
    552     }
    553 
    554     std::unique_ptr<OrchestratorQueueItem> OrchestratorQueueItemFactory::CreateItemForUninstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr<COMContext> context)
    555     {
    556         std::unique_ptr<OrchestratorQueueItem> item = std::make_unique<OrchestratorQueueItem>(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Uninstall);
    557         item->AddCommand(std::make_unique<::AppInstaller::CLI::COMUninstallCommand>(RootCommand::CommandName));
    558         return item;
    559     }
    560 
    561     std::unique_ptr<OrchestratorQueueItem> OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring packageId, std::wstring sourceId, std::unique_ptr<COMContext> context)
    562     {
    563         std::unique_ptr<OrchestratorQueueItem> item = std::make_unique<OrchestratorQueueItem>(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Search);
    564         return item;
    565     }
    566 
    567     std::unique_ptr<OrchestratorQueueItem> OrchestratorQueueItemFactory::CreateItemForDownload(std::wstring packageId, std::wstring sourceId, std::unique_ptr<COMContext> context)
    568     {
    569         std::unique_ptr<OrchestratorQueueItem> item = std::make_unique<OrchestratorQueueItem>(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Download);
    570         item->AddCommand(std::make_unique<::AppInstaller::CLI::COMDownloadCommand>(RootCommand::CommandName));
    571         return item;
    572     }
    573 
    574     std::unique_ptr<OrchestratorQueueItem> OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring packageId, std::wstring sourceId, std::unique_ptr<COMContext> context)
    575     {
    576         std::unique_ptr<OrchestratorQueueItem> item = std::make_unique<OrchestratorQueueItem>(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Repair);
    577         item->AddCommand(std::make_unique<::AppInstaller::CLI::COMRepairCommand>(RootCommand::CommandName));
    578         return item;
    579     }
    580 
    581     std::string_view ToString(OrchestratorQueueItemState state)
    582     {
    583         switch (state)
    584         {
    585         case OrchestratorQueueItemState::NotQueued: return "NotQueued";
    586         case OrchestratorQueueItemState::Queued: return "Queued";
    587         case OrchestratorQueueItemState::Running: return "Running";
    588         case OrchestratorQueueItemState::Cancelled: return "Cancelled";
    589         default: return "Unknown";
    590         }
    591     }
    592 }