winget-cli

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

ShellExecuteInstallerHandler.cpp (24460B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "ShellExecuteInstallerHandler.h"
      5 #include <AppInstallerFileLogger.h>
      6 #include <AppInstallerRuntime.h>
      7 #include <winget/Filesystem.h>
      8 
      9 using namespace AppInstaller::CLI;
     10 using namespace AppInstaller::Utility;
     11 using namespace AppInstaller::Manifest;
     12 using namespace AppInstaller::Repository;
     13 
     14 namespace AppInstaller::CLI::Workflow
     15 {
     16     namespace
     17     {
     18         // ShellExecutes the given path.
     19         std::optional<DWORD> InvokeShellExecuteEx(const std::filesystem::path& filePath, const std::string& args, bool useRunAs, int show, IProgressCallback& progress)
     20         {
     21             AICLI_LOG(CLI, Info, << "Starting: '" << filePath.u8string() << "' with arguments '" << args << '\'');
     22 
     23             SHELLEXECUTEINFOW execInfo = { 0 };
     24             execInfo.cbSize = sizeof(execInfo);
     25             execInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
     26             execInfo.lpFile = filePath.c_str();
     27             std::wstring argsUtf16 = Utility::ConvertToUTF16(args);
     28             execInfo.lpParameters = argsUtf16.c_str();
     29             execInfo.nShow = show;
     30 
     31             // This installer must be run elevated, but we are not currently.
     32             // Have ShellExecute elevate the installer since it won't do so itself.
     33             if (useRunAs)
     34             {
     35                 execInfo.lpVerb = L"runas";
     36             }
     37 
     38             THROW_LAST_ERROR_IF(!ShellExecuteExW(&execInfo) || !execInfo.hProcess);
     39 
     40             wil::unique_process_handle process{ execInfo.hProcess };
     41 
     42             // Wait for installation to finish
     43             while (!progress.IsCancelledBy(CancelReason::User))
     44             {
     45                 DWORD waitResult = WaitForSingleObject(process.get(), 250);
     46                 if (waitResult == WAIT_OBJECT_0)
     47                 {
     48                     break;
     49                 }
     50                 if (waitResult != WAIT_TIMEOUT)
     51                 {
     52                     THROW_LAST_ERROR_MSG("Unexpected WaitForSingleObjectResult: %lu", waitResult);
     53                 }
     54             }
     55 
     56             if (progress.IsCancelledBy(CancelReason::Any))
     57             {
     58                 return {};
     59             }
     60             else
     61             {
     62                 DWORD exitCode = 0;
     63                 GetExitCodeProcess(process.get(), &exitCode);
     64                 return exitCode;
     65             }
     66         }
     67 
     68         std::optional<DWORD> InvokeShellExecute(const std::filesystem::path& filePath, const std::string& args, IProgressCallback& progress)
     69         {
     70             // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete.
     71             // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown.
     72             return InvokeShellExecuteEx(filePath, args, false, SW_SHOW, progress);
     73         }
     74 
     75         // Gets the escaped installer args.
     76         std::string GetInstallerArgsTemplate(Execution::Context& context)
     77         {
     78             bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);
     79             bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair);
     80 
     81             const auto& installer = context.Get<Execution::Data::Installer>();
     82             const auto& installerSwitches = installer->Switches;
     83             std::string installerArgs = {};
     84 
     85             // Construct install experience arg.
     86             // SilentWithProgress is default, so look for it first.
     87             auto experienceArgsItr = installerSwitches.find(InstallerSwitchType::SilentWithProgress);
     88 
     89             if (context.Args.Contains(Execution::Args::Type::Interactive))
     90             {
     91                 // If interactive requested, always use Interactive (or nothing). If the installer supports
     92                 // interactive it is usually the default, and thus it is cumbersome to put a blank entry in
     93                 // the manifest.
     94                 experienceArgsItr = installerSwitches.find(InstallerSwitchType::Interactive);
     95             }
     96             // If no SilentWithProgress exists, or Silent requested, try to find Silent.
     97             else if (experienceArgsItr == installerSwitches.end() || context.Args.Contains(Execution::Args::Type::Silent))
     98             {
     99                 auto silentItr = installerSwitches.find(InstallerSwitchType::Silent);
    100                 // If Silent requested, but doesn't exist, then continue using SilentWithProgress.
    101                 if (silentItr != installerSwitches.end())
    102                 {
    103                     experienceArgsItr = silentItr;
    104                 }
    105             }
    106 
    107             if (experienceArgsItr != installerSwitches.end())
    108             {
    109                 installerArgs += experienceArgsItr->second;
    110             }
    111 
    112             // Construct log path arg.
    113             if (installerSwitches.find(InstallerSwitchType::Log) != installerSwitches.end())
    114             {
    115                 installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Log);
    116             }
    117 
    118             // Construct repair arg. Custom switches and other args are not applicable for repair scenario so we can return here.
    119             if (isRepair)
    120             {
    121                 if (installerSwitches.find(InstallerSwitchType::Repair) != installerSwitches.end())
    122                 {
    123                     installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Repair);
    124                 }
    125 
    126                 return installerArgs;
    127             }
    128 
    129             // Construct custom arg.
    130             if (installerSwitches.find(InstallerSwitchType::Custom) != installerSwitches.end())
    131             {
    132                 installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Custom);
    133             }
    134 
    135             // Construct custom arg passed in by cli arg
    136             if (context.Args.Contains(Execution::Args::Type::CustomSwitches))
    137             {
    138                 std::string_view customSwitches = context.Args.GetArg(Execution::Args::Type::CustomSwitches);
    139                 // Since these arguments are appended to the installer at runtime, it doesn't make sense to append them if empty or whitespace
    140                 if (!Utility::IsEmptyOrWhitespace(customSwitches))
    141                 {
    142                     installerArgs += ' ' + std::string{ customSwitches };
    143                 }
    144             }
    145 
    146             // Construct update arg if applicable
    147             if (isUpdate && installerSwitches.find(InstallerSwitchType::Update) != installerSwitches.end())
    148             {
    149                 installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Update);
    150             }
    151 
    152             // Construct install location arg if necessary.
    153             if (context.Args.Contains(Execution::Args::Type::InstallLocation) &&
    154                 installerSwitches.find(InstallerSwitchType::InstallLocation) != installerSwitches.end())
    155             {
    156                 installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::InstallLocation);
    157             }
    158 
    159             return installerArgs;
    160         }
    161 
    162         // Applies values to the template.
    163         void PopulateInstallerArgsTemplate(Execution::Context& context, std::string& installerArgs)
    164         {
    165             // Populate <LogPath> with value from command line or temp path.
    166             std::string logPath;
    167             if (context.Args.Contains(Execution::Args::Type::Log))
    168             {
    169                 logPath = context.Args.GetArg(Execution::Args::Type::Log);
    170             }
    171             else
    172             {
    173                 const auto& manifest = context.Get<Execution::Data::Manifest>();
    174 
    175                 auto path = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation);
    176                 path /= Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version);
    177                 path += '-';
    178                 path += Utility::GetCurrentTimeForFilename(true);
    179                 path += Logging::FileLogger::DefaultExt();
    180 
    181                 logPath = path.u8string();
    182             }
    183 
    184             if (Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_LOGPATH), logPath))
    185             {
    186                 context.Add<Execution::Data::LogPath>(Utility::ConvertToUTF16(logPath));
    187             }
    188 
    189             // Populate <InstallPath> with value from command line.
    190             if (context.Args.Contains(Execution::Args::Type::InstallLocation))
    191             {
    192                 Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_INSTALLPATH), context.Args.GetArg(Execution::Args::Type::InstallLocation));
    193             }
    194 
    195             // Todo: language token support will be implemented later
    196         }
    197 
    198         // Gets the arguments for uninstalling an MSI with MsiExec
    199         std::string GetMsiExecUninstallArgs(Execution::Context& context, const Utility::LocIndString& productCode)
    200         {
    201             std::string args = "/x" + productCode.get();
    202 
    203             // https://learn.microsoft.com/en-us/windows/win32/msi/standard-installer-command-line-options
    204             if (context.Args.Contains(Execution::Args::Type::Silent))
    205             {
    206                 args += " /quiet /norestart";
    207             }
    208             else if (!context.Args.Contains(Execution::Args::Type::Interactive))
    209             {
    210                 args += " /passive /norestart";
    211             }
    212 
    213             return args;
    214         }
    215 
    216         // Gets the arguments for repairing an MSI with MsiExec
    217         std::string GetMsiExecRepairArgs(Execution::Context& context, const Utility::LocIndString& productCode)
    218         {
    219             // https://learn.microsoft.com/en-us/windows/win32/msi/command-line-options
    220             // Available Options for '/f [p|o|e|d|c|a|u|m|s|v] <Product.msi | ProductCode>'
    221             // Default parameter for '/f' is 'omus'
    222             // o - Reinstall all files regardless of version
    223             // m - Rewrite all required registry entries (This is the default option)
    224             // u - Rewrite all required user-specific registry entries (This is the default option)
    225             // s - Overwrite all existing shortcuts (This is the default option)
    226             std::string args = "/f " + productCode.get();
    227 
    228             // https://learn.microsoft.com/en-us/windows/win32/msi/standard-installer-command-line-options
    229             if (context.Args.Contains(Execution::Args::Type::Silent))
    230             {
    231                 args += " /quiet /norestart";
    232             }
    233             else if (!context.Args.Contains(Execution::Args::Type::Interactive))
    234             {
    235                 args += " /passive /norestart";
    236             }
    237 
    238             return args;
    239         }
    240     }
    241 
    242     void ShellExecuteInstallImpl(Execution::Context& context)
    243     {
    244         bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair);
    245 
    246         if (isRepair)
    247         {
    248             context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl;
    249         }
    250         else
    251         {
    252             context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl;
    253         }
    254 
    255         const auto& installer = context.Get<Execution::Data::Installer>();
    256         const std::string& installerArgs = context.Get<Execution::Data::InstallerArgs>();
    257 
    258         // Inform of elevation requirements
    259         bool isElevated = Runtime::IsRunningAsAdmin();
    260 
    261         // The installer will run elevated, either by direct request or through the installer itself doing so.
    262         if ((installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired ||
    263             installer->ElevationRequirement == ElevationRequirementEnum::ElevatesSelf)
    264             && !isElevated)
    265         {
    266             context.Reporter.Warn() << Resource::String::InstallerElevationExpected << std::endl;
    267         }
    268 
    269         // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete.
    270         // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown.
    271         auto installResult = context.Reporter.ExecuteWithProgress(
    272             std::bind(InvokeShellExecuteEx,
    273                 context.Get<Execution::Data::InstallerPath>(),
    274                 installerArgs,
    275                 installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired && !isElevated,
    276                 SW_SHOW,
    277                 std::placeholders::_1));
    278 
    279         if (!installResult)
    280         {
    281             if (isRepair)
    282             {
    283                 context.Reporter.Warn() << Resource::String::RepairAbandoned << std::endl;
    284             }
    285             else
    286             {
    287                 context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl;
    288             }
    289 
    290             AICLI_TERMINATE_CONTEXT(E_ABORT);
    291         }
    292         else
    293         {
    294             context.Add<Execution::Data::OperationReturnCode>(installResult.value());
    295         }
    296     }
    297 
    298     void GetInstallerArgs(Execution::Context& context)
    299     {
    300         // If override switch is specified, use the override value as installer args.
    301         if (context.Args.Contains(Execution::Args::Type::Override))
    302         {
    303             context.Add<Execution::Data::InstallerArgs>(std::string{ context.Args.GetArg(Execution::Args::Type::Override) });
    304             return;
    305         }
    306 
    307         std::string installerArgs = GetInstallerArgsTemplate(context);
    308 
    309         PopulateInstallerArgsTemplate(context, installerArgs);
    310 
    311         AICLI_LOG(CLI, Info, << "Installer args: " << installerArgs);
    312         context.Add<Execution::Data::InstallerArgs>(std::move(installerArgs));
    313     }
    314 
    315     void ShellExecuteUninstallImpl(Execution::Context& context)
    316     {
    317         context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl;
    318         std::wstring commandUtf16 = Utility::ConvertToUTF16(context.Get<Execution::Data::UninstallString>());
    319 
    320         // Parse the command string as application and command line for CreateProcess
    321         wil::unique_cotaskmem_string app = nullptr;
    322         wil::unique_cotaskmem_string args = nullptr;
    323         THROW_IF_FAILED(SHEvaluateSystemCommandTemplate(commandUtf16.c_str(), &app, NULL, &args));
    324 
    325         auto uninstallResult = context.Reporter.ExecuteWithProgress(
    326             std::bind(InvokeShellExecute,
    327                 std::filesystem::path(app.get()),
    328                 Utility::ConvertToUTF8(args.get()),
    329                 std::placeholders::_1));
    330 
    331         if (!uninstallResult)
    332         {
    333             context.Reporter.Warn() << Resource::String::UninstallAbandoned << std::endl;
    334             AICLI_TERMINATE_CONTEXT(E_ABORT);
    335         }
    336         else
    337         {
    338             context.Add<Execution::Data::OperationReturnCode>(uninstallResult.value());
    339         }
    340     }
    341 
    342     void ShellExecuteRepairImpl(Execution::Context& context)
    343     {
    344         context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl;
    345 
    346         std::wstring commandUtf16 = Utility::ConvertToUTF16(context.Get<Execution::Data::RepairString>());
    347 
    348         // When running as admin, block attempt to repair user scope installed package. 
    349         // [NOTE:] This check is to address the security concern related to above scenario.
    350         if (Runtime::IsRunningAsAdmin())
    351         {
    352             auto installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
    353             const std::string installedScopeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledScope];
    354             auto scopeEnum = ConvertToScopeEnum(installedScopeString);
    355 
    356             if (scopeEnum == ScopeEnum::User)
    357             {
    358                 context.Reporter.Error() << Resource::String::NoAdminRepairForUserScopePackage << std::endl;
    359                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED);
    360             }
    361         }
    362 
    363         // Parse the command string as application and command line for CreateProcess
    364         wil::unique_cotaskmem_string app = nullptr;
    365         wil::unique_cotaskmem_string args = nullptr;
    366         THROW_IF_FAILED(SHEvaluateSystemCommandTemplate(commandUtf16.c_str(), &app, NULL, &args));
    367 
    368         auto repairResult = context.Reporter.ExecuteWithProgress(
    369             std::bind(InvokeShellExecute,
    370                 std::filesystem::path(app.get()),
    371                 Utility::ConvertToUTF8(args.get()),
    372                 std::placeholders::_1));
    373 
    374         if (!repairResult)
    375         {
    376             context.Reporter.Error() << Resource::String::RepairAbandoned << std::endl;
    377             AICLI_TERMINATE_CONTEXT(E_ABORT);
    378         }
    379         else
    380         {
    381             context.Add<Execution::Data::OperationReturnCode>(repairResult.value());
    382         }
    383     }
    384 
    385     void ShellExecuteMsiExecUninstall(Execution::Context& context)
    386     {
    387         const auto& productCodes = context.Get<Execution::Data::ProductCodes>();
    388         context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl;
    389 
    390         const std::filesystem::path msiexecPath{ ExpandEnvironmentVariables(L"%windir%\\system32\\msiexec.exe") };
    391 
    392         for (const auto& productCode : productCodes)
    393         {
    394             AICLI_LOG(CLI, Info, << "Removing: " << productCode);
    395             auto uninstallResult = context.Reporter.ExecuteWithProgress(
    396                 std::bind(InvokeShellExecute,
    397                     msiexecPath,
    398                     GetMsiExecUninstallArgs(context, productCode),
    399                     std::placeholders::_1));
    400 
    401             if (!uninstallResult)
    402             {
    403                 context.Reporter.Error() << Resource::String::UninstallAbandoned << std::endl;
    404                 AICLI_TERMINATE_CONTEXT(E_ABORT);
    405             }
    406             else
    407             {
    408                 context.Add<Execution::Data::OperationReturnCode>(uninstallResult.value());
    409             }
    410         }
    411     }
    412 
    413     void ShellExecuteMsiExecRepair(Execution::Context& context)
    414     {
    415         const auto& productCodes = context.Get<Execution::Data::ProductCodes>();
    416         context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl;
    417 
    418         const std::filesystem::path msiexecPath{ ExpandEnvironmentVariables(L"%windir%\\system32\\msiexec.exe") };
    419 
    420         for (const auto& productCode : productCodes)
    421         {
    422             AICLI_LOG(CLI, Info, << "Repairing: " << productCode);
    423             auto repairResult = context.Reporter.ExecuteWithProgress(
    424                 std::bind(InvokeShellExecute,
    425                     msiexecPath,
    426                     GetMsiExecRepairArgs(context, productCode),
    427                     std::placeholders::_1));
    428 
    429             if (!repairResult)
    430             {
    431                 context.Reporter.Error() << Resource::String::RepairAbandoned << std::endl;
    432                 AICLI_TERMINATE_CONTEXT(E_ABORT);
    433             }
    434             else
    435             {
    436                 context.Add<Execution::Data::OperationReturnCode>(repairResult.value());
    437             }
    438         }
    439     }
    440 
    441 #ifndef AICLI_DISABLE_TEST_HOOKS
    442     std::optional<DWORD> s_EnableWindowsFeatureResult_Override{};
    443 
    444     void TestHook_SetEnableWindowsFeatureResult_Override(std::optional<DWORD>&& result)
    445     {
    446         s_EnableWindowsFeatureResult_Override = std::move(result);
    447     }
    448 
    449     std::optional<DWORD> s_DoesWindowsFeatureExistResult_Override{};
    450 
    451     void TestHook_SetDoesWindowsFeatureExistResult_Override(std::optional<DWORD>&& result)
    452     {
    453         s_DoesWindowsFeatureExistResult_Override = std::move(result);
    454     }
    455 #endif
    456 
    457     std::filesystem::path GetDismExecutablePath()
    458     {
    459         return AppInstaller::Filesystem::GetExpandedPath("%windir%\\system32\\dism.exe");
    460     }
    461 
    462     std::optional<DWORD> DoesWindowsFeatureExist(Execution::Context& context, std::string_view featureName)
    463     {
    464 #ifndef AICLI_DISABLE_TEST_HOOKS
    465         if (s_DoesWindowsFeatureExistResult_Override)
    466         {
    467             return s_DoesWindowsFeatureExistResult_Override;
    468         }
    469 #endif
    470 
    471         std::string args = "/Online /Get-FeatureInfo /FeatureName:" + std::string{ featureName };
    472         auto dismExecPath = GetDismExecutablePath();
    473 
    474         auto getFeatureInfoResult = context.Reporter.ExecuteWithProgress(
    475             std::bind(InvokeShellExecuteEx,
    476                 dismExecPath,
    477                 args,
    478                 false,
    479                 SW_HIDE,
    480                 std::placeholders::_1));
    481 
    482         return getFeatureInfoResult;
    483     }
    484 
    485     std::optional<DWORD> EnableWindowsFeature(Execution::Context& context, std::string_view featureName)
    486     {
    487 #ifndef AICLI_DISABLE_TEST_HOOKS
    488         if (s_EnableWindowsFeatureResult_Override)
    489         {
    490             return s_EnableWindowsFeatureResult_Override;
    491         }
    492 #endif
    493 
    494         std::string args = "/Online /Enable-Feature /NoRestart /FeatureName:" + std::string{ featureName };
    495         auto dismExecPath = GetDismExecutablePath();
    496 
    497         AICLI_LOG(Core, Info, << "Enabling Windows Feature [" << featureName << "]");
    498 
    499         auto enableFeatureResult = context.Reporter.ExecuteWithProgress(
    500             std::bind(InvokeShellExecuteEx,
    501                 dismExecPath,
    502                 args,
    503                 false,
    504                 SW_HIDE,
    505                 std::placeholders::_1));
    506 
    507         return enableFeatureResult;
    508     }
    509 
    510     void ShellExecuteEnableWindowsFeature::operator()(Execution::Context& context) const
    511     {
    512         Utility::LocIndView locIndFeatureName{ m_featureName };
    513 
    514         std::optional<DWORD> doesFeatureExistResult = DoesWindowsFeatureExist(context, m_featureName);
    515 
    516         if (!doesFeatureExistResult)
    517         {
    518             AICLI_TERMINATE_CONTEXT(E_ABORT);
    519         }
    520         else if (doesFeatureExistResult.value() != ERROR_SUCCESS)
    521         {
    522             context.Add<Execution::Data::OperationReturnCode>(doesFeatureExistResult.value());
    523             return;
    524         }
    525 
    526         context.Reporter.Info() << Resource::String::EnablingWindowsFeature(locIndFeatureName) << std::endl;
    527 
    528         std::optional<DWORD> enableFeatureResult = EnableWindowsFeature(context, m_featureName);
    529 
    530         if (!enableFeatureResult)
    531         {
    532             AICLI_TERMINATE_CONTEXT(E_ABORT);
    533         }
    534         else
    535         {
    536             context.Add<Execution::Data::OperationReturnCode>(enableFeatureResult.value());
    537         }
    538     }
    539 
    540 #ifndef AICLI_DISABLE_TEST_HOOKS
    541     std::optional<DWORD> s_ExtractArchiveWithTarResult_Override{};
    542 
    543     void TestHook_SetExtractArchiveWithTarResult_Override(std::optional<DWORD>&& result)
    544     {
    545         s_ExtractArchiveWithTarResult_Override = std::move(result);
    546     }
    547 #endif
    548 
    549     void ShellExecuteExtractArchive::operator()(Execution::Context& context) const
    550     {
    551         auto tarExecPath = AppInstaller::Filesystem::GetExpandedPath("%windir%\\system32\\tar.exe");
    552 
    553         std::string args = "-xf \"" + m_archivePath.u8string() + "\" -C \"" + m_destPath.u8string() + "\"";
    554 
    555         std::optional<DWORD> extractArchiveResult;
    556 #ifndef AICLI_DISABLE_TEST_HOOKS
    557         if (s_ExtractArchiveWithTarResult_Override)
    558         {
    559             extractArchiveResult =  *s_ExtractArchiveWithTarResult_Override;
    560         }
    561         else
    562 #endif
    563         {
    564             extractArchiveResult = context.Reporter.ExecuteWithProgress(
    565                 std::bind(InvokeShellExecuteEx,
    566                     tarExecPath,
    567                     args,
    568                     false,
    569                     SW_HIDE,
    570                     std::placeholders::_1));
    571         }
    572 
    573         if (!extractArchiveResult)
    574         {
    575             AICLI_TERMINATE_CONTEXT(E_ABORT);
    576         }
    577 
    578         if (extractArchiveResult.value() == ERROR_SUCCESS)
    579         {
    580             AICLI_LOG(CLI, Info, << "Successfully extracted archive");
    581             context.Reporter.Info() << Resource::String::ExtractArchiveSucceeded << std::endl;
    582         }
    583         else
    584         {
    585             AICLI_LOG(CLI, Info, << "Failed to extract archive with exit code " << extractArchiveResult.value());
    586             context.Reporter.Error() << Resource::String::ExtractArchiveFailed << std::endl;
    587             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED);
    588         }
    589     }
    590 }