winget-cli

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

WorkflowBase.cpp (67039B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "WorkflowBase.h"
      5 #include "ExecutionContext.h"
      6 #include <winget/ManifestComparator.h>
      7 #include "PromptFlow.h"
      8 #include "Sixel.h"
      9 #include "TableOutput.h"
     10 #include <winget/FileCache.h>
     11 #include <winget/ExperimentalFeature.h>
     12 #include <winget/ManifestYamlParser.h>
     13 #include <winget/Pin.h>
     14 #include <winget/PinningData.h>
     15 #include <AppInstallerSHA256.h>
     16 #include <winget/Runtime.h>
     17 #include <winget/PackageVersionSelection.h>
     18 
     19 EXTERN_C IMAGE_DOS_HEADER __ImageBase;
     20 
     21 using namespace std::string_literals;
     22 using namespace AppInstaller::Utility::literals;
     23 using namespace AppInstaller::Pinning;
     24 using namespace AppInstaller::Repository;
     25 using namespace AppInstaller::Settings;
     26 using namespace winrt::Windows::Foundation;
     27 
     28 namespace AppInstaller::CLI::Workflow
     29 {
     30     namespace
     31     {
     32         std::string GetMatchCriteriaDescriptor(const ResultMatch& match)
     33         {
     34             if (match.MatchCriteria.Field != PackageMatchField::Id && match.MatchCriteria.Field != PackageMatchField::Name)
     35             {
     36                 std::string result{ ToString(match.MatchCriteria.Field) };
     37                 result += ": ";
     38                 result += match.MatchCriteria.Value;
     39                 return result;
     40             }
     41             else
     42             {
     43                 return {};
     44             }
     45         }
     46 
     47         void ReportIdentity(
     48             Execution::Context& context,
     49             Utility::LocIndView prefix,
     50             std::optional<Resource::StringId> label,
     51             std::string_view name,
     52             std::string_view id,
     53             std::string_view version = {},
     54             Execution::Reporter::Level level = Execution::Reporter::Level::Info)
     55         {
     56             auto out = context.Reporter.GetOutputStream(level);
     57             out << prefix;
     58             if (label)
     59             {
     60                 out << *label << ' ';
     61             }
     62             out << Execution::NameEmphasis << name << " ["_liv << Execution::IdEmphasis << id << ']';
     63 
     64             if (!version.empty())
     65             {
     66                 out << ' ' << Resource::String::ShowVersion << ' ' << version;
     67             }
     68 
     69             out << std::endl;
     70         }
     71 
     72         // Determines icon fit given two options.
     73         // Targets an 80x80 icon as the best resolution for this use case.
     74         // TODO: Consider theme based on current background color.
     75         bool IsSecondIconBetter(const Manifest::Icon& current, const Manifest::Icon& alternative)
     76         {
     77             static constexpr std::array<uint8_t, ToIntegral(Manifest::IconResolutionEnum::Square256) + 1> s_iconResolutionOrder
     78             {
     79                 9, // Unknown
     80                 8, // Custom
     81                 15, // Square16
     82                 14, // Square20
     83                 13, // Square24
     84                 12, // Square30
     85                 11, // Square32
     86                 10, // Square36
     87                 6, // Square40
     88                 5, // Square48
     89                 4, // Square60
     90                 3, // Square64
     91                 2, // Square72
     92                 0, // Square80
     93                 1, // Square96
     94                 7, // Square256
     95             };
     96 
     97             return s_iconResolutionOrder.at(ToIntegral(alternative.Resolution)) < s_iconResolutionOrder.at(ToIntegral(current.Resolution));
     98         }
     99 
    100         void ShowManifestIcon(Execution::Context& context, const Manifest::Manifest& manifest) try
    101         {
    102             if (!context.Reporter.SixelsEnabled())
    103             {
    104                 return;
    105             }
    106 
    107             auto icons = manifest.CurrentLocalization.Get<Manifest::Localization::Icons>();
    108             const Manifest::Icon* bestFitIcon = nullptr;
    109 
    110             for (const auto& icon : icons)
    111             {
    112                 if (!bestFitIcon || IsSecondIconBetter(*bestFitIcon, icon))
    113                 {
    114                     bestFitIcon = &icon;
    115                 }
    116             }
    117 
    118             if (!bestFitIcon)
    119             {
    120                 return;
    121             }
    122 
    123             // Use a cache to hold the icons
    124             auto splitUri = Utility::SplitFileNameFromURI(bestFitIcon->Url);
    125             Caching::FileCache fileCache{ Caching::FileCache::Type::Icon, Utility::SHA256::ConvertToString(bestFitIcon->Sha256), { splitUri.first } };
    126             auto iconStream = fileCache.GetFile(splitUri.second, bestFitIcon->Sha256);
    127 
    128             VirtualTerminal::Sixel::Image sixelIcon{ *iconStream, bestFitIcon->FileType };
    129 
    130             // Using a height of 4 arbitrarily; allow width up to the entire console.
    131             UINT imageHeightCells = 4;
    132             UINT imageWidthCells = static_cast<UINT>(Execution::GetConsoleWidth());
    133 
    134             sixelIcon.RenderSizeInCells(imageWidthCells, imageHeightCells);
    135             auto infoOut = context.Reporter.Info();
    136             sixelIcon.RenderTo(infoOut);
    137 
    138             // Force the final sixel line to not be overwritten
    139             infoOut << std::endl;
    140         }
    141         CATCH_LOG();
    142 
    143         Repository::Source OpenNamedSource(Execution::Context& context, Utility::LocIndView sourceName)
    144         {
    145             Repository::Source source;
    146 
    147             try
    148             {
    149                 source = Source{ sourceName };
    150 
    151                 if (!source)
    152                 {
    153                     std::vector<SourceDetails> sources = Source::GetCurrentSources();
    154 
    155                     if (!sourceName.empty() && !sources.empty())
    156                     {
    157                         // A bad name was given, try to help.
    158                         context.Reporter.Error() << Resource::String::OpenSourceFailedNoMatch(sourceName) << std::endl;
    159                         context.Reporter.Info() << Resource::String::OpenSourceFailedNoMatchHelp << std::endl;
    160                         for (const auto& details : sources)
    161                         {
    162                             context.Reporter.Info() << "  "_liv << details.Name << std::endl;
    163                         }
    164 
    165                         AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, {});
    166                     }
    167                     else
    168                     {
    169                         // Even if a name was given, there are no sources
    170                         context.Reporter.Error() << Resource::String::OpenSourceFailedNoSourceDefined << std::endl;
    171                         AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED, {});
    172                     }
    173                 }
    174 
    175                 if (context.Args.Contains(Execution::Args::Type::CustomHeader))
    176                 {
    177                     std::string customHeader{ context.Args.GetArg(Execution::Args::Type::CustomHeader) };
    178                     if (!source.SetCustomHeader(customHeader))
    179                     {
    180                         context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl;
    181                     }
    182                 }
    183 
    184                 auto openFunction = [&](IProgressCallback& progress)->std::vector<Repository::SourceDetails>
    185                 {
    186                     source.SetCaller("winget-cli");
    187                     source.SetAuthenticationArguments(GetAuthenticationArguments(context));
    188                     return source.Open(progress);
    189                 };
    190                 auto updateFailures = context.Reporter.ExecuteWithProgress(openFunction, true);
    191 
    192                 // We'll only report the source update failure as warning and continue
    193                 for (const auto& s : updateFailures)
    194                 {
    195                     context.Reporter.Warn() << Resource::String::SourceOpenWithFailedUpdate(Utility::LocIndView{ s.Name }) << std::endl;
    196                 }
    197 
    198                 // Report sources that may need authentication
    199                 if (source.IsComposite())
    200                 {
    201                     for (const auto& s : source.GetAvailableSources())
    202                     {
    203                         if (s.GetInformation().Authentication.Type != Authentication::AuthenticationType::None)
    204                         {
    205                             context.Reporter.Info() << Execution::AuthenticationEmphasis << Resource::String::SourceRequiresAuthentication(Utility::LocIndView{ s.GetDetails().Name }) << std::endl;
    206                         }
    207                     }
    208                 }
    209                 else if (source.GetInformation().Authentication.Type != Authentication::AuthenticationType::None)
    210                 {
    211                     context.Reporter.Info() << Execution::AuthenticationEmphasis << Resource::String::SourceRequiresAuthentication(Utility::LocIndView{ source.GetDetails().Name }) << std::endl;
    212                 }
    213             }
    214             catch (const wil::ResultException& re)
    215             {
    216                 context.Reporter.Error() << Resource::String::SourceOpenFailedSuggestion << std::endl;
    217                 if (re.GetErrorCode() == APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES)
    218                 {
    219                     // Since we know there must have been multiple errors here, just fail the context rather
    220                     // than trying to get one of the exceptions back out.
    221                     AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, {});
    222                 }
    223                 else
    224                 {
    225                     throw;
    226                 }
    227             }
    228             catch (...)
    229             {
    230                 context.Reporter.Error() << Resource::String::SourceOpenFailedSuggestion << std::endl;
    231                 throw;
    232             }
    233 
    234             return source;
    235         }
    236 
    237         void SearchSourceApplyFilters(Execution::Context& context, SearchRequest& searchRequest, MatchType matchType)
    238         {
    239             const auto& args = context.Args;
    240 
    241             if (args.Contains(Execution::Args::Type::Id))
    242             {
    243                 searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, args.GetArg(Execution::Args::Type::Id)));
    244             }
    245 
    246             if (args.Contains(Execution::Args::Type::Name))
    247             {
    248                 searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, args.GetArg(Execution::Args::Type::Name)));
    249             }
    250 
    251             if (args.Contains(Execution::Args::Type::Moniker))
    252             {
    253                 searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, args.GetArg(Execution::Args::Type::Moniker)));
    254             }
    255 
    256             if (args.Contains(Execution::Args::Type::ProductCode))
    257             {
    258                 searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, matchType, args.GetArg(Execution::Args::Type::ProductCode)));
    259             }
    260 
    261             if (args.Contains(Execution::Args::Type::Tag))
    262             {
    263                 searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Tag, matchType, args.GetArg(Execution::Args::Type::Tag)));
    264             }
    265 
    266             if (args.Contains(Execution::Args::Type::Command))
    267             {
    268                 searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Command, matchType, args.GetArg(Execution::Args::Type::Command)));
    269             }
    270 
    271             if (args.Contains(Execution::Args::Type::Count))
    272             {
    273                 searchRequest.MaximumResults = std::stoi(std::string(args.GetArg(Execution::Args::Type::Count)));
    274             }
    275         }
    276 
    277         // Data shown on a line of a table displaying installed packages
    278         struct InstalledPackagesTableLine
    279         {
    280             InstalledPackagesTableLine(Utility::LocIndString name, Utility::LocIndString id, Utility::LocIndString installedVersion, Utility::LocIndString availableVersion, Utility::LocIndString source)
    281                 : Name(name), Id(id), InstalledVersion(installedVersion), AvailableVersion(availableVersion), Source(source) {}
    282 
    283             Utility::LocIndString Name;
    284             Utility::LocIndString Id;
    285             Utility::LocIndString InstalledVersion;
    286             Utility::LocIndString AvailableVersion;
    287             Utility::LocIndString Source;
    288         };
    289 
    290         void OutputInstalledPackagesTable(Execution::Context& context, const std::vector<InstalledPackagesTableLine>& lines)
    291         {
    292             Execution::TableOutput<5> table(context.Reporter,
    293                 {
    294                     Resource::String::SearchName,
    295                     Resource::String::SearchId,
    296                     Resource::String::SearchVersion,
    297                     Resource::String::AvailableHeader,
    298                     Resource::String::SearchSource
    299                 });
    300 
    301             for (const auto& line : lines)
    302             {
    303                 table.OutputLine({
    304                     line.Name,
    305                     line.Id,
    306                     line.InstalledVersion,
    307                     line.AvailableVersion,
    308                     line.Source
    309                     });
    310             }
    311 
    312             table.Complete();
    313         }
    314     }
    315 
    316     bool WorkflowTask::operator==(const WorkflowTask& other) const
    317     {
    318         if (m_isFunc && other.m_isFunc)
    319         {
    320             return m_func == other.m_func;
    321         }
    322         else if (!m_isFunc && !other.m_isFunc)
    323         {
    324             return m_name == other.m_name;
    325         }
    326         else
    327         {
    328             return false;
    329         }
    330     }
    331 
    332     void WorkflowTask::operator()(Execution::Context& context) const
    333     {
    334         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_isFunc);
    335         m_func(context);
    336     }
    337 
    338     void WorkflowTask::Log() const
    339     {
    340         if (m_isFunc)
    341         {
    342             // Using `00000001`80000000` as base address default when loading dll into windbg as dump file.
    343             AICLI_LOG(Workflow, Verbose, << "Running task: 0x" << m_func << " [ln 00000001`80000000+" << std::hex << (reinterpret_cast<char*>(m_func) - reinterpret_cast<char*>(&__ImageBase)) << "]");
    344         }
    345         else
    346         {
    347             AICLI_LOG(Workflow, Verbose, << "Running task: " << m_name);
    348         }
    349     }
    350 
    351     Repository::PredefinedSource DetermineInstalledSource(const Execution::Context& context)
    352     {
    353         Repository::PredefinedSource installedSource = Repository::PredefinedSource::Installed;
    354         Manifest::ScopeEnum scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope));
    355         if (scope == Manifest::ScopeEnum::Machine)
    356         {
    357             installedSource = Repository::PredefinedSource::InstalledMachine;
    358         }
    359         else if (scope == Manifest::ScopeEnum::User)
    360         {
    361             installedSource = Repository::PredefinedSource::InstalledUser;
    362         }
    363 
    364         return installedSource;
    365     }
    366 
    367     Authentication::AuthenticationArguments GetAuthenticationArguments(const Execution::Context& context)
    368     {
    369         AppInstaller::Authentication::AuthenticationArguments authArgs;
    370 
    371         if (context.Args.Contains(Execution::Args::Type::AuthenticationMode))
    372         {
    373             authArgs.Mode = Authentication::ConvertToAuthenticationMode(context.Args.GetArg(Execution::Args::Type::AuthenticationMode));
    374         }
    375         else
    376         {
    377             // If user did not specify authentication mode, determine based on if disable interactivity flag exists.
    378             authArgs.Mode = context.Args.Contains(Execution::Args::Type::DisableInteractivity) ? Authentication::AuthenticationMode::Silent : Authentication::AuthenticationMode::SilentPreferred;
    379         }
    380 
    381         if (context.Args.Contains(Execution::Args::Type::AuthenticationAccount))
    382         {
    383             authArgs.AuthenticationAccount = context.Args.GetArg(Execution::Args::Type::AuthenticationAccount);
    384         }
    385 
    386         AICLI_LOG(CLI, Info, << "Created authentication arguments. Mode: " << Authentication::AuthenticationModeToString(authArgs.Mode) << ", Account: " << authArgs.AuthenticationAccount);
    387 
    388         return authArgs;
    389     }
    390 
    391     HRESULT HandleException(Execution::Context* context, std::exception_ptr exception)
    392     {
    393         try
    394         {
    395             std::rethrow_exception(exception);
    396         }
    397         // Exceptions that may occur in the process of executing an arbitrary command
    398         catch (const wil::ResultException& re)
    399         {
    400             // Even though they are logged at their source, log again here for completeness.
    401             Logging::Telemetry().LogException(Logging::FailureTypeEnum::ResultException, re.what());
    402             if (context)
    403             {
    404                 context->Reporter.Error() <<
    405                     Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl <<
    406                     GetUserPresentableMessage(re) << std::endl;
    407             }
    408             return re.GetErrorCode();
    409         }
    410         catch (const winrt::hresult_error& hre)
    411         {
    412             std::string message = GetUserPresentableMessage(hre);
    413             Logging::Telemetry().LogException(Logging::FailureTypeEnum::WinrtHResultError, message);
    414             if (context)
    415             {
    416                 context->Reporter.Error() <<
    417                     Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl <<
    418                     message << std::endl;
    419             }
    420             return hre.code();
    421         }
    422         catch (const Settings::GroupPolicyException& e)
    423         {
    424             if (context)
    425             {
    426                 auto policy = Settings::TogglePolicy::GetPolicy(e.Policy());
    427                 auto policyNameId = policy.PolicyName();
    428                 context->Reporter.Error() << Resource::String::DisabledByGroupPolicy(policyNameId) << std::endl;
    429             }
    430             return APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY;
    431         }
    432         catch (const std::exception& e)
    433         {
    434             Logging::Telemetry().LogException(Logging::FailureTypeEnum::StdException, e.what());
    435             if (context)
    436             {
    437                 context->Reporter.Error() <<
    438                     Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl <<
    439                     GetUserPresentableMessage(e) << std::endl;
    440             }
    441             return APPINSTALLER_CLI_ERROR_COMMAND_FAILED;
    442         }
    443         catch (...)
    444         {
    445             LOG_CAUGHT_EXCEPTION();
    446             Logging::Telemetry().LogException(Logging::FailureTypeEnum::Unknown, {});
    447             if (context)
    448             {
    449                 context->Reporter.Error() <<
    450                     Resource::String::UnexpectedErrorExecutingCommand << " ???"_liv << std::endl;
    451             }
    452             return APPINSTALLER_CLI_ERROR_COMMAND_FAILED;
    453         }
    454 
    455         return E_UNEXPECTED;
    456     }
    457 
    458     HRESULT HandleException(Execution::Context& context, std::exception_ptr exception)
    459     {
    460         return HandleException(&context, exception);
    461     }
    462 
    463     AppInstaller::Manifest::ManifestComparator::Options GetManifestComparatorOptions(const Execution::Context& context, const IPackageVersion::Metadata& metadata)
    464     {
    465         AppInstaller::Manifest::ManifestComparator::Options options;
    466         bool getAllowedArchitecturesFromMetadata = false;
    467 
    468         if (context.Contains(Execution::Data::AllowedArchitectures))
    469         {
    470             // Com caller can directly set allowed architectures
    471             options.AllowedArchitectures = context.Get<Execution::Data::AllowedArchitectures>();
    472         }
    473         else if (context.Args.Contains(Execution::Args::Type::InstallArchitecture))
    474         {
    475             // Arguments provided in command line
    476             options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallArchitecture)));
    477         }
    478         else if (context.Args.Contains(Execution::Args::Type::InstallerArchitecture))
    479         {
    480             // Arguments provided in command line. Also skips applicability check.
    481             options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallerArchitecture)));
    482             options.SkipApplicabilityCheck = true;
    483         }
    484         else
    485         {
    486             getAllowedArchitecturesFromMetadata = true;
    487         }
    488 
    489         if (context.Args.Contains(Execution::Args::Type::InstallerType))
    490         {
    491             options.RequestedInstallerType = Manifest::ConvertToInstallerTypeEnum(std::string(context.Args.GetArg(Execution::Args::Type::InstallerType)));
    492         }
    493 
    494         if (context.Args.Contains(Execution::Args::Type::InstallScope))
    495         {
    496             options.RequestedInstallerScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope));
    497         }
    498 
    499         if (context.Contains(Execution::Data::AllowUnknownScope))
    500         {
    501             options.AllowUnknownScope = context.Get<Execution::Data::AllowUnknownScope>();
    502         }
    503 
    504         if (context.Args.Contains(Execution::Args::Type::Locale))
    505         {
    506             options.RequestedInstallerLocale = context.Args.GetArg(Execution::Args::Type::Locale);
    507         }
    508 
    509         Repository::GetManifestComparatorOptionsFromMetadata(options, metadata, getAllowedArchitecturesFromMetadata);
    510 
    511         return options;
    512     }
    513 
    514     void OpenSource::operator()(Execution::Context& context) const
    515     {
    516         std::string_view sourceName;
    517         if (m_forDependencies)
    518         {
    519             if (context.Args.Contains(Execution::Args::Type::DependencySource))
    520             {
    521                 sourceName = context.Args.GetArg(Execution::Args::Type::DependencySource);
    522             }
    523         }
    524         else
    525         {
    526             if (context.Args.Contains(Execution::Args::Type::Source))
    527             {
    528                 sourceName = context.Args.GetArg(Execution::Args::Type::Source);
    529             }
    530         }
    531 
    532         auto source = OpenNamedSource(context, Utility::LocIndView{ sourceName });
    533         if (context.IsTerminated())
    534         {
    535             return;
    536         }
    537 
    538         context << HandleSourceAgreements(source);
    539         if (context.IsTerminated())
    540         {
    541             return;
    542         }
    543 
    544         if (m_forDependencies)
    545         {
    546             context.Add<Execution::Data::DependencySource>(std::move(source));
    547         }
    548         else
    549         {
    550             context.Add<Execution::Data::Source>(std::move(source));
    551         }
    552     }
    553 
    554     void OpenNamedSourceForSources::operator()(Execution::Context& context) const
    555     {
    556         auto source = OpenNamedSource(context, m_sourceName);
    557         if (context.IsTerminated())
    558         {
    559             return;
    560         }
    561 
    562         context << HandleSourceAgreements(source);
    563         if (context.IsTerminated())
    564         {
    565             return;
    566         }
    567 
    568         if (!context.Contains(Execution::Data::Sources))
    569         {
    570             context.Add<Execution::Data::Sources>({ std::move(source) });
    571         }
    572         else
    573         {
    574             context.Get<Execution::Data::Sources>().emplace_back(std::move(source));
    575         }
    576     }
    577 
    578     void OpenPredefinedSource::operator()(Execution::Context& context) const
    579     {
    580         Repository::Source source;
    581         try
    582         {
    583             source = Source{ m_predefinedSource };
    584 
    585             // A well known predefined source should return a value.
    586             THROW_HR_IF(E_UNEXPECTED, !source);
    587 
    588             auto openFunction = [&](IProgressCallback& progress)->std::vector<Repository::SourceDetails>
    589             {
    590                 return source.Open(progress);
    591             };
    592             context.Reporter.ExecuteWithProgress(openFunction, true);
    593         }
    594         catch (...)
    595         {
    596             context.Reporter.Error() << Resource::String::SourceOpenPredefinedFailedSuggestion << std::endl;
    597             throw;
    598         }
    599 
    600         if (m_forDependencies)
    601         {
    602             context.Add<Execution::Data::DependencySource>(std::move(source));
    603         }
    604         else 
    605         {
    606             context.Add<Execution::Data::Source>(std::move(source));
    607         }
    608     }
    609 
    610     void OpenCompositeSource::operator()(Execution::Context& context) const
    611     {
    612         // Get the already open source for use as the available.
    613         Repository::Source availableSource;
    614         if (m_forDependencies)
    615         {
    616             availableSource = context.Get<Execution::Data::DependencySource>();
    617         }
    618         else
    619         {
    620             availableSource = context.Get<Execution::Data::Source>();
    621         }
    622 
    623         // Open the predefined source.
    624         context << OpenPredefinedSource(m_predefinedSource, m_forDependencies);
    625 
    626         // Create the composite source from the two.
    627         Repository::Source source;
    628         if (m_forDependencies)
    629         {
    630             source = context.Get<Execution::Data::DependencySource>();
    631         }
    632         else
    633         {
    634             source = context.Get<Execution::Data::Source>();
    635         }
    636 
    637         Repository::Source compositeSource{ source, availableSource, m_searchBehavior };
    638 
    639         // Overwrite the source with the composite.
    640         if (m_forDependencies)
    641         {
    642             context.Add<Execution::Data::DependencySource>(std::move(compositeSource));
    643         }
    644         else
    645         {
    646             context.Add<Execution::Data::Source>(std::move(compositeSource));
    647         }
    648     }
    649 
    650     void SearchSourceForMany(Execution::Context& context)
    651     {
    652         const auto& args = context.Args;
    653 
    654         MatchType matchType = MatchType::Substring;
    655         if (args.Contains(Execution::Args::Type::Exact))
    656         {
    657             matchType = MatchType::Exact;
    658         }
    659 
    660         SearchRequest searchRequest;
    661 
    662         if (args.Contains(Execution::Args::Type::Query))
    663         {
    664             searchRequest.Query.emplace(RequestMatch(matchType, args.GetArg(Execution::Args::Type::Query)));
    665         }
    666 
    667         SearchSourceApplyFilters(context, searchRequest, matchType);
    668 
    669         Logging::Telemetry().LogSearchRequest(
    670             "many",
    671             args.GetArg(Execution::Args::Type::Query),
    672             args.GetArg(Execution::Args::Type::Id),
    673             args.GetArg(Execution::Args::Type::Name),
    674             args.GetArg(Execution::Args::Type::Moniker),
    675             args.GetArg(Execution::Args::Type::Tag),
    676             args.GetArg(Execution::Args::Type::Command),
    677             searchRequest.MaximumResults,
    678             searchRequest.ToString());
    679 
    680         context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest));
    681     }
    682 
    683     void GetSearchRequestForSingle(Execution::Context& context)
    684     {
    685         const auto& args = context.Args;
    686 
    687         MatchType matchType = MatchType::CaseInsensitive;
    688         if (args.Contains(Execution::Args::Type::Exact))
    689         {
    690             matchType = MatchType::Exact;
    691         }
    692 
    693         SearchRequest searchRequest;
    694         // Note: MultiQuery when we need search for single is handled with one sub-context per query.
    695         if (args.Contains(Execution::Args::Type::Query))
    696         {
    697             std::string_view query = args.GetArg(Execution::Args::Type::Query);
    698 
    699             // Regardless of match type, always use an exact match for the system reference strings.
    700             searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, query));
    701             searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, query));
    702             searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, query));
    703             searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, query));
    704             searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, query));
    705         }
    706 
    707         SearchSourceApplyFilters(context, searchRequest, matchType);
    708 
    709         context.Add<Execution::Data::SearchRequest>(std::move(searchRequest));
    710     }
    711 
    712     void SearchSourceForSingle(Execution::Context& context)
    713     {
    714         const auto& args = context.Args;
    715         context << GetSearchRequestForSingle;
    716         if (!context.IsTerminated())
    717         {
    718             const auto& searchRequest = context.Get<Execution::Data::SearchRequest>();
    719 
    720             Logging::Telemetry().LogSearchRequest(
    721                 "single",
    722                 args.GetArg(Execution::Args::Type::Query),
    723                 args.GetArg(Execution::Args::Type::Id),
    724                 args.GetArg(Execution::Args::Type::Name),
    725                 args.GetArg(Execution::Args::Type::Moniker),
    726                 args.GetArg(Execution::Args::Type::Tag),
    727                 args.GetArg(Execution::Args::Type::Command),
    728                 searchRequest.MaximumResults,
    729                 searchRequest.ToString());
    730 
    731             context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest));
    732         }
    733     }
    734 
    735     void SearchSourceForManyCompletion(Execution::Context& context)
    736     {
    737         MatchType matchType = MatchType::StartsWith;
    738 
    739         SearchRequest searchRequest;
    740         std::string_view query = context.Get<Execution::Data::CompletionData>().Word();
    741         searchRequest.Query.emplace(RequestMatch(matchType, query));
    742 
    743         SearchSourceApplyFilters(context, searchRequest, matchType);
    744 
    745         context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest));
    746     }
    747 
    748     void SearchSourceForSingleCompletion(Execution::Context& context)
    749     {
    750         MatchType matchType = MatchType::StartsWith;
    751 
    752         SearchRequest searchRequest;
    753         std::string_view query = context.Get<Execution::Data::CompletionData>().Word();
    754         searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, query));
    755         searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, query));
    756         searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, query));
    757 
    758         SearchSourceApplyFilters(context, searchRequest, matchType);
    759 
    760         context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest));
    761     }
    762 
    763     void SearchSourceForCompletionField::operator()(Execution::Context& context) const
    764     {
    765         const std::string& word = context.Get<Execution::Data::CompletionData>().Word();
    766 
    767         SearchRequest searchRequest;
    768         searchRequest.Inclusions.emplace_back(PackageMatchFilter(m_field, MatchType::StartsWith, word));
    769 
    770         // If filters are provided, be generous with the search no matter the intended result.
    771         SearchSourceApplyFilters(context, searchRequest, MatchType::Substring);
    772 
    773         context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest));
    774     }
    775 
    776     void ReportSearchResult(Execution::Context& context)
    777     {
    778         auto& searchResult = context.Get<Execution::Data::SearchResult>();
    779 
    780         bool sourceIsComposite = context.Get<Execution::Data::Source>().IsComposite();
    781         Execution::TableOutput<5> table(context.Reporter,
    782             {
    783                 Resource::String::SearchName,
    784                 Resource::String::SearchId,
    785                 Resource::String::SearchVersion,
    786                 Resource::String::SearchMatch,
    787                 Resource::String::SearchSource
    788             });
    789 
    790         for (size_t i = 0; i < searchResult.Matches.size(); ++i)
    791         {
    792             auto latestVersion = GetAllAvailableVersions(searchResult.Matches[i].Package)->GetLatestVersion();
    793 
    794             table.OutputLine({
    795                 latestVersion->GetProperty(PackageVersionProperty::Name),
    796                 latestVersion->GetProperty(PackageVersionProperty::Id),
    797                 latestVersion->GetProperty(PackageVersionProperty::Version),
    798                 GetMatchCriteriaDescriptor(searchResult.Matches[i]),
    799                 sourceIsComposite ? static_cast<std::string>(latestVersion->GetProperty(PackageVersionProperty::SourceName)) : ""s
    800                 });
    801         }
    802 
    803         table.Complete();
    804 
    805         if (searchResult.Truncated)
    806         {
    807             context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl;
    808         }
    809     }
    810 
    811     void HandleSearchResultFailures(Execution::Context& context)
    812     {
    813         const auto& searchResult = context.Get<Execution::Data::SearchResult>();
    814 
    815         if (!searchResult.Failures.empty())
    816         {
    817             if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::TreatSourceFailuresAsWarning))
    818             {
    819                 auto warn = context.Reporter.Warn();
    820                 for (const auto& failure : searchResult.Failures)
    821                 {
    822                     warn << Resource::String::SearchFailureWarning(Utility::LocIndView{ failure.SourceName }) << std::endl;
    823                 }
    824             }
    825             else
    826             {
    827                 HRESULT overallHR = S_OK;
    828                 auto error = context.Reporter.Error();
    829                 for (const auto& failure : searchResult.Failures)
    830                 {
    831                     error << Resource::String::SearchFailureError(Utility::LocIndView{ failure.SourceName }) << std::endl;
    832                     HRESULT failureHR = HandleException(context, failure.Exception);
    833 
    834                     // Just take first failure for now
    835                     if (overallHR == S_OK)
    836                     {
    837                         overallHR = failureHR;
    838                     }
    839                 }
    840 
    841                 if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::ShowSearchResultsOnPartialFailure))
    842                 {
    843                     if (searchResult.Matches.empty())
    844                     {
    845                         context.Reporter.Info() << std::endl << Resource::String::SearchFailureErrorNoMatches << std::endl;
    846                     }
    847                     else
    848                     {
    849                         context.Reporter.Info() << std::endl << Resource::String::SearchFailureErrorListMatches << std::endl;
    850                         context << ReportMultiplePackageFoundResultWithSource;
    851                     }
    852                 }
    853 
    854                 context.SetTerminationHR(overallHR);
    855             }
    856         }
    857     }
    858 
    859     void ReportMultiplePackageFoundResult(Execution::Context& context)
    860     {
    861         auto& searchResult = context.Get<Execution::Data::SearchResult>();
    862 
    863         Execution::TableOutput<2> table(context.Reporter,
    864             {
    865                 Resource::String::SearchName,
    866                 Resource::String::SearchId
    867             });
    868 
    869         for (size_t i = 0; i < searchResult.Matches.size(); ++i)
    870         {
    871             auto package = searchResult.Matches[i].Package;
    872 
    873             table.OutputLine({
    874                 package->GetProperty(PackageProperty::Name),
    875                 package->GetProperty(PackageProperty::Id)
    876                 });
    877         }
    878 
    879         table.Complete();
    880 
    881         if (searchResult.Truncated)
    882         {
    883             context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl;
    884         }
    885     }
    886 
    887     void ReportMultiplePackageFoundResultWithSource(Execution::Context& context)
    888     {
    889         auto& searchResult = context.Get<Execution::Data::SearchResult>();
    890 
    891         Execution::TableOutput<3> table(context.Reporter,
    892             {
    893                 Resource::String::SearchName,
    894                 Resource::String::SearchId,
    895                 Resource::String::SearchSource
    896             });
    897 
    898         for (size_t i = 0; i < searchResult.Matches.size(); ++i)
    899         {
    900             auto package = searchResult.Matches[i].Package;
    901 
    902             std::string sourceName;
    903             auto available = package->GetAvailable();
    904             if (!available.empty())
    905             {
    906                 auto source = available[0]->GetSource();
    907                 if (source)
    908                 {
    909                     sourceName = source.GetDetails().Name;
    910                 }
    911             }
    912 
    913             table.OutputLine({
    914                 package->GetProperty(PackageProperty::Name),
    915                 package->GetProperty(PackageProperty::Id),
    916                 std::move(sourceName)
    917                 });
    918         }
    919 
    920         table.Complete();
    921 
    922         if (searchResult.Truncated)
    923         {
    924             context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl;
    925         }
    926     }
    927 
    928     void ReportListResult::operator()(Execution::Context& context) const
    929     {
    930         auto& searchResult = context.Get<Execution::Data::SearchResult>();
    931 
    932         std::vector<InstalledPackagesTableLine> lines;
    933         std::vector<InstalledPackagesTableLine> linesForExplicitUpgrade;
    934         std::vector<InstalledPackagesTableLine> linesForPins;
    935 
    936         int availableUpgradesCount = 0;
    937 
    938         // We will show a line with a summary for skipped and pinned packages at the end.
    939         // The strings suggest using a --include-unknown/pinned argument, so we should
    940         // ensure that the count is 0 when using the arguments.
    941         int packagesWithUnknownVersionSkipped = 0;
    942         int packagesWithUserPinsSkipped = 0;
    943 
    944         auto &source = context.Get<Execution::Data::Source>();
    945         bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1;
    946 
    947         PinBehavior pinBehavior;
    948         if (m_onlyShowUpgrades && !context.Args.Contains(Execution::Args::Type::Force))
    949         {
    950             // For listing upgrades, show the version we would upgrade to with the given pins.
    951             pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins;
    952         }
    953         else
    954         {
    955             // For listing installed apps or if we are ignoring pins due to --force, show the latest available.
    956             pinBehavior = PinBehavior::IgnorePins;
    957         }
    958 
    959         PinningData pinningData{ PinningData::Disposition::ReadOnly };
    960 
    961         for (const auto& match : searchResult.Matches)
    962         {
    963             auto installedPackage = match.Package->GetInstalled();
    964             if (!installedPackage)
    965             {
    966                 continue;
    967             }
    968 
    969             // We only want to evaluate update availability for the latest version.
    970             bool isFirstInstalledVersion = true;
    971 
    972             for (const auto& installedVersionKey : installedPackage->GetVersionKeys())
    973             {
    974                 bool isFirstInstalledVersionLocal = isFirstInstalledVersion;
    975                 isFirstInstalledVersion = false;
    976 
    977                 auto installedVersion = installedPackage->GetVersion(installedVersionKey);
    978 
    979                 auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, installedVersion);
    980                 auto availableVersions = GetAvailableVersionsForInstalledVersion(match.Package, installedVersion);
    981 
    982                 auto latestVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions);
    983                 bool updateAvailable = isFirstInstalledVersionLocal && evaluator.IsUpdate(latestVersion);
    984                 bool updateIsPinned = false;
    985 
    986                 if (m_onlyShowUpgrades && !context.Args.Contains(Execution::Args::Type::IncludeUnknown) && Utility::Version(installedVersion->GetProperty(PackageVersionProperty::Version)).IsUnknown() && updateAvailable)
    987                 {
    988                     // We are only showing upgrades, and the user did not request to include packages with unknown versions.
    989                     ++packagesWithUnknownVersionSkipped;
    990                     continue;
    991                 }
    992 
    993                 if (m_onlyShowUpgrades && !updateAvailable && isFirstInstalledVersionLocal)
    994                 {
    995                     // Reuse the evaluator to check if there is an update outside of the pinning
    996                     auto unpinnedLatestVersion = availableVersions->GetLatestVersion();
    997                     bool updateAvailableWithoutPins = evaluator.IsUpdate(unpinnedLatestVersion);
    998 
    999                     if (updateAvailableWithoutPins)
   1000                     {
   1001                         // When given the --include-pinned argument, report blocking and gating pins in a separate table.
   1002                         // Otherwise, simply show a count of them
   1003                         if (context.Args.Contains(Execution::Args::Type::IncludePinned))
   1004                         {
   1005                             updateIsPinned = true;
   1006 
   1007                             // Override these so we generate the table line below.
   1008                             latestVersion = std::move(unpinnedLatestVersion);
   1009                             updateAvailable = true;
   1010                         }
   1011                         else
   1012                         {
   1013                             ++packagesWithUserPinsSkipped;
   1014                             continue;
   1015                         }
   1016                     }
   1017                 }
   1018 
   1019                 // The only time we don't want to output a line is when filtering and no update is available.
   1020                 if (updateAvailable || !m_onlyShowUpgrades)
   1021                 {
   1022                     Utility::LocIndString availableVersion, sourceName;
   1023 
   1024                     if (latestVersion)
   1025                     {
   1026                         // Always show the source for correlated packages
   1027                         sourceName = latestVersion->GetProperty(PackageVersionProperty::SourceName);
   1028 
   1029                         if (updateAvailable)
   1030                         {
   1031                             availableVersion = latestVersion->GetProperty(PackageVersionProperty::Version);
   1032                             availableUpgradesCount++;
   1033                         }
   1034                     }
   1035 
   1036                     // Output using the local PackageName instead of the name in the manifest, to prevent confusion for packages that add multiple
   1037                     // Add/Remove Programs entries.
   1038                     // TODO: De-duplicate this list, and only show (by default) one entry per matched package.
   1039                     InstalledPackagesTableLine line(
   1040                          installedVersion->GetProperty(PackageVersionProperty::Name),
   1041                          match.Package->GetProperty(PackageProperty::Id),
   1042                          installedVersion->GetProperty(PackageVersionProperty::Version),
   1043                          availableVersion,
   1044                          shouldShowSource ? sourceName : Utility::LocIndString()
   1045                     );
   1046 
   1047                     auto pinnedState = ConvertToPinTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::PinnedState]);
   1048                     if (updateIsPinned)
   1049                     {
   1050                         linesForPins.push_back(std::move(line));
   1051                     }
   1052                     else if (m_onlyShowUpgrades && pinnedState == PinType::PinnedByManifest)
   1053                     {
   1054                         linesForExplicitUpgrade.push_back(std::move(line));
   1055                     }
   1056                     else
   1057                     {
   1058                         lines.push_back(std::move(line));
   1059                     }
   1060                 }
   1061             }
   1062         }
   1063 
   1064         OutputInstalledPackagesTable(context, lines);
   1065 
   1066         if (lines.empty())
   1067         {
   1068             context.Reporter.Info() << Resource::String::NoInstalledPackageFound << std::endl;
   1069         }
   1070         else
   1071         {
   1072             if (searchResult.Truncated)
   1073             {
   1074                 context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl;
   1075             }
   1076 
   1077             if (m_onlyShowUpgrades)
   1078             {
   1079                 context.Reporter.Info() << Resource::String::AvailableUpgrades(availableUpgradesCount) << std::endl;
   1080             }
   1081         }
   1082 
   1083         if (!linesForExplicitUpgrade.empty())
   1084         {
   1085             context.Reporter.Info() << std::endl << Resource::String::UpgradeAvailableForPinned << std::endl;
   1086             OutputInstalledPackagesTable(context, linesForExplicitUpgrade);
   1087         }
   1088 
   1089         if (!linesForPins.empty())
   1090         {
   1091             context.Reporter.Info() << std::endl << Resource::String::UpgradeBlockedByPinCount(linesForPins.size()) << std::endl;
   1092             OutputInstalledPackagesTable(context, linesForPins);
   1093         }
   1094 
   1095         if (m_onlyShowUpgrades)
   1096         {
   1097             if (packagesWithUnknownVersionSkipped > 0)
   1098             {
   1099                 AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version");
   1100                 context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl;
   1101             }
   1102 
   1103             if (packagesWithUserPinsSkipped > 0)
   1104             {
   1105                 AICLI_LOG(CLI, Info, << packagesWithUserPinsSkipped << " package(s) skipped due to user pins");
   1106                 context.Reporter.Info() << Resource::String::UpgradePinnedByUserCount(packagesWithUserPinsSkipped) << std::endl;
   1107             }
   1108         }
   1109     }
   1110 
   1111     void EnsureMatchesFromSearchResult::operator()(Execution::Context& context) const
   1112     {
   1113         auto& searchResult = context.Get<Execution::Data::SearchResult>();
   1114 
   1115         Logging::Telemetry().LogSearchResultCount(searchResult.Matches.size());
   1116 
   1117         if (searchResult.Matches.size() == 0)
   1118         {
   1119             Logging::Telemetry().LogNoAppMatch();
   1120             
   1121             switch (m_operationType)
   1122             {
   1123                 // These search purposes require a package to be found in the Installed Packages
   1124                 case OperationType::Export:
   1125                 case OperationType::List:
   1126                 case OperationType::Uninstall:
   1127                 case OperationType::Pin:
   1128                 case OperationType::Upgrade:
   1129                 case OperationType::Repair:
   1130                     context.Reporter.Info() << Resource::String::NoInstalledPackageFound << std::endl;
   1131                     break;
   1132                 case OperationType::Completion:
   1133                 case OperationType::Install:
   1134                 case OperationType::Search:
   1135                 case OperationType::Show:
   1136                 case OperationType::Download:
   1137                 default:
   1138                     context.Reporter.Info() << Resource::String::NoPackageFound << std::endl;
   1139                     break;
   1140             }
   1141 
   1142             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND);
   1143         }
   1144     }
   1145 
   1146     void EnsureOneMatchFromSearchResult::operator()(Execution::Context& context) const
   1147     {
   1148         context <<
   1149             EnsureMatchesFromSearchResult(m_operationType);
   1150 
   1151         if (!context.IsTerminated())
   1152         {
   1153             auto& searchResult = context.Get<Execution::Data::SearchResult>();
   1154 
   1155             if (searchResult.Matches.size() > 1)
   1156             {
   1157                 Logging::Telemetry().LogMultiAppMatch();
   1158 
   1159                 if (m_operationType == OperationType::Upgrade || m_operationType == OperationType::Uninstall || m_operationType == OperationType::Repair || m_operationType == OperationType::Export)
   1160                 {
   1161                     context.Reporter.Warn() << Resource::String::MultipleInstalledPackagesFound << std::endl;
   1162                     context << ReportMultiplePackageFoundResult;
   1163                 }
   1164                 else
   1165                 {
   1166                     context.Reporter.Warn() << Resource::String::MultiplePackagesFound << std::endl;
   1167                     context << ReportMultiplePackageFoundResultWithSource;
   1168                 }
   1169 
   1170                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND);
   1171             }
   1172 
   1173             std::shared_ptr<ICompositePackage> package = searchResult.Matches.at(0).Package;
   1174             Logging::Telemetry().LogAppFound(package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id));
   1175 
   1176             context.Add<Execution::Data::Package>(std::move(package));
   1177         }
   1178     }
   1179 
   1180     void GetManifestWithVersionFromPackage::operator()(Execution::Context& context) const
   1181     {
   1182         PackageVersionKey key("", m_version, m_channel);
   1183 
   1184         std::shared_ptr<ICompositePackage> package = context.Get<Execution::Data::Package>();
   1185         std::shared_ptr<IPackageVersion> requestedVersion;
   1186         auto availableVersions = GetAvailableVersionsForInstalledVersion(package);
   1187 
   1188         if (m_considerPins)
   1189         {
   1190             bool isPinned = false;
   1191 
   1192             PinBehavior pinBehavior;
   1193             if (context.Args.Contains(Execution::Args::Type::Force))
   1194             {
   1195                 // --force ignores any pins
   1196                 pinBehavior = PinBehavior::IgnorePins;
   1197             }
   1198             else
   1199             {
   1200                 pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins;
   1201             }
   1202 
   1203             PinningData pinningData{ PinningData::Disposition::ReadOnly };
   1204             auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, GetInstalledVersion(package));
   1205 
   1206             // TODO: The logic here will probably have to get more difficult once we support channels
   1207             if (Utility::IsEmptyOrWhitespace(m_version) && Utility::IsEmptyOrWhitespace(m_channel))
   1208             {
   1209                 requestedVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions);
   1210 
   1211                 if (!requestedVersion)
   1212                 {
   1213                     // Check whether we didn't find the latest version because it was pinned or because there wasn't one
   1214                     auto latestVersion = availableVersions->GetLatestVersion();
   1215                     if (latestVersion)
   1216                     {
   1217                         isPinned = true;
   1218                     }
   1219                 }
   1220             }
   1221             else
   1222             {
   1223                 requestedVersion = availableVersions->GetVersion(key);
   1224                 isPinned = evaluator.EvaluatePinType(requestedVersion) != PinType::Unknown;
   1225             }
   1226 
   1227             if (isPinned)
   1228             {
   1229                 if (context.Args.Contains(Execution::Args::Type::Force))
   1230                 {
   1231                     AICLI_LOG(CLI, Info, << "Ignoring pin on package due to --force argument");
   1232                 }
   1233                 else
   1234                 {
   1235                     AICLI_LOG(CLI, Error, << "The requested package version is unavailable because of a pin");
   1236                     context.Reporter.Error() << Resource::String::PackageIsPinned << std::endl;
   1237                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED);
   1238                 }
   1239             }
   1240         }
   1241         else
   1242         {
   1243             // The simple case: Just look up the requested version
   1244             requestedVersion = availableVersions->GetVersion(key);
   1245         }
   1246 
   1247         std::optional<Manifest::Manifest> manifest;
   1248         if (requestedVersion)
   1249         {
   1250             manifest = requestedVersion->GetManifest();
   1251         }
   1252 
   1253         if (!manifest)
   1254         {
   1255             std::ostringstream ssVersionInfo;
   1256             if (!m_version.empty())
   1257             {
   1258                 ssVersionInfo << m_version;
   1259             }
   1260             if (!m_channel.empty())
   1261             {
   1262                 ssVersionInfo << '[' << m_channel << ']';
   1263             }
   1264 
   1265             context.Reporter.Error() << Resource::String::GetManifestResultVersionNotFound(Utility::LocIndView{ ssVersionInfo.str()}) << std::endl;
   1266             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND);
   1267         }
   1268 
   1269         Logging::Telemetry().LogManifestFields(manifest->Id, manifest->DefaultLocalization.Get<Manifest::Localization::PackageName>(), manifest->Version);
   1270 
   1271         std::string targetLocale;
   1272         if (context.Args.Contains(Execution::Args::Type::Locale))
   1273         {
   1274             targetLocale = context.Args.GetArg(Execution::Args::Type::Locale);
   1275         }
   1276         manifest->ApplyLocale(targetLocale);
   1277 
   1278         context.Add<Execution::Data::Manifest>(std::move(manifest.value()));
   1279         context.Add<Execution::Data::PackageVersion>(std::move(requestedVersion));
   1280     }
   1281 
   1282     void GetManifestFromPackage::operator()(Execution::Context& context) const
   1283     {
   1284         context << GetManifestWithVersionFromPackage(
   1285             context.Args.GetArg(Execution::Args::Type::Version),
   1286             context.Args.GetArg(Execution::Args::Type::Channel),
   1287             m_considerPins);
   1288     }
   1289 
   1290     void VerifyFile::operator()(Execution::Context& context) const
   1291     {
   1292         std::filesystem::path path = Utility::ConvertToUTF16(context.Args.GetArg(m_arg));
   1293 
   1294         if (!std::filesystem::exists(path))
   1295         {
   1296             context.Reporter.Error() << Resource::String::VerifyFileFailedNotExist(Utility::LocIndView{ path.u8string() }) << std::endl;
   1297             AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
   1298         }
   1299 
   1300         if (std::filesystem::is_directory(path))
   1301         {
   1302             context.Reporter.Error() << Resource::String::VerifyFileFailedIsDirectory(Utility::LocIndView{ path.u8string() }) << std::endl;
   1303             AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_DIRECTORY_NOT_SUPPORTED));
   1304         }
   1305     }
   1306 
   1307     void VerifyPath::operator()(Execution::Context& context) const
   1308     {
   1309         std::filesystem::path path = Utility::ConvertToUTF16(context.Args.GetArg(m_arg));
   1310 
   1311         if (!std::filesystem::exists(path))
   1312         {
   1313             context.Reporter.Error() << Resource::String::VerifyPathFailedNotExist(Utility::LocIndView{ path.u8string() }) << std::endl;
   1314             AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
   1315         }
   1316     }
   1317 
   1318     void VerifyFileOrUri::operator()(Execution::Context& context) const
   1319     {
   1320         // Argument requirement is handled elsewhere.
   1321         if (!context.Args.Contains(m_arg))
   1322         {
   1323             return;
   1324         }
   1325 
   1326         auto path = context.Args.GetArg(m_arg);
   1327 
   1328         // try uri first
   1329         Uri pathAsUri = nullptr;
   1330         try
   1331         {
   1332             pathAsUri = Uri{ Utility::ConvertToUTF16(path) };
   1333         }
   1334         catch (...) {}
   1335 
   1336         if (pathAsUri)
   1337         {
   1338             if (pathAsUri.Suspicious())
   1339             {
   1340                 context.Reporter.Error() << Resource::String::UriNotWellFormed(Utility::LocIndView{ path }) << std::endl;
   1341                 AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
   1342             }
   1343             // SchemeName() always returns lower case
   1344             else if (L"file" == pathAsUri.SchemeName() && !Utility::CaseInsensitiveStartsWith(path, "file:"))
   1345             {
   1346                 // Uri constructor is smart enough to parse an absolute local file path to file uri.
   1347                 // In this case, we should continue with VerifyFile.
   1348                 context << VerifyFile(m_arg);
   1349             }
   1350             else if (std::find(m_supportedSchemes.begin(), m_supportedSchemes.end(), pathAsUri.SchemeName()) != m_supportedSchemes.end())
   1351             {
   1352                 // Scheme supported.
   1353                 return;
   1354             }
   1355             else
   1356             {
   1357                 context.Reporter.Error() << Resource::String::UriSchemeNotSupported(Utility::LocIndView{ path }) << std::endl;
   1358                 AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
   1359             }
   1360         }
   1361         else
   1362         {
   1363             context << VerifyFile(m_arg);
   1364         }
   1365     }
   1366 
   1367     void GetManifestFromArg(Execution::Context& context)
   1368     {
   1369         Logging::Telemetry().LogIsManifestLocal(true);
   1370 
   1371         context <<
   1372             VerifyPath(Execution::Args::Type::Manifest) <<
   1373             [](Execution::Context& context)
   1374         {
   1375             Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::Manifest)));
   1376             Logging::Telemetry().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>(), manifest.Version);
   1377 
   1378             std::string targetLocale;
   1379             if (context.Args.Contains(Execution::Args::Type::Locale))
   1380             {
   1381                 targetLocale = context.Args.GetArg(Execution::Args::Type::Locale);
   1382             }
   1383             manifest.ApplyLocale(targetLocale);
   1384 
   1385             context.Add<Execution::Data::Manifest>(std::move(manifest));
   1386         };
   1387     }
   1388 
   1389     void ReportPackageIdentity(Execution::Context& context)
   1390     {
   1391         auto package = context.Get<Execution::Data::Package>();
   1392         ReportIdentity(context, {}, Resource::String::ReportIdentityFound, package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id));
   1393     }
   1394 
   1395     void ReportInstalledPackageVersionIdentity(Execution::Context& context)
   1396     {
   1397         auto package = context.Get<Execution::Data::Package>();
   1398         auto version = context.Get<Execution::Data::InstalledPackageVersion>();
   1399         ReportIdentity(context, {}, Resource::String::ReportIdentityFound, version->GetProperty(PackageVersionProperty::Name), package ? package->GetProperty(PackageProperty::Id) : version->GetProperty(PackageVersionProperty::Id));
   1400     }
   1401 
   1402     void ReportManifestIdentity(Execution::Context& context)
   1403     {
   1404         const auto& manifest = context.Get<Execution::Data::Manifest>();
   1405         ReportIdentity(context, {}, Resource::String::ReportIdentityFound, manifest.CurrentLocalization.Get<Manifest::Localization::PackageName>(), manifest.Id);
   1406         ShowManifestIcon(context, manifest);
   1407     }
   1408 
   1409     void ReportManifestIdentityWithVersion::operator()(Execution::Context& context) const
   1410     {
   1411         const auto& manifest = context.Get<Execution::Data::Manifest>();
   1412         ReportIdentity(context, m_prefix, m_label, manifest.CurrentLocalization.Get<Manifest::Localization::PackageName>(), manifest.Id, manifest.Version, m_level);
   1413         ShowManifestIcon(context, manifest);
   1414     }
   1415 
   1416     void SelectInstaller(Execution::Context& context)
   1417     {
   1418         bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);
   1419         bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair);
   1420 
   1421         IPackageVersion::Metadata installationMetadata;
   1422 
   1423         if (isUpdate || isRepair)
   1424         {
   1425             installationMetadata = context.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata();
   1426         }
   1427 
   1428         Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(context, installationMetadata));
   1429         auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(context.Get<Execution::Data::Manifest>());
   1430 
   1431         if (!installer.has_value())
   1432         {
   1433             auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), Manifest::InapplicabilityFlags::InstalledType);
   1434             if (onlyInstalledType != inapplicabilities.end())
   1435             {
   1436                 if (isRepair)
   1437                 {
   1438                     context.Reporter.Info() << Resource::String::RepairDifferentInstallTechnology << std::endl;
   1439                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE);
   1440                 }
   1441                 else
   1442                 {
   1443                     context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnology << std::endl;
   1444                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE);
   1445                 }
   1446             }
   1447         }
   1448 
   1449         if (installer.has_value())
   1450         {
   1451             Logging::Telemetry().LogSelectedInstaller(
   1452                 static_cast<int>(installer->Arch),
   1453                 installer->Url,
   1454                 Manifest::InstallerTypeToString(installer->EffectiveInstallerType()),
   1455                 Manifest::ScopeToString(installer->Scope),
   1456                 installer->Locale);
   1457         }
   1458 
   1459         context.Add<Execution::Data::Installer>(installer);
   1460     }
   1461 
   1462     void EnsureRunningAsAdmin(Execution::Context& context)
   1463     {
   1464         if (!Runtime::IsRunningAsAdmin())
   1465         {
   1466             context.Reporter.Error() << Resource::String::CommandRequiresAdmin;
   1467             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN);
   1468         }
   1469     }
   1470 
   1471     void EnsureFeatureEnabled::operator()(Execution::Context& context) const
   1472     {
   1473         if (!Settings::ExperimentalFeature::IsEnabled(m_feature))
   1474         {
   1475             context.Reporter.Error()
   1476                 << Resource::String::FeatureDisabledMessage(Utility::LocIndView{ Settings::ExperimentalFeature::GetFeature(m_feature).JsonName() })
   1477                 << std::endl;
   1478             AICLI_LOG(CLI, Error, << Settings::ExperimentalFeature::GetFeature(m_feature).Name() << " feature is disabled. Execution cancelled.");
   1479             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED);
   1480         }
   1481     }
   1482 
   1483     void SearchSourceUsingManifest(Execution::Context& context)
   1484     {
   1485         const auto& manifest = context.Get<Execution::Data::Manifest>();
   1486         auto source = context.Get<Execution::Data::Source>();
   1487 
   1488         // First try search using ProductId or PackageFamilyName
   1489         for (const auto& installer : manifest.Installers)
   1490         {
   1491             SearchRequest searchRequest;
   1492             if (!installer.PackageFamilyName.empty())
   1493             {
   1494                 searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, installer.PackageFamilyName));
   1495             }
   1496             else if (!installer.ProductCode.empty())
   1497             {
   1498                 searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, installer.ProductCode));
   1499             }
   1500             else if (installer.EffectiveInstallerType() == Manifest::InstallerTypeEnum::Portable)
   1501             {
   1502                 const auto& productCode = Utility::MakeSuitablePathPart(manifest.Id + '_' + source.GetIdentifier());
   1503                 searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::CaseInsensitive, Utility::Normalize(productCode)));
   1504             }
   1505 
   1506             if (!searchRequest.Inclusions.empty())
   1507             {
   1508                 auto searchResult = source.Search(searchRequest);
   1509 
   1510                 if (!searchResult.Matches.empty())
   1511                 {
   1512                     context.Add<Execution::Data::SearchResult>(std::move(searchResult));
   1513                     return;
   1514                 }
   1515             }
   1516         }
   1517 
   1518         // If we cannot find a package using PackageFamilyName or ProductId, try manifest Id and Name pair
   1519         SearchRequest searchRequest;
   1520         searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id));
   1521         
   1522         // In case there are same Ids from different sources, filter the result using package name
   1523         for (const auto& localization : manifest.Localizations)
   1524         {
   1525             const auto& localizedPackageName = localization.Get<Manifest::Localization::PackageName>();
   1526             if (!localizedPackageName.empty())
   1527             {
   1528                 searchRequest.Filters.emplace_back(PackageMatchField::Name, MatchType::CaseInsensitive, localizedPackageName);
   1529             }
   1530         }
   1531 
   1532         searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::CaseInsensitive, manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>()));
   1533 
   1534         context.Add<Execution::Data::SearchResult>(source.Search(searchRequest));
   1535     }
   1536 
   1537     void GetInstalledPackageVersion(Execution::Context& context)
   1538     {
   1539         std::shared_ptr<IPackage> installed = context.Get<Execution::Data::Package>()->GetInstalled();
   1540 
   1541         if (installed)
   1542         {
   1543             // TODO: This may need to be expanded dramatically to enable targeting across a variety of dimensions (architecture, etc.)
   1544             //       Alternatively, if we make it easier to see the fully unique package identifiers, we may avoid that need.
   1545             if (context.Args.Contains(Execution::Args::Type::TargetVersion))
   1546             {
   1547                 Repository::PackageVersionKey versionKey{ "", context.Args.GetArg(Execution::Args::Type::TargetVersion) , "" };
   1548                 std::shared_ptr<IPackageVersion> installedVersion = installed->GetVersion(versionKey);
   1549 
   1550                 if (!installedVersion)
   1551                 {
   1552                     context.Reporter.Error() << Resource::String::GetManifestResultVersionNotFound(Utility::LocIndView{ versionKey.Version }) << std::endl;
   1553                     // This error maintains consistency with passing an available version to commands
   1554                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND);
   1555                 }
   1556 
   1557                 context.Add<Execution::Data::InstalledPackageVersion>(std::move(installedVersion));
   1558             }
   1559             else
   1560             {
   1561                 context.Add<Execution::Data::InstalledPackageVersion>(installed->GetLatestVersion());
   1562             }
   1563         }
   1564         else
   1565         {
   1566             context.Add<Execution::Data::InstalledPackageVersion>(nullptr);
   1567         }
   1568     }
   1569 
   1570     void ReportExecutionStage::operator()(Execution::Context& context) const
   1571     {
   1572         context.SetExecutionStage(m_stage);
   1573     }
   1574 
   1575     void ShowAppVersions(Execution::Context& context)
   1576     {
   1577         auto versions = GetAllAvailableVersions(context.Get<Execution::Data::Package>())->GetVersionKeys();
   1578 
   1579         Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel });
   1580         for (const auto& version : versions)
   1581         {
   1582             table.OutputLine({ version.Version, version.Channel });
   1583         }
   1584         table.Complete();
   1585     }
   1586 }
   1587 
   1588 AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, AppInstaller::CLI::Workflow::WorkflowTask::Func f)
   1589 {
   1590     return (context << AppInstaller::CLI::Workflow::WorkflowTask(f));
   1591 }
   1592 
   1593 AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, const AppInstaller::CLI::Workflow::WorkflowTask& task)
   1594 {
   1595     if (!context.IsTerminated() || task.ExecuteAlways())
   1596     {
   1597 #ifndef AICLI_DISABLE_TEST_HOOKS
   1598         if (context.ShouldExecuteWorkflowTask(task))
   1599 #endif
   1600         {
   1601             task.Log();
   1602             task(context);
   1603         }
   1604     }
   1605     return context;
   1606 }