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 }