winget-cli

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

InstallFlow.cpp (45042B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "InstallFlow.h"
      5 #include "DownloadFlow.h"
      6 #include "UninstallFlow.h"
      7 #include "UpdateFlow.h"
      8 #include "ResumeFlow.h"
      9 #include "ShowFlow.h"
     10 #include "Resources.h"
     11 #include "ShellExecuteInstallerHandler.h"
     12 #include "MSStoreInstallerHandler.h"
     13 #include "MsiInstallFlow.h"
     14 #include "ArchiveFlow.h"
     15 #include "PortableFlow.h"
     16 #include "WorkflowBase.h"
     17 #include "DependenciesFlow.h"
     18 #include "PromptFlow.h"
     19 #include "SourceFlow.h"
     20 #include <AppInstallerMsixInfo.h>
     21 #include <AppInstallerDeployment.h>
     22 #include <AppInstallerSynchronization.h>
     23 #include <Argument.h>
     24 #include <Command.h>
     25 #include <winget/ARPCorrelation.h>
     26 #include <winget/Archive.h>
     27 #include <winget/PathVariable.h>
     28 #include <winget/Runtime.h>
     29 
     30 using namespace winrt::Windows::Foundation;
     31 using namespace winrt::Windows::Foundation::Collections;
     32 using namespace winrt::Windows::Management::Deployment;
     33 using namespace AppInstaller::CLI::Execution;
     34 using namespace AppInstaller::Manifest;
     35 using namespace AppInstaller::Repository;
     36 using namespace AppInstaller::Registry::Environment;
     37 using namespace AppInstaller::Settings;
     38 using namespace AppInstaller::Utility;
     39 using namespace AppInstaller::Utility::literals;
     40 
     41 namespace AppInstaller::CLI::Workflow
     42 {
     43     namespace
     44     {
     45         bool MightWriteToARP(InstallerTypeEnum type)
     46         {
     47             switch (type)
     48             {
     49             case InstallerTypeEnum::Exe:
     50             case InstallerTypeEnum::Burn:
     51             case InstallerTypeEnum::Inno:
     52             case InstallerTypeEnum::Msi:
     53             case InstallerTypeEnum::Nullsoft:
     54             case InstallerTypeEnum::Wix:
     55                 return true;
     56             default:
     57                 return false;
     58             }
     59         }
     60 
     61         bool ShouldUseDirectMSIInstall(InstallerTypeEnum type, bool isSilentInstall)
     62         {
     63             switch (type)
     64             {
     65             case InstallerTypeEnum::Msi:
     66             case InstallerTypeEnum::Wix:
     67                 return isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI);
     68             default:
     69                 return false;
     70             }
     71         }
     72 
     73         bool ShouldErrorForUnsupportedArgument(UnsupportedArgumentEnum arg)
     74         {
     75             switch (arg)
     76             {
     77             case UnsupportedArgumentEnum::Location:
     78                 return true;
     79             default:
     80                 return false;
     81             }
     82         }
     83 
     84         Execution::Args::Type GetUnsupportedArgumentType(UnsupportedArgumentEnum unsupportedArgument)
     85         {
     86             Execution::Args::Type execArg;
     87 
     88             switch (unsupportedArgument)
     89             {
     90             case UnsupportedArgumentEnum::Log:
     91                 execArg = Execution::Args::Type::Log;
     92                 break;
     93             case UnsupportedArgumentEnum::Location:
     94                 execArg = Execution::Args::Type::InstallLocation;
     95                 break;
     96             default:
     97                 THROW_HR(E_UNEXPECTED);
     98             }
     99 
    100             return execArg;
    101         }
    102 
    103         struct ExpectedReturnCode
    104         {
    105             ExpectedReturnCode(ExpectedReturnCodeEnum installerReturnCode, HRESULT hr, Resource::StringId message) :
    106                 InstallerReturnCode(installerReturnCode), HResult(hr), Message(message) {}
    107 
    108             static ExpectedReturnCode GetExpectedReturnCode(ExpectedReturnCodeEnum returnCode)
    109             {
    110                 switch (returnCode)
    111                 {
    112                 case ExpectedReturnCodeEnum::PackageInUse:
    113                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, Resource::String::InstallFlowReturnCodePackageInUse);
    114                 case ExpectedReturnCodeEnum::PackageInUseByApplication:
    115                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION, Resource::String::InstallFlowReturnCodePackageInUseByApplication);
    116                 case ExpectedReturnCodeEnum::InstallInProgress:
    117                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS, Resource::String::InstallFlowReturnCodeInstallInProgress);
    118                 case ExpectedReturnCodeEnum::FileInUse:
    119                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE, Resource::String::InstallFlowReturnCodeFileInUse);
    120                 case ExpectedReturnCodeEnum::MissingDependency:
    121                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY, Resource::String::InstallFlowReturnCodeMissingDependency);
    122                 case ExpectedReturnCodeEnum::DiskFull:
    123                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL, Resource::String::InstallFlowReturnCodeDiskFull);
    124                 case ExpectedReturnCodeEnum::InsufficientMemory:
    125                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY, Resource::String::InstallFlowReturnCodeInsufficientMemory);
    126                 case ExpectedReturnCodeEnum::InvalidParameter:
    127                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER, Resource::String::InstallFlowReturnCodeInvalidParameter);
    128                 case ExpectedReturnCodeEnum::NoNetwork:
    129                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK, Resource::String::InstallFlowReturnCodeNoNetwork);
    130                 case ExpectedReturnCodeEnum::ContactSupport:
    131                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT, Resource::String::InstallFlowReturnCodeContactSupport);
    132                 case ExpectedReturnCodeEnum::RebootRequiredToFinish:
    133                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH, Resource::String::InstallFlowReturnCodeRebootRequiredToFinish);
    134                 case ExpectedReturnCodeEnum::RebootRequiredForInstall:
    135                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL, Resource::String::InstallFlowReturnCodeRebootRequiredForInstall);
    136                 case ExpectedReturnCodeEnum::RebootInitiated:
    137                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED, Resource::String::InstallFlowReturnCodeRebootInitiated);
    138                 case ExpectedReturnCodeEnum::CancelledByUser:
    139                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER, Resource::String::InstallFlowReturnCodeCancelledByUser);
    140                 case ExpectedReturnCodeEnum::AlreadyInstalled:
    141                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED, Resource::String::InstallFlowReturnCodeAlreadyInstalled);
    142                 case ExpectedReturnCodeEnum::Downgrade:
    143                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE, Resource::String::InstallFlowReturnCodeDowngrade);
    144                 case ExpectedReturnCodeEnum::BlockedByPolicy:
    145                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY, Resource::String::InstallFlowReturnCodeBlockedByPolicy);
    146                 case ExpectedReturnCodeEnum::SystemNotSupported:
    147                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, Resource::String::InstallFlowReturnCodeSystemNotSupported);
    148                 case ExpectedReturnCodeEnum::Custom:
    149                     return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR, Resource::String::InstallFlowReturnCodeCustomError);
    150                 default:
    151                     THROW_HR(E_UNEXPECTED);
    152                 }
    153             }
    154 
    155             ExpectedReturnCodeEnum InstallerReturnCode;
    156             HRESULT HResult;
    157             Resource::StringId Message;
    158         };
    159     }
    160 
    161     namespace details
    162     {
    163         // Runs the installer via ShellExecute.
    164         // Required Args: None
    165         // Inputs: Installer, InstallerPath
    166         // Outputs: None
    167         void ShellExecuteInstall(Execution::Context& context)
    168         {
    169             context <<
    170                 GetInstallerArgs <<
    171                 ShellExecuteInstallImpl <<
    172                 ReportInstallerResult("ShellExecute"sv, APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED);
    173         }
    174 
    175         // Runs an MSI installer directly via MSI APIs.
    176         // Required Args: None
    177         // Inputs: Installer, InstallerPath
    178         // Outputs: None
    179         void DirectMSIInstall(Execution::Context& context)
    180         {
    181             context <<
    182                 GetInstallerArgs <<
    183                 DirectMSIInstallImpl <<
    184                 ReportInstallerResult("MsiInstallProduct"sv, APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED);
    185         }
    186 
    187         // Deploys the MSIX.
    188         // Required Args: None
    189         // Inputs: Manifest?, Installer || InstallerPath
    190         // Outputs: None
    191         void MsixInstall(Execution::Context& context)
    192         {
    193             std::string uri;
    194             Deployment::Options deploymentOptions;
    195             if (context.Contains(Execution::Data::InstallerPath))
    196             {
    197                 uri = context.Get<Execution::Data::InstallerPath>().u8string();
    198             }
    199             else
    200             {
    201                 uri = context.Get<Execution::Data::Installer>()->Url;
    202                 deploymentOptions.ExpectedDigests = context.Get<Execution::Data::MsixDigests>();
    203             }
    204 
    205             deploymentOptions.SkipReputationCheck = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted);
    206 
    207             bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine;
    208 
    209             // TODO: There was a bug in deployment api if provision api was called in packaged context.
    210             // Remove this check when the OS bug is fixed and back ported.
    211             if (isMachineScope && Runtime::IsRunningInPackagedContext())
    212             {
    213                 context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl;
    214                 context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED));
    215                 AICLI_LOG(CLI, Error, << "Device wide install for msix type is not supported in packaged context.");
    216                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED);
    217             }
    218 
    219             context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl;
    220 
    221             bool registrationDeferred = false;
    222 
    223             try
    224             {
    225                 registrationDeferred = context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback)
    226                     {
    227                         if (isMachineScope)
    228                         {
    229                             return Deployment::AddPackageMachineScope(uri, deploymentOptions, callback);
    230                         }
    231                         else
    232                         {
    233                             return Deployment::AddPackageWithDeferredFallback(uri, deploymentOptions, callback);
    234                         }
    235                     });
    236             }
    237             catch (const wil::ResultException& re)
    238             {
    239                 context.Add<Execution::Data::OperationReturnCode>(re.GetErrorCode());
    240                 context << ReportInstallerResult("MSIX"sv, re.GetErrorCode(), /* isHResult */ true);
    241                 return;
    242             }
    243 
    244             if (registrationDeferred)
    245             {
    246                 context.Reporter.Warn() << Resource::String::InstallFlowRegistrationDeferred << std::endl;
    247             }
    248             else
    249             {
    250                 context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl;
    251             }
    252         }
    253 
    254         // Runs the flow for installing a Portable package.
    255         // Required Args: None
    256         // Inputs: Installer, InstallerPath
    257         // Outputs: None
    258         void PortableInstall(Execution::Context& context)
    259         {
    260             context <<
    261                 InitializePortableInstaller <<
    262                 VerifyPackageAndSourceMatch <<
    263                 PortableInstallImpl <<
    264                 ReportInstallerResult("Portable"sv, APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED, true);
    265         }
    266 
    267         // Runs the flow for installing a package from an archive.
    268         // Required Args: None
    269         // Inputs: Installer, InstallerPath, Manifest
    270         // Outputs: None
    271         void ArchiveInstall(Execution::Context& context)
    272         {
    273             context <<
    274                 ScanArchiveFromLocalManifest <<
    275                 ExtractFilesFromArchive <<
    276                 VerifyAndSetNestedInstaller <<
    277                 ExecuteInstallerForType(context.Get<Execution::Data::Installer>().value().NestedInstallerType);
    278         }
    279     }
    280 
    281     bool ExemptFromSingleInstallLocking(InstallerTypeEnum type)
    282     {
    283         switch (type)
    284         {
    285             // MSStore installs are always MSIX based; MSIX installs are safe to run in parallel.
    286         case InstallerTypeEnum::Msix:
    287         case InstallerTypeEnum::MSStore:
    288             return true;
    289         default:
    290             return false;
    291         }
    292     }
    293 
    294     void EnsureApplicableInstaller(Execution::Context& context)
    295     {
    296         const auto& installer = context.Get<Execution::Data::Installer>();
    297 
    298         if (!installer.has_value())
    299         {
    300             context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl;
    301             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER);
    302         }
    303 
    304         context <<
    305             EnsureSupportForDownload <<
    306             EnsureSupportForInstall;
    307     }
    308 
    309     void CheckForUnsupportedArgs(Execution::Context& context)
    310     {
    311         bool messageDisplayed = false;
    312         const auto& unsupportedArgs = context.Get<Execution::Data::Installer>()->UnsupportedArguments;
    313         for (auto unsupportedArg : unsupportedArgs)
    314         {
    315             const auto& unsupportedArgType = GetUnsupportedArgumentType(unsupportedArg);
    316             if (context.Args.Contains(unsupportedArgType))
    317             {
    318                 if (!messageDisplayed)
    319                 {
    320                     context.Reporter.Warn() << Resource::String::UnsupportedArgument << std::endl;
    321                     messageDisplayed = true;
    322                 }
    323 
    324                 const auto& executingCommand = context.GetExecutingCommand();
    325                 if (executingCommand != nullptr)
    326                 {
    327                     const auto& commandArguments = executingCommand->GetArguments();
    328                     for (const auto& argument : commandArguments)
    329                     {
    330                         if (unsupportedArgType == argument.ExecArgType())
    331                         {
    332                             const auto& usageString = argument.GetUsageString();
    333                             if (ShouldErrorForUnsupportedArgument(unsupportedArg))
    334                             {
    335                                 context.Reporter.Error() << usageString << std::endl;
    336                                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT);
    337                             }
    338                             else
    339                             {
    340                                 context.Reporter.Warn() << usageString << std::endl;
    341                                 break;
    342                             }
    343                         }
    344                     }
    345                 }
    346             }
    347         }
    348     }
    349 
    350     void ShowInstallationDisclaimer(Execution::Context& context)
    351     {
    352         auto installerType = context.Get<Execution::Data::Installer>().value().EffectiveInstallerType();
    353 
    354         if (installerType == InstallerTypeEnum::MSStore)
    355         {
    356             context.Reporter.Info() << Execution::PromptEmphasis << Resource::String::InstallationDisclaimerMSStore << std::endl;
    357         }
    358         else
    359         {
    360             context.Reporter.Info() <<
    361                 Resource::String::InstallationDisclaimer1 << std::endl <<
    362                 Resource::String::InstallationDisclaimer2 << std::endl;
    363         }
    364     }
    365 
    366     void DisplayInstallationNotes(Execution::Context& context)
    367     {
    368         if (!Settings::User().Get<Settings::Setting::DisableInstallNotes>())
    369         {
    370             const auto& manifest = context.Get<Execution::Data::Manifest>();
    371             auto installationNotes = manifest.CurrentLocalization.Get<AppInstaller::Manifest::Localization::InstallationNotes>();
    372 
    373             if (!installationNotes.empty())
    374             {
    375                 context.Reporter.Info() << Resource::String::Notes(installationNotes) << std::endl;
    376             }
    377         }
    378     }
    379 
    380     void ExecuteInstallerForType::operator()(Execution::Context& context) const
    381     {
    382         bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);
    383         UpdateBehaviorEnum updateBehavior = context.Get<Execution::Data::Installer>().value().UpdateBehavior;
    384         bool doUninstallPrevious = isUpdate && (updateBehavior == UpdateBehaviorEnum::UninstallPrevious || context.Args.Contains(Execution::Args::Type::UninstallPrevious));
    385 
    386         Synchronization::CrossProcessInstallLock lock;
    387         if (!ExemptFromSingleInstallLocking(m_installerType))
    388         {
    389             // Acquire install lock; if the operation is cancelled it will return false so we will also return.
    390             if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback)
    391                 {
    392                     callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother());
    393                     return lock.Acquire(callback);
    394                 }))
    395             {
    396                 AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire install lock due to cancellation");
    397                 return;
    398             }
    399         }
    400 
    401         switch (m_installerType)
    402         {
    403         case InstallerTypeEnum::Exe:
    404         case InstallerTypeEnum::Burn:
    405         case InstallerTypeEnum::Inno:
    406         case InstallerTypeEnum::Msi:
    407         case InstallerTypeEnum::Nullsoft:
    408         case InstallerTypeEnum::Wix:
    409             if (doUninstallPrevious)
    410             {
    411                 context <<
    412                     GetUninstallInfo <<
    413                     ExecuteUninstaller;
    414                 context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate);
    415             }
    416             if (ShouldUseDirectMSIInstall(m_installerType, context.Args.Contains(Execution::Args::Type::Silent)))
    417             {
    418                 context << details::DirectMSIInstall;
    419             }
    420             else
    421             {
    422                 context << details::ShellExecuteInstall;
    423             }
    424             break;
    425         case InstallerTypeEnum::Msix:
    426             context << details::MsixInstall;
    427             break;
    428         case InstallerTypeEnum::MSStore:
    429             context <<
    430                 EnsureStorePolicySatisfied <<
    431                 (isUpdate ? MSStoreUpdate : MSStoreInstall);
    432             break;
    433         case InstallerTypeEnum::Portable:
    434             if (doUninstallPrevious)
    435             {
    436                 context <<
    437                     GetUninstallInfo <<
    438                     ExecuteUninstaller;
    439                 context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate);
    440             }
    441             context << details::PortableInstall;
    442             break;
    443         case InstallerTypeEnum::Zip:
    444             context << details::ArchiveInstall;
    445             break;
    446         default:
    447             THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
    448         }
    449     }
    450 
    451     void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context)
    452     {
    453         // Admin is required for machine scope install for installer types like portable, msix and msstore.
    454         auto installerType = context.Get<Execution::Data::Installer>().value().EffectiveInstallerType();
    455 
    456         if (Manifest::DoesInstallerTypeRequireAdminForMachineScopeInstall(installerType))
    457         {
    458             Manifest::ScopeEnum scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope));
    459             if (scope == Manifest::ScopeEnum::Machine)
    460             {
    461                 context << Workflow::EnsureRunningAsAdmin;
    462             }
    463         }
    464     }
    465 
    466     void ExecuteInstaller(Execution::Context& context)
    467     {
    468         context << Workflow::ExecuteInstallerForType(context.Get<Execution::Data::Installer>().value().BaseInstallerType);
    469     }
    470 
    471     void ReportInstallerResult::operator()(Execution::Context& context) const
    472     {
    473         bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair);
    474 
    475         DWORD installResult = context.Get<Execution::Data::OperationReturnCode>();
    476         const auto& additionalSuccessCodes = context.Get<Execution::Data::Installer>()->InstallerSuccessCodes;
    477         if (installResult != 0 && (std::find(additionalSuccessCodes.begin(), additionalSuccessCodes.end(), installResult) == additionalSuccessCodes.end()))
    478         {
    479             HRESULT terminationHR = m_hr;
    480             const auto& expectedReturnCodes = context.Get<Execution::Data::Installer>()->ExpectedReturnCodes;
    481             auto expectedReturnCodeItr = expectedReturnCodes.find(installResult);
    482             if (expectedReturnCodeItr != expectedReturnCodes.end() && expectedReturnCodeItr->second.ReturnResponseEnum != ExpectedReturnCodeEnum::Unknown)
    483             {
    484                 auto returnCode = ExpectedReturnCode::GetExpectedReturnCode(expectedReturnCodeItr->second.ReturnResponseEnum);
    485                 terminationHR = returnCode.HResult;
    486 
    487                 switch (terminationHR)
    488                 {
    489                 case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH:
    490                     // REBOOT_REQUIRED_TO_FINISH is treated as a success since installation has completed but is pending a reboot.
    491                     context.SetFlags(ContextFlag::RebootRequired);
    492                     context.Reporter.Warn() << returnCode.Message << std::endl;
    493                     terminationHR = S_OK;
    494                     break;
    495                 case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL:
    496                     // REBOOT_REQUIRED_FOR_INSTALL is treated as an error since installation has not yet completed.
    497                     context.SetFlags(ContextFlag::RebootRequired);
    498                     // TODO: Add separate workflow to handle restart registration for resume.
    499                     context.SetFlags(ContextFlag::RegisterResume);
    500                     break;
    501                 }
    502 
    503                 if (FAILED(terminationHR))
    504                 {
    505                     context.Reporter.Error() << returnCode.Message << std::endl;
    506                     const auto& returnResponseUrl = expectedReturnCodeItr->second.ReturnResponseUrl;
    507                     if (!returnResponseUrl.empty())
    508                     {
    509                         context.Reporter.Error() << Resource::String::RelatedLink << ' ' << returnResponseUrl << std::endl;
    510                     }
    511                 }
    512             }
    513 
    514             if (FAILED(terminationHR))
    515             {
    516                 const auto& manifest = context.Get<Execution::Data::Manifest>();
    517 
    518                 if (isRepair)
    519                 {
    520                     Logging::Telemetry().LogRepairFailure(manifest.Id, manifest.Version, m_installerType, installResult);
    521                 }
    522                 else
    523                 {
    524                     Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, m_installerType, installResult);
    525                 }
    526 
    527                 if (m_isHResult)
    528                 {
    529                     context.Reporter.Error()
    530                         << Resource::String::InstallerFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(installResult) })
    531                         << std::endl;
    532                 }
    533                 else
    534                 {
    535                     context.Reporter.Error()
    536                         << Resource::String::InstallerFailedWithCode(installResult)
    537                         << std::endl;
    538                 }
    539 
    540                 // Show installer log path if exists
    541                 if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get<Execution::Data::LogPath>()))
    542                 {
    543                     auto installerLogPath = Utility::LocIndString{ context.Get<Execution::Data::LogPath>().u8string() };
    544                     context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl;
    545                 }
    546 
    547                 AICLI_TERMINATE_CONTEXT(terminationHR);
    548             }
    549         }
    550         else
    551         {
    552             if (isRepair)
    553             {
    554                 context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl;
    555             }
    556             else
    557             {
    558                 context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl;
    559             }
    560         }
    561     }
    562 
    563     void ReportIdentityAndInstallationDisclaimer(Execution::Context& context)
    564     {
    565         context <<
    566             Workflow::ReportManifestIdentityWithVersion() <<
    567             Workflow::ShowInstallationDisclaimer;
    568     }
    569 
    570     void InstallPackageInstaller(Execution::Context& context)
    571     {
    572         context <<
    573             Workflow::ReportExecutionStage(ExecutionStage::PreExecution) <<
    574             Workflow::SnapshotARPEntries <<
    575             Workflow::ReportExecutionStage(ExecutionStage::Execution) <<
    576             Workflow::ExecuteInstaller <<
    577             Workflow::ReportExecutionStage(ExecutionStage::PostExecution) <<
    578             Workflow::ReportARPChanges <<
    579             Workflow::RecordInstall <<
    580             Workflow::ForceInstalledCacheUpdate <<
    581             Workflow::RemoveInstaller <<
    582             Workflow::DisplayInstallationNotes;
    583     }
    584 
    585     void InstallDependencies(Execution::Context& context)
    586     {
    587         using Flags = ProcessMultiplePackages::Flags;
    588 
    589         if (Settings::User().Get<Settings::Setting::InstallSkipDependencies>() || context.Args.Contains(Execution::Args::Type::SkipDependencies))
    590         {
    591             context.Reporter.Warn() << Resource::String::DependenciesSkippedMessage << std::endl;
    592             return;
    593         }
    594 
    595         context <<
    596             GetDependenciesFromInstaller <<
    597             ReportDependencies(Resource::String::PackageRequiresDependencies) <<
    598             EnableWindowsFeaturesDependencies <<
    599             ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable);
    600     }
    601 
    602     void DownloadPackageDependencies(Execution::Context& context)
    603     {
    604         using Flags = ProcessMultiplePackages::Flags;
    605 
    606         if (Settings::User().Get<Settings::Setting::InstallSkipDependencies>() || context.Args.Contains(Execution::Args::Type::SkipDependencies))
    607         {
    608             context.Reporter.Warn() << Resource::String::DependenciesSkippedMessage << std::endl;
    609             return;
    610         }
    611 
    612         context <<
    613             GetDependenciesFromInstaller <<
    614             ReportDependencies(Resource::String::PackageRequiresDependencies) <<
    615             CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) <<
    616             ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::DownloadOnly);
    617     }
    618 
    619     void InstallSinglePackage(Execution::Context& context)
    620     {
    621         context <<
    622             Workflow::CheckForUnsupportedArgs <<
    623             Workflow::ReportIdentityAndInstallationDisclaimer <<
    624             Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) <<
    625             Workflow::CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) <<
    626             Workflow::InstallDependencies <<
    627             Workflow::DownloadInstaller <<
    628             Workflow::InstallPackageInstaller <<
    629             Workflow::RegisterStartupAfterReboot();
    630     }
    631 
    632     void EnsureSupportForInstall(Execution::Context& context)
    633     {
    634         if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly))
    635         {
    636             return;
    637         }
    638 
    639         const auto& installer = context.Get<Execution::Data::Installer>();
    640 
    641         // This check is only necessary for the Repair workflow when operating on an installer with RepairBehavior set to Installer.
    642         if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair))
    643         {
    644             if (installer->RepairBehavior != RepairBehaviorEnum::Installer)
    645             {
    646                 return;
    647             }
    648 
    649             // At present, the installer repair behavior scenario is restricted to Exe, Inno, Nullsoft, and Burn installer types.
    650             if (!DoesInstallerTypeRequireRepairBehaviorForRepair(installer->EffectiveInstallerType()))
    651             {
    652                 return;
    653             }
    654         }
    655 
    656         // This installer cannot be run elevated, but we are running elevated.
    657         // Implementation of de-elevation is complex; simply block for now.
    658         if (installer->ElevationRequirement == ElevationRequirementEnum::ElevationProhibited && Runtime::IsRunningAsAdmin())
    659         {
    660             AICLI_LOG(CLI, Error, << "The installer cannot be run from an administrator context.");
    661             context.Reporter.Error() << Resource::String::InstallerProhibitsElevation << std::endl;
    662             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION);
    663         }
    664 
    665         // This installer cannot be used to upgrade the currently installed application
    666         // Because the upgrade mechanism may be package-specific, simply block.
    667         bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);
    668         UpdateBehaviorEnum updateBehavior = installer->UpdateBehavior;
    669         if (isUpdate && (updateBehavior == UpdateBehaviorEnum::Deny))
    670         {
    671             AICLI_LOG(CLI, Error, << "Manifest specifies update behavior is denied. The attempt will be cancelled.");
    672             context.Reporter.Error() << Resource::String::UpgradeBlockedByManifest << std::endl;
    673             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED);
    674         }
    675 
    676         context <<
    677             Workflow::EnsureRunningAsAdminForMachineScopeInstall <<
    678             Workflow::EnsureSupportForPortableInstall <<
    679             Workflow::EnsureValidNestedInstallerMetadataForArchiveInstall;
    680     }
    681 
    682     ProcessMultiplePackages::ProcessMultiplePackages(
    683         StringResource::StringId dependenciesReportMessage,
    684         HRESULT resultOnFailure,
    685         Flags flags,
    686         std::vector<HRESULT>&& ignorableInstallResults) :
    687             WorkflowTask("ProcessMultiplePackages"),
    688             m_dependenciesReportMessage(dependenciesReportMessage),
    689             m_resultOnFailure(resultOnFailure),
    690             m_ignorableInstallResults(std::move(ignorableInstallResults))
    691     {
    692         // Inverted
    693         m_ensurePackageAgreements = !WI_IsFlagSet(flags, Flags::SkipPackageAgreements);
    694 
    695         m_ignorePackageDependencies = WI_IsFlagSet(flags, Flags::IgnoreDependencies);
    696         m_stopOnFailure = WI_IsFlagSet(flags, Flags::StopOnFailure);
    697         m_refreshPathVariable = WI_IsFlagSet(flags, Flags::RefreshPathVariable);
    698         m_downloadOnly = WI_IsFlagSet(flags, Flags::DownloadOnly);
    699     }
    700 
    701     void ProcessMultiplePackages::operator()(Execution::Context& context) const
    702     {
    703         if (!context.Contains(Execution::Data::PackageSubContexts))
    704         {
    705             return;
    706         }
    707 
    708         bool downloadInstallerOnly = m_downloadOnly ? true : WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly);
    709 
    710         // Show all prompts needed for every package before installing anything
    711         context << Workflow::ShowPromptsForMultiplePackages(m_ensurePackageAgreements, downloadInstallerOnly);
    712 
    713         if (context.IsTerminated())
    714         {
    715             return;
    716         }
    717 
    718         auto& packageSubContexts = context.Get<Execution::Data::PackageSubContexts>();
    719 
    720         // Report dependencies
    721         if (!m_ignorePackageDependencies)
    722         {
    723             DependencyList allDependencies;
    724 
    725             for (auto& packageContext : packageSubContexts)
    726             {
    727                 allDependencies.Add(packageContext->Get<Execution::Data::Installer>().value().Dependencies);
    728             }
    729 
    730             if (!allDependencies.Empty())
    731             {
    732                 if (downloadInstallerOnly)
    733                 {
    734                     context.Reporter.Info() << Resource::String::DependenciesFlowDownload << std::endl;
    735                 }
    736                 else
    737                 {
    738                     context.Reporter.Info() << Resource::String::DependenciesFlowInstall << std::endl;
    739                 }
    740             }
    741 
    742             context.Add<Execution::Data::Dependencies>(allDependencies);
    743             context << Workflow::ReportDependencies(m_dependenciesReportMessage);
    744         }
    745 
    746         bool allSucceeded = true;
    747         size_t packagesCount = packageSubContexts.size();
    748         size_t packagesProgress = 0;
    749 
    750         for (auto& packageContext : packageSubContexts)
    751         {
    752             packagesProgress++;
    753             context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv;
    754 
    755             // We want to do best effort to install all packages regardless of previous failures
    756             Execution::Context& currentContext = *packageContext;
    757             auto previousThreadGlobals = currentContext.SetForCurrentThread();
    758 
    759             currentContext << Workflow::ReportIdentityAndInstallationDisclaimer;
    760 
    761             // Prevent individual exceptions from breaking out of the loop
    762             try
    763             {
    764                 // Handle dependencies if requested.
    765                 if (!m_ignorePackageDependencies && !downloadInstallerOnly)
    766                 {
    767                     currentContext <<
    768                         Workflow::EnableWindowsFeaturesDependencies <<
    769                         Workflow::CreateDependencySubContexts(m_dependenciesReportMessage) <<
    770                         Workflow::ProcessMultiplePackages(m_dependenciesReportMessage, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable);
    771                 }
    772 
    773                 currentContext << Workflow::DownloadInstaller;
    774 
    775                 if (!downloadInstallerOnly)
    776                 {
    777                     currentContext << Workflow::InstallPackageInstaller;
    778                 }
    779             }
    780             catch (...)
    781             {
    782                 currentContext.SetTerminationHR(Workflow::HandleException(currentContext, std::current_exception()));
    783             }
    784 
    785             if (m_refreshPathVariable)
    786             {
    787                 if (RefreshPathVariableForCurrentProcess())
    788                 {
    789                     AICLI_LOG(CLI, Info, << "Successfully refreshed process PATH environment variable.");
    790                 }
    791                 else
    792                 {
    793                     AICLI_LOG(CLI, Warning, << "Failed to refresh process PATH environment variable.");
    794                     context.Reporter.Warn() << Resource::String::FailedToRefreshPathWarning << std::endl;
    795                 }
    796             }
    797 
    798             currentContext.Reporter.Info() << std::endl;
    799 
    800             if (currentContext.IsTerminated())
    801             {
    802                 if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT)
    803                 {
    804                     // This means that the subcontext being terminated is due to an overall abort
    805                     context.Reporter.Info() << Resource::String::Cancelled << std::endl;
    806                     return;
    807                 }
    808 
    809                 if (m_ignorableInstallResults.end() == std::find(m_ignorableInstallResults.begin(), m_ignorableInstallResults.end(), currentContext.GetTerminationHR()))
    810                 {
    811                     allSucceeded = false;
    812                     if (m_stopOnFailure)
    813                     {
    814                         break;
    815                     }
    816                 }
    817             }
    818         }
    819 
    820         if (!allSucceeded)
    821         {
    822             AICLI_TERMINATE_CONTEXT(m_resultOnFailure);
    823         }
    824     }
    825 
    826     void SnapshotARPEntries(Execution::Context& context) try
    827     {
    828         // Ensure that installer type might actually write to ARP, otherwise this is a waste of time
    829         auto installer = context.Get<Execution::Data::Installer>();
    830 
    831         if (installer && MightWriteToARP(installer->EffectiveInstallerType()))
    832         {
    833             Repository::Correlation::ARPCorrelationData data;
    834             data.CapturePreInstallSnapshot();
    835             context.Add<Execution::Data::ARPCorrelationData>(std::move(data));
    836         }
    837     }
    838     CATCH_LOG()
    839 
    840     void ReportARPChanges(Execution::Context& context) try
    841     {
    842         if (!context.Contains(Execution::Data::ARPCorrelationData))
    843         {
    844             return;
    845         }
    846 
    847         // If the installer claims to have a PackageFamilyName, and that family name is currently registered for the user,
    848         // let that be the correlated item and skip any attempt at further ARP correlation.
    849         const auto& installer = context.Get<Execution::Data::Installer>();
    850 
    851         if (installer && !installer->PackageFamilyName.empty() && Deployment::IsRegistered(installer->PackageFamilyName))
    852         {
    853             return;
    854         }
    855 
    856         const auto& manifest = context.Get<Execution::Data::Manifest>();
    857         auto& arpCorrelationData = context.Get<Execution::Data::ARPCorrelationData>();
    858 
    859         arpCorrelationData.CapturePostInstallSnapshot();
    860         auto correlationResult = arpCorrelationData.CorrelateForNewlyInstalled(manifest);
    861 
    862         // Store the ARP entry found to match the package to record it in the tracking catalog later
    863         if (correlationResult.Package)
    864         {
    865             std::vector<AppsAndFeaturesEntry> entries;
    866 
    867             auto metadata = correlationResult.Package->GetMetadata();
    868 
    869             AppsAndFeaturesEntry baseEntry;
    870 
    871             // Display name and publisher are also available as multi properties, but
    872             // for ARP there will always be only 0 or 1 values.
    873             baseEntry.DisplayName = correlationResult.Package->GetProperty(PackageVersionProperty::Name).get();
    874             baseEntry.Publisher = correlationResult.Package->GetProperty(PackageVersionProperty::Publisher).get();
    875             baseEntry.DisplayVersion = correlationResult.Package->GetProperty(PackageVersionProperty::Version).get();
    876             baseEntry.InstallerType = Manifest::ConvertToInstallerTypeEnum(metadata[PackageVersionMetadata::InstalledType]);
    877 
    878             auto productCodes = correlationResult.Package->GetMultiProperty(PackageVersionMultiProperty::ProductCode);
    879             for (auto&& productCode : productCodes)
    880             {
    881                 AppsAndFeaturesEntry entry = baseEntry;
    882                 entry.ProductCode = std::move(productCode).get();
    883                 entries.push_back(std::move(entry));
    884             }
    885 
    886             auto upgradeCodes = correlationResult.Package->GetMultiProperty(PackageVersionMultiProperty::UpgradeCode);
    887             for (auto&& upgradeCode : upgradeCodes)
    888             {
    889                 AppsAndFeaturesEntry entry = baseEntry;
    890                 entry.UpgradeCode = std::move(upgradeCode).get();
    891                 entries.push_back(std::move(entry));
    892             }
    893 
    894             context.Add<Data::CorrelatedAppsAndFeaturesEntries>(std::move(entries));
    895         }
    896 
    897         // We can only get the source identifier from an active source
    898         std::string sourceIdentifier;
    899         if (context.Contains(Execution::Data::PackageVersion))
    900         {
    901             sourceIdentifier = context.Get<Execution::Data::PackageVersion>()->GetProperty(PackageVersionProperty::SourceIdentifier);
    902         }
    903 
    904         IPackageVersion::Metadata arpEntryMetadata;
    905         if (correlationResult.Package)
    906         {
    907             arpEntryMetadata = correlationResult.Package->GetMetadata();
    908         }
    909 
    910         Logging::Telemetry().LogSuccessfulInstallARPChange(
    911             sourceIdentifier,
    912             manifest.Id,
    913             manifest.Version,
    914             manifest.Channel,
    915             correlationResult.ChangesToARP,
    916             correlationResult.MatchesInARP,
    917             correlationResult.CountOfIntersectionOfChangesAndMatches,
    918             correlationResult.Package ? static_cast<std::string>(correlationResult.Package->GetProperty(PackageVersionProperty::Name)) : "",
    919             correlationResult.Package ? static_cast<std::string>(correlationResult.Package->GetProperty(PackageVersionProperty::Version)) : "",
    920             correlationResult.Package ? static_cast<std::string>(correlationResult.Package->GetProperty(PackageVersionProperty::Publisher)) : "",
    921             correlationResult.Package ? static_cast<std::string_view>(arpEntryMetadata[PackageVersionMetadata::InstalledLocale]) : ""
    922         );
    923     }
    924     CATCH_LOG();
    925 
    926     void RecordInstall(Context& context)
    927     {
    928         // Local manifest installs won't have a package version, and tracking them doesn't provide much
    929         // value currently. If we ever do use our own database as a primary source of packages that we
    930         // maintain, this decision will probably have to be reconsidered.
    931         if (!context.Contains(Data::PackageVersion))
    932         {
    933             return;
    934         }
    935 
    936         auto manifest = context.Get<Data::Manifest>();
    937 
    938         // If we have determined an ARP entry matches the installed package,
    939         // we set its product code in the manifest we record to ensure we can
    940         // find it in the future.
    941         // Note that this may overwrite existing information.
    942         if (context.Contains(Data::CorrelatedAppsAndFeaturesEntries))
    943         {
    944             // Use a new Installer entry
    945             manifest.Installers.emplace_back();
    946             manifest.Installers.back().AppsAndFeaturesEntries = context.Get<Data::CorrelatedAppsAndFeaturesEntries>();
    947         }
    948 
    949         auto trackingCatalog = context.Get<Data::PackageVersion>()->GetSource().GetTrackingCatalog();
    950 
    951         auto version = trackingCatalog.RecordInstall(
    952             manifest,
    953             context.Get<Data::Installer>().value(),
    954             WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerExecutionUseUpdate));
    955 
    956         // Record user intent values. Command args takes precedence. Then previous user intent values.
    957         Repository::IPackageVersion::Metadata installedMetadata;
    958         if (context.Contains(Data::InstalledPackageVersion) && context.Get<Execution::Data::InstalledPackageVersion>())
    959         {
    960             installedMetadata = context.Get<Data::InstalledPackageVersion>()->GetMetadata();
    961         }
    962 
    963         if (context.Args.Contains(Execution::Args::Type::InstallArchitecture))
    964         {
    965             version.SetMetadata(Repository::PackageVersionMetadata::UserIntentArchitecture, context.Args.GetArg(Execution::Args::Type::InstallArchitecture));
    966         }
    967         else
    968         {
    969             auto itr = installedMetadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture);
    970             if (itr != installedMetadata.end())
    971             {
    972                 version.SetMetadata(Repository::PackageVersionMetadata::UserIntentArchitecture, itr->second);
    973             }
    974         }
    975 
    976         if (context.Args.Contains(Execution::Args::Type::Locale))
    977         {
    978             version.SetMetadata(Repository::PackageVersionMetadata::UserIntentLocale, context.Args.GetArg(Execution::Args::Type::Locale));
    979         }
    980         else
    981         {
    982             auto itr = installedMetadata.find(Repository::PackageVersionMetadata::UserIntentLocale);
    983             if (itr != installedMetadata.end())
    984             {
    985                 version.SetMetadata(Repository::PackageVersionMetadata::UserIntentLocale, itr->second);
    986             }
    987         }
    988     }
    989 }