MSStoreInstallerHandler.cpp (22010B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "MSStoreInstallerHandler.h" 5 #include "WorkflowBase.h" 6 #include <AppInstallerSHA256.h> 7 #include <AppInstallerDownloader.h> 8 #include <AppInstallerRuntime.h> 9 #include <winget/Filesystem.h> 10 #include <winget/MSStore.h> 11 #include <winget/MSStoreDownload.h> 12 #include <winget/SelfManagement.h> 13 14 namespace AppInstaller::CLI::Workflow 15 { 16 void DownloadInstallerFile(Execution::Context& context); 17 } 18 19 namespace AppInstaller::CLI::Workflow 20 { 21 using namespace AppInstaller::MSStore; 22 using namespace AppInstaller::SelfManagement; 23 using namespace winrt::Windows::Foundation; 24 using namespace winrt::Windows::Foundation::Collections; 25 using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; 26 27 namespace 28 { 29 Utility::LocIndString GetErrorCodeString(const HRESULT errorCode) 30 { 31 std::ostringstream ssError; 32 ssError << WINGET_OSTREAM_FORMAT_HRESULT(errorCode); 33 return Utility::LocIndString{ ssError.str() }; 34 } 35 36 HRESULT EnsureStorePolicySatisfiedImpl(const std::wstring& productId, bool bypassPolicy) 37 { 38 constexpr std::wstring_view s_StoreClientName = L"Microsoft.WindowsStore"sv; 39 constexpr std::wstring_view s_StoreClientPublisher = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"sv; 40 41 // Policy check 42 AppInstallManager installManager; 43 44 if (!bypassPolicy && installManager.IsStoreBlockedByPolicyAsync(s_StoreClientName, s_StoreClientPublisher).get()) 45 { 46 AICLI_LOG(CLI, Error, << "Store client is blocked by policy. MSStore execution failed."); 47 return APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY; 48 } 49 50 if (!installManager.GetIsAppAllowedToInstallAsync(productId).get()) 51 { 52 AICLI_LOG(CLI, Error, << "App is blocked by policy. MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId)); 53 return APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY; 54 } 55 56 return S_OK; 57 } 58 59 void AppInstallerUpdate(bool preferStub, bool bypassPolicy, Execution::Context& context) 60 { 61 auto appInstId = std::wstring{ s_AppInstallerProductId }; 62 THROW_IF_FAILED(EnsureStorePolicySatisfiedImpl(appInstId, bypassPolicy)); 63 SetStubPreferred(preferStub); 64 65 auto installOperation = MSStoreOperation(MSStoreOperationType::Update, appInstId, Manifest::ScopeEnum::User, true, true); 66 67 HRESULT hr = S_OK; 68 context.Reporter.ExecuteWithProgress( 69 [&](IProgressCallback& progress) 70 { 71 hr = installOperation.StartAndWaitForOperation(progress); 72 }); 73 74 THROW_IF_FAILED(hr); 75 } 76 77 HRESULT DownloadMSStorePackageFile(const MSStore::MSStoreDownloadFile& downloadFile, const std::filesystem::path& downloadDirectory, Execution::Context& context) 78 { 79 try 80 { 81 // Create a sub context to execute the package download 82 auto subContextPtr = context.CreateSubContext(); 83 Execution::Context& subContext = *subContextPtr; 84 auto previousThreadGlobals = subContext.SetForCurrentThread(); 85 86 // Populate Installer and temp download path for sub context 87 Manifest::ManifestInstaller installer; 88 installer.Url = downloadFile.Url; 89 installer.Sha256 = downloadFile.Sha256; 90 subContext.Add<Execution::Data::Installer>(std::move(installer)); 91 92 auto tempInstallerPath = Runtime::GetPathTo(Runtime::PathName::Temp); 93 tempInstallerPath /= Utility::SHA256::ConvertToString(downloadFile.Sha256); 94 AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); 95 subContext.Add<Execution::Data::InstallerPath>(tempInstallerPath); 96 97 subContext << Workflow::DownloadInstallerFile; 98 if (subContext.IsTerminated()) 99 { 100 RETURN_HR(subContext.GetTerminationHR()); 101 } 102 103 // Verify hash 104 const auto& hashPair = subContext.Get<Execution::Data::DownloadHashInfo>(); 105 if (std::equal(hashPair.first.begin(), hashPair.first.end(), hashPair.second.Sha256Hash.begin())) 106 { 107 AICLI_LOG(CLI, Info, << "Microsoft Store package hash verified"); 108 subContext.Reporter.Info() << Resource::String::MSStoreDownloadPackageHashVerified << std::endl; 109 // Trust direct download from Store if hash matched 110 Utility::ApplyMotwIfApplicable(tempInstallerPath, URLZONE_TRUSTED); 111 } 112 else 113 { 114 if (!subContext.Args.Contains(Execution::Args::Type::HashOverride)) 115 { 116 AICLI_LOG(CLI, Error, << "Microsoft Store package hash mismatch"); 117 subContext.Reporter.Error() << Resource::String::MSStoreDownloadPackageHashMismatch << std::endl; 118 RETURN_HR(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); 119 } 120 else 121 { 122 AICLI_LOG(CLI, Warning, << "Microsoft Store package hash mismatch, but overridden."); 123 subContext.Reporter.Warn() << Resource::String::MSStoreDownloadPackageHashMismatch << std::endl; 124 } 125 } 126 127 auto renamedDownloadedPackage = downloadDirectory / Utility::ConvertToUTF16(downloadFile.FileName); 128 Filesystem::RenameFile(tempInstallerPath, renamedDownloadedPackage); 129 subContext.Reporter.Info() << Resource::String::MSStoreDownloadPackageDownloaded(Utility::LocIndView{ renamedDownloadedPackage.u8string() }) << std::endl; 130 131 return S_OK; 132 } 133 catch (...) 134 { 135 AICLI_LOG(CLI, Error, << "Microsoft Store package download failed. File: " << downloadFile.FileName); 136 context.Reporter.Error() << Resource::String::MSStoreDownloadPackageDownloadFailed(Utility::LocIndView{ downloadFile.FileName }) << std::endl; 137 RETURN_HR(APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED); 138 } 139 } 140 } 141 142 void MSStoreInstall(Execution::Context& context) 143 { 144 auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); 145 auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); 146 bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); 147 bool force = context.Args.Contains(Execution::Args::Type::Force); 148 149 auto installOperation = MSStoreOperation(MSStoreOperationType::Install, productId, scope, isSilentMode, force); 150 151 context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; 152 153 HRESULT hr = S_OK; 154 context.Reporter.ExecuteWithProgress( 155 [&](IProgressCallback& progress) 156 { 157 hr = installOperation.StartAndWaitForOperation(progress); 158 }); 159 160 if (SUCCEEDED(hr)) 161 { 162 context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; 163 } 164 else 165 { 166 if (hr == APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED) 167 { 168 context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; 169 context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); 170 } 171 else 172 { 173 auto errorCodeString = GetErrorCodeString(hr); 174 context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; 175 context.Add<Execution::Data::OperationReturnCode>(hr); 176 AICLI_LOG(CLI, Error, << "MSStore install failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); 177 } 178 179 AICLI_TERMINATE_CONTEXT(hr); 180 } 181 } 182 183 void MSStoreUpdate(Execution::Context& context) 184 { 185 bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); 186 auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); 187 auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); 188 bool force = context.Args.Contains(Execution::Args::Type::Force); 189 190 auto installOperation = MSStoreOperation(MSStoreOperationType::Update, productId, scope, isSilentMode, force); 191 192 context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; 193 194 HRESULT hr = S_OK; 195 context.Reporter.ExecuteWithProgress( 196 [&](IProgressCallback& progress) 197 { 198 hr = installOperation.StartAndWaitForOperation(progress); 199 }); 200 201 if (SUCCEEDED(hr)) 202 { 203 context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; 204 } 205 else 206 { 207 if (hr == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE) 208 { 209 context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl 210 << Resource::String::UpdateNotApplicableReason << std::endl; 211 } 212 else 213 { 214 auto errorCodeString = GetErrorCodeString(hr); 215 context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; 216 context.Add<Execution::Data::OperationReturnCode>(hr); 217 AICLI_LOG(CLI, Error, << "MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); 218 } 219 220 AICLI_TERMINATE_CONTEXT(hr); 221 } 222 } 223 224 void MSStoreRepair(Execution::Context& context) 225 { 226 auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); 227 auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); 228 bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); 229 bool force = context.Args.Contains(Execution::Args::Type::Force); 230 231 auto repairOperation = MSStoreOperation(MSStoreOperationType::Repair, productId, scope, isSilentMode, force); 232 233 context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; 234 235 HRESULT hr = S_OK; 236 context.Reporter.ExecuteWithProgress( 237 [&](IProgressCallback& progress) 238 { 239 hr = repairOperation.StartAndWaitForOperation(progress); 240 }); 241 242 if (SUCCEEDED(hr)) 243 { 244 context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; 245 } 246 else 247 { 248 if (hr == APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED) 249 { 250 context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; 251 context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); 252 } 253 else 254 { 255 auto errorCodeString = GetErrorCodeString(hr); 256 context.Reporter.Error() << Resource::String::MSStoreRepairFailed(errorCodeString) << std::endl; 257 context.Add<Execution::Data::OperationReturnCode>(hr); 258 AICLI_LOG(CLI, Error, << "MSStore repair failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); 259 } 260 261 AICLI_TERMINATE_CONTEXT(hr); 262 } 263 } 264 265 void MSStoreDownload(Execution::Context& context) 266 { 267 if (context.Args.Contains(Execution::Args::Type::Rename)) 268 { 269 context.Reporter.Warn() << Resource::String::MSStoreDownloadRenameNotSupported << std::endl; 270 } 271 272 // Authentication notice 273 context.Reporter.Warn() << Resource::String::MSStoreDownloadAuthenticationNotice << std::endl; 274 context.Reporter.Warn() << Resource::String::MSStoreDownloadMultiplePackagesNotice << std::endl; 275 276 const auto& installer = context.Get<Execution::Data::Installer>().value(); 277 278 Utility::Architecture requiredArchitecture = Utility::Architecture::Unknown; 279 Manifest::PlatformEnum requiredPlatform = Manifest::PlatformEnum::Unknown; 280 std::string requiredLocale; 281 if (context.Args.Contains(Execution::Args::Type::InstallerArchitecture)) 282 { 283 requiredArchitecture = Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallerArchitecture)); 284 } 285 if (context.Args.Contains(Execution::Args::Type::Platform)) 286 { 287 requiredPlatform = Manifest::ConvertToPlatformEnumForMSStoreDownload(context.Args.GetArg(Execution::Args::Type::Platform)); 288 } 289 if (context.Args.Contains(Execution::Args::Type::Locale)) 290 { 291 requiredLocale = context.Args.GetArg(Execution::Args::Type::Locale); 292 } 293 294 MSStoreDownloadContext downloadContext{ installer.ProductId, requiredArchitecture, requiredPlatform, requiredLocale, GetAuthenticationArguments(context) }; 295 296 if (context.Args.Contains(Execution::Args::Type::OSVersion)) 297 { 298 Utility::UInt64Version targetOSVersion{ std::string{ context.Args.GetArg(Execution::Args::Type::OSVersion) } }; 299 downloadContext.TargetOSVersion(std::move(targetOSVersion)); 300 } 301 302 MSStoreDownloadInfo downloadInfo; 303 try 304 { 305 context.Reporter.Info() << Resource::String::MSStoreDownloadGetDownloadInfo << std::endl; 306 307 downloadInfo = downloadContext.GetDownloadInfo(); 308 } 309 catch (const wil::ResultException& re) 310 { 311 AICLI_LOG(CLI, Error, << "Getting MSStore package download info failed. Error code: " << re.GetErrorCode()); 312 313 switch (re.GetErrorCode()) 314 { 315 case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE: 316 case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE: 317 context.Reporter.Error() << Resource::String::MSStoreDownloadNoApplicablePackageFound << std::endl; 318 break; 319 case APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED: 320 context.Reporter.Error() << Resource::String::MSStoreDownloadPackageDownloadNotSupported << std::endl; 321 break; 322 default: 323 context.Reporter.Error() << Resource::String::MSStoreDownloadGetDownloadInfoFailed << std::endl; 324 } 325 326 AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); 327 } 328 329 bool skipDependencies = context.Args.Contains(Execution::Args::Type::SkipDependencies); 330 331 // Prepare directories 332 std::filesystem::path downloadDirectory = context.Get<Execution::Data::DownloadDirectory>(); 333 std::filesystem::path dependenciesDirectory = downloadDirectory / L"Dependencies"; 334 335 // Create directories if needed. 336 auto directoryToCreate = (skipDependencies || downloadInfo.DependencyPackages.empty()) ? downloadDirectory : dependenciesDirectory; 337 if (!std::filesystem::exists(directoryToCreate)) 338 { 339 std::filesystem::create_directories(directoryToCreate); 340 } 341 else 342 { 343 THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !std::filesystem::is_directory(directoryToCreate)); 344 } 345 346 // Download dependency packages 347 if (!skipDependencies) 348 { 349 AICLI_LOG(CLI, Info, << "Downloading MSStore dependency packages"); 350 context.Reporter.Info() << Resource::String::MSStoreDownloadDependencyPackages << std::endl; 351 352 for (auto const& dependencyPackage : downloadInfo.DependencyPackages) 353 { 354 THROW_IF_FAILED(DownloadMSStorePackageFile(dependencyPackage, dependenciesDirectory, context)); 355 } 356 } 357 358 // Download main packages 359 AICLI_LOG(CLI, Info, << "Downloading MSStore main packages"); 360 context.Reporter.Info() << Resource::String::MSStoreDownloadMainPackages << std::endl; 361 for (auto const& mainPackage : downloadInfo.MainPackages) 362 { 363 THROW_IF_FAILED(DownloadMSStorePackageFile(mainPackage, downloadDirectory, context)); 364 } 365 366 context.Reporter.Info() << Resource::String::MSStoreDownloadPackageDownloadSuccess << std::endl; 367 368 // Get license 369 if (!context.Args.Contains(Execution::Args::Type::SkipMicrosoftStorePackageLicense)) 370 { 371 AICLI_LOG(CLI, Info, << "Getting MSStore package license"); 372 context.Reporter.Info() << Resource::String::MSStoreDownloadGetLicense << std::endl; 373 374 std::vector<BYTE> licenseContent; 375 try 376 { 377 licenseContent = downloadContext.GetLicense(downloadInfo.ContentId); 378 } 379 catch (const wil::ResultException& re) 380 { 381 if (re.GetErrorCode() == APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN) 382 { 383 AICLI_LOG(CLI, Warning, << "Getting MSStore package license failed. The Microsoft Entra Id account does not have privilege."); 384 context.Reporter.Warn() << Resource::String::MSStoreDownloadGetLicenseForbidden << std::endl; 385 } 386 else 387 { 388 AICLI_LOG(CLI, Warning, << "Getting MSStore package license failed. Error code: " << re.GetErrorCode()); 389 context.Reporter.Warn() << Resource::String::MSStoreDownloadGetLicenseFailed << std::endl; 390 } 391 392 AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); 393 } 394 395 std::filesystem::path licenseFilePath = downloadDirectory / Utility::ConvertToUTF16(installer.ProductId + "_License.xml"); 396 std::ofstream licenseFile(licenseFilePath, std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); 397 licenseFile.write((const char *)&licenseContent[0], licenseContent.size()); 398 licenseFile.flush(); 399 licenseFile.close(); 400 401 AICLI_LOG(CLI, Info, << "Getting MSStore package license success"); 402 context.Reporter.Info() << Resource::String::MSStoreDownloadGetLicenseSuccess(Utility::LocIndView{ licenseFilePath.u8string() }) << std::endl; 403 } 404 } 405 406 void EnsureStorePolicySatisfied(Execution::Context& context) 407 { 408 auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); 409 bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); 410 411 HRESULT hr = EnsureStorePolicySatisfiedImpl(productId, bypassStorePolicy); 412 if (FAILED(hr)) 413 { 414 if (hr == APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY) 415 { 416 context.Reporter.Error() << Resource::String::MSStoreStoreClientBlocked << std::endl; 417 } 418 else if (hr == APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY) 419 { 420 context.Reporter.Error() << Resource::String::MSStoreAppBlocked << std::endl; 421 } 422 423 AICLI_TERMINATE_CONTEXT(hr); 424 } 425 } 426 427 void VerifyIsFullPackage(Execution::Context& context) 428 { 429 if (IsStubPackage()) 430 { 431 context.Reporter.Error() << Resource::String::ExtendedFeaturesNotEnabledMessage << std::endl; 432 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); 433 } 434 } 435 436 void EnableExtendedFeatures(Execution::Context& context) 437 { 438 #ifndef AICLI_DISABLE_TEST_HOOKS 439 AppInstallerUpdate(false, true, context); 440 #else 441 if (IsStubPackage()) 442 { 443 context.Reporter.Info() << Resource::String::ExtendedFeaturesEnablingMessage << std::endl; 444 bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); 445 AppInstallerUpdate(false, bypassStorePolicy, context); 446 } 447 else 448 { 449 context.Reporter.Info() << Resource::String::ExtendedFeaturesEnabledMessage << std::endl; 450 } 451 #endif 452 } 453 454 void DisableExtendedFeatures(Execution::Context& context) 455 { 456 #ifndef AICLI_DISABLE_TEST_HOOKS 457 AppInstallerUpdate(true, true, context); 458 #else 459 if (!IsStubPackage()) 460 { 461 context.Reporter.Info() << Resource::String::ExtendedFeaturesDisablingMessage << std::endl; 462 bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); 463 AppInstallerUpdate(true, bypassStorePolicy, context); 464 } 465 else 466 { 467 context.Reporter.Info() << Resource::String::ExtendedFeaturesDisabledMessage << std::endl; 468 } 469 #endif 470 } 471 }