winget-cli

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

commit fcce38f532439fc4a7294c246c1eb19d146acea4
parent 665ddd9c91c94f035ece1f6f4abd50c9bb5dadf6
Author: Flor Chacón <14323496+florelis@users.noreply.github.com>
Date:   Mon,  6 Feb 2023 13:52:42 -0800

Allow multiple apps in a single command (#2861)


Diffstat:
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj | 2++
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters | 6++++++
Msrc/AppInstallerCLICore/Argument.cpp | 4++++
Msrc/AppInstallerCLICore/Argument.h | 1+
Msrc/AppInstallerCLICore/Command.cpp | 16++++++++++------
Msrc/AppInstallerCLICore/Commands/ImportCommand.cpp | 4+++-
Msrc/AppInstallerCLICore/Commands/InstallCommand.cpp | 20+++++++++++++++++---
Msrc/AppInstallerCLICore/Commands/UninstallCommand.cpp | 34++++++++++++++++++++++------------
Msrc/AppInstallerCLICore/Commands/UpgradeCommand.cpp | 24+++++++++++++++++++-----
Msrc/AppInstallerCLICore/ExecutionArgs.h | 19++++++++++++++++++-
Msrc/AppInstallerCLICore/ExecutionContextData.h | 14+++++++++++---
Msrc/AppInstallerCLICore/Resources.h | 7+++++--
Msrc/AppInstallerCLICore/Workflows/CompletionFlow.cpp | 2++
Msrc/AppInstallerCLICore/Workflows/DependenciesFlow.cpp | 2+-
Msrc/AppInstallerCLICore/Workflows/ImportExportFlow.cpp | 60++++++------------------------------------------------------
Msrc/AppInstallerCLICore/Workflows/ImportExportFlow.h | 10++++++----
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 7++++---
Msrc/AppInstallerCLICore/Workflows/InstallFlow.h | 2+-
Asrc/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/Workflows/MultiQueryFlow.h | 41+++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/Workflows/PromptFlow.cpp | 2+-
Msrc/AppInstallerCLICore/Workflows/PromptFlow.h | 2+-
Msrc/AppInstallerCLICore/Workflows/UninstallFlow.cpp | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/Workflows/UninstallFlow.h | 10+++++++++-
Msrc/AppInstallerCLICore/Workflows/UpdateFlow.cpp | 12++++++------
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 38++++++++++++++++++++++++++------------
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.h | 8+++++++-
Msrc/AppInstallerCLIE2ETests/Constants.cs | 6++++++
Msrc/AppInstallerCLIE2ETests/ImportCommand.cs | 8++++----
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 23+++++++++++++++++------
Msrc/AppInstallerCLITests/Argument.cpp | 2+-
Msrc/AppInstallerCLITests/Command.cpp | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/ImportFlow.cpp | 12++++++------
Msrc/AppInstallerCLITests/InstallDependenciesFlow.cpp | 6+++---
Msrc/AppInstallerCLITests/InstallFlow.cpp | 40++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/UninstallFlow.cpp | 39+++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/UpdateFlow.cpp | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/AppInstallerTelemetry.cpp | 1694++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/AppInstallerSharedLib/Errors.cpp | 6++++++
Msrc/AppInstallerSharedLib/Public/AppInstallerErrors.h | 3+++
40 files changed, 1570 insertions(+), 985 deletions(-)

diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -284,6 +284,7 @@ <ClInclude Include="Workflows\ImportExportFlow.h" /> <ClInclude Include="Workflows\MsiInstallFlow.h" /> <ClInclude Include="Workflows\MSStoreInstallerHandler.h" /> + <ClInclude Include="Workflows\MultiQueryFlow.h" /> <ClInclude Include="Workflows\PinFlow.h" /> <ClInclude Include="Workflows\PortableFlow.h" /> <ClInclude Include="Workflows\PromptFlow.h" /> @@ -341,6 +342,7 @@ <ClCompile Include="Workflows\ImportExportFlow.cpp" /> <ClCompile Include="Workflows\MsiInstallFlow.cpp" /> <ClCompile Include="Workflows\MSStoreInstallerHandler.cpp" /> + <ClCompile Include="Workflows\MultiQueryFlow.cpp" /> <ClCompile Include="Workflows\PinFlow.cpp" /> <ClCompile Include="Workflows\PortableFlow.cpp" /> <ClCompile Include="Workflows\PromptFlow.cpp" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -194,6 +194,9 @@ <ClInclude Include="Workflows\PinFlow.h"> <Filter>Workflows</Filter> </ClInclude> + <ClInclude Include="Workflows\MultiQueryFlow.h"> + <Filter>Workflows</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -355,6 +358,9 @@ <ClCompile Include="Commands\PinCommand.cpp"> <Filter>Commands</Filter> </ClCompile> + <ClCompile Include="Workflows\MultiQueryFlow.cpp"> + <Filter>Workflows</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git 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 @@ -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 @@ -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 @@ -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 @@ -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<Argument> 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 @@ -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 @@ -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<Argument> 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 @@ -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<Type, std::vector<std::string>> m_parsedArgs; }; diff --git 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, @@ -82,6 +84,12 @@ namespace AppInstaller::CLI::Execution }; template <> + struct DataMapping<Data::SearchRequest> + { + using value_t = Repository::SearchRequest; + }; + + template <> struct DataMapping<Data::SearchResult> { using value_t = Repository::SearchResult; @@ -184,7 +192,7 @@ namespace AppInstaller::CLI::Execution }; template <> - struct DataMapping<Data::PackagesToInstall> + struct DataMapping<Data::PackageSubContexts> { using value_t = std::vector<std::unique_ptr<Context>>; }; diff --git 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 @@ -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 @@ -242,7 +242,7 @@ namespace AppInstaller::CLI::Workflow } // Install dependencies in the correct order - context.Add<Execution::Data::PackagesToInstall>(std::move(dependencyPackageContexts)); + context.Add<Execution::Data::PackageSubContexts>(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 @@ -242,11 +242,10 @@ namespace AppInstaller::CLI::Workflow } } - void SearchPackagesForImport(Execution::Context& context) + void GetSearchRequestsForImport(Execution::Context& context) { const auto& sources = context.Get<Execution::Data::Sources>(); - std::vector<std::unique_ptr<Execution::Context>> packagesToInstall; - bool foundAll = true; + std::vector<std::unique_ptr<Execution::Context>> 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<Execution::Data::Source>(), *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<Execution::Data::Source>(source); - searchContext.Add<Execution::Data::SearchResult>(source.Search(searchRequest)); + searchContext.Add<Execution::Data::SearchRequest>(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<Execution::Data::PackagesToInstall>(std::move(packagesToInstall)); + context.Add<Execution::Data::PackageSubContexts>(std::move(packageSubContexts)); } void InstallImportedPackages(Execution::Context& context) diff --git 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 @@ -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<Execution::Data::PackagesToInstall>()) + for (auto& packageContext : context.Get<Execution::Data::PackageSubContexts>()) { allDependencies.Add(packageContext->Get<Execution::Data::Installer>().value().Dependencies); } @@ -537,10 +538,10 @@ namespace AppInstaller::CLI::Workflow } bool allSucceeded = true; - size_t packagesCount = context.Get<Execution::Data::PackagesToInstall>().size(); + size_t packagesCount = context.Get<Execution::Data::PackageSubContexts>().size(); size_t packagesProgress = 0; - for (auto& packageContext : context.Get<Execution::Data::PackagesToInstall>()) + for (auto& packageContext : context.Get<Execution::Data::PackageSubContexts>()) { packagesProgress++; context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; diff --git 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 @@ -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<std::unique_ptr<Execution::Context>> packageSubContexts; + auto& source = context.Get<Execution::Data::Source>(); + 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<Execution::Data::Source>(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<Execution::Data::PackageSubContexts>(std::move(packageSubContexts)); + } + + void SearchSubContextsForSingle::operator()(Execution::Context& context) const + { + std::vector<std::unique_ptr<Execution::Context>> packageSubContexts; + bool foundAll = true; + + for (auto& searchContextPtr : context.Get<Execution::Data::PackageSubContexts>()) + { + auto& searchContext = *searchContextPtr; + SearchRequest searchRequest = searchContext.Get<Execution::Data::SearchRequest>(); + searchContext.Add<Execution::Data::SearchResult>(searchContext.Get<Execution::Data::Source>().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<Execution::Data::PackageSubContexts>(std::move(packageSubContexts)); + } + +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.h 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 @@ -440,7 +440,7 @@ namespace AppInstaller::CLI::Workflow { // Find which packages need this prompt std::vector<Execution::Context*> packagesToPrompt; - for (auto& packageContext : context.Get<Execution::Data::PackagesToInstall>()) + for (auto& packageContext : context.Get<Execution::Data::PackageSubContexts>()) { if (prompt->PackageNeedsPrompt(*packageContext)) { diff --git 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 @@ -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<Execution::Data::PackageSubContexts>().size(); + size_t packagesProgress = 0; + + for (auto& packageContext : context.Get<Execution::Data::PackageSubContexts>()) + { + 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<Execution::Data::InstalledPackageVersion>(); diff --git 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 @@ -19,9 +19,9 @@ namespace AppInstaller::CLI::Workflow return installedVersion < updateVersion; } - void AddToPackagesToInstallIfNotPresent(std::vector<std::unique_ptr<Execution::Context>>& packagesToInstall, std::unique_ptr<Execution::Context> packageContext) + void AddToPackageSubContextsIfNotPresent(std::vector<std::unique_ptr<Execution::Context>>& packageSubContexts, std::unique_ptr<Execution::Context> packageContext) { - for (auto const& existing : packagesToInstall) + for (auto const& existing : packageSubContexts) { if (existing->Get<Execution::Data::Manifest>().Id == packageContext->Get<Execution::Data::Manifest>().Id && existing->Get<Execution::Data::Manifest>().Version == packageContext->Get<Execution::Data::Manifest>().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<Execution::Data::SearchResult>().Matches; - std::vector<std::unique_ptr<Execution::Context>> packagesToInstall; + std::vector<std::unique_ptr<Execution::Context>> 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<Execution::Data::PackagesToInstall>(std::move(packagesToInstall)); + context.Add<Execution::Data::PackageSubContexts>(std::move(packageSubContexts)); context.Reporter.Info() << std::endl; context << InstallMultiplePackages( diff --git 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<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().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<Execution::Data::SearchRequest>(std::move(searchRequest)); + } - context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest)); + void SearchSourceForSingle(Execution::Context& context) + { + const auto& args = context.Args; + context << GetSearchRequestForSingle; + if (!context.IsTerminated()) + { + const auto& searchRequest = context.Get<Execution::Data::SearchRequest>(); + + 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<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest)); + } } void SearchSourceForManyCompletion(Execution::Context& context) diff --git 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 @@ -223,6 +223,12 @@ namespace AppInstallerCLIE2ETests 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 @@ -79,8 +79,8 @@ namespace AppInstallerCLIE2ETests { // 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")); } /// <summary> @@ -91,8 +91,8 @@ namespace AppInstallerCLIE2ETests { // 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")); } /// <summary> diff --git 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'.</value> <data name="ImportInstallFailed" xml:space="preserve"> <value>One or more imported packages failed to install</value> </data> - <data name="ImportSearchFailed" xml:space="preserve"> - <value>Package not found for import: {0}</value> - <comment>{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 .</comment> - </data> <data name="ImportSourceNotInstalled" xml:space="preserve"> <value>Source required for import is not installed: {0}</value> <comment>{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.</comment> @@ -853,9 +849,9 @@ They can be configured through the settings file 'winget settings'.</value> <data name="InvalidJsonFile" xml:space="preserve"> <value>JSON file is not valid</value> </data> - <data name="ImportPackageAlreadyInstalled" xml:space="preserve"> + <data name="MultiQueryPackageAlreadyInstalled" xml:space="preserve"> <value>Package is already installed: {0}</value> - <comment>{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.</comment> + <comment>{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.</comment> </data> <data name="ImportIgnoreUnavailableArgumentDescription" xml:space="preserve"> <value>Ignore unavailable packages</value> @@ -1652,4 +1648,19 @@ Please specify one of them using the --source option to proceed.</value> <value>State</value> <comment>Header for a table listing the state (enabled/disabled) of Group Policies and Settings</comment> </data> + <data name="MultiQueryArgumentDescription" xml:space="preserve"> + <value>The query used to search for a package</value> + </data> + <data name="MultiQueryPackageNotFound" xml:space="preserve"> + <value>Package not found: {0}</value> + <comment>{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.</comment> + </data> + <data name="MultiQuerySearchFoundMultiple" xml:space="preserve"> + <value>Multiple packages found for: {0}</value> + <comment>{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.</comment> + </data> + <data name="MultiQuerySearchFailed" xml:space="preserve"> + <value>Search failed for: {0}</value> + <comment>{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.</comment> + </data> </root> \ No newline at end of file diff --git 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<Execution::Args::Type>; for (Arg_t i = static_cast<Arg_t>(0); i < static_cast<Arg_t>(Execution::Args::Type::Max); ++i) diff --git 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<std::string>& 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<std::string>& 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<std::string> values{ "value1" "value2", "value3" }; + Invocation inv{ std::vector<std::string>(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<std::string> values{ "positional", "-q", "anotherPos", "multiValue1", "multiValue2", "-f" }; + Invocation inv{ std::vector<std::string>(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<std::string> values{ "--multi", "one", "two", "three" }; + Invocation inv{ std::vector<std::string>(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<std::string> values{ "singleValue" }; + Invocation inv{ std::vector<std::string>(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<std::string> values{ "1", "2", "3", "4", "5", "tooMany" }; + Invocation inv{ std::vector<std::string>(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 @@ -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 @@ -75,7 +75,7 @@ TEST_CASE("DependencyGraph_SkipInstalled", "[InstallFlow][workflow][dependencyGr context << ManagePackageDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies); - auto& dependencyPackages = context.Get<Execution::Data::PackagesToInstall>(); + auto& dependencyPackages = context.Get<Execution::Data::PackageSubContexts>(); 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<Execution::Data::PackagesToInstall>(); + auto& dependencyPackages = context.Get<Execution::Data::PackageSubContexts>(); 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<Execution::Data::PackagesToInstall>(); + auto& dependencyPackages = context.Get<Execution::Data::PackageSubContexts>(); REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); diff --git 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 @@ -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 @@ -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 @@ -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<ULONG>(_sv_.size()), _name_) -#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast<ULONG>(_sv_.size()), _name_) - -#define AICLI_TraceLoggingWriteActivity(_eventName_,...) TraceLoggingWriteActivity(\ -g_hTraceProvider,\ -_eventName_,\ -s_useGlobalTelemetryActivityId ? &s_globalTelemetryLoggerActivityId : GetActivityId(),\ -nullptr,\ -TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast<ULONG>(m_caller.size()), "Caller"),\ -TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast<ULONG>((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<ULONG>(_sv_.size()), _name_) +#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast<ULONG>(_sv_.size()), _name_) + +#define AICLI_TraceLoggingWriteActivity(_eventName_,...) TraceLoggingWriteActivity(\ +g_hTraceProvider,\ +_eventName_,\ +s_useGlobalTelemetryActivityId ? &s_globalTelemetryLoggerActivityId : GetActivityId(),\ +nullptr,\ +TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast<ULONG>(m_caller.size()), "Caller"),\ +TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast<ULONG>((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<Json::CharReader> jsonReader(jsonBuilder.newCharReader()); - std::unique_ptr<Json::Value> pJsonValue = std::make_unique<Json::Value>(); - 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<Json::CharReader> jsonReader(jsonBuilder.newCharReader()); + std::unique_ptr<Json::Value> pJsonValue = std::make_unique<Json::Value>(); + 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> 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<uint32_t>(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<ULONG>(version->size()), "Version"), - TraceLoggingCountedString(packageVersion->c_str(), static_cast<ULONG>(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<UINT64>(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<UINT32>(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<UINT64>(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<UINT64>(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<uint8_t>& expected, - const std::vector<uint8_t>& 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<ULONG>(expected.size()), "Expected"), - TraceLoggingBinary(actual.data(), static_cast<ULONG>(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<UINT64>(changesToARP), "ChangesToARP"), - TraceLoggingUInt64(static_cast<UINT64>(matchesInARP), "MatchesInARP"), - TraceLoggingUInt64(static_cast<UINT64>(countOfIntersectionOfChangesAndMatches), "ChangesThatMatch"), - AICLI_TraceLoggingStringView(arpName, "ARPName"), - AICLI_TraceLoggingStringView(arpVersion, "ARPVersion"), - AICLI_TraceLoggingStringView(arpPublisher, "ARPPublisher"), - TraceLoggingUInt64(static_cast<UINT64>(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<UINT64>(changesToARP); - m_summary.MatchesInARP = static_cast<UINT64>(matchesInARP); - m_summary.ChangesThatMatch = static_cast<UINT64>(countOfIntersectionOfChangesAndMatches); - m_summary.ARPName = arpName; - m_summary.ARPVersion = arpVersion; - m_summary.ARPPublisher = arpPublisher; - m_summary.ARPLanguage = static_cast<UINT64>(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<uint32_t>(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<ULONG>(version->size()), "Version"), + TraceLoggingCountedString(packageVersion->c_str(), static_cast<ULONG>(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<UINT64>(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<UINT32>(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<UINT64>(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<UINT64>(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<uint8_t>& expected, + const std::vector<uint8_t>& 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<ULONG>(expected.size()), "Expected"), + TraceLoggingBinary(actual.data(), static_cast<ULONG>(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<UINT64>(changesToARP), "ChangesToARP"), + TraceLoggingUInt64(static_cast<UINT64>(matchesInARP), "MatchesInARP"), + TraceLoggingUInt64(static_cast<UINT64>(countOfIntersectionOfChangesAndMatches), "ChangesThatMatch"), + AICLI_TraceLoggingStringView(arpName, "ARPName"), + AICLI_TraceLoggingStringView(arpVersion, "ARPVersion"), + AICLI_TraceLoggingStringView(arpPublisher, "ARPPublisher"), + TraceLoggingUInt64(static_cast<UINT64>(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<UINT64>(changesToARP); + m_summary.MatchesInARP = static_cast<UINT64>(matchesInARP); + m_summary.ChangesThatMatch = static_cast<UINT64>(countOfIntersectionOfChangesAndMatches); + m_summary.ARPName = arpName; + m_summary.ARPVersion = arpVersion; + m_summary.ARPPublisher = arpPublisher; + m_summary.ARPLanguage = static_cast<UINT64>(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<ULONG>((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"), - TraceLoggingCountedString(version->c_str(), static_cast<ULONG>(version->size()), "ClientVersion"), - TraceLoggingCountedString(packageVersion->c_str(), static_cast<ULONG>(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<UINT32>(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<ULONG>(m_summary.HashMismatchExpected.size()), "HashMismatchExpected"), - TraceLoggingBinary(m_summary.HashMismatchActual.data(), static_cast<ULONG>(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<ULONG>((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"), + TraceLoggingCountedString(version->c_str(), static_cast<ULONG>(version->size()), "ClientVersion"), + TraceLoggingCountedString(packageVersion->c_str(), static_cast<ULONG>(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<UINT32>(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<ULONG>(m_summary.HashMismatchExpected.size()), "HashMismatchExpected"), + TraceLoggingBinary(m_summary.HashMismatchActual.data(), static_cast<ULONG>(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<Settings::Setting::TelemetryDisable>(); + m_isSettingEnabled = !userSettings.Get<Settings::Setting::TelemetryDisable>(); 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<TelemetryTraceLogger> 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<TelemetryTraceLogger> 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<TelemetryTraceLogger> 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<TelemetryTraceLogger> ttl) + { + s_TelemetryTraceLogger_TestOverride = std::move(ttl); + } #endif -} +} diff --git 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 @@ -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)