diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index b3bb1763ca..dfa299de75 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -284,6 +284,7 @@ + @@ -341,6 +342,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index f2983e3f44..aeda4f158d 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -194,6 +194,9 @@ Workflows + + Workflows + @@ -355,6 +358,9 @@ Commands + + Workflows + diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 86723b5fd3..a5f15c9ca6 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -29,6 +29,8 @@ namespace AppInstaller::CLI // Args to specify where to get app case Execution::Args::Type::Query: return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::MultiQuery: + return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery }; case Execution::Args::Type::Manifest: return { type, "manifest"_liv, 'm', ArgTypeCategory::Manifest }; @@ -215,6 +217,8 @@ namespace AppInstaller::CLI { case Args::Type::Query: return Argument{ type, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; + case Args::Type::MultiQuery: + return Argument{ type, Resource::String::MultiQueryArgumentDescription, ArgumentType::Positional }.SetCountLimit(128); case Args::Type::Manifest: return Argument{ type, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles, Settings::AdminSetting::LocalManifestFiles }; case Args::Type::Id: diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h index 93d41cd4d1..8c60e5fac9 100644 --- a/src/AppInstallerCLICore/Argument.h +++ b/src/AppInstallerCLICore/Argument.h @@ -206,6 +206,7 @@ namespace AppInstaller::CLI Settings::AdminSetting AdminSetting() const { return m_adminSetting; } Argument& SetRequired(bool required) { m_required = required; return *this; } + Argument& SetCountLimit(size_t countLimit) { m_countLimit = countLimit; return *this; } private: // Constructors that set a Feature or Policy are private to force callers to go through the ForType() function. diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp index 03e2fe4b8a..33d0c74a1c 100644 --- a/src/AppInstallerCLICore/Command.cpp +++ b/src/AppInstallerCLICore/Command.cpp @@ -135,6 +135,11 @@ namespace AppInstaller::CLI infoOut << "] <"_liv << arg.Name() << '>'; + if (arg.Limit() > 1) + { + infoOut << "..."_liv; + } + if (!arg.Required()) { infoOut << ']'; @@ -405,14 +410,10 @@ namespace AppInstaller::CLI const CLI::Argument* ParseArgumentsStateMachine::NextPositional() { // Find the next appropriate positional arg if the current itr isn't one or has hit its limit. - if (m_positionalSearchItr != m_arguments.end() && + while (m_positionalSearchItr != m_arguments.end() && (m_positionalSearchItr->Type() != ArgumentType::Positional || m_executionArgs.GetCount(m_positionalSearchItr->ExecArgType()) == m_positionalSearchItr->Limit())) { - do - { - ++m_positionalSearchItr; - } - while (m_positionalSearchItr != m_arguments.end() && m_positionalSearchItr->Type() != ArgumentType::Positional); + ++m_positionalSearchItr; } if (m_positionalSearchItr == m_arguments.end()) @@ -600,6 +601,9 @@ namespace AppInstaller::CLI { stateMachine.ThrowIfError(); } + + // Special handling for multi-query arguments: + execArgs.MoveMultiQueryToSingleQueryIfNeeded(); } void Command::ValidateArguments(Execution::Args& execArgs) const diff --git a/src/AppInstallerCLICore/Commands/ImportCommand.cpp b/src/AppInstallerCLICore/Commands/ImportCommand.cpp index a2ce2d215f..744599c1d5 100644 --- a/src/AppInstallerCLICore/Commands/ImportCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ImportCommand.cpp @@ -4,6 +4,7 @@ #include "ImportCommand.h" #include "Workflows/CompletionFlow.h" #include "Workflows/ImportExportFlow.h" +#include "Workflows/MultiQueryFlow.h" #include "Workflows/WorkflowBase.h" #include "Resources.h" @@ -46,7 +47,8 @@ namespace AppInstaller::CLI Workflow::ReadImportFile << Workflow::OpenSourcesForImport << Workflow::OpenPredefinedSource(Repository::PredefinedSource::Installed) << - Workflow::SearchPackagesForImport << + Workflow::GetSearchRequestsForImport << + Workflow::SearchSubContextsForSingle() << Workflow::ReportExecutionStage(Workflow::ExecutionStage::Execution) << Workflow::InstallImportedPackages; } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index 858c8aa2a4..5092fc4b5a 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -5,6 +5,7 @@ #include "Workflows/CompletionFlow.h" #include "Workflows/InstallFlow.h" #include "Workflows/UpdateFlow.h" +#include "Workflows/MultiQueryFlow.h" #include "Workflows/WorkflowBase.h" #include "Resources.h" @@ -18,7 +19,7 @@ namespace AppInstaller::CLI std::vector InstallCommand::GetArguments() const { return { - Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::MultiQuery), Argument::ForType(Args::Type::Manifest), Argument::ForType(Args::Type::Id), Argument::ForType(Args::Type::Name), @@ -63,7 +64,7 @@ namespace AppInstaller::CLI { switch (valueType) { - case Args::Type::Query: + case Args::Type::MultiQuery: case Args::Type::Manifest: case Args::Type::Id: case Args::Type::Name: @@ -123,7 +124,20 @@ namespace AppInstaller::CLI Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, false, Repository::CompositeSearchBehavior::AvailablePackages); } - context << Workflow::InstallOrUpgradeSinglePackage(false); + if (context.Args.Contains(Execution::Args::Type::MultiQuery)) + { + context << + Workflow::GetMultiSearchRequests << + Workflow::SearchSubContextsForSingle() << + Workflow::ReportExecutionStage(Workflow::ExecutionStage::Execution) << + Workflow::InstallMultiplePackages( + Resource::String::InstallAndUpgradeCommandsReportDependencies, + APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED); + } + else + { + context << Workflow::InstallOrUpgradeSinglePackage(false); + } } } } diff --git a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp index e0a5f7c993..ed659d48d3 100644 --- a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp @@ -5,6 +5,7 @@ #include "Workflows/UninstallFlow.h" #include "Workflows/CompletionFlow.h" #include "Workflows/WorkflowBase.h" +#include "Workflows/MultiQueryFlow.h" #include "Resources.h" using AppInstaller::CLI::Execution::Args; @@ -16,7 +17,7 @@ namespace AppInstaller::CLI { return { - Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::MultiQuery), Argument::ForType(Args::Type::Manifest), Argument::ForType(Args::Type::Id), Argument::ForType(Args::Type::Name), @@ -63,7 +64,7 @@ namespace AppInstaller::CLI switch (valueType) { - case Execution::Args::Type::Query: + case Execution::Args::Type::MultiQuery: context << Workflow::RequireCompletionWordNonEmpty << Workflow::SearchSourceForManyCompletion << @@ -110,19 +111,28 @@ namespace AppInstaller::CLI Workflow::GetManifestFromArg << Workflow::ReportManifestIdentity << Workflow::SearchSourceUsingManifest << - Workflow::EnsureOneMatchFromSearchResult(true); + Workflow::EnsureOneMatchFromSearchResult(true) << + Workflow::UninstallSinglePackage; } else { - // search for a single package to uninstall - context << - Workflow::SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(true) << - Workflow::ReportPackageIdentity; + // search for specific packages to uninstall + if (!context.Args.Contains(Execution::Args::Type::MultiQuery)) + { + context << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(true) << + Workflow::ReportPackageIdentity << + Workflow::UninstallSinglePackage; + } + else + { + context << + Workflow::GetMultiSearchRequests << + Workflow::SearchSubContextsForSingle(Workflow::SearchSubContextsForSingle::SearchPurpose::Uninstall) << + Workflow::UninstallMultiplePackages; + } } - - context << - Workflow::UninstallSinglePackage; } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index 8aa87eb5ae..2702ed4688 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -4,6 +4,7 @@ #include "UpgradeCommand.h" #include "Workflows/CompletionFlow.h" #include "Workflows/InstallFlow.h" +#include "Workflows/MultiQueryFlow.h" #include "Workflows/UpdateFlow.h" #include "Workflows/WorkflowBase.h" #include "Workflows/DependenciesFlow.h" @@ -31,14 +32,14 @@ namespace AppInstaller::CLI // Valid arguments for list are only those related to the sources and which packages to include (e.g. --include-unknown). // Instead of checking for them, we check that there aren't any other arguments present. return !args.Contains(Args::Type::All) && - WI_AreAllFlagsClear(argCategories, ArgTypeCategory::Manifest | ArgTypeCategory::SinglePackageQuery | ArgTypeCategory::InstallerBehavior); + WI_AreAllFlagsClear(argCategories, ArgTypeCategory::Manifest | ArgTypeCategory::PackageQuery | ArgTypeCategory::InstallerBehavior); } } std::vector UpgradeCommand::GetArguments() const { return { - Argument::ForType(Args::Type::Query), // -q + Argument::ForType(Args::Type::MultiQuery), // -q Argument::ForType(Args::Type::Manifest), // -m Argument::ForType(Args::Type::Id), Argument::ForType(Args::Type::Name), @@ -96,7 +97,7 @@ namespace AppInstaller::CLI switch (valueType) { - case Execution::Args::Type::Query: + case Execution::Args::Type::MultiQuery: context << RequireCompletionWordNonEmpty << SearchSourceForManyCompletion << @@ -188,8 +189,21 @@ namespace AppInstaller::CLI } else { - // The remaining case: search for single installed package to update - context << InstallOrUpgradeSinglePackage(true); + // The remaining case: search for specific packages to update + if (!context.Args.Contains(Execution::Args::Type::MultiQuery)) + { + context << Workflow::InstallOrUpgradeSinglePackage(/* isUpgrade */ true); + } + else + { + context << + Workflow::GetMultiSearchRequests << + Workflow::SearchSubContextsForSingle(Workflow::SearchSubContextsForSingle::SearchPurpose::Upgrade) << + Workflow::ReportExecutionStage(Workflow::ExecutionStage::Execution) << + Workflow::InstallMultiplePackages( + Resource::String::InstallAndUpgradeCommandsReportDependencies, + APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED); + } } } } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index a4fd379318..cc66d9f98a 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -14,6 +14,7 @@ namespace AppInstaller::CLI::Execution { // Args to specify where to get app Query, // Query to be performed against index + MultiQuery, // Like query, but can take multiple values Manifest, // Provide the app manifest directly // Query filtering criteria and query behavior @@ -160,7 +161,7 @@ namespace AppInstaller::CLI::Execution m_parsedArgs[arg].emplace_back(value); } - bool Empty() const + bool Empty() { return m_parsedArgs.empty(); } @@ -182,6 +183,22 @@ namespace AppInstaller::CLI::Execution return types; } + // If we get a single value for multi-query, we remove the argument and add it back as a single query. + // This way the rest of the code can assume that if there is a MultiQuery we will always have multiple values, + // and if there is a single one it will be in the Query type. + // This is the only case where we modify the parsed args from user input. + void MoveMultiQueryToSingleQueryIfNeeded() + { + auto itr = m_parsedArgs.find(Type::MultiQuery); + if (itr != m_parsedArgs.end() && itr->second.size() == 1) + { + // A test ensures that commands don't have both Query and MultiQuery arguments, + // so if we had a MultiQuery value, we can be sure there is no Query value + m_parsedArgs[Type::Query].emplace_back(std::move(itr->second[0])); + m_parsedArgs.erase(itr); + } + } + private: std::map> m_parsedArgs; }; diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index 6011e4b397..844ed0bca9 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -30,6 +30,7 @@ namespace AppInstaller::CLI::Execution enum class Data : size_t { Source, + SearchRequest, // Only set for multiple installs SearchResult, SourceList, Package, @@ -49,8 +50,9 @@ namespace AppInstaller::CLI::Execution // On export: A collection of packages to be exported to a file // On import: A collection of packages read from a file PackageCollection, - // On import and upgrade all: A collection of specific package versions to install - PackagesToInstall, + // When installing multiple packages at once (upgrade all, import, install with multiple args, dependencies): + // A collection of sub-contexts, each of which handles the installation of a single package. + PackageSubContexts, // On import: Sources for the imported packages Sources, ARPCorrelationData, @@ -81,6 +83,12 @@ namespace AppInstaller::CLI::Execution using value_t = Repository::Source; }; + template <> + struct DataMapping + { + using value_t = Repository::SearchRequest; + }; + template <> struct DataMapping { @@ -184,7 +192,7 @@ namespace AppInstaller::CLI::Execution }; template <> - struct DataMapping + struct DataMapping { using value_t = std::vector>; }; diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 0ebb0965d0..155ba8594e 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -120,8 +120,6 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnorePackageVersionsArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnoreUnavailableArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportInstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ImportPackageAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(ImportSearchFailed); WINGET_DEFINE_RESOURCE_STRINGID(ImportSourceNotInstalled); WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(IncompatibleArgumentsProvided); @@ -215,6 +213,11 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(MultipleInstalledPackagesFound); WINGET_DEFINE_RESOURCE_STRINGID(MultipleNonPortableNestedInstallersSpecified); WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFound); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFoundMultiple); WINGET_DEFINE_RESOURCE_STRINGID(NameArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotFound); WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSpecified); diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp index 361d7a44e5..e4da2b0fa9 100644 --- a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp @@ -106,6 +106,7 @@ namespace AppInstaller::CLI::Workflow switch (m_type) { case Execution::Args::Type::Query: + case Execution::Args::Type::MultiQuery: case Execution::Args::Type::Id: case Execution::Args::Type::Name: case Execution::Args::Type::Moniker: @@ -126,6 +127,7 @@ namespace AppInstaller::CLI::Workflow switch (m_type) { case Execution::Args::Type::Query: + case Execution::Args::Type::MultiQuery: context << Workflow::RequireCompletionWordNonEmpty << Workflow::SearchSourceForSingleCompletion << diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index a00ce06fe5..2acc0a40a3 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -242,7 +242,7 @@ namespace AppInstaller::CLI::Workflow } // Install dependencies in the correct order - context.Add(std::move(dependencyPackageContexts)); + context.Add(std::move(dependencyPackageContexts)); context << Workflow::InstallMultiplePackages(m_dependencyReportMessage, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, {}, false, true, true); } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp index db9e9b4e82..1770bd22e1 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -242,11 +242,10 @@ namespace AppInstaller::CLI::Workflow } } - void SearchPackagesForImport(Execution::Context& context) + void GetSearchRequestsForImport(Execution::Context& context) { const auto& sources = context.Get(); - std::vector> packagesToInstall; - bool foundAll = true; + std::vector> packageSubContexts; // Look for the packages needed from each source independently. // If a package is available from multiple sources, this ensures we will get it from the right one. @@ -262,7 +261,7 @@ namespace AppInstaller::CLI::Workflow // Search for all the packages in the source. // Each search is done in a sub context to search everything regardless of previous failures. Repository::Source source{ context.Get(), *sourceItr, CompositeSearchBehavior::AllPackages }; - AICLI_LOG(CLI, Info, << "Searching for packages requested from source [" << requiredSource.Details.Identifier << "]"); + AICLI_LOG(CLI, Info, << "Identifying packages requested from source [" << requiredSource.Details.Identifier << "]"); for (const auto& packageRequest : requiredSource.Packages) { AICLI_LOG(CLI, Info, << "Searching for package [" << packageRequest.Id << "]"); @@ -276,7 +275,7 @@ namespace AppInstaller::CLI::Workflow auto previousThreadGlobals = searchContext.SetForCurrentThread(); searchContext.Add(source); - searchContext.Add(source.Search(searchRequest)); + searchContext.Add(std::move(searchRequest)); if (packageRequest.Scope != Manifest::ScopeEnum::Unknown) { @@ -296,58 +295,11 @@ namespace AppInstaller::CLI::Workflow searchContext.Args.AddArg(Execution::Args::Type::Channel, channelString); } - // Find the single version we want is available - searchContext << - Workflow::SelectSinglePackageVersionForInstallOrUpgrade(false); - - if (searchContext.IsTerminated()) - { - if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) - { - // This means that the subcontext being terminated is due to an overall abort - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - return; - } - else - { - auto searchTerminationHR = searchContext.GetTerminationHR(); - if (searchTerminationHR == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE || - searchTerminationHR == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED) - { - AICLI_LOG(CLI, Info, << "Package is already installed: [" << packageRequest.Id << "]"); - context.Reporter.Info() << Resource::String::ImportPackageAlreadyInstalled(packageRequest.Id) << std::endl; - continue; - } - else - { - AICLI_LOG(CLI, Info, << "Package not found for import: [" << packageRequest.Id << "], Version " << packageRequest.VersionAndChannel.ToString()); - context.Reporter.Info() << Resource::String::ImportSearchFailed(packageRequest.Id) << std::endl; - - // Keep searching for the remaining packages and only fail at the end. - foundAll = false; - continue; - } - } - } - - packagesToInstall.emplace_back(std::move(searchContextPtr)); - } - } - - if (!foundAll) - { - AICLI_LOG(CLI, Info, << "Could not find one or more packages for import"); - if (context.Args.Contains(Execution::Args::Type::IgnoreUnavailable)) - { - AICLI_LOG(CLI, Info, << "Ignoring unavailable packages due to command line argument"); - } - else - { - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND); + packageSubContexts.emplace_back(std::move(searchContextPtr)); } } - context.Add(std::move(packagesToInstall)); + context.Add(std::move(packageSubContexts)); } void InstallImportedPackages(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.h b/src/AppInstallerCLICore/Workflows/ImportExportFlow.h index 1e689d3e52..85ecf5e856 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.h +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.h @@ -29,16 +29,18 @@ namespace AppInstaller::CLI::Workflow // Outputs: Sources void OpenSourcesForImport(Execution::Context& context); - // Finds the package versions to install matching their descriptions + // Create the search requests and install sub-contexts for all the imported packages. // Needs the sources for all packages and the installed source // Required Args: None // Inputs: PackageCollection, Sources, Source - // Outputs: PackagesToInstall - void SearchPackagesForImport(Execution::Context& context); + // Outputs: PackageSubContexts + // SubContext Inputs: None + // SubContext Outputs: Source, SearchRequest + void GetSearchRequestsForImport(Execution::Context& context); // Installs all the packages found in the import file. // Required Args: None - // Inputs: PackagesToInstall + // Inputs: PackageSubContexts // Outputs: None void InstallImportedPackages(Execution::Context& context); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 36db83db44..6a1ce0d66f 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -4,6 +4,7 @@ #include "InstallFlow.h" #include "DownloadFlow.h" #include "UninstallFlow.h" +#include "UpdateFlow.h" #include "ShowFlow.h" #include "Resources.h" #include "ShellExecuteInstallerHandler.h" @@ -527,7 +528,7 @@ namespace AppInstaller::CLI::Workflow if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) { DependencyList allDependencies; - for (auto& packageContext : context.Get()) + for (auto& packageContext : context.Get()) { allDependencies.Add(packageContext->Get().value().Dependencies); } @@ -537,10 +538,10 @@ namespace AppInstaller::CLI::Workflow } bool allSucceeded = true; - size_t packagesCount = context.Get().size(); + size_t packagesCount = context.Get().size(); size_t packagesProgress = 0; - for (auto& packageContext : context.Get()) + for (auto& packageContext : context.Get()) { packagesProgress++; context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index a613a934b2..947eac5bf4 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -147,7 +147,7 @@ namespace AppInstaller::CLI::Workflow // Installs multiple packages. This also does the reporting and user interaction needed. // Required Args: None - // Inputs: PackagesToInstall + // Inputs: PackageSubContexts // Outputs: None struct InstallMultiplePackages : public WorkflowTask { diff --git a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp new file mode 100644 index 0000000000..8dc1118407 --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "MultiQueryFlow.h" +#include "UpdateFlow.h" + +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + Utility::LocIndString GetPackageStringFromSearchRequest(const SearchRequest& searchRequest) + { + if (searchRequest.Query) + { + return Utility::LocIndString{ searchRequest.Query->Value }; + } + + if (!searchRequest.Inclusions.empty()) + { + return Utility::LocIndString{ searchRequest.Inclusions[0].Value }; + } + + if (!searchRequest.Filters.empty()) + { + return Utility::LocIndString{ searchRequest.Filters[0].Value }; + } + + return ""_lis; + } + } + + void GetMultiSearchRequests(Execution::Context& context) + { + std::vector> packageSubContexts; + auto& source = context.Get(); + for (const auto& query : *context.Args.GetArgs(Execution::Args::Type::MultiQuery)) + { + + auto searchContextPtr = context.CreateSubContext(); + Execution::Context& searchContext = *searchContextPtr; + auto previousThreadGlobals = searchContext.SetForCurrentThread(); + + searchContext.Add(source); + searchContext.Args.AddArg(Execution::Args::Type::Query, query); + + AICLI_LOG(CLI, Info, << "Creating search query for package [" << query << "]"); + searchContext << GetSearchRequestForSingle; + + + packageSubContexts.emplace_back(std::move(searchContextPtr)); + } + + context.Add(std::move(packageSubContexts)); + } + + void SearchSubContextsForSingle::operator()(Execution::Context& context) const + { + std::vector> packageSubContexts; + bool foundAll = true; + + for (auto& searchContextPtr : context.Get()) + { + auto& searchContext = *searchContextPtr; + SearchRequest searchRequest = searchContext.Get(); + searchContext.Add(searchContext.Get().Search(searchRequest)); + + switch (m_searchPurpose) + { + case SearchPurpose::Install: + searchContext << Workflow::SelectSinglePackageVersionForInstallOrUpgrade(/* isUpgrade */ false); + break; + case SearchPurpose::Upgrade: + searchContext << Workflow::SelectSinglePackageVersionForInstallOrUpgrade(/* isUpgrade */ true); + break; + case SearchPurpose::Uninstall: + searchContext << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(true); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + if (searchContext.IsTerminated()) + { + if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) + { + // This means that the subcontext being terminated is due to an overall abort + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + return; + } + else + { + // We already reported the error from the sub-context, but we repeat it here because + // for multi-queries we can a bit more verbose as the queries here are easier to report. + auto packageString = GetPackageStringFromSearchRequest(searchRequest); + auto searchTerminationHR = searchContext.GetTerminationHR(); + if (searchTerminationHR == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE || + searchTerminationHR == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED) + { + AICLI_LOG(CLI, Info, << "Package is already installed: [" << packageString << "]"); + context.Reporter.Info() << Resource::String::MultiQueryPackageAlreadyInstalled(packageString) << std::endl; + continue; + } + else + { + if (searchTerminationHR == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND) + { + AICLI_LOG(CLI, Info, << "Package not found for query: [" << packageString << "]"); + context.Reporter.Warn() << Resource::String::MultiQueryPackageNotFound(packageString) << std::endl; + } + else if (searchTerminationHR == APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND) + { + AICLI_LOG(CLI, Info, << "Multiple packages found for query: [" << packageString << "]"); + context.Reporter.Warn() << Resource::String::MultiQuerySearchFoundMultiple(packageString) << std::endl; + } + else + { + AICLI_LOG(CLI, Info, << "Search failed for query: [" << packageString << "]"); + context.Reporter.Info() << Resource::String::MultiQuerySearchFailed(packageString) << std::endl; + } + + // Keep searching for the remaining packages and only fail at the end. + foundAll = false; + continue; + } + } + } + + packageSubContexts.emplace_back(std::move(searchContextPtr)); + } + + if (!foundAll) + { + AICLI_LOG(CLI, Info, << "Not all queries returned one result"); + if (context.Args.Contains(Execution::Args::Type::IgnoreUnavailable)) + { + AICLI_LOG(CLI, Info, << "Ignoring unavailable packages due to command line argument"); + } + else + { + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); + } + } + + context.Add(std::move(packageSubContexts)); + } + +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.h b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.h new file mode 100644 index 0000000000..d01ae3f64e --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.h @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +// Workflow tasks related to dealing with multiple package queries at once. + +namespace AppInstaller::CLI::Workflow +{ + // Gets the search requests for multiple queries from the command line. + // Required Args: None + // Inputs: Source + // Outputs: PackageSubContexts + // SubContext Inputs: None + // SubContext Outputs: Source, SearchRequest + void GetMultiSearchRequests(Execution::Context& context); + + // Performs searches on each of the sub-contexts with the semantics of targeting a single package for each one. + // Required Args: a value indicating the purpose of the search + // Inputs: PackageSubContexts + // Outputs: None + // SubContext Inputs: Source, SearchRequest + // SubContext Outputs: SearchResult + struct SearchSubContextsForSingle : public WorkflowTask + { + enum class SearchPurpose + { + Install, + Upgrade, + Uninstall, + }; + + SearchSubContextsForSingle(SearchPurpose searchPurpose = SearchPurpose::Install) : WorkflowTask("SearchSubContextsForSingle"), m_searchPurpose(searchPurpose) {} + + void operator()(Execution::Context& context) const override; + + private: + SearchPurpose m_searchPurpose; + }; +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp index 8ce6c9cb40..9b5afc2a62 100644 --- a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp @@ -440,7 +440,7 @@ namespace AppInstaller::CLI::Workflow { // Find which packages need this prompt std::vector packagesToPrompt; - for (auto& packageContext : context.Get()) + for (auto& packageContext : context.Get()) { if (prompt->PackageNeedsPrompt(*packageContext)) { diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.h b/src/AppInstallerCLICore/Workflows/PromptFlow.h index 0c79df84b6..7df7e40ce6 100644 --- a/src/AppInstallerCLICore/Workflows/PromptFlow.h +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.h @@ -36,7 +36,7 @@ namespace AppInstaller::CLI::Workflow // Shows all the prompts required for multiple package, e.g. for package agreements // Required Args: None - // Inputs: PackagesToInstall + // Inputs: PackageSubContexts // Outputs: None struct ShowPromptsForMultiplePackages : public WorkflowTask { diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index 5b82479033..9372f7f69e 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -15,6 +15,7 @@ using namespace AppInstaller::Msix; using namespace AppInstaller::Repository; using namespace AppInstaller::Registry; using namespace AppInstaller::CLI::Portable; +using namespace AppInstaller::Utility::literals; namespace AppInstaller::CLI::Workflow { @@ -68,6 +69,54 @@ namespace AppInstaller::CLI::Workflow Workflow::RecordUninstall; } + void UninstallMultiplePackages(Execution::Context& context) + { + bool allSucceeded = true; + size_t packagesCount = context.Get().size(); + size_t packagesProgress = 0; + + for (auto& packageContext : context.Get()) + { + packagesProgress++; + context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; + + // We want to do best effort to uninstall all packages regardless of previous failures + Execution::Context& uninstallContext = *packageContext; + auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); + + // Prevent individual exceptions from breaking out of the loop + try + { + uninstallContext << + Workflow::ReportPackageIdentity << + Workflow::UninstallSinglePackage; + } + catch (...) + { + uninstallContext.SetTerminationHR(Workflow::HandleException(uninstallContext, std::current_exception())); + } + + uninstallContext.Reporter.Info() << std::endl; + + if (uninstallContext.IsTerminated()) + { + if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) + { + // This means that the subcontext being terminated is due to an overall abort + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + return; + } + + allSucceeded = false; + } + } + + if (!allSucceeded) + { + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED); + } + } + void GetUninstallInfo(Execution::Context& context) { auto installedPackageVersion = context.Get(); diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.h b/src/AppInstallerCLICore/Workflows/UninstallFlow.h index 03a17e8d0e..5beb75fcde 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.h @@ -8,10 +8,18 @@ namespace AppInstaller::CLI::Workflow // Uninstalls a single package. This also does the reporting, user interaction, and recording // for single-package uninstallation. // RequiredArgs: None - // Inputs: InstalledPackageVersion + // Inputs: Package // Outputs: None void UninstallSinglePackage(Execution::Context& context); + // Uninstalls multiple packages. + // RequiredArgs: None + // Inputs: PackageSubContexts + // Outputs: None + // SubContext Inputs: Package + // SubContext Outputs: None + void UninstallMultiplePackages(Execution::Context& context); + // Gets the command string or package family names used to uninstall the package. // Required Args: None // Inputs: InstalledPackageVersion diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 76a4353963..e0e6536c07 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -19,9 +19,9 @@ namespace AppInstaller::CLI::Workflow return installedVersion < updateVersion; } - void AddToPackagesToInstallIfNotPresent(std::vector>& packagesToInstall, std::unique_ptr packageContext) + void AddToPackageSubContextsIfNotPresent(std::vector>& packageSubContexts, std::unique_ptr packageContext) { - for (auto const& existing : packagesToInstall) + for (auto const& existing : packageSubContexts) { if (existing->Get().Id == packageContext->Get().Id && existing->Get().Version == packageContext->Get().Version && @@ -31,7 +31,7 @@ namespace AppInstaller::CLI::Workflow } } - packagesToInstall.emplace_back(std::move(packageContext)); + packageSubContexts.emplace_back(std::move(packageContext)); } } @@ -152,7 +152,7 @@ namespace AppInstaller::CLI::Workflow void UpdateAllApplicable(Execution::Context& context) { const auto& matches = context.Get().Matches; - std::vector> packagesToInstall; + std::vector> packageSubContexts; bool updateAllFoundUpdate = false; int packagesWithUnknownVersionSkipped = 0; int packagesThatRequireExplicitSkipped = 0; @@ -210,12 +210,12 @@ namespace AppInstaller::CLI::Workflow updateAllFoundUpdate = true; - AddToPackagesToInstallIfNotPresent(packagesToInstall, std::move(updateContextPtr)); + AddToPackageSubContextsIfNotPresent(packageSubContexts, std::move(updateContextPtr)); } if (updateAllFoundUpdate) { - context.Add(std::move(packagesToInstall)); + context.Add(std::move(packageSubContexts)); context.Reporter.Info() << std::endl; context << InstallMultiplePackages( diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 90e5487c3f..9d0e2797f4 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -443,6 +443,7 @@ namespace AppInstaller::CLI::Workflow } SearchRequest searchRequest; + if (args.Contains(Execution::Args::Type::Query)) { searchRequest.Query.emplace(RequestMatch(matchType, args.GetArg(Execution::Args::Type::Query))); @@ -464,7 +465,7 @@ namespace AppInstaller::CLI::Workflow context.Add(context.Get().Search(searchRequest)); } - void SearchSourceForSingle(Execution::Context& context) + void GetSearchRequestForSingle(Execution::Context& context) { const auto& args = context.Args; @@ -475,6 +476,7 @@ namespace AppInstaller::CLI::Workflow } SearchRequest searchRequest; + // Note: MultiQuery when we need search for single is handled with one sub-context per query. if (args.Contains(Execution::Args::Type::Query)) { std::string_view query = args.GetArg(Execution::Args::Type::Query); @@ -489,18 +491,30 @@ namespace AppInstaller::CLI::Workflow SearchSourceApplyFilters(context, searchRequest, matchType); - Logging::Telemetry().LogSearchRequest( - "single", - args.GetArg(Execution::Args::Type::Query), - args.GetArg(Execution::Args::Type::Id), - args.GetArg(Execution::Args::Type::Name), - args.GetArg(Execution::Args::Type::Moniker), - args.GetArg(Execution::Args::Type::Tag), - args.GetArg(Execution::Args::Type::Command), - searchRequest.MaximumResults, - searchRequest.ToString()); + context.Add(std::move(searchRequest)); + } - context.Add(context.Get().Search(searchRequest)); + void SearchSourceForSingle(Execution::Context& context) + { + const auto& args = context.Args; + context << GetSearchRequestForSingle; + if (!context.IsTerminated()) + { + const auto& searchRequest = context.Get(); + + Logging::Telemetry().LogSearchRequest( + "single", + args.GetArg(Execution::Args::Type::Query), + args.GetArg(Execution::Args::Type::Id), + args.GetArg(Execution::Args::Type::Name), + args.GetArg(Execution::Args::Type::Moniker), + args.GetArg(Execution::Args::Type::Tag), + args.GetArg(Execution::Args::Type::Command), + searchRequest.MaximumResults, + searchRequest.ToString()); + + context.Add(context.Get().Search(searchRequest)); + } } void SearchSourceForManyCompletion(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index de577c6881..bf28cfe0f6 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -132,9 +132,15 @@ namespace AppInstaller::CLI::Workflow // Outputs: SearchResult void SearchSourceForMany(Execution::Context& context); + // Creates a search request object with the semantics of targeting a single package. + // Required Args: None + // Inputs: Query, search filters (Id, Name, etc.) + // Outputs: SearchRequest + void GetSearchRequestForSingle(Execution::Context& context); + // Performs a search on the source with the semantics of targeting a single package. // Required Args: None - // Inputs: Source + // Inputs: Source, SearchRequest // Outputs: SearchResult void SearchSourceForSingle(Execution::Context& context); diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index f692971237..a2d5acb3d2 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -223,6 +223,12 @@ public class ErrorCode public const int ERROR_INSTALL_LOCATION_REQUIRED = unchecked((int)0x8A15005F); public const int ERROR_ARCHIVE_SCAN_FAILED = unchecked((int)0x8A150060); public const int ERROR_PACKAGE_ALREADY_INSTALLED = unchecked((int)0x8A150061); + public const int ERROR_PIN_ALREADY_EXISTS = unchecked((int)0x8A150062); + public const int ERROR_PIN_DOES_NOT_EXIST = unchecked((int)0x8A150063); + public const int ERROR_CANNOT_OPEN_PINNING_INDEX = unchecked((int)0x8A150064); + public const int ERROR_MULTIPLE_INSTALL_FAILED = unchecked((int)0x8A150065); + public const int ERROR_MULTIPLE_UNINSTALL_FAILED = unchecked((int)0x8A150066); + public const int ERROR_NOT_ALL_QUERIES_FOUND_SINGLE = unchecked((int)0x8A150067); public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); diff --git a/src/AppInstallerCLIE2ETests/ImportCommand.cs b/src/AppInstallerCLIE2ETests/ImportCommand.cs index df60d612cf..66f03f195b 100644 --- a/src/AppInstallerCLIE2ETests/ImportCommand.cs +++ b/src/AppInstallerCLIE2ETests/ImportCommand.cs @@ -79,8 +79,8 @@ public void ImportUnavailablePackage() { // Verify failure when trying to import an unavailable package var result = TestCommon.RunAICLICommand("import", this.GetTestImportFile("ImportFile-Bad-UnknownPackage.json")); - Assert.AreEqual(Constants.ErrorCode.ERROR_NOT_ALL_PACKAGES_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("Package not found for import")); + Assert.AreEqual(Constants.ErrorCode.ERROR_NOT_ALL_QUERIES_FOUND_SINGLE, result.ExitCode); + Assert.True(result.StdOut.Contains("Package not found: MissingPackage")); } /// @@ -91,8 +91,8 @@ public void ImportUnavailableVersion() { // Verify failure when trying to import an unavailable package var result = TestCommon.RunAICLICommand("import", this.GetTestImportFile("ImportFile-Bad-UnknownPackageVersion.json")); - Assert.AreEqual(Constants.ErrorCode.ERROR_NOT_ALL_PACKAGES_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("Package not found for import")); + Assert.AreEqual(Constants.ErrorCode.ERROR_NOT_ALL_QUERIES_FOUND_SINGLE, result.ExitCode); + Assert.True(result.StdOut.Contains("Search failed for: AppInstallerTest.TestExeInstaller")); } /// diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index d881cf7a4d..18c7ba8888 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -831,10 +831,6 @@ They can be configured through the settings file 'winget settings'. One or more imported packages failed to install - - Package not found for import: {0} - {Locked="{0}"} Error message displayed when the user attempts to import an application package that was not found. {0} is a placeholder replaced by the import package name . - Source required for import is not installed: {0} {Locked="{0}"} Error message displayed when the user attempts to import application package(s) from a repository source that is not installed. {0} is a placeholder replaced by the repository source name. @@ -853,9 +849,9 @@ They can be configured through the settings file 'winget settings'. JSON file is not valid - + Package is already installed: {0} - {Locked="{0}"} Message displayed to inform the user that an import application package is already installed. {0} is a placeholder replaced by the package identifier. + {Locked="{0}"} Message displayed to inform the user that an application package is already installed. {0} is a placeholder replaced by the package identifier or search query. Ignore unavailable packages @@ -1652,4 +1648,19 @@ Please specify one of them using the --source option to proceed. State Header for a table listing the state (enabled/disabled) of Group Policies and Settings + + The query used to search for a package + + + Package not found: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns no results. {0} is a placeholder replaced by the package name or query. + + + Multiple packages found for: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns more than one result. {0} is a placeholder replaced by the package name or query. + + + Search failed for: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches fails. This message is for generic failures, we have more specific messages for when the search returns no results, or when it returns more than one result. {0} is a placeholder replaced by the package name or query. + \ No newline at end of file diff --git a/src/AppInstallerCLITests/Argument.cpp b/src/AppInstallerCLITests/Argument.cpp index 8dc23ca821..77911b5323 100644 --- a/src/AppInstallerCLITests/Argument.cpp +++ b/src/AppInstallerCLITests/Argument.cpp @@ -6,7 +6,7 @@ using namespace AppInstaller::CLI; -TEST_CASE("EnsureAllCommandsDefined", "[argument]") +TEST_CASE("EnsureAllArgumentsDefined", "[argument]") { using Arg_t = std::underlying_type_t; for (Arg_t i = static_cast(0); i < static_cast(Execution::Args::Type::Max); ++i) diff --git a/src/AppInstallerCLITests/Command.cpp b/src/AppInstallerCLITests/Command.cpp index 8705fb6c1c..f63d50b718 100644 --- a/src/AppInstallerCLITests/Command.cpp +++ b/src/AppInstallerCLITests/Command.cpp @@ -148,6 +148,8 @@ void EnsureCommandConsistency(const Command& command) // No = allowed in arguments // All positional args should be listed first bool foundNonPositional = false; + bool queryArgPresent = false; + bool multiQueryArgPresent = false; for (const auto& arg : command.GetArguments()) { INFO(command.FullName()); @@ -163,8 +165,20 @@ void EnsureCommandConsistency(const Command& command) { foundNonPositional = true; } + + if (arg.ExecArgType() == Execution::Args::Type::Query) + { + queryArgPresent = true; + } + + if (arg.ExecArgType() == Execution::Args::Type::MultiQuery) + { + multiQueryArgPresent = true; + } } + REQUIRE((!queryArgPresent || !multiQueryArgPresent)); + // Recurse for all subcommands for (const auto& sub : command.GetCommands()) { @@ -181,6 +195,7 @@ void EnsureCommandConsistency(const Command& command) // 6. All argument alias are lower cased // 7. No argument names contain '=' // 8. All positional arguments are first in the list +// 9. No command includes both Query and MultiQuery arguments TEST_CASE("EnsureCommandTreeConsistency", "[command]") { RootCommand root; @@ -237,6 +252,23 @@ void RequireValueParsedToArg(const std::string& value, const Argument& arg, cons REQUIRE(value == args.GetArg(arg.ExecArgType())); } +void RequireValuesParsedToArg(const std::vector& values, Args::Type execArgType, const Args& args) +{ + REQUIRE(args.Contains(execArgType)); + REQUIRE(args.GetCount(execArgType) == values.size()); + + auto argValues = args.GetArgs(execArgType); + for (size_t i = 0; i < values.size(); ++i) + { + REQUIRE(argValues->at(i) == values[i]); + } +} + +void RequireValuesParsedToArg(const std::vector& values, const Argument& arg, const Args& args) +{ + RequireValuesParsedToArg(values, arg.ExecArgType(), args); +} + // Description used for tests; doesn't need to be anything in particular. static constexpr CLI::Resource::StringId DefaultDesc{ L""sv }; @@ -554,3 +586,79 @@ TEST_CASE("ParseArguments_UnknownName", "[command]") REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidNameError(Utility::LocIndView{ values[1] })); } + +TEST_CASE("ParseArguments_PositionalWithMultipleValues", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + }); + + std::vector values{ "value1" "value2", "value3" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + RequireValuesParsedToArg(values, command.m_args[0], args); +} + +TEST_CASE("ParseArguments_PositionalWithMultipleValuesAndOtherArgs", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Source, DefaultDesc, ArgumentType::Positional }, + Argument{ "pos2", 'q', Args::Type::All, DefaultDesc, ArgumentType::Positional }, + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + Argument{ "flag", 'f', Args::Type::BlockingPin, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "positional", "-q", "anotherPos", "multiValue1", "multiValue2", "-f" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + RequireValueParsedToArg(values[0], command.m_args[0], args); + RequireValueParsedToArg(values[2], command.m_args[1], args); + RequireValuesParsedToArg({ values[3], values[4] }, command.m_args[2], args); + REQUIRE(args.Contains(command.m_args[3].ExecArgType())); +} + +TEST_CASE("ParseArguments_PositionalWithMultipleValuesAndName", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + }); + + std::vector values{ "--multi", "one", "two", "three" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + RequireValuesParsedToArg({ values[1], values[2], values[3] }, command.m_args[0], args); +} + +TEST_CASE("ParseArguments_MultiQueryConvertedToSingleQuery", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + }); + + std::vector values{ "singleValue" }; + Invocation inv{ std::vector(values) }; + + // ParseArguments converts MultiQuery args with a single value into Query args + command.ParseArguments(inv, args); + RequireValuesParsedToArg({ values[0] }, Args::Type::Query, args); +} + +TEST_CASE("ParseArguments_PositionalWithTooManyValues", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + }); + + std::vector values{ "1", "2", "3", "4", "5", "tooMany" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::ExtraPositionalError(Utility::LocIndView{ values.back() })); +} \ No newline at end of file diff --git a/src/AppInstallerCLITests/ImportFlow.cpp b/src/AppInstallerCLITests/ImportFlow.cpp index b47017df56..609cefd3cd 100644 --- a/src/AppInstallerCLITests/ImportFlow.cpp +++ b/src/AppInstallerCLITests/ImportFlow.cpp @@ -71,7 +71,7 @@ TEST_CASE("ImportFlow_PackageAlreadyInstalled", "[ImportFlow][workflow]") // Exe should not have been installed again REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportPackageAlreadyInstalled("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageAlreadyInstalled("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); } TEST_CASE("ImportFlow_IgnoreVersions", "[ImportFlow][workflow]") @@ -129,8 +129,8 @@ TEST_CASE("ImportFlow_MissingPackage", "[ImportFlow][workflow]") // Installer should not be called REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportSearchFailed("MissingPackage"_liv)).get()) != std::string::npos); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageNotFound("MissingPackage"_liv)).get()) != std::string::npos); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); } TEST_CASE("ImportFlow_IgnoreMissingPackage", "[ImportFlow][workflow]") @@ -151,7 +151,7 @@ TEST_CASE("ImportFlow_IgnoreMissingPackage", "[ImportFlow][workflow]") // Verify installer was called for the package that was available. REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportSearchFailed("MissingPackage"_liv)).get()) != std::string::npos); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageNotFound("MissingPackage"_liv)).get()) != std::string::npos); } TEST_CASE("ImportFlow_MissingVersion", "[ImportFlow][workflow]") @@ -170,8 +170,8 @@ TEST_CASE("ImportFlow_MissingVersion", "[ImportFlow][workflow]") // Installer should not be called REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportSearchFailed("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQuerySearchFailed("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); } TEST_CASE("ImportFlow_MalformedJsonFile", "[ImportFlow][workflow]") diff --git a/src/AppInstallerCLITests/InstallDependenciesFlow.cpp b/src/AppInstallerCLITests/InstallDependenciesFlow.cpp index 6d6bc8fffc..e63621b3ee 100644 --- a/src/AppInstallerCLITests/InstallDependenciesFlow.cpp +++ b/src/AppInstallerCLITests/InstallDependenciesFlow.cpp @@ -75,7 +75,7 @@ TEST_CASE("DependencyGraph_SkipInstalled", "[InstallFlow][workflow][dependencyGr context << ManagePackageDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies); - auto& dependencyPackages = context.Get(); + auto& dependencyPackages = context.Get(); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); REQUIRE(dependencyPackages.size() == 0); } @@ -100,7 +100,7 @@ TEST_CASE("DependencyGraph_validMinVersions", "[InstallFlow][workflow][dependenc context << ManagePackageDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies); - auto& dependencyPackages = context.Get(); + auto& dependencyPackages = context.Get(); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); REQUIRE(dependencyPackages.size() == 1); @@ -127,7 +127,7 @@ TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph context << ManagePackageDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies); - auto& dependencyPackages = context.Get(); + auto& dependencyPackages = context.Get(); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index d59d090f4c..1b72599dd7 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -1111,3 +1111,42 @@ TEST_CASE("InstallFlowMultiLocale_PreferenceWithBetterLocale", "[InstallFlow][wo std::getline(installResultFile, installResultStr); REQUIRE(installResultStr.find("/en-GB") != std::string::npos); } + +TEST_CASE("InstallFlow_InstallMultiple", "[InstallFlow][workflow][MultiQuery]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + TestCommon::TempFile msixInstallResultPath("TestMsixInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForMSIX(context); + OverrideForShellExecute(context); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix }), true); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + InstallCommand installCommand({}); + installCommand.Execute(context); + INFO(installOutput.str()); + + // Verify all packages were installed + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); + REQUIRE(std::filesystem::exists(msixInstallResultPath.GetPath())); +} + +TEST_CASE("InstallFlow_InstallMultiple_SearchFailed", "[InstallFlow][workflow][MultiQuery]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestInstaller_Exe }), true); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + InstallCommand installCommand({}); + installCommand.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); +} \ No newline at end of file diff --git a/src/AppInstallerCLITests/UninstallFlow.cpp b/src/AppInstallerCLITests/UninstallFlow.cpp index f1e324f354..480c830e41 100644 --- a/src/AppInstallerCLITests/UninstallFlow.cpp +++ b/src/AppInstallerCLITests/UninstallFlow.cpp @@ -171,3 +171,42 @@ TEST_CASE("UninstallFlow_UninstallExeNotFound", "[UninstallFlow][workflow]") REQUIRE(uninstallOutput.str().find(Resource::LocString(Resource::String::NoInstalledPackageFound).get()) != std::string::npos); REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); } + +TEST_CASE("UninstallFlow_UninstallMultiple", "[UninstallFlow][workflow][MultiQuery]") +{ + TestCommon::TempFile exeUninstallResultPath("TestExeUninstalled.txt"); + TestCommon::TempFile msixUninstallResultPath("TestMsixUninstalled.txt"); + + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix })); + OverrideForExeUninstall(context); + OverrideForMSIXUninstall(context); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + + // Verify Uninstallers are called + REQUIRE(std::filesystem::exists(exeUninstallResultPath.GetPath())); + REQUIRE(std::filesystem::exists(msixUninstallResultPath.GetPath())); +} + +TEST_CASE("UninstallFlow_UninstallMultiple_NotAllInstalled", "[UninstallFlow][workflow][MultiQuery]") +{ + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); +} diff --git a/src/AppInstallerCLITests/UpdateFlow.cpp b/src/AppInstallerCLITests/UpdateFlow.cpp index 124df3f86c..88abd06e9d 100644 --- a/src/AppInstallerCLITests/UpdateFlow.cpp +++ b/src/AppInstallerCLITests/UpdateFlow.cpp @@ -947,4 +947,60 @@ TEST_CASE("UpdateFlow_UpdateAll_ForwardArgs", "[UpdateFlow][workflow]") REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpdateMultiple", "[UpdateFlow][workflow][MultiQuery]") +{ + TestCommon::TempFile exeUpdateResultPath("TestExeInstalled.txt"); + TestCommon::TempFile msixUpdateResultPath("TestMsixInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix })); + OverrideForShellExecute(context); + OverrideForMSIX(context); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installers are called called. + REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); + REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpdateMultiple_NotAllFound", "[UpdateFlow][workflow][MultiQuery]") +{ + TestCommon::TempFile exeUpdateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + SECTION("Ignore unavailable") + { + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::IgnoreUnavailable); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE(!context.IsTerminated()); + REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); + } + SECTION("Don't ignore unavailable") + { + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp index a7bf5083a6..4832db1206 100644 --- a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp +++ b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -1,137 +1,137 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerTelemetry.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerSHA256.h" -#include "Public/AppInstallerStrings.h" -#include "winget/UserSettings.h" -#include "Public/winget/ThreadGlobals.h" - -#define AICLI_TraceLoggingStringView(_sv_,_name_) TraceLoggingCountedUtf8String(_sv_.data(), static_cast(_sv_.size()), _name_) -#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast(_sv_.size()), _name_) - -#define AICLI_TraceLoggingWriteActivity(_eventName_,...) TraceLoggingWriteActivity(\ -g_hTraceProvider,\ -_eventName_,\ -s_useGlobalTelemetryActivityId ? &s_globalTelemetryLoggerActivityId : GetActivityId(),\ -nullptr,\ -TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast(m_caller.size()), "Caller"),\ -TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"),\ -__VA_ARGS__) - -// Helper to print a GUID -std::ostream& operator<<(std::ostream& out, const GUID& guid) -{ - wchar_t buffer[256]; - - if (StringFromGUID2(guid, buffer, ARRAYSIZE(buffer))) - { - out << AppInstaller::Utility::ConvertToUTF8(buffer); - } - else - { - out << "error"; - } - - return out; -} - -namespace AppInstaller::Logging -{ - using namespace Utility; - - namespace - { - // TODO: This and all usages should be removed after transition to summary event in back end. - static const uint32_t s_RootExecutionId = 0; - static std::atomic_uint32_t s_subExecutionId{ s_RootExecutionId }; - - constexpr std::wstring_view s_UserProfileReplacement = L"%USERPROFILE%"sv; - - // TODO: Temporary code to keep existing telemetry behavior - static bool s_useGlobalTelemetryActivityId = false; - static GUID s_globalTelemetryLoggerActivityId = GUID_NULL; - - void __stdcall wilResultLoggingCallback(const wil::FailureInfo& info) noexcept - { - Telemetry().LogFailure(info); - } - - FailureTypeEnum ConvertWilFailureTypeToFailureType(wil::FailureType failureType) - { - switch (failureType) - { - case wil::FailureType::Exception: - return FailureTypeEnum::ResultException; - case wil::FailureType::Return: - return FailureTypeEnum::ResultReturn; - case wil::FailureType::Log: - return FailureTypeEnum::ResultLog; - case wil::FailureType::FailFast: - return FailureTypeEnum::ResultFailFast; - default: - return FailureTypeEnum::Unknown; - } - } - - std::string_view LogExceptionTypeToString(FailureTypeEnum exceptionType) - { - switch (exceptionType) - { - case FailureTypeEnum::ResultException: - return "wil::ResultException"sv; - case FailureTypeEnum::WinrtHResultError: - return "winrt::hresult_error"sv; - case FailureTypeEnum::ResourceOpen: - return "ResourceOpenException"sv; - case FailureTypeEnum::StdException: - return "std::exception"sv; - case FailureTypeEnum::Unknown: - default: - return "unknown"sv; - } - } - } - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerTelemetry.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerSHA256.h" +#include "Public/AppInstallerStrings.h" +#include "winget/UserSettings.h" +#include "Public/winget/ThreadGlobals.h" + +#define AICLI_TraceLoggingStringView(_sv_,_name_) TraceLoggingCountedUtf8String(_sv_.data(), static_cast(_sv_.size()), _name_) +#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast(_sv_.size()), _name_) + +#define AICLI_TraceLoggingWriteActivity(_eventName_,...) TraceLoggingWriteActivity(\ +g_hTraceProvider,\ +_eventName_,\ +s_useGlobalTelemetryActivityId ? &s_globalTelemetryLoggerActivityId : GetActivityId(),\ +nullptr,\ +TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast(m_caller.size()), "Caller"),\ +TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"),\ +__VA_ARGS__) + +// Helper to print a GUID +std::ostream& operator<<(std::ostream& out, const GUID& guid) +{ + wchar_t buffer[256]; + + if (StringFromGUID2(guid, buffer, ARRAYSIZE(buffer))) + { + out << AppInstaller::Utility::ConvertToUTF8(buffer); + } + else + { + out << "error"; + } + + return out; +} + +namespace AppInstaller::Logging +{ + using namespace Utility; + + namespace + { + // TODO: This and all usages should be removed after transition to summary event in back end. + static const uint32_t s_RootExecutionId = 0; + static std::atomic_uint32_t s_subExecutionId{ s_RootExecutionId }; + + constexpr std::wstring_view s_UserProfileReplacement = L"%USERPROFILE%"sv; + + // TODO: Temporary code to keep existing telemetry behavior + static bool s_useGlobalTelemetryActivityId = false; + static GUID s_globalTelemetryLoggerActivityId = GUID_NULL; + + void __stdcall wilResultLoggingCallback(const wil::FailureInfo& info) noexcept + { + Telemetry().LogFailure(info); + } + + FailureTypeEnum ConvertWilFailureTypeToFailureType(wil::FailureType failureType) + { + switch (failureType) + { + case wil::FailureType::Exception: + return FailureTypeEnum::ResultException; + case wil::FailureType::Return: + return FailureTypeEnum::ResultReturn; + case wil::FailureType::Log: + return FailureTypeEnum::ResultLog; + case wil::FailureType::FailFast: + return FailureTypeEnum::ResultFailFast; + default: + return FailureTypeEnum::Unknown; + } + } + + std::string_view LogExceptionTypeToString(FailureTypeEnum exceptionType) + { + switch (exceptionType) + { + case FailureTypeEnum::ResultException: + return "wil::ResultException"sv; + case FailureTypeEnum::WinrtHResultError: + return "winrt::hresult_error"sv; + case FailureTypeEnum::ResourceOpen: + return "ResourceOpenException"sv; + case FailureTypeEnum::StdException: + return "std::exception"sv; + case FailureTypeEnum::Unknown: + default: + return "unknown"sv; + } + } + } + TelemetrySummary::TelemetrySummary(const TelemetrySummary& other) { this->IsCOMCall = other.IsCOMCall; - } - - TelemetryTraceLogger::TelemetryTraceLogger(bool useSummary) : m_useSummary(useSummary) - { - std::ignore = CoCreateGuid(&m_activityId); - m_subExecutionId = s_RootExecutionId; - } - - const GUID* TelemetryTraceLogger::GetActivityId() const - { - return &m_activityId; - } - - const GUID* TelemetryTraceLogger::GetParentActivityId() const - { - return &m_parentActivityId; - } - - bool TelemetryTraceLogger::DisableRuntime() - { - return m_isRuntimeEnabled.exchange(false); - } - - void TelemetryTraceLogger::EnableRuntime() - { - m_isRuntimeEnabled = true; - } - - void TelemetryTraceLogger::Initialize() - { + } + + TelemetryTraceLogger::TelemetryTraceLogger(bool useSummary) : m_useSummary(useSummary) + { + std::ignore = CoCreateGuid(&m_activityId); + m_subExecutionId = s_RootExecutionId; + } + + const GUID* TelemetryTraceLogger::GetActivityId() const + { + return &m_activityId; + } + + const GUID* TelemetryTraceLogger::GetParentActivityId() const + { + return &m_parentActivityId; + } + + bool TelemetryTraceLogger::DisableRuntime() + { + return m_isRuntimeEnabled.exchange(false); + } + + void TelemetryTraceLogger::EnableRuntime() + { + m_isRuntimeEnabled = true; + } + + void TelemetryTraceLogger::Initialize() + { if (!m_isInitialized) { InitializeInternal(Settings::User()); - } + } } bool TelemetryTraceLogger::TryInitialize() @@ -146,45 +146,45 @@ namespace AppInstaller::Logging } return m_isInitialized; - } - - void TelemetryTraceLogger::SetTelemetryCorrelationJson(const std::wstring_view jsonStr_view) noexcept - { - // Check if passed in string is a valid Json formatted before returning the value - // If invalid, return empty Json - Json::CharReaderBuilder jsonBuilder; - std::unique_ptr jsonReader(jsonBuilder.newCharReader()); - std::unique_ptr pJsonValue = std::make_unique(); - std::string errors; - std::wstring jsonStrW{ jsonStr_view }; - std::string jsonStr = ConvertToUTF8(jsonStrW.c_str()); - - bool result = jsonReader->parse(jsonStr.c_str(), - jsonStr.c_str() + jsonStr.size(), - pJsonValue.get(), - &errors); - - if (result) - { - m_telemetryCorrelationJsonW = jsonStrW; - AICLI_LOG(Core, Info, << "Passed in Correlation Vector Json is valid: " << jsonStr); - } - else - { - AICLI_LOG(Core, Error, << "Passed in Correlation Vector Json is invalid: " << jsonStr << "; Error: " << errors); - } - } - - void TelemetryTraceLogger::SetCaller(const std::string& caller) - { - auto callerUTF16 = Utility::ConvertToUTF16(caller); - auto anonCaller = AnonymizeString(callerUTF16); - m_caller = Utility::ConvertToUTF8(anonCaller); - } - - void TelemetryTraceLogger::SetExecutionStage(uint32_t stage) noexcept - { - m_executionStage = stage; + } + + void TelemetryTraceLogger::SetTelemetryCorrelationJson(const std::wstring_view jsonStr_view) noexcept + { + // Check if passed in string is a valid Json formatted before returning the value + // If invalid, return empty Json + Json::CharReaderBuilder jsonBuilder; + std::unique_ptr jsonReader(jsonBuilder.newCharReader()); + std::unique_ptr pJsonValue = std::make_unique(); + std::string errors; + std::wstring jsonStrW{ jsonStr_view }; + std::string jsonStr = ConvertToUTF8(jsonStrW.c_str()); + + bool result = jsonReader->parse(jsonStr.c_str(), + jsonStr.c_str() + jsonStr.size(), + pJsonValue.get(), + &errors); + + if (result) + { + m_telemetryCorrelationJsonW = jsonStrW; + AICLI_LOG(Core, Info, << "Passed in Correlation Vector Json is valid: " << jsonStr); + } + else + { + AICLI_LOG(Core, Error, << "Passed in Correlation Vector Json is invalid: " << jsonStr << "; Error: " << errors); + } + } + + void TelemetryTraceLogger::SetCaller(const std::string& caller) + { + auto callerUTF16 = Utility::ConvertToUTF16(caller); + auto anonCaller = AnonymizeString(callerUTF16); + m_caller = Utility::ConvertToUTF8(anonCaller); + } + + void TelemetryTraceLogger::SetExecutionStage(uint32_t stage) noexcept + { + m_executionStage = stage; } std::unique_ptr TelemetryTraceLogger::CreateSubTraceLogger() const @@ -197,701 +197,701 @@ namespace AppInstaller::Logging subTraceLogger->m_subExecutionId = s_subExecutionId++; return subTraceLogger; - } - - void TelemetryTraceLogger::LogFailure(const wil::FailureInfo& failure) const noexcept - { - if (IsTelemetryEnabled()) - { - auto anonMessage = AnonymizeString(failure.pszMessage); - - AICLI_TraceLoggingWriteActivity( - "FailureInfo", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingHResult(failure.hr, "HResult"), - AICLI_TraceLoggingWStringView(anonMessage, "Message"), - TraceLoggingString(failure.pszModule, "Module"), - TraceLoggingUInt32(failure.threadId, "ThreadId"), - TraceLoggingUInt32(static_cast(failure.type), "Type"), - TraceLoggingString(failure.pszFile, "File"), - TraceLoggingUInt32(failure.uLineNumber, "Line"), - TraceLoggingUInt32(m_executionStage, "ExecutionStage"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.FailureHResult = failure.hr; - m_summary.FailureMessage = anonMessage; - m_summary.FailureModule = StringOrEmptyIfNull(failure.pszModule); - m_summary.FailureThreadId = failure.threadId; - m_summary.FailureType = ConvertWilFailureTypeToFailureType(failure.type); - m_summary.FailureFile = StringOrEmptyIfNull(failure.pszFile); - m_summary.FailureLine = failure.uLineNumber; - } - } - - // Also send failure to the log - AICLI_LOG(Fail, Error, << [&]() { - wchar_t message[2048]; - GetFailureLogString(message, ARRAYSIZE(message), failure); - return Utility::ConvertToUTF8(message); - }()); - } - - void TelemetryTraceLogger::LogStartup(bool isCOMCall) const noexcept - { - LocIndString version = Runtime::GetClientVersion(); - LocIndString packageVersion; - if (Runtime::IsRunningInPackagedContext()) - { - packageVersion = Runtime::GetPackageVersion(); - } - - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "ClientVersion", - TraceLoggingBool(isCOMCall, "IsCOMCall"), - TraceLoggingCountedString(version->c_str(), static_cast(version->size()), "Version"), - TraceLoggingCountedString(packageVersion->c_str(), static_cast(packageVersion->size()), "PackageVersion"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.IsCOMCall = isCOMCall; - } - } - - AICLI_LOG(Core, Info, << "WinGet, version [" << version << "], activity [" << *GetActivityId() << ']'); - AICLI_LOG(Core, Info, << "OS: " << Runtime::GetOSVersion()); - AICLI_LOG(Core, Info, << "Command line Args: " << Utility::ConvertToUTF8(GetCommandLineW())); - if (Runtime::IsRunningInPackagedContext()) - { - AICLI_LOG(Core, Info, << "Package: " << packageVersion); - } - AICLI_LOG(Core, Info, << "IsCOMCall:" << isCOMCall << "; Caller: " << m_caller); - } - - void TelemetryTraceLogger::LogCommand(std::string_view commandName) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "CommandFound", - AICLI_TraceLoggingStringView(commandName, "Command"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.Command = commandName; - } - } - - AICLI_LOG(CLI, Info, << "Leaf command to execute: " << commandName); - } - - void TelemetryTraceLogger::LogCommandSuccess(std::string_view commandName) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "CommandSuccess", - AICLI_TraceLoggingStringView(commandName, "Command"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.CommandSuccess = true; - } - } - - AICLI_LOG(CLI, Info, << "Leaf command succeeded: " << commandName); - } - - void TelemetryTraceLogger::LogCommandTermination(HRESULT hr, std::string_view file, size_t line) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "CommandTermination", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingHResult(hr, "HResult"), - AICLI_TraceLoggingStringView(file, "File"), - TraceLoggingUInt64(static_cast(line), "Line"), - TraceLoggingUInt32(m_executionStage, "ExecutionStage"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.FailureHResult = hr; - m_summary.FailureType = FailureTypeEnum::CommandTermination; - m_summary.FailureFile = file; - m_summary.FailureLine = static_cast(line); - } - } - - AICLI_LOG(CLI, Error, << "Terminating context: 0x" << SetHRFormat << hr << " at " << file << ":" << line); - } - - void TelemetryTraceLogger::LogException(FailureTypeEnum type, std::string_view message) const noexcept - { - auto exceptionTypeString = LogExceptionTypeToString(type); - - if (IsTelemetryEnabled()) - { - auto anonMessage = AnonymizeString(Utility::ConvertToUTF16(message)); - - AICLI_TraceLoggingWriteActivity( - "Exception", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(exceptionTypeString, "Type"), - AICLI_TraceLoggingWStringView(anonMessage, "Message"), - TraceLoggingUInt32(m_executionStage, "ExecutionStage"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.FailureType = type; - m_summary.FailureMessage = anonMessage; - } - } - - AICLI_LOG(CLI, Error, << "Caught " << exceptionTypeString << ": " << message); - } - - void TelemetryTraceLogger::LogIsManifestLocal(bool isLocalManifest) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "GetManifest", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingBool(isLocalManifest, "IsManifestLocal"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.IsManifestLocal = isLocalManifest; - } - } - } - - void TelemetryTraceLogger::LogManifestFields(std::string_view id, std::string_view name, std::string_view version) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "ManifestFields", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(name, "Name"), - AICLI_TraceLoggingStringView(version, "Version"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageName = name; - m_summary.PackageVersion = version; - } - } - - AICLI_LOG(CLI, Info, << "Manifest fields: Name [" << name << "], Version [" << version << ']'); - } - - void TelemetryTraceLogger::LogNoAppMatch() const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "NoAppMatch", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - } - - AICLI_LOG(CLI, Info, << "No app found matching input criteria"); - } - - void TelemetryTraceLogger::LogMultiAppMatch() const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "MultiAppMatch", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - } - - AICLI_LOG(CLI, Info, << "Multiple apps found matching input criteria"); - } - - void TelemetryTraceLogger::LogAppFound(std::string_view name, std::string_view id) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "AppFound", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(name, "Name"), - AICLI_TraceLoggingStringView(id, "Id"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageName = name; - } - } - - AICLI_LOG(CLI, Info, << "Found one app. App id: " << id << " App name: " << name); - } - - void TelemetryTraceLogger::LogSelectedInstaller(int arch, std::string_view url, std::string_view installerType, std::string_view scope, std::string_view language) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "SelectedInstaller", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingInt32(arch, "Arch"), - AICLI_TraceLoggingStringView(url, "Url"), - AICLI_TraceLoggingStringView(installerType, "InstallerType"), - AICLI_TraceLoggingStringView(scope, "Scope"), - AICLI_TraceLoggingStringView(language, "Language"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.InstallerArchitecture = arch; - m_summary.InstallerUrl = url; - m_summary.InstallerType = installerType; - m_summary.InstallerScope = scope; - m_summary.InstallerLocale = language; - } - } - - AICLI_LOG(CLI, Info, << "Completed installer selection."); - AICLI_LOG(CLI, Verbose, << "Selected installer Architecture: " << arch); - AICLI_LOG(CLI, Verbose, << "Selected installer URL: " << url); - AICLI_LOG(CLI, Verbose, << "Selected installer InstallerType: " << installerType); - AICLI_LOG(CLI, Verbose, << "Selected installer Scope: " << scope); - AICLI_LOG(CLI, Verbose, << "Selected installer Language: " << language); - } - - void TelemetryTraceLogger::LogSearchRequest( - std::string_view type, - std::string_view query, - std::string_view id, - std::string_view name, - std::string_view moniker, - std::string_view tag, - std::string_view command, - size_t maximum, - std::string_view request) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "SearchRequest", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(type, "Type"), - AICLI_TraceLoggingStringView(query, "Query"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(name, "Name"), - AICLI_TraceLoggingStringView(moniker, "Moniker"), - AICLI_TraceLoggingStringView(tag, "Tag"), - AICLI_TraceLoggingStringView(command, "Command"), - TraceLoggingUInt64(static_cast(maximum), "Maximum"), - AICLI_TraceLoggingStringView(request, "Request"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.SearchType = type; - m_summary.SearchQuery = query; - m_summary.SearchId = id; - m_summary.SearchName = name; - m_summary.SearchMoniker = moniker; - m_summary.SearchTag = tag; - m_summary.SearchCommand = command; - m_summary.SearchMaximum = static_cast(maximum); - m_summary.SearchRequest = request; - } - } - } - - void TelemetryTraceLogger::LogSearchResultCount(uint64_t resultCount) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "SearchResultCount", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingUInt64(resultCount, "ResultCount"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.SearchResultCount = resultCount; - } - } - } - - void TelemetryTraceLogger::LogInstallerHashMismatch( - std::string_view id, - std::string_view version, - std::string_view channel, - const std::vector& expected, - const std::vector& actual, - bool overrideHashMismatch) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "HashMismatch", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(version, "Version"), - AICLI_TraceLoggingStringView(channel, "Channel"), - TraceLoggingBinary(expected.data(), static_cast(expected.size()), "Expected"), - TraceLoggingBinary(actual.data(), static_cast(actual.size()), "Actual"), - TraceLoggingBool(overrideHashMismatch, "Override"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageVersion = version; - m_summary.Channel = channel; - m_summary.HashMismatchExpected = expected; - m_summary.HashMismatchActual = actual; - m_summary.HashMismatchOverride = overrideHashMismatch; - } - } - - AICLI_LOG(CLI, Error, - << "Package hash verification failed. SHA256 in manifest [" - << Utility::SHA256::ConvertToString(expected) - << "] does not match download [" - << Utility::SHA256::ConvertToString(actual) - << ']'); - } - - void TelemetryTraceLogger::LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "InstallerFailure", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(version, "Version"), - AICLI_TraceLoggingStringView(channel, "Channel"), - AICLI_TraceLoggingStringView(type, "Type"), - TraceLoggingUInt32(errorCode, "ErrorCode"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageVersion = version; - m_summary.Channel = channel; - m_summary.InstallerExecutionType = type; - m_summary.InstallerErrorCode = errorCode; - } - } - - AICLI_LOG(CLI, Error, << type << " installer failed: " << errorCode); - } - - void TelemetryTraceLogger::LogUninstallerFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "UninstallerFailure", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(version, "Version"), - AICLI_TraceLoggingStringView(type, "Type"), - TraceLoggingUInt32(errorCode, "ErrorCode"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageVersion = version; - m_summary.UninstallerExecutionType = type; - m_summary.UninstallerErrorCode = errorCode; - } - } - - AICLI_LOG(CLI, Error, << type << " uninstaller failed: " << errorCode); - } - - void TelemetryTraceLogger::LogSuccessfulInstallARPChange( - std::string_view sourceIdentifier, - std::string_view packageIdentifier, - std::string_view packageVersion, - std::string_view packageChannel, - size_t changesToARP, - size_t matchesInARP, - size_t countOfIntersectionOfChangesAndMatches, - std::string_view arpName, - std::string_view arpVersion, - std::string_view arpPublisher, - std::string_view arpLanguage) const noexcept - { - if (IsTelemetryEnabled()) - { - size_t languageNumber = 0xFFFF; - - try - { - std::istringstream languageConversion{ std::string{ arpLanguage } }; - languageConversion >> languageNumber; - } - catch (...) {} - - AICLI_TraceLoggingWriteActivity( - "InstallARPChange", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(sourceIdentifier, "SourceIdentifier"), - AICLI_TraceLoggingStringView(packageIdentifier, "PackageIdentifier"), - AICLI_TraceLoggingStringView(packageVersion, "PackageVersion"), - AICLI_TraceLoggingStringView(packageChannel, "PackageChannel"), - TraceLoggingUInt64(static_cast(changesToARP), "ChangesToARP"), - TraceLoggingUInt64(static_cast(matchesInARP), "MatchesInARP"), - TraceLoggingUInt64(static_cast(countOfIntersectionOfChangesAndMatches), "ChangesThatMatch"), - AICLI_TraceLoggingStringView(arpName, "ARPName"), - AICLI_TraceLoggingStringView(arpVersion, "ARPVersion"), - AICLI_TraceLoggingStringView(arpPublisher, "ARPPublisher"), - TraceLoggingUInt64(static_cast(languageNumber), "ARPLanguage"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.SourceIdentifier = sourceIdentifier; - m_summary.PackageIdentifier = packageIdentifier; - m_summary.PackageVersion = packageVersion; - m_summary.Channel = packageChannel; - m_summary.ChangesToARP = static_cast(changesToARP); - m_summary.MatchesInARP = static_cast(matchesInARP); - m_summary.ChangesThatMatch = static_cast(countOfIntersectionOfChangesAndMatches); - m_summary.ARPName = arpName; - m_summary.ARPVersion = arpVersion; - m_summary.ARPPublisher = arpPublisher; - m_summary.ARPLanguage = static_cast(languageNumber); - } - } - - AICLI_LOG(CLI, Info, << "During package install, " << changesToARP << " changes to ARP were observed, " - << matchesInARP << " matches were found for the package, and " << countOfIntersectionOfChangesAndMatches << " packages were in both"); - - if (arpName.empty()) - { - AICLI_LOG(CLI, Info, << "No single entry was determined to be associated with the package"); - } - else - { - AICLI_LOG(CLI, Info, << "The entry determined to be associated with the package is '" << arpName << "', with publisher '" << arpPublisher << "'"); - } - } - - void TelemetryTraceLogger::LogNonFatalDOError(std::string_view url, HRESULT hr) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "NonFatalDOError", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(url, "Url"), - TraceLoggingHResult(hr, "HResult"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); - - if (m_useSummary) - { - m_summary.DOUrl = url; - m_summary.DOHResult = hr; - } - } - } - + } + + void TelemetryTraceLogger::LogFailure(const wil::FailureInfo& failure) const noexcept + { + if (IsTelemetryEnabled()) + { + auto anonMessage = AnonymizeString(failure.pszMessage); + + AICLI_TraceLoggingWriteActivity( + "FailureInfo", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingHResult(failure.hr, "HResult"), + AICLI_TraceLoggingWStringView(anonMessage, "Message"), + TraceLoggingString(failure.pszModule, "Module"), + TraceLoggingUInt32(failure.threadId, "ThreadId"), + TraceLoggingUInt32(static_cast(failure.type), "Type"), + TraceLoggingString(failure.pszFile, "File"), + TraceLoggingUInt32(failure.uLineNumber, "Line"), + TraceLoggingUInt32(m_executionStage, "ExecutionStage"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.FailureHResult = failure.hr; + m_summary.FailureMessage = anonMessage; + m_summary.FailureModule = StringOrEmptyIfNull(failure.pszModule); + m_summary.FailureThreadId = failure.threadId; + m_summary.FailureType = ConvertWilFailureTypeToFailureType(failure.type); + m_summary.FailureFile = StringOrEmptyIfNull(failure.pszFile); + m_summary.FailureLine = failure.uLineNumber; + } + } + + // Also send failure to the log + AICLI_LOG(Fail, Error, << [&]() { + wchar_t message[2048]; + GetFailureLogString(message, ARRAYSIZE(message), failure); + return Utility::ConvertToUTF8(message); + }()); + } + + void TelemetryTraceLogger::LogStartup(bool isCOMCall) const noexcept + { + LocIndString version = Runtime::GetClientVersion(); + LocIndString packageVersion; + if (Runtime::IsRunningInPackagedContext()) + { + packageVersion = Runtime::GetPackageVersion(); + } + + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "ClientVersion", + TraceLoggingBool(isCOMCall, "IsCOMCall"), + TraceLoggingCountedString(version->c_str(), static_cast(version->size()), "Version"), + TraceLoggingCountedString(packageVersion->c_str(), static_cast(packageVersion->size()), "PackageVersion"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.IsCOMCall = isCOMCall; + } + } + + AICLI_LOG(Core, Info, << "WinGet, version [" << version << "], activity [" << *GetActivityId() << ']'); + AICLI_LOG(Core, Info, << "OS: " << Runtime::GetOSVersion()); + AICLI_LOG(Core, Info, << "Command line Args: " << Utility::ConvertToUTF8(GetCommandLineW())); + if (Runtime::IsRunningInPackagedContext()) + { + AICLI_LOG(Core, Info, << "Package: " << packageVersion); + } + AICLI_LOG(Core, Info, << "IsCOMCall:" << isCOMCall << "; Caller: " << m_caller); + } + + void TelemetryTraceLogger::LogCommand(std::string_view commandName) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "CommandFound", + AICLI_TraceLoggingStringView(commandName, "Command"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.Command = commandName; + } + } + + AICLI_LOG(CLI, Info, << "Leaf command to execute: " << commandName); + } + + void TelemetryTraceLogger::LogCommandSuccess(std::string_view commandName) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "CommandSuccess", + AICLI_TraceLoggingStringView(commandName, "Command"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.CommandSuccess = true; + } + } + + AICLI_LOG(CLI, Info, << "Leaf command succeeded: " << commandName); + } + + void TelemetryTraceLogger::LogCommandTermination(HRESULT hr, std::string_view file, size_t line) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "CommandTermination", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingHResult(hr, "HResult"), + AICLI_TraceLoggingStringView(file, "File"), + TraceLoggingUInt64(static_cast(line), "Line"), + TraceLoggingUInt32(m_executionStage, "ExecutionStage"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.FailureHResult = hr; + m_summary.FailureType = FailureTypeEnum::CommandTermination; + m_summary.FailureFile = file; + m_summary.FailureLine = static_cast(line); + } + } + + AICLI_LOG(CLI, Error, << "Terminating context: 0x" << SetHRFormat << hr << " at " << file << ":" << line); + } + + void TelemetryTraceLogger::LogException(FailureTypeEnum type, std::string_view message) const noexcept + { + auto exceptionTypeString = LogExceptionTypeToString(type); + + if (IsTelemetryEnabled()) + { + auto anonMessage = AnonymizeString(Utility::ConvertToUTF16(message)); + + AICLI_TraceLoggingWriteActivity( + "Exception", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(exceptionTypeString, "Type"), + AICLI_TraceLoggingWStringView(anonMessage, "Message"), + TraceLoggingUInt32(m_executionStage, "ExecutionStage"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.FailureType = type; + m_summary.FailureMessage = anonMessage; + } + } + + AICLI_LOG(CLI, Error, << "Caught " << exceptionTypeString << ": " << message); + } + + void TelemetryTraceLogger::LogIsManifestLocal(bool isLocalManifest) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "GetManifest", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingBool(isLocalManifest, "IsManifestLocal"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.IsManifestLocal = isLocalManifest; + } + } + } + + void TelemetryTraceLogger::LogManifestFields(std::string_view id, std::string_view name, std::string_view version) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "ManifestFields", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(name, "Name"), + AICLI_TraceLoggingStringView(version, "Version"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageName = name; + m_summary.PackageVersion = version; + } + } + + AICLI_LOG(CLI, Info, << "Manifest fields: Name [" << name << "], Version [" << version << ']'); + } + + void TelemetryTraceLogger::LogNoAppMatch() const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "NoAppMatch", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + } + + AICLI_LOG(CLI, Info, << "No app found matching input criteria"); + } + + void TelemetryTraceLogger::LogMultiAppMatch() const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "MultiAppMatch", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + } + + AICLI_LOG(CLI, Info, << "Multiple apps found matching input criteria"); + } + + void TelemetryTraceLogger::LogAppFound(std::string_view name, std::string_view id) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "AppFound", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(name, "Name"), + AICLI_TraceLoggingStringView(id, "Id"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageName = name; + } + } + + AICLI_LOG(CLI, Info, << "Found one app. App id: " << id << " App name: " << name); + } + + void TelemetryTraceLogger::LogSelectedInstaller(int arch, std::string_view url, std::string_view installerType, std::string_view scope, std::string_view language) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "SelectedInstaller", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingInt32(arch, "Arch"), + AICLI_TraceLoggingStringView(url, "Url"), + AICLI_TraceLoggingStringView(installerType, "InstallerType"), + AICLI_TraceLoggingStringView(scope, "Scope"), + AICLI_TraceLoggingStringView(language, "Language"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.InstallerArchitecture = arch; + m_summary.InstallerUrl = url; + m_summary.InstallerType = installerType; + m_summary.InstallerScope = scope; + m_summary.InstallerLocale = language; + } + } + + AICLI_LOG(CLI, Info, << "Completed installer selection."); + AICLI_LOG(CLI, Verbose, << "Selected installer Architecture: " << arch); + AICLI_LOG(CLI, Verbose, << "Selected installer URL: " << url); + AICLI_LOG(CLI, Verbose, << "Selected installer InstallerType: " << installerType); + AICLI_LOG(CLI, Verbose, << "Selected installer Scope: " << scope); + AICLI_LOG(CLI, Verbose, << "Selected installer Language: " << language); + } + + void TelemetryTraceLogger::LogSearchRequest( + std::string_view type, + std::string_view query, + std::string_view id, + std::string_view name, + std::string_view moniker, + std::string_view tag, + std::string_view command, + size_t maximum, + std::string_view request) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "SearchRequest", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(type, "Type"), + AICLI_TraceLoggingStringView(query, "Query"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(name, "Name"), + AICLI_TraceLoggingStringView(moniker, "Moniker"), + AICLI_TraceLoggingStringView(tag, "Tag"), + AICLI_TraceLoggingStringView(command, "Command"), + TraceLoggingUInt64(static_cast(maximum), "Maximum"), + AICLI_TraceLoggingStringView(request, "Request"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.SearchType = type; + m_summary.SearchQuery = query; + m_summary.SearchId = id; + m_summary.SearchName = name; + m_summary.SearchMoniker = moniker; + m_summary.SearchTag = tag; + m_summary.SearchCommand = command; + m_summary.SearchMaximum = static_cast(maximum); + m_summary.SearchRequest = request; + } + } + } + + void TelemetryTraceLogger::LogSearchResultCount(uint64_t resultCount) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "SearchResultCount", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingUInt64(resultCount, "ResultCount"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.SearchResultCount = resultCount; + } + } + } + + void TelemetryTraceLogger::LogInstallerHashMismatch( + std::string_view id, + std::string_view version, + std::string_view channel, + const std::vector& expected, + const std::vector& actual, + bool overrideHashMismatch) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "HashMismatch", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(version, "Version"), + AICLI_TraceLoggingStringView(channel, "Channel"), + TraceLoggingBinary(expected.data(), static_cast(expected.size()), "Expected"), + TraceLoggingBinary(actual.data(), static_cast(actual.size()), "Actual"), + TraceLoggingBool(overrideHashMismatch, "Override"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageVersion = version; + m_summary.Channel = channel; + m_summary.HashMismatchExpected = expected; + m_summary.HashMismatchActual = actual; + m_summary.HashMismatchOverride = overrideHashMismatch; + } + } + + AICLI_LOG(CLI, Error, + << "Package hash verification failed. SHA256 in manifest [" + << Utility::SHA256::ConvertToString(expected) + << "] does not match download [" + << Utility::SHA256::ConvertToString(actual) + << ']'); + } + + void TelemetryTraceLogger::LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "InstallerFailure", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(version, "Version"), + AICLI_TraceLoggingStringView(channel, "Channel"), + AICLI_TraceLoggingStringView(type, "Type"), + TraceLoggingUInt32(errorCode, "ErrorCode"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageVersion = version; + m_summary.Channel = channel; + m_summary.InstallerExecutionType = type; + m_summary.InstallerErrorCode = errorCode; + } + } + + AICLI_LOG(CLI, Error, << type << " installer failed: " << errorCode); + } + + void TelemetryTraceLogger::LogUninstallerFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "UninstallerFailure", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(version, "Version"), + AICLI_TraceLoggingStringView(type, "Type"), + TraceLoggingUInt32(errorCode, "ErrorCode"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageVersion = version; + m_summary.UninstallerExecutionType = type; + m_summary.UninstallerErrorCode = errorCode; + } + } + + AICLI_LOG(CLI, Error, << type << " uninstaller failed: " << errorCode); + } + + void TelemetryTraceLogger::LogSuccessfulInstallARPChange( + std::string_view sourceIdentifier, + std::string_view packageIdentifier, + std::string_view packageVersion, + std::string_view packageChannel, + size_t changesToARP, + size_t matchesInARP, + size_t countOfIntersectionOfChangesAndMatches, + std::string_view arpName, + std::string_view arpVersion, + std::string_view arpPublisher, + std::string_view arpLanguage) const noexcept + { + if (IsTelemetryEnabled()) + { + size_t languageNumber = 0xFFFF; + + try + { + std::istringstream languageConversion{ std::string{ arpLanguage } }; + languageConversion >> languageNumber; + } + catch (...) {} + + AICLI_TraceLoggingWriteActivity( + "InstallARPChange", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(sourceIdentifier, "SourceIdentifier"), + AICLI_TraceLoggingStringView(packageIdentifier, "PackageIdentifier"), + AICLI_TraceLoggingStringView(packageVersion, "PackageVersion"), + AICLI_TraceLoggingStringView(packageChannel, "PackageChannel"), + TraceLoggingUInt64(static_cast(changesToARP), "ChangesToARP"), + TraceLoggingUInt64(static_cast(matchesInARP), "MatchesInARP"), + TraceLoggingUInt64(static_cast(countOfIntersectionOfChangesAndMatches), "ChangesThatMatch"), + AICLI_TraceLoggingStringView(arpName, "ARPName"), + AICLI_TraceLoggingStringView(arpVersion, "ARPVersion"), + AICLI_TraceLoggingStringView(arpPublisher, "ARPPublisher"), + TraceLoggingUInt64(static_cast(languageNumber), "ARPLanguage"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.SourceIdentifier = sourceIdentifier; + m_summary.PackageIdentifier = packageIdentifier; + m_summary.PackageVersion = packageVersion; + m_summary.Channel = packageChannel; + m_summary.ChangesToARP = static_cast(changesToARP); + m_summary.MatchesInARP = static_cast(matchesInARP); + m_summary.ChangesThatMatch = static_cast(countOfIntersectionOfChangesAndMatches); + m_summary.ARPName = arpName; + m_summary.ARPVersion = arpVersion; + m_summary.ARPPublisher = arpPublisher; + m_summary.ARPLanguage = static_cast(languageNumber); + } + } + + AICLI_LOG(CLI, Info, << "During package install, " << changesToARP << " changes to ARP were observed, " + << matchesInARP << " matches were found for the package, and " << countOfIntersectionOfChangesAndMatches << " packages were in both"); + + if (arpName.empty()) + { + AICLI_LOG(CLI, Info, << "No single entry was determined to be associated with the package"); + } + else + { + AICLI_LOG(CLI, Info, << "The entry determined to be associated with the package is '" << arpName << "', with publisher '" << arpPublisher << "'"); + } + } + + void TelemetryTraceLogger::LogNonFatalDOError(std::string_view url, HRESULT hr) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "NonFatalDOError", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(url, "Url"), + TraceLoggingHResult(hr, "HResult"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); + + if (m_useSummary) + { + m_summary.DOUrl = url; + m_summary.DOHResult = hr; + } + } + } + TelemetryTraceLogger::~TelemetryTraceLogger() { if (IsTelemetryEnabled()) { - LocIndString version = Runtime::GetClientVersion(); - LocIndString packageVersion; - if (Runtime::IsRunningInPackagedContext()) - { - packageVersion = Runtime::GetPackageVersion(); + LocIndString version = Runtime::GetClientVersion(); + LocIndString packageVersion; + if (Runtime::IsRunningInPackagedContext()) + { + packageVersion = Runtime::GetPackageVersion(); } if (m_useSummary) { - TraceLoggingWriteActivity( - g_hTraceProvider, - "Summary", - GetActivityId(), - GetParentActivityId(), - // From member fields or program info. - AICLI_TraceLoggingStringView(m_caller, "Caller"), - TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"), - TraceLoggingCountedString(version->c_str(), static_cast(version->size()), "ClientVersion"), - TraceLoggingCountedString(packageVersion->c_str(), static_cast(packageVersion->size()), "PackageVersion"), - TraceLoggingBool(Runtime::IsReleaseBuild(), "IsReleaseBuild"), - TraceLoggingUInt32(m_executionStage, "ExecutionStage"), - // From TelemetrySummary - TraceLoggingHResult(m_summary.FailureHResult, "FailureHResult"), - AICLI_TraceLoggingWStringView(m_summary.FailureMessage, "FailureMessage"), - AICLI_TraceLoggingStringView(m_summary.FailureModule, "FailureModule"), - TraceLoggingUInt32(m_summary.FailureThreadId, "FailureThreadId"), - TraceLoggingUInt32(static_cast(m_summary.FailureType), "FailureType"), - AICLI_TraceLoggingStringView(m_summary.FailureFile, "FailureFile"), - TraceLoggingUInt32(m_summary.FailureLine, "FailureLine"), - TraceLoggingBool(m_summary.IsCOMCall, "IsCOMCall"), - AICLI_TraceLoggingStringView(m_summary.Command, "Command"), - TraceLoggingBool(m_summary.CommandSuccess, "CommandSuccess"), - TraceLoggingBool(m_summary.IsManifestLocal, "IsManifestLocal"), - AICLI_TraceLoggingStringView(m_summary.PackageIdentifier, "PackageIdentifier"), - AICLI_TraceLoggingStringView(m_summary.PackageName, "PackageName"), - AICLI_TraceLoggingStringView(m_summary.PackageVersion, "PackageVersion"), - AICLI_TraceLoggingStringView(m_summary.Channel, "Channel"), - AICLI_TraceLoggingStringView(m_summary.SourceIdentifier, "SourceIdentifier"), - TraceLoggingInt32(m_summary.InstallerArchitecture, "InstallerArchitecture"), - AICLI_TraceLoggingStringView(m_summary.InstallerUrl, "InstallerUrl"), - AICLI_TraceLoggingStringView(m_summary.InstallerType, "InstallerType"), - AICLI_TraceLoggingStringView(m_summary.InstallerScope, "InstallerScope"), - AICLI_TraceLoggingStringView(m_summary.InstallerLocale, "InstallerLocale"), - AICLI_TraceLoggingStringView(m_summary.SearchType, "SearchType"), - AICLI_TraceLoggingStringView(m_summary.SearchQuery, "SearchQuery"), - AICLI_TraceLoggingStringView(m_summary.SearchId, "SearchId"), - AICLI_TraceLoggingStringView(m_summary.SearchName, "SearchName"), - AICLI_TraceLoggingStringView(m_summary.SearchMoniker, "SearchMoniker"), - AICLI_TraceLoggingStringView(m_summary.SearchTag, "SearchTag"), - AICLI_TraceLoggingStringView(m_summary.SearchCommand, "SearchCommand"), - TraceLoggingUInt64(m_summary.SearchMaximum, "SearchMaximum"), - AICLI_TraceLoggingStringView(m_summary.SearchRequest, "SearchRequest"), - TraceLoggingUInt64(m_summary.SearchResultCount, "SearchResultCount"), - TraceLoggingBinary(m_summary.HashMismatchExpected.data(), static_cast(m_summary.HashMismatchExpected.size()), "HashMismatchExpected"), - TraceLoggingBinary(m_summary.HashMismatchActual.data(), static_cast(m_summary.HashMismatchActual.size()), "HashMismatchActual"), - TraceLoggingBool(m_summary.HashMismatchOverride, "HashMismatchOverride"), - AICLI_TraceLoggingStringView(m_summary.InstallerExecutionType, "InstallerExecutionType"), - TraceLoggingUInt32(m_summary.InstallerErrorCode, "InstallerErrorCode"), - AICLI_TraceLoggingStringView(m_summary.UninstallerExecutionType, "UninstallerExecutionType"), - TraceLoggingUInt32(m_summary.UninstallerErrorCode, "UninstallerErrorCode"), - TraceLoggingUInt64(m_summary.ChangesToARP, "ChangesToARP"), - TraceLoggingUInt64(m_summary.MatchesInARP, "MatchesInARP"), - TraceLoggingUInt64(m_summary.ChangesThatMatch, "ChangesThatMatch"), - TraceLoggingUInt64(m_summary.ARPLanguage, "ARPLanguage"), - AICLI_TraceLoggingStringView(m_summary.ARPName, "ARPName"), - AICLI_TraceLoggingStringView(m_summary.ARPVersion, "ARPVersion"), - AICLI_TraceLoggingStringView(m_summary.ARPPublisher, "ARPPublisher"), - AICLI_TraceLoggingStringView(m_summary.DOUrl, "DOUrl"), - TraceLoggingHResult(m_summary.DOHResult, "DOHResult"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), + TraceLoggingWriteActivity( + g_hTraceProvider, + "Summary", + GetActivityId(), + GetParentActivityId(), + // From member fields or program info. + AICLI_TraceLoggingStringView(m_caller, "Caller"), + TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"), + TraceLoggingCountedString(version->c_str(), static_cast(version->size()), "ClientVersion"), + TraceLoggingCountedString(packageVersion->c_str(), static_cast(packageVersion->size()), "PackageVersion"), + TraceLoggingBool(Runtime::IsReleaseBuild(), "IsReleaseBuild"), + TraceLoggingUInt32(m_executionStage, "ExecutionStage"), + // From TelemetrySummary + TraceLoggingHResult(m_summary.FailureHResult, "FailureHResult"), + AICLI_TraceLoggingWStringView(m_summary.FailureMessage, "FailureMessage"), + AICLI_TraceLoggingStringView(m_summary.FailureModule, "FailureModule"), + TraceLoggingUInt32(m_summary.FailureThreadId, "FailureThreadId"), + TraceLoggingUInt32(static_cast(m_summary.FailureType), "FailureType"), + AICLI_TraceLoggingStringView(m_summary.FailureFile, "FailureFile"), + TraceLoggingUInt32(m_summary.FailureLine, "FailureLine"), + TraceLoggingBool(m_summary.IsCOMCall, "IsCOMCall"), + AICLI_TraceLoggingStringView(m_summary.Command, "Command"), + TraceLoggingBool(m_summary.CommandSuccess, "CommandSuccess"), + TraceLoggingBool(m_summary.IsManifestLocal, "IsManifestLocal"), + AICLI_TraceLoggingStringView(m_summary.PackageIdentifier, "PackageIdentifier"), + AICLI_TraceLoggingStringView(m_summary.PackageName, "PackageName"), + AICLI_TraceLoggingStringView(m_summary.PackageVersion, "PackageVersion"), + AICLI_TraceLoggingStringView(m_summary.Channel, "Channel"), + AICLI_TraceLoggingStringView(m_summary.SourceIdentifier, "SourceIdentifier"), + TraceLoggingInt32(m_summary.InstallerArchitecture, "InstallerArchitecture"), + AICLI_TraceLoggingStringView(m_summary.InstallerUrl, "InstallerUrl"), + AICLI_TraceLoggingStringView(m_summary.InstallerType, "InstallerType"), + AICLI_TraceLoggingStringView(m_summary.InstallerScope, "InstallerScope"), + AICLI_TraceLoggingStringView(m_summary.InstallerLocale, "InstallerLocale"), + AICLI_TraceLoggingStringView(m_summary.SearchType, "SearchType"), + AICLI_TraceLoggingStringView(m_summary.SearchQuery, "SearchQuery"), + AICLI_TraceLoggingStringView(m_summary.SearchId, "SearchId"), + AICLI_TraceLoggingStringView(m_summary.SearchName, "SearchName"), + AICLI_TraceLoggingStringView(m_summary.SearchMoniker, "SearchMoniker"), + AICLI_TraceLoggingStringView(m_summary.SearchTag, "SearchTag"), + AICLI_TraceLoggingStringView(m_summary.SearchCommand, "SearchCommand"), + TraceLoggingUInt64(m_summary.SearchMaximum, "SearchMaximum"), + AICLI_TraceLoggingStringView(m_summary.SearchRequest, "SearchRequest"), + TraceLoggingUInt64(m_summary.SearchResultCount, "SearchResultCount"), + TraceLoggingBinary(m_summary.HashMismatchExpected.data(), static_cast(m_summary.HashMismatchExpected.size()), "HashMismatchExpected"), + TraceLoggingBinary(m_summary.HashMismatchActual.data(), static_cast(m_summary.HashMismatchActual.size()), "HashMismatchActual"), + TraceLoggingBool(m_summary.HashMismatchOverride, "HashMismatchOverride"), + AICLI_TraceLoggingStringView(m_summary.InstallerExecutionType, "InstallerExecutionType"), + TraceLoggingUInt32(m_summary.InstallerErrorCode, "InstallerErrorCode"), + AICLI_TraceLoggingStringView(m_summary.UninstallerExecutionType, "UninstallerExecutionType"), + TraceLoggingUInt32(m_summary.UninstallerErrorCode, "UninstallerErrorCode"), + TraceLoggingUInt64(m_summary.ChangesToARP, "ChangesToARP"), + TraceLoggingUInt64(m_summary.MatchesInARP, "MatchesInARP"), + TraceLoggingUInt64(m_summary.ChangesThatMatch, "ChangesThatMatch"), + TraceLoggingUInt64(m_summary.ARPLanguage, "ARPLanguage"), + AICLI_TraceLoggingStringView(m_summary.ARPName, "ARPName"), + AICLI_TraceLoggingStringView(m_summary.ARPVersion, "ARPVersion"), + AICLI_TraceLoggingStringView(m_summary.ARPPublisher, "ARPPublisher"), + AICLI_TraceLoggingStringView(m_summary.DOUrl, "DOUrl"), + TraceLoggingHResult(m_summary.DOHResult, "DOHResult"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); } } - } - - bool TelemetryTraceLogger::IsTelemetryEnabled() const noexcept - { - return g_IsTelemetryProviderEnabled && m_isInitialized && m_isSettingEnabled && m_isRuntimeEnabled; + } + + bool TelemetryTraceLogger::IsTelemetryEnabled() const noexcept + { + return g_IsTelemetryProviderEnabled && m_isInitialized && m_isSettingEnabled && m_isRuntimeEnabled; } void TelemetryTraceLogger::InitializeInternal(const AppInstaller::Settings::UserSettings& userSettings) { - m_isSettingEnabled = !userSettings.Get(); + m_isSettingEnabled = !userSettings.Get(); m_userProfile = Runtime::GetPathTo(Runtime::PathName::UserProfile).wstring(); m_isInitialized = true; - } - - std::wstring TelemetryTraceLogger::AnonymizeString(const wchar_t* input) const noexcept - { - return input ? AnonymizeString(std::wstring_view{ input }) : std::wstring{}; - } - - std::wstring TelemetryTraceLogger::AnonymizeString(std::wstring_view input) const noexcept try - { - return Utility::ReplaceWhileCopying(input, m_userProfile, s_UserProfileReplacement); - } - catch (...) { return std::wstring{ input }; } - -#ifndef AICLI_DISABLE_TEST_HOOKS - static std::shared_ptr s_TelemetryTraceLogger_TestOverride; -#endif - - TelemetryTraceLogger& Telemetry() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_TelemetryTraceLogger_TestOverride) - { - return *s_TelemetryTraceLogger_TestOverride.get(); - } -#endif - ThreadLocalStorage::ThreadGlobals* pThreadGlobals = ThreadLocalStorage::ThreadGlobals::GetForCurrentThread(); - if (pThreadGlobals) - { - return pThreadGlobals->GetTelemetryLogger(); - } - else - { - static TelemetryTraceLogger processGlobalTelemetry(/* useSummary */ false); - processGlobalTelemetry.TryInitialize(); - return processGlobalTelemetry; - } - } - - void EnableWilFailureTelemetry() - { - wil::SetResultLoggingCallback(wilResultLoggingCallback); - } - - void UseGlobalTelemetryLoggerActivityIdOnly() - { - s_useGlobalTelemetryActivityId = true; - std::ignore = CoCreateGuid(&s_globalTelemetryLoggerActivityId); - } - - DisableTelemetryScope::DisableTelemetryScope() - { - m_token = Telemetry().DisableRuntime(); - } - - DisableTelemetryScope::~DisableTelemetryScope() - { - if (m_token) - { - Telemetry().EnableRuntime(); - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Replace this test hook with context telemetry when it gets moved over - void TestHook_SetTelemetryOverride(std::shared_ptr ttl) - { - s_TelemetryTraceLogger_TestOverride = std::move(ttl); - } + } + + std::wstring TelemetryTraceLogger::AnonymizeString(const wchar_t* input) const noexcept + { + return input ? AnonymizeString(std::wstring_view{ input }) : std::wstring{}; + } + + std::wstring TelemetryTraceLogger::AnonymizeString(std::wstring_view input) const noexcept try + { + return Utility::ReplaceWhileCopying(input, m_userProfile, s_UserProfileReplacement); + } + catch (...) { return std::wstring{ input }; } + +#ifndef AICLI_DISABLE_TEST_HOOKS + static std::shared_ptr s_TelemetryTraceLogger_TestOverride; +#endif + + TelemetryTraceLogger& Telemetry() + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_TelemetryTraceLogger_TestOverride) + { + return *s_TelemetryTraceLogger_TestOverride.get(); + } +#endif + ThreadLocalStorage::ThreadGlobals* pThreadGlobals = ThreadLocalStorage::ThreadGlobals::GetForCurrentThread(); + if (pThreadGlobals) + { + return pThreadGlobals->GetTelemetryLogger(); + } + else + { + static TelemetryTraceLogger processGlobalTelemetry(/* useSummary */ false); + processGlobalTelemetry.TryInitialize(); + return processGlobalTelemetry; + } + } + + void EnableWilFailureTelemetry() + { + wil::SetResultLoggingCallback(wilResultLoggingCallback); + } + + void UseGlobalTelemetryLoggerActivityIdOnly() + { + s_useGlobalTelemetryActivityId = true; + std::ignore = CoCreateGuid(&s_globalTelemetryLoggerActivityId); + } + + DisableTelemetryScope::DisableTelemetryScope() + { + m_token = Telemetry().DisableRuntime(); + } + + DisableTelemetryScope::~DisableTelemetryScope() + { + if (m_token) + { + Telemetry().EnableRuntime(); + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Replace this test hook with context telemetry when it gets moved over + void TestHook_SetTelemetryOverride(std::shared_ptr ttl) + { + s_TelemetryTraceLogger_TestOverride = std::move(ttl); + } #endif -} +} diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp index 524920f0fe..be3120a484 100644 --- a/src/AppInstallerSharedLib/Errors.cpp +++ b/src/AppInstallerSharedLib/Errors.cpp @@ -214,6 +214,12 @@ namespace AppInstaller return "There is no pin for the package."; case APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX: return "Unable to open the pin database."; + case APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED: + return "One or more applications failed to install"; + case APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED: + return "One or more applications failed to uninstall"; + case APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE: + return "One or more queries did not return exactly one match"; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index f7864f751a..97e096c3a8 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -113,6 +113,9 @@ #define APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS ((HRESULT)0x8A150062) #define APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST ((HRESULT)0x8A150063) #define APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX ((HRESULT)0x8A150064) +#define APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED ((HRESULT)0x8A150065) +#define APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED ((HRESULT)0x8A150066) +#define APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE ((HRESULT)0x8A150067) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101)