winget-cli

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

MSStore.cpp (15386B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include <winget/MSStore.h>
      5 #include <winget/ManifestCommon.h>
      6 #include <winget/Runtime.h>
      7 #include <AppInstallerFileLogger.h>
      8 #include <AppInstallerErrors.h>
      9 
     10 namespace AppInstaller::MSStore
     11 {
     12     using namespace std::string_view_literals;
     13     using namespace winrt::Windows::Foundation;
     14     using namespace winrt::Windows::Foundation::Collections;
     15     using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl;
     16 
     17     namespace
     18     {
     19         // The type of entitlement we were able to acquire/ensure.
     20         enum class EntitlementType
     21         {
     22             None,
     23             User,
     24             Device,
     25         };
     26 
     27         EntitlementType EnsureFreeEntitlement(const std::wstring& productId, Manifest::ScopeEnum scope)
     28         {
     29             AppInstallManager installManager;
     30 
     31             AICLI_LOG(Core, Info, << "Getting entitlement for ProductId: " << Utility::ConvertToUTF8(productId));
     32 
     33             // Verifying/Acquiring product ownership
     34             GetEntitlementResult entitlementResult{ nullptr };
     35             EntitlementType result = EntitlementType::None;
     36 
     37             if (scope == Manifest::ScopeEnum::Machine)
     38             {
     39                 AICLI_LOG(Core, Info, << "Get device entitlement (machine scope install).");
     40                 result = EntitlementType::Device;
     41                 try
     42                 {
     43                     entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get();
     44                 }
     45                 CATCH_LOG();
     46             }
     47             else
     48             {
     49                 AICLI_LOG(Core, Info, << "Get user entitlement.");
     50                 result = EntitlementType::User;
     51                 try
     52                 {
     53                     entitlementResult = installManager.GetFreeUserEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get();
     54                 }
     55                 CATCH_LOG();
     56 
     57                 if (!entitlementResult || entitlementResult.Status() == GetEntitlementStatus::NoStoreAccount)
     58                 {
     59                     AICLI_LOG(Core, Info, << "Get device entitlement (no store account).");
     60                     result = EntitlementType::Device;
     61                     try
     62                     {
     63                         entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get();
     64                     }
     65                     CATCH_LOG();
     66                 }
     67             }
     68 
     69             if (entitlementResult && entitlementResult.Status() == GetEntitlementStatus::Succeeded)
     70             {
     71                 AICLI_LOG(Core, Info, << "Get entitlement succeeded.");
     72             }
     73             else if (entitlementResult)
     74             {
     75                 result = EntitlementType::None;
     76 
     77                 if (entitlementResult.Status() == GetEntitlementStatus::NetworkError)
     78                 {
     79                     AICLI_LOG(Core, Error, << "Get entitlement failed. Network error.");
     80                 }
     81                 else if (entitlementResult.Status() == GetEntitlementStatus::ServerError)
     82                 {
     83                     AICLI_LOG(Core, Error, << "Get entitlement failed. Server error.");
     84                 }
     85                 else
     86                 {
     87                     AICLI_LOG(Core, Error, << "Get entitlement failed. Unknown status: " << static_cast<int32_t>(entitlementResult.Status()));
     88                 }
     89             }
     90             else
     91             {
     92                 result = EntitlementType::None;
     93                 AICLI_LOG(Core, Error, << "Get entitlement failed. Exception.");
     94             }
     95 
     96             return result;
     97         }
     98 
     99         enum class CheckExistingItemResult
    100         {
    101             None,
    102             Restart,
    103             Cancel,
    104         };
    105 
    106         CheckExistingItemResult CheckRestartOrCancelForPossibleExistingOperation(const IVectorView<AppInstallItem>& installItems)
    107         {
    108             CheckExistingItemResult result = CheckExistingItemResult::None;
    109 
    110             for (auto const& installItem : installItems)
    111             {
    112                 const auto& status = installItem.GetCurrentStatus();
    113                 switch (status.InstallState())
    114                 {
    115                 case AppInstallState::Canceled:
    116                 case AppInstallState::Error:
    117                     // For these states, always do a cancel;
    118                     result = CheckExistingItemResult::Cancel;
    119                     return result;
    120                 case AppInstallState::Paused:
    121                 case AppInstallState::PausedLowBattery:
    122                 case AppInstallState::PausedWiFiRecommended:
    123                 case AppInstallState::PausedWiFiRequired:
    124                 case AppInstallState::ReadyToDownload:
    125                     // For these states, set result to restart and continue the loop to see if future items need cancel.
    126                     result = CheckExistingItemResult::Restart;
    127                     break;
    128                 }
    129             }
    130 
    131             return result;
    132         }
    133 
    134         bool DoesInstallItemsContainProduct(const IVectorView<AppInstallItem>& installItems, std::wstring_view productId)
    135         {
    136             for (auto const& installItem : installItems)
    137             {
    138                 if (Utility::CaseInsensitiveEquals(installItem.ProductId(), productId))
    139                 {
    140                     return true;
    141                 }
    142             }
    143 
    144             return false;
    145         }
    146 
    147         // Returns true if Restart or Cancel happened. False otherwise.
    148         HRESULT RestartOrCancelExistingOperationIfNecessary(const IVectorView<AppInstallItem>& installItems, AppInstallManager& installManager, std::wstring_view productId)
    149         {
    150             auto existingItemResult = CheckRestartOrCancelForPossibleExistingOperation(installItems);
    151 
    152             if (existingItemResult == CheckExistingItemResult::Cancel || existingItemResult == CheckExistingItemResult::Restart)
    153             {
    154                 if (existingItemResult == CheckExistingItemResult::Cancel)
    155                 {
    156                     installManager.Cancel(productId);
    157 
    158                     // Wait for at most 10 seconds for install item to be removed from queue.
    159                     for (int i = 0; i < 50; ++i)
    160                     {
    161                         Sleep(200);
    162                         if (!DoesInstallItemsContainProduct(installManager.AppInstallItems(), productId))
    163                         {
    164                             return S_OK;
    165                         }
    166                     }
    167 
    168                     RETURN_HR(HRESULT_FROM_WIN32(ERROR_TIMEOUT));
    169                 }
    170                 else
    171                 {
    172                     installManager.Restart(productId);
    173                     return S_OK;
    174                 }
    175             }
    176 
    177             return S_FALSE;
    178         }
    179     }
    180 
    181     HRESULT MSStoreOperation::StartAndWaitForOperation(IProgressCallback& progress)
    182     {
    183         // Best effort verifying/acquiring product ownership.
    184         std::ignore = EnsureFreeEntitlement(m_productId, m_scope);
    185 
    186         if (m_type == MSStoreOperationType::Update)
    187         {
    188             return UpdatePackage(progress);
    189         }
    190         else
    191         {
    192             return InstallPackage(progress);
    193         }
    194     }
    195 
    196     HRESULT MSStoreOperation::InstallPackage(IProgressCallback& progress)
    197     {
    198         AppInstallManager installManager;
    199         AppInstallOptions installOptions;
    200 
    201         installOptions.AllowForcedAppRestart(m_force);
    202         if (m_isSilentMode)
    203         {
    204             installOptions.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast);
    205             installOptions.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast);
    206         }
    207 
    208         if (m_type == MSStoreOperationType::Repair)
    209         {
    210             // Attempt to repair the installation of an app that is already installed.
    211             installOptions.Repair(true);
    212         }
    213 
    214         if (m_scope == Manifest::ScopeEnum::Machine)
    215         {
    216             // TODO: There was a bug in InstallService where admin user is incorrectly identified as not admin,
    217             // causing false access denied on many OS versions.
    218             // Remove this check when the OS bug is fixed and back ported.
    219             if (!Runtime::IsRunningAsSystem())
    220             {
    221                 AICLI_LOG(Core, Error, << "Device wide install for msstore type is not supported under admin context.");
    222                 return APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED;
    223             }
    224 
    225             installOptions.InstallForAllUsers(true);
    226         }
    227 
    228         IVectorView<AppInstallItem> installItems = installManager.StartProductInstallAsync(
    229             m_productId,            // ProductId
    230             winrt::hstring(),       // FlightId
    231             L"WinGetCli",           // ClientId
    232             winrt::hstring(),
    233             installOptions).get();
    234 
    235         // Check if we need to restart or cancel existing items.
    236         auto restartOrCancelResult = RestartOrCancelExistingOperationIfNecessary(installItems, installManager, m_productId);
    237         RETURN_IF_FAILED(restartOrCancelResult);
    238 
    239         // If restart or cancel happened, try again.
    240         if (restartOrCancelResult == S_OK)
    241         {
    242             // Try again
    243             installItems = installManager.StartProductInstallAsync(
    244                 m_productId,            // ProductId
    245                 winrt::hstring(),       // FlightId
    246                 L"WinGetCli",           // ClientId
    247                 winrt::hstring(),
    248                 installOptions).get();
    249         }
    250 
    251         return WaitForOperation(installItems, progress);
    252     }
    253 
    254     HRESULT MSStoreOperation::UpdatePackage(IProgressCallback& progress)
    255     {
    256         AppInstallManager installManager;
    257         AppUpdateOptions updateOptions;
    258         updateOptions.AllowForcedAppRestart(m_force);
    259 
    260         // SearchForUpdateAsync will automatically trigger update if found.
    261         AppInstallItem installItem = installManager.SearchForUpdatesAsync(
    262             m_productId,          // ProductId
    263             winrt::hstring(),   // SkuId
    264             winrt::hstring(),
    265             winrt::hstring(),   // ClientId
    266             updateOptions
    267         ).get();
    268 
    269         if (!installItem)
    270         {
    271             return APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE;
    272         }
    273 
    274         std::vector<AppInstallItem> installItemVector{ installItem };
    275         IVectorView<AppInstallItem> installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView();
    276 
    277         // Check if we need to restart or cancel existing items.
    278         auto restartOrCancelResult = RestartOrCancelExistingOperationIfNecessary(installItems, installManager, m_productId);
    279         RETURN_IF_FAILED(restartOrCancelResult);
    280 
    281         // If restart or cancel happened, try again.
    282         if (restartOrCancelResult == S_OK)
    283         {
    284             // Try again
    285             installItem = installManager.SearchForUpdatesAsync(
    286                 m_productId,          // ProductId
    287                 winrt::hstring(),   // SkuId
    288                 winrt::hstring(),
    289                 winrt::hstring(),   // ClientId
    290                 updateOptions
    291             ).get();
    292 
    293             if (!installItem)
    294             {
    295                 return APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE;
    296             }
    297 
    298             installItemVector.clear();
    299             installItemVector.emplace_back(installItem);
    300             installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView();
    301         }
    302 
    303         return WaitForOperation(installItems, progress);
    304     }
    305 
    306     HRESULT MSStoreOperation::WaitForOperation(IVectorView<AppInstallItem>& installItems, IProgressCallback& progress)
    307     {
    308         auto cancelIfOperationFailed = wil::scope_exit(
    309             [&]()
    310             {
    311                 try
    312                 {
    313                     AppInstallManager installManager;
    314                     installManager.Cancel(m_productId);
    315                 }
    316                 CATCH_LOG();
    317             });
    318 
    319         for (auto const& installItem : installItems)
    320         {
    321             AICLI_LOG(Core, Info, <<
    322                 "Started MSStore package execution. ProductId: " << Utility::ConvertToUTF8(installItem.ProductId()) <<
    323                 " PackageFamilyName: " << Utility::ConvertToUTF8(installItem.PackageFamilyName()));
    324 
    325             if (m_isSilentMode)
    326             {
    327                 installItem.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast);
    328                 installItem.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast);
    329             }
    330         }
    331 
    332         HRESULT errorCode = S_OK;
    333 
    334         // We are aggregating all AppInstallItem progresses into one.
    335         // Averaging every progress for now until we have a better way to find overall progress.
    336         uint64_t overallProgressMax = 100 * static_cast<uint64_t>(installItems.Size());
    337         uint64_t currentProgress = 0;
    338 
    339         while (currentProgress < overallProgressMax)
    340         {
    341             currentProgress = 0;
    342 
    343             for (auto const& installItem : installItems)
    344             {
    345                 const auto& status = installItem.GetCurrentStatus();
    346                 currentProgress += static_cast<uint64_t>(status.PercentComplete());
    347 
    348                 errorCode = status.ErrorCode();
    349 
    350                 if (!SUCCEEDED(errorCode))
    351                 {
    352                     return errorCode;
    353                 }
    354             }
    355 
    356             // It may take a while for Store client to pick up the install request.
    357             // So we show indefinite progress here to avoid a progress bar stuck at 0.
    358             if (currentProgress > 0)
    359             {
    360                 progress.OnProgress(currentProgress, overallProgressMax, ProgressType::Percent);
    361             }
    362 
    363             if (progress.IsCancelledBy(CancelReason::User))
    364             {
    365                 for (auto const& installItem : installItems)
    366                 {
    367                     installItem.Cancel();
    368                 }
    369             }
    370 
    371             // If app shutdown then we have 30s to keep installing, keep going and hope for the best.
    372             else if (progress.IsCancelledBy(CancelReason::AppShutdown))
    373             {
    374                 for (auto const& installItem : installItems)
    375                 {
    376                     // Insert spiderman meme.
    377                     if (installItem.ProductId() == std::wstring{ s_AppInstallerProductId })
    378                     {
    379                         AICLI_LOG(Core, Info, << "Asked to shutdown while installing AppInstaller.");
    380                         progress.OnProgress(overallProgressMax, overallProgressMax, ProgressType::Percent);
    381                         cancelIfOperationFailed.release();
    382                         return S_OK;
    383                     }
    384                 }
    385             }
    386 
    387             Sleep(100);
    388         }
    389 
    390         if (SUCCEEDED(errorCode))
    391         {
    392             cancelIfOperationFailed.release();
    393         }
    394 
    395         return errorCode;
    396     }
    397 }