winget-cli

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

RepairFlow.cpp (23905B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "RepairFlow.h"
      5 #include "Workflows/ShellExecuteInstallerHandler.h"
      6 #include "Workflows/WorkflowBase.h"
      7 #include "Workflows/DownloadFlow.h"
      8 #include "Workflows/ArchiveFlow.h"
      9 #include "Workflows/InstallFlow.h"
     10 #include "Workflows/PromptFlow.h"
     11 #include "winget/ManifestCommon.h"
     12 #include "AppInstallerDeployment.h"
     13 #include "AppInstallerMsixInfo.h"
     14 #include "AppInstallerSynchronization.h"
     15 #include "MSStoreInstallerHandler.h"
     16 #include <winget/ManifestComparator.h>
     17 #include <winget/PackageVersionSelection.h>
     18 
     19 using namespace AppInstaller::Manifest;
     20 using namespace AppInstaller::Msix;
     21 using namespace AppInstaller::Repository;
     22 using namespace AppInstaller::CLI::Execution;
     23 
     24 namespace AppInstaller::CLI::Workflow
     25 {
     26     // Internal implementation details
     27     namespace
     28     {
     29 
     30         void SetUninstallStringInContext(Execution::Context& context)
     31         {
     32             const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
     33             IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata();
     34 
     35             // Default to silent unless it is not present or interactivity is requested
     36             auto uninstallCommandItr = packageMetadata.find(PackageVersionMetadata::SilentUninstallCommand);
     37 
     38             if ((!context.Args.Contains(Execution::Args::Type::Silent) && uninstallCommandItr == packageMetadata.end())
     39                 || context.Args.Contains(Execution::Args::Type::Interactive))
     40             {
     41                 auto interactiveItr = packageMetadata.find(PackageVersionMetadata::StandardUninstallCommand);
     42                 if (interactiveItr != packageMetadata.end())
     43                 {
     44                     uninstallCommandItr = interactiveItr;
     45                 }
     46             }
     47 
     48             if (uninstallCommandItr == packageMetadata.end())
     49             {
     50                 context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl;
     51                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND);
     52             }
     53 
     54             context.Add<Execution::Data::UninstallString>(uninstallCommandItr->second);
     55         }
     56 
     57         void SetModifyPathInContext(Execution::Context& context)
     58         {
     59             const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
     60             IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata();
     61 
     62             // Default to silent unless it is not present or interactivity is requested
     63             auto modifyPathItr = packageMetadata.find(PackageVersionMetadata::StandardModifyCommand);
     64             if (modifyPathItr == packageMetadata.end())
     65             {
     66                 context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl;
     67                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND);
     68             }
     69 
     70             context.Add<Execution::Data::ModifyPath>(modifyPathItr->second);
     71         }
     72 
     73         void SetProductCodesInContext(Execution::Context& context)
     74         {
     75             const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
     76             auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode);
     77 
     78             if (productCodes.empty())
     79             {
     80                 context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl;
     81                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND);
     82             }
     83 
     84             context.Add<Execution::Data::ProductCodes>(productCodes);
     85         }
     86 
     87         void SetPackageFamilyNamesInContext(Execution::Context& context)
     88         {
     89             const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
     90 
     91             auto packageFamilyNames = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName);
     92             if (packageFamilyNames.empty())
     93             {
     94                 context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl;
     95                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND);
     96             }
     97 
     98             context.Add<Execution::Data::PackageFamilyNames>(packageFamilyNames);
     99         }
    100 
    101         InstallerTypeEnum GetInstalledType(Execution::Context& context)
    102         {
    103             const auto& installedPackage = context.Get<Execution::Data::InstalledPackageVersion>();
    104             std::string installedType = installedPackage->GetMetadata()[PackageVersionMetadata::InstalledType];
    105             return ConvertToInstallerTypeEnum(installedType);
    106         }
    107 
    108         void ApplicabilityCheckForInstalledPackage(Execution::Context& context)
    109         {
    110             // Installed Package repair applicability check
    111             const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
    112 
    113             const std::string installerType = context.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata()[PackageVersionMetadata::InstalledType];
    114             InstallerTypeEnum installerTypeEnum = ConvertToInstallerTypeEnum(installerType);
    115 
    116             if (installerTypeEnum == InstallerTypeEnum::Portable || installerTypeEnum == InstallerTypeEnum::Unknown)
    117             {
    118                 context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl;
    119                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED);
    120             }
    121 
    122             IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata();
    123 
    124             auto noModifyItr = packageMetadata.find(PackageVersionMetadata::NoModify);
    125             std::string noModifyARPFlag = noModifyItr != packageMetadata.end() ? noModifyItr->second : std::string();
    126 
    127             auto noRepairItr = packageMetadata.find(PackageVersionMetadata::NoRepair);
    128             std::string noRepairARPFlag = noRepairItr != packageMetadata.end() ? noRepairItr->second : std::string();
    129 
    130             if (Utility::IsDwordFlagSet(noModifyARPFlag) || Utility::IsDwordFlagSet(noRepairARPFlag))
    131             {
    132                 context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl;
    133                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED);
    134             }
    135         }
    136 
    137         void ApplicabilityCheckForAvailablePackage(Execution::Context& context)
    138         {
    139             // Skip the Available Package applicability check for MSI and MSIX repair as they aren't needed.
    140             if (!context.Contains(Execution::Data::Installer))
    141             {
    142                 return;
    143             }
    144 
    145             // Selected Installer repair applicability check
    146             auto installerType = context.Get<Execution::Data::Installer>()->EffectiveInstallerType();
    147             auto repairBehavior = context.Get<Execution::Data::Installer>()->RepairBehavior;
    148 
    149             if (installerType == InstallerTypeEnum::Portable || installerType == InstallerTypeEnum::Unknown)
    150             {
    151                 context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl;
    152                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED);
    153             }
    154 
    155             // Repair behavior is required for Burn, Inno, Nullsoft, Exe installers
    156             if (DoesInstallerTypeRequireRepairBehaviorForRepair(installerType) &&
    157                 repairBehavior == RepairBehaviorEnum::Unknown)
    158             {
    159                 context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl;
    160                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND);
    161             }
    162         }
    163 
    164         void HandleModifyRepairBehavior(Execution::Context& context, std::string& repairCommand)
    165         {
    166             SetModifyPathInContext(context);
    167             repairCommand += context.Get<Execution::Data::ModifyPath>();
    168         }
    169 
    170         void HandleInstallerRepairBehavior(Execution::Context& context, InstallerTypeEnum installerType)
    171         {
    172             context <<
    173                 ShowInstallationDisclaimer <<
    174                 ShowPromptsForSinglePackage(/* ensureAcceptance */ true) <<
    175                 DownloadInstaller;
    176 
    177             if (installerType == InstallerTypeEnum::Zip)
    178             {
    179                 context <<
    180                     ScanArchiveFromLocalManifest <<
    181                     ExtractFilesFromArchive <<
    182                     VerifyAndSetNestedInstaller;
    183             }
    184         }
    185 
    186         void HandleUninstallerRepairBehavior(Execution::Context& context, std::string& repairCommand)
    187         {
    188             SetUninstallStringInContext(context);
    189             repairCommand += context.Get<Execution::Data::UninstallString>();
    190         }
    191 
    192         void GenerateRepairString(Execution::Context& context)
    193         {
    194             const auto& installer = context.Get<Execution::Data::Installer>();
    195             auto installerType = installer->BaseInstallerType;
    196             auto repairBehavior = installer->RepairBehavior;
    197 
    198             std::string repairCommand;
    199 
    200             switch (repairBehavior)
    201             {
    202             case RepairBehaviorEnum::Modify:
    203                 HandleModifyRepairBehavior(context, repairCommand);
    204                 break;
    205             case RepairBehaviorEnum::Installer:
    206                 HandleInstallerRepairBehavior(context, installerType);
    207                 break;
    208             case RepairBehaviorEnum::Uninstaller:
    209                 HandleUninstallerRepairBehavior(context, repairCommand);
    210                 break;
    211             case RepairBehaviorEnum::Unknown:
    212             default:
    213                 context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl;
    214                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND);
    215             }
    216 
    217             context <<
    218                 GetInstallerArgs;
    219 
    220             // If the repair behavior is set to 'Installer', we can proceed with the repair command as is.
    221             // For repair behaviors other than 'Installer', subsequent steps will be necessary to prepare the repair command.
    222             if (repairBehavior == RepairBehaviorEnum::Installer)
    223             {
    224                 return;
    225             }
    226 
    227             if (repairCommand.empty())
    228             {
    229                 context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl;
    230                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND);
    231             }
    232 
    233             repairCommand += " ";
    234             repairCommand += context.Get<Execution::Data::InstallerArgs>();
    235             context.Add<Execution::Data::RepairString>(repairCommand);
    236         }
    237 
    238         bool IsInstallerMappingRequired(Execution::Context& context)
    239         {
    240             InstallerTypeEnum installerTypeEnum = GetInstalledType(context);
    241 
    242             switch (installerTypeEnum)
    243             {
    244             case InstallerTypeEnum::Msi:
    245                 return false;
    246             case InstallerTypeEnum::Msix:
    247                 // For MSIX packages that are from the Microsoft Store, selecting an installer is required.
    248                 if (context.Contains(Execution::Data::Package))
    249                 {
    250                     auto availablePackages = context.Get<Execution::Data::Package>()->GetAvailable();
    251 
    252                     if (availablePackages.size() == 1 && availablePackages[0]->GetSource() == WellKnownSource::MicrosoftStore)
    253                     {
    254                         return true;
    255                     }
    256                 }
    257 
    258                 // For MSIX packages that are not from the Microsoft Store, selecting an installer is not required.
    259                 return false;
    260             default:
    261                 return true;
    262             }
    263         }
    264 
    265         void HandleModifyOrUninstallerRepair(Execution::Context& context, RepairBehaviorEnum repairBehavior)
    266         {
    267             context <<
    268                 ShellExecuteRepairImpl <<
    269                 ReportRepairResult(RepairBehaviorToString(repairBehavior), APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED);
    270         }
    271 
    272         void HandleInstallerRepair(Execution::Context& context, RepairBehaviorEnum repairBehavior)
    273         {
    274             context <<
    275                 ShellExecuteInstallImpl <<
    276                 ReportInstallerResult(RepairBehaviorToString(repairBehavior), APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED);
    277         }
    278     }
    279 
    280     void RunRepairForRepairBehaviorBasedInstaller(Execution::Context& context)
    281     {
    282         const auto& installer = context.Get<Execution::Data::Installer>();
    283         auto repairBehavior = installer->RepairBehavior;
    284 
    285         switch (repairBehavior)
    286         {
    287         case RepairBehaviorEnum::Modify:
    288         case RepairBehaviorEnum::Uninstaller:
    289             HandleModifyOrUninstallerRepair(context, repairBehavior);
    290             break;
    291         case RepairBehaviorEnum::Installer:
    292             HandleInstallerRepair(context, repairBehavior);
    293             break;
    294         default:
    295             context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl;
    296             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND);
    297         }
    298     }
    299 
    300     void RepairMsiBasedInstaller(Execution::Context& context)
    301     {
    302         context <<
    303             ShellExecuteMsiExecRepair <<
    304             ReportRepairResult("MsiExec", APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED);
    305     }
    306 
    307     void RepairApplicabilityCheck(Execution::Context& context)
    308     {
    309         context <<
    310             ApplicabilityCheckForInstalledPackage <<
    311             ApplicabilityCheckForAvailablePackage;
    312     }
    313 
    314     void ExecuteRepair(Execution::Context& context)
    315     {
    316         InstallerTypeEnum installerTypeEnum = context.Contains(Execution::Data::Installer) ? context.Get<Execution::Data::Installer>()->EffectiveInstallerType() : GetInstalledType(context);
    317 
    318         Synchronization::CrossProcessInstallLock lock;
    319 
    320         if (!ExemptFromSingleInstallLocking(installerTypeEnum))
    321         {
    322             // Acquire the lock , if the operation is cancelled it will return false so we will also return.
    323             if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback)
    324                 {
    325                     callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother());
    326                     return lock.Acquire(callback);
    327                 }))
    328             {
    329                 AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire repair lock due to cancellation");
    330                 return;
    331             }
    332         }
    333 
    334         switch (installerTypeEnum)
    335         {
    336         case InstallerTypeEnum::Exe:
    337         case InstallerTypeEnum::Burn:
    338         case InstallerTypeEnum::Inno:
    339         case InstallerTypeEnum::Nullsoft:
    340         {
    341             context <<
    342                 RunRepairForRepairBehaviorBasedInstaller;
    343         }
    344         break;
    345         case InstallerTypeEnum::Msi:
    346         case InstallerTypeEnum::Wix:
    347         {
    348             context <<
    349                 RepairMsiBasedInstaller;
    350         }
    351         break;
    352         case InstallerTypeEnum::Msix:
    353         {
    354             context <<
    355                 RepairMsixPackage;
    356         }
    357         break;
    358         case InstallerTypeEnum::MSStore:
    359         {
    360             context <<
    361                 EnsureStorePolicySatisfied <<
    362                 MSStoreRepair;
    363         }
    364         break;
    365         case InstallerTypeEnum::Portable:
    366         default:
    367             THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
    368         }
    369     }
    370 
    371     void GetRepairInfo(Execution::Context& context)
    372     {
    373         InstallerTypeEnum installerTypeEnum = context.Contains(Execution::Data::Installer) ? context.Get<Execution::Data::Installer>()->BaseInstallerType : GetInstalledType(context);
    374 
    375         switch (installerTypeEnum)
    376         {
    377             // Exe based installers, for installed package all gets mapped to exe extension.
    378         case InstallerTypeEnum::Burn:
    379         case InstallerTypeEnum::Exe:
    380         case InstallerTypeEnum::Inno:
    381         case InstallerTypeEnum::Nullsoft:
    382         {
    383             context <<
    384                 GenerateRepairString;
    385         }
    386         break;
    387         // MSI based installers, for installed package all gets mapped to msi extension.
    388         case InstallerTypeEnum::Msi:
    389         case InstallerTypeEnum::Wix:
    390         {
    391             context <<
    392                 SetProductCodesInContext;
    393         }
    394         break;
    395         // MSIX based installers, msix.
    396         case InstallerTypeEnum::Msix:
    397         {
    398             context <<
    399                 SetPackageFamilyNamesInContext;
    400         }
    401         break;
    402         case InstallerTypeEnum::MSStore:
    403             break;
    404         case InstallerTypeEnum::Portable:
    405         default:
    406             THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
    407         }
    408     }
    409 
    410     void RepairMsixPackage(Execution::Context& context)
    411     {
    412         bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine;
    413 
    414         const auto& packageFamilyNames = context.Get<Execution::Data::PackageFamilyNames>();
    415         context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl;
    416 
    417         for (const auto& packageFamilyName : packageFamilyNames)
    418         {
    419             auto packageFullName = Msix::GetPackageFullNameFromFamilyName(packageFamilyName);
    420 
    421             if (!packageFullName.has_value())
    422             {
    423                 AICLI_LOG(CLI, Warning, << "No package found with family name: " << packageFamilyName);
    424                 continue;
    425             }
    426 
    427             AICLI_LOG(CLI, Info, << "Repairing package: " << packageFullName.value());
    428 
    429             try
    430             {
    431                 if (!isMachineScope)
    432                 {
    433                     // Best effort repair by registering the package.
    434                     context.Reporter.ExecuteWithProgress(std::bind(Deployment::RegisterPackage, packageFamilyName, std::placeholders::_1));
    435                 }
    436                 else
    437                 {
    438                     context.Reporter.Error() << Resource::String::RepairFlowReturnCodeSystemNotSupported << std::endl;
    439                     context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED));
    440                     AICLI_LOG(CLI, Error, << "Device wide repair for msix type is not supported.");
    441                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED);
    442                 }
    443             }
    444             catch (const wil::ResultException& re)
    445             {
    446                 context.Add<Execution::Data::OperationReturnCode>(re.GetErrorCode());
    447                 context << ReportRepairResult("MSIX", re.GetErrorCode(), true);
    448                 return;
    449             }
    450         }
    451 
    452         context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl;
    453     }
    454 
    455     void RepairSinglePackage(Execution::Context& context)
    456     {
    457         context <<
    458             RepairApplicabilityCheck <<
    459             GetRepairInfo <<
    460             ReportExecutionStage(ExecutionStage::Execution) <<
    461             ExecuteRepair <<
    462             ReportExecutionStage(ExecutionStage::PostExecution);
    463     }
    464 
    465     void SelectApplicablePackageVersion(Execution::Context& context)
    466     {
    467         // If the repair flow is initiated with manifest, then we don't need to select the applicable package version.
    468         if (context.Args.Contains(Args::Type::Manifest))
    469         {
    470             return;
    471         }
    472 
    473         const auto& installedPackage = context.Get<Execution::Data::InstalledPackageVersion>();
    474 
    475         Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version));
    476         if (installedVersion.IsUnknown())
    477         {
    478             context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl;
    479             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER);
    480         }
    481 
    482         std::string_view requestedVersion = context.Args.Contains(Execution::Args::Type::TargetVersion) ? context.Args.GetArg(Execution::Args::Type::TargetVersion) : installedVersion.ToString();
    483         // If it's Store source with only one version unknown, use the unknown version for available version mapping.
    484         const auto& package = context.Get<Execution::Data::Package>();
    485         auto packageVersions = GetAvailableVersionsForInstalledVersion(package, installedPackage);
    486         auto versionKeys = packageVersions->GetVersionKeys();
    487         if (versionKeys.size() == 1)
    488         {
    489             auto packageVersion = packageVersions->GetVersion(versionKeys.at(0));
    490             if (packageVersion->GetSource().IsWellKnownSource(WellKnownSource::MicrosoftStore) &&
    491                 Utility::Version{ packageVersion->GetProperty(PackageVersionProperty::Version) }.IsUnknown())
    492             {
    493                 requestedVersion = "";
    494             }
    495         }
    496 
    497         context <<
    498             GetManifestWithVersionFromPackage(
    499                 requestedVersion,
    500                 context.Args.GetArg(Execution::Args::Type::Channel), false);
    501     }
    502 
    503     void SelectApplicableInstallerIfNecessary(Execution::Context& context)
    504     {
    505         // For MSI installers, the platform provides built-in support for repair via msiexec, hence no need to select an installer.
    506         // Similarly, for MSIX packages that are not from the Microsoft Store, selecting an installer is not required.
    507         if (IsInstallerMappingRequired(context))
    508         {
    509             context <<
    510                 SelectApplicablePackageVersion <<
    511                 SelectInstaller <<
    512                 EnsureApplicableInstaller;
    513         }
    514     }
    515 
    516     void ReportRepairResult::operator()(Execution::Context& context) const
    517     {
    518         DWORD repairResult = context.Get<Execution::Data::OperationReturnCode>();
    519 
    520         if (repairResult != 0)
    521         {
    522             auto& repairPackage = context.Contains(Execution::Data::PackageVersion) ?
    523                 context.Get<Execution::Data::PackageVersion>() :
    524                 context.Get<Execution::Data::InstalledPackageVersion>();
    525 
    526             Logging::Telemetry().LogRepairFailure(
    527                 repairPackage->GetProperty(PackageVersionProperty::Id),
    528                 repairPackage->GetProperty(PackageVersionProperty::Version),
    529                 m_repairType,
    530                 repairResult);
    531 
    532             if (m_isHResult)
    533             {
    534                 context.Reporter.Error()
    535                     << Resource::String::RepairFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(repairResult) })
    536                     << std::endl;
    537             }
    538             else
    539             {
    540                 context.Reporter.Error()
    541                     << Resource::String::RepairFailedWithCode(repairResult)
    542                     << std::endl;
    543             }
    544 
    545             // Show log path if available
    546             if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get<Execution::Data::LogPath>()))
    547             {
    548                 auto installerLogPath = Utility::LocIndString{ context.Get<Execution::Data::LogPath>().u8string() };
    549                 context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl;
    550             }
    551 
    552             AICLI_TERMINATE_CONTEXT(m_hr);
    553         }
    554         else
    555         {
    556             context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl;
    557         }
    558     }
    559 }