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 }