winget-cli

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

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 }