winget-cli

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

commit a363d7d416c958ada7e81c8c359a2e3414d8ea08
parent 3fda0610bab37d44d8414f376806474de2db6989
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Wed,  8 Jul 2020 10:53:28 -0700

Block elevated process from overriding hash mismatch (#482)

To prevent the user from running a package whose hash does not match in an elevated context, this change explicitly blocks the scenario. In addition, the inline prompt to proceed when the hash does not match is replaced with usage of the --force option.
Diffstat:
Msrc/AppInstallerCLICore/Argument.cpp | 30+++++++++++++-----------------
Msrc/AppInstallerCLICore/Argument.h | 5+++--
Msrc/AppInstallerCLICore/Command.cpp | 4++--
Msrc/AppInstallerCLICore/Commands/FeaturesCommand.cpp | 12++++++++++--
Msrc/AppInstallerCLICore/Commands/InstallCommand.cpp | 32+++++++++++++++++---------------
Msrc/AppInstallerCLICore/Commands/RootCommand.cpp | 4++--
Msrc/AppInstallerCLICore/Commands/SourceCommand.cpp | 2+-
Msrc/AppInstallerCLICore/ExecutionReporter.cpp | 12------------
Msrc/AppInstallerCLICore/ExecutionReporter.h | 3---
Msrc/AppInstallerCLICore/Resources.h | 46+++++++++++++++++++++++++++++++++++++++++++---
Msrc/AppInstallerCLICore/TableOutput.h | 10++++++----
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 24+++++++++++++++++++-----
Msrc/AppInstallerCLICore/Workflows/ShowFlow.cpp | 4++--
Msrc/AppInstallerCLICore/Workflows/SourceFlow.cpp | 78+++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 4++--
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/AppInstallerCLITests/Command.cpp | 2+-
Msrc/AppInstallerCommonCore/AppInstallerTelemetry.cpp | 9++++++++-
Msrc/AppInstallerCommonCore/Public/AppInstallerTelemetry.h | 8+++++++-
19 files changed, 312 insertions(+), 115 deletions(-)

diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -13,8 +13,6 @@ namespace AppInstaller::CLI Argument Argument::ForType(Execution::Args::Type type) { - constexpr char None = APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER; - switch (type) { case Args::Type::Query: @@ -22,15 +20,15 @@ namespace AppInstaller::CLI case Args::Type::Manifest: return Argument{ "manifest", 'm', Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Id: - return Argument{ "id", None, Args::Type::Id,Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ "id", NoAlias, Args::Type::Id,Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Name: - return Argument{ "name", None, Args::Type::Name, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ "name", NoAlias, Args::Type::Name, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Moniker: - return Argument{ "moniker", None, Args::Type::Moniker, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ "moniker", NoAlias, Args::Type::Moniker, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Tag: - return Argument{ "tag", None, Args::Type::Tag, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ "tag", NoAlias, Args::Type::Tag, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Command: - return Argument{ "command", None, Args::Type::Command, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ "command", NoAlias, Args::Type::Command, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Source: return Argument{ "source", 's', Args::Type::Source, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; case Args::Type::Count: @@ -50,7 +48,7 @@ namespace AppInstaller::CLI case Args::Type::Log: return Argument{ "log", 'o', Args::Type::Log, Resource::String::LogArgumentDescription, ArgumentType::Standard }; case Args::Type::Override: - return Argument{ "override", None, Args::Type::Override, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + return Argument{ "override", NoAlias, Args::Type::Override, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::InstallLocation: return Argument{ "location", 'l', Args::Type::InstallLocation, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; case Args::Type::HashFile: @@ -58,7 +56,7 @@ namespace AppInstaller::CLI case Args::Type::Msix: return Argument{ "msix", 'm', Args::Type::Msix, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; case Args::Type::ListVersions: - return Argument{ "versions", None, Args::Type::ListVersions, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; + return Argument{ "versions", NoAlias, Args::Type::ListVersions, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; case Args::Type::Help: return Argument{ "help", APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR, Args::Type::Help, Resource::String::HelpArgumentDescription, ArgumentType::Flag }; case Args::Type::SourceName: @@ -68,19 +66,17 @@ namespace AppInstaller::CLI case Args::Type::SourceType: return Argument{ "type", 't', Args::Type::SourceType, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional }; case Args::Type::ValidateManifest: - return Argument{ "manifest", None, Args::Type::ValidateManifest, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; + return Argument{ "manifest", NoAlias, Args::Type::ValidateManifest, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; case Args::Type::NoVT: - return Argument{ "no-vt", None, Args::Type::NoVT, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + return Argument{ "no-vt", NoAlias, Args::Type::NoVT, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::RainbowStyle: - return Argument{ "rainbow", None, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + return Argument{ "rainbow", NoAlias, Args::Type::RainbowStyle, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::RetroStyle: - return Argument{ "retro", None, Args::Type::RetroStyle, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::Force: - return Argument{ "force", None, Args::Type::Force, Resource::String::ForceArgumentDescription, ArgumentType::Flag }; + return Argument{ "retro", NoAlias, Args::Type::RetroStyle, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; case Args::Type::VerboseLogs: - return Argument{ "verbose-logs", None, Args::Type::VerboseLogs, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; + return Argument{ "verbose-logs", NoAlias, Args::Type::VerboseLogs, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; case Args::Type::ExperimentalArg: - return Argument{ "arg", None, Args::Type::ExperimentalArg, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; + return Argument{ "arg", NoAlias, Args::Type::ExperimentalArg, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h @@ -17,8 +17,6 @@ #define APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING "?" #define APPINSTALLER_CLI_HELP_ARGUMENT APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING -#define APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER '\0' - namespace AppInstaller::CLI { // The type of argument. @@ -46,6 +44,9 @@ namespace AppInstaller::CLI Hidden, }; + // Defines an argument with no alias. + constexpr static char NoAlias = '\0'; + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc) : m_name(name), m_alias(alias), m_execArgType(execArgType), m_desc(std::move(desc)) {} diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -117,7 +117,7 @@ namespace AppInstaller::CLI infoOut << '['; - if (arg.Alias() == APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER) + if (arg.Alias() == Argument::NoAlias) { infoOut << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name(); } @@ -186,7 +186,7 @@ namespace AppInstaller::CLI for (const auto& arg : arguments) { std::ostringstream strstr; - if (arg.Alias() != APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER) + if (arg.Alias() != Argument::NoAlias) { strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias() << ','; } diff --git a/src/AppInstallerCLICore/Commands/FeaturesCommand.cpp b/src/AppInstallerCLICore/Commands/FeaturesCommand.cpp @@ -33,10 +33,18 @@ namespace AppInstaller::CLI if (!features.empty()) { - Execution::TableOutput<4> table(context.Reporter, { "Feature", "Status", "Property", "Link" }); + Execution::TableOutput<4> table(context.Reporter, { + Resource::String::FeaturesFeature, + Resource::String::FeaturesStatus, + Resource::String::FeaturesProperty, + Resource::String::FeaturesLink }); for (const auto& feature : features) { - table.OutputLine({ std::string{ feature.Name() }, ExperimentalFeature::IsEnabled(feature.GetFeature()) ? "Enabled" : "Disabled", std::string { feature.JsonName() }, std::string{ feature.Link() } }); + table.OutputLine({ + std::string{ feature.Name() }, + Resource::Loader::Instance().ResolveString(ExperimentalFeature::IsEnabled(feature.GetFeature()) ? Resource::String::FeaturesEnabled : Resource::String::FeaturesDisabled), + std::string { feature.JsonName() }, + std::string{ feature.Link() } }); } table.Complete(); } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -6,6 +6,7 @@ #include "Workflows/WorkflowBase.h" #include "Resources.h" +using namespace AppInstaller::CLI::Execution; using namespace AppInstaller::Manifest; using namespace AppInstaller::CLI::Workflow; @@ -18,21 +19,22 @@ namespace AppInstaller::CLI std::vector<Argument> InstallCommand::GetArguments() const { return { - Argument::ForType(Execution::Args::Type::Query), - Argument::ForType(Execution::Args::Type::Manifest), - Argument::ForType(Execution::Args::Type::Id), - Argument::ForType(Execution::Args::Type::Name), - Argument::ForType(Execution::Args::Type::Moniker), - Argument::ForType(Execution::Args::Type::Version), - Argument::ForType(Execution::Args::Type::Channel), - Argument::ForType(Execution::Args::Type::Source), - Argument::ForType(Execution::Args::Type::Exact), - Argument::ForType(Execution::Args::Type::Interactive), - Argument::ForType(Execution::Args::Type::Silent), - Argument::ForType(Execution::Args::Type::Language), - Argument::ForType(Execution::Args::Type::Log), - Argument::ForType(Execution::Args::Type::Override), - Argument::ForType(Execution::Args::Type::InstallLocation), + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Manifest), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Version), + Argument::ForType(Args::Type::Channel), + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::Interactive), + Argument::ForType(Args::Type::Silent), + Argument::ForType(Args::Type::Language), + Argument::ForType(Args::Type::Log), + Argument::ForType(Args::Type::Override), + Argument::ForType(Args::Type::InstallLocation), + Argument{ "force", Argument::NoAlias, Args::Type::Force, Resource::String::InstallForceArgumentDescription, ArgumentType::Flag }, }; } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -40,7 +40,7 @@ namespace AppInstaller::CLI return { Argument{ "version", 'v', Execution::Args::Type::ListVersions, Resource::String::ToolVersionArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ "info", APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER, Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ "info", Argument::NoAlias, Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, }; } @@ -72,7 +72,7 @@ namespace AppInstaller::CLI info << std::endl; - Execution::TableOutput<2> links{ context.Reporter, { Resource::LocString(Resource::String::Links).get(), "" } }; + Execution::TableOutput<2> links{ context.Reporter, { Resource::String::Links, {} } }; links.OutputLine({ Resource::LocString(Resource::String::PrivacyStatement).get(), "https://aka.ms/winget-privacy" }); links.OutputLine({ Resource::LocString(Resource::String::LicenseAgreement).get(), "https://aka.ms/winget-license" }); diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.cpp b/src/AppInstallerCLICore/Commands/SourceCommand.cpp @@ -169,7 +169,7 @@ namespace AppInstaller::CLI { return { Argument::ForType(Args::Type::SourceName), - Argument::ForType(Args::Type::Force), + Argument{ "force", Argument::NoAlias, Args::Type::Force, Resource::String::SourceResetForceArgumentDescription, ArgumentType::Flag }, }; } diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -104,18 +104,6 @@ namespace AppInstaller::CLI::Execution } } - bool Reporter::PromptForBoolResponse(const std::string& msg, Level level) - { - UNREFERENCED_PARAMETER(level); - - m_out << msg << " (Y|N)" << std::endl; - - char response; - m_in.get(response); - - return tolower(response) == 'y'; - } - void Reporter::ShowIndefiniteProgress(bool running) { if (running) diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h @@ -126,9 +126,6 @@ namespace AppInstaller::CLI::Execution // Sets the visual style (mostly for progress currently) void SetStyle(AppInstaller::Settings::VisualStyle style); - // Prompts the user, return true if they consented. - bool PromptForBoolResponse(const std::string& msg, Level level = Level::Info); - // Used to show indefinite progress. Currently an indefinite spinner is the form of // showing indefinite progress. // running: shows indefinite progress if set to true, stops indefinite progress if set to false diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -38,18 +38,24 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(CommandArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(CommandRequiresAdmin); WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Done); WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); + WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesDisabled); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesEnabled); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesFeature); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesLink); WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesProperty); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesStatus); WINGET_DEFINE_RESOURCE_STRINGID(FileArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); - WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(HelpArgumentDescription); @@ -61,6 +67,11 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallationRequiresHigherWindows); WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchAdminBlock); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); + WINGET_DEFINE_RESOURCE_STRINGID(InstallForceArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(InteractiveArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(InvalidAliasError); WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentSpecifierError); @@ -96,30 +107,59 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SearchId); + WINGET_DEFINE_RESOURCE_STRINGID(SearchMatch); + WINGET_DEFINE_RESOURCE_STRINGID(SearchName); + WINGET_DEFINE_RESOURCE_STRINGID(SearchTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(SearchVersion); + WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); + WINGET_DEFINE_RESOURCE_STRINGID(ShowChannel); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowVersion); WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(SingleCharAfterDashError); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentArg); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentName); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsMatch); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddBegin); WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceArgArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListArg); WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListData); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListField); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListName); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoneFound); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoSources); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListType); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdated); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdatedNever); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListValue); WINGET_DEFINE_RESOURCE_STRINGID(SourceNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveAll); WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveOne); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetAll); WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetForceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetListAndOverridePreamble); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetOne); WINGET_DEFINE_RESOURCE_STRINGID(SourceTypeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateAll); WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateOne); WINGET_DEFINE_RESOURCE_STRINGID(TagArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ThankYou); WINGET_DEFINE_RESOURCE_STRINGID(ThirdPartSoftwareNotices); diff --git a/src/AppInstallerCLICore/TableOutput.h b/src/AppInstallerCLICore/TableOutput.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include "ExecutionReporter.h" +#include "Resources.h" #include <array> #include <ostream> @@ -32,15 +33,16 @@ namespace AppInstaller::CLI::Execution template <size_t FieldCount> struct TableOutput { + using header_t = std::array<Resource::LocString, FieldCount>; using line_t = std::array<std::string, FieldCount>; - TableOutput(Reporter& reporter, line_t&& header, size_t sizingBuffer = 50) : + TableOutput(Reporter& reporter, header_t&& header, size_t sizingBuffer = 50) : m_reporter(reporter), m_sizingBuffer(sizingBuffer) { for (size_t i = 0; i < FieldCount; ++i) { m_columns[i].Name = std::move(header[i]); - m_columns[i].MinLength = Utility::UTF8Length(m_columns[i].Name); + m_columns[i].MinLength = Utility::UTF8Length(m_columns[i].Name.get()); m_columns[i].MaxLength = 0; } } @@ -67,7 +69,7 @@ namespace AppInstaller::CLI::Execution // A column in the table. struct Column { - std::string Name; + Resource::LocString Name; size_t MinLength = 0; size_t MaxLength = 0; bool SpaceAfter = true; @@ -162,7 +164,7 @@ namespace AppInstaller::CLI::Execution for (size_t i = 0; i < FieldCount; ++i) { - headerLine[i] = m_columns[i].Name; + headerLine[i] = m_columns[i].Name.get(); } OutputLineToStream(headerLine); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "InstallFlow.h" +#include "Resources.h" #include "ShellExecuteInstallerHandler.h" #include "WorkflowBase.h" @@ -145,19 +146,32 @@ namespace AppInstaller::CLI::Workflow hashPair.first.end(), hashPair.second.begin())) { + bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::Force); + const auto& manifest = context.Get<Execution::Data::Manifest>(); - Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, hashPair.first, hashPair.second); + Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, hashPair.first, hashPair.second, overrideHashMismatch); - if (!context.Reporter.PromptForBoolResponse("Installer hash verification failed. Continue?", Execution::Reporter::Level::Warning)) + // If running as admin, do not allow the user to override the hash failure. + if (Runtime::IsRunningAsAdmin()) + { + context.Reporter.Error() << Resource::String::InstallerHashMismatchAdminBlock << std::endl; + } + else if (overrideHashMismatch) { - context.Reporter.Error() << "Canceled; Installer hash mismatch" << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); + context.Reporter.Warn() << Resource::String::InstallerHashMismatchOverridden << std::endl; + return; } + else + { + context.Reporter.Error() << Resource::String::InstallerHashMismatchOverrideRequired << std::endl; + } + + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); } else { AICLI_LOG(CLI, Info, << "Installer hash verified"); - context.Reporter.Info() << "Successfully verified installer hash" << std::endl; + context.Reporter.Info() << Resource::String::InstallerHashVerified << std::endl; } } diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -67,7 +67,7 @@ namespace AppInstaller::CLI::Workflow { const auto& manifest = context.Get<Execution::Data::Manifest>(); - Execution::TableOutput<2> table(context.Reporter, { "Version", "Channel" }); + Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); table.OutputLine({ manifest.Version, manifest.Channel }); table.Complete(); } @@ -76,7 +76,7 @@ namespace AppInstaller::CLI::Workflow { auto app = context.Get<Execution::Data::SearchResult>().Matches.at(0).Application.get(); - Execution::TableOutput<2> table(context.Reporter, { "Version", "Channel" }); + Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); for (auto& version : app->GetVersions()) { table.OutputLine({ version.GetVersion().ToString(), version.GetChannel().ToString() }); diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include "pch.h" +#include "Resources.h" #include "SourceFlow.h" #include "TableOutput.h" #include "WorkflowBase.h" @@ -9,6 +10,7 @@ namespace AppInstaller::CLI::Workflow { using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::Utility::literals; void GetSourceList(Execution::Context& context) { @@ -24,7 +26,7 @@ namespace AppInstaller::CLI::Workflow if (!source) { - context.Reporter.Error() << "Did not find a source named: " << name << std::endl; + context.Reporter.Error() << Resource::String::SourceListNoneFound << ' ' << name << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); } @@ -50,14 +52,14 @@ namespace AppInstaller::CLI::Workflow if (source->Arg == arg) { // Name and arg match, indicate this to the user and bail. - context.Reporter.Info() << "A source with the given name already exists and refers to the same location: " << std::endl << - " " << source->Name << " -> " << source->Arg << std::endl; + context.Reporter.Info() << Resource::String::SourceAddAlreadyExistsMatch << std::endl << + " "_liv << source->Name << " -> "_liv << source->Arg << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); } else { - context.Reporter.Error() << "A source with the given name already exists and refers to a different location: " << std::endl << - " " << source->Name << " -> " << source->Arg << std::endl; + context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentArg << std::endl << + " "_liv << source->Name << " -> "_liv << source->Arg << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); } } @@ -70,8 +72,8 @@ namespace AppInstaller::CLI::Workflow { if (!details.Arg.empty() && details.Arg == arg && details.Type == type) { - context.Reporter.Error() << "A source with a different name already refers to this location: " << std::endl << - " " << details.Name << " -> " << details.Arg << std::endl; + context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentName << std::endl << + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS); } } @@ -88,12 +90,12 @@ namespace AppInstaller::CLI::Workflow } context.Reporter.Info() << - "Adding source:" << std::endl << - " " << name << " -> " << arg << std::endl; + Resource::String::SourceAddBegin << std::endl << + " "_liv << name << " -> "_liv << arg << std::endl; context.Reporter.ExecuteWithProgress(std::bind(Repository::AddSource, std::move(name), std::move(type), std::move(arg), std::placeholders::_1)); - context.Reporter.Info() << "Done"; + context.Reporter.Info() << Resource::String::Done; } void ListSources(Execution::Context& context) @@ -105,32 +107,38 @@ namespace AppInstaller::CLI::Workflow // If a source name was specified, list full details of the one and only source. const Repository::SourceDetails& source = sources[0]; - context.Reporter.Info() << - "Name : " + source.Name << std::endl << - "Type : " + source.Type << std::endl << - "Arg : " + source.Arg << std::endl << - "Data : " + source.Data << std::endl << - "Updated: "; + Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceListValue }); + + table.OutputLine({ Resource::Loader::Instance().ResolveString(Resource::String::SourceListName), source.Name }); + table.OutputLine({ Resource::Loader::Instance().ResolveString(Resource::String::SourceListType), source.Type }); + table.OutputLine({ Resource::Loader::Instance().ResolveString(Resource::String::SourceListArg), source.Arg }); + table.OutputLine({ Resource::Loader::Instance().ResolveString(Resource::String::SourceListData), source.Data }); if (source.LastUpdateTime == Utility::ConvertUnixEpochToSystemClock(0)) { - context.Reporter.Info() << "<never>" << std::endl; + table.OutputLine({ + Resource::Loader::Instance().ResolveString(Resource::String::SourceListUpdated), + Resource::Loader::Instance().ResolveString(Resource::String::SourceListUpdatedNever) + }); } else { - context.Reporter.Info() << source.LastUpdateTime << std::endl; + std::ostringstream strstr; + strstr << source.LastUpdateTime; + table.OutputLine({ Resource::Loader::Instance().ResolveString(Resource::String::SourceListUpdated), strstr.str() }); } + + table.Complete(); } else { - if (sources.empty()) { - context.Reporter.Info() << "There are no sources configured." << std::endl; + context.Reporter.Info() << Resource::String::SourceListNoSources << std::endl; } else { - Execution::TableOutput<2> table(context.Reporter, { "Name", "Arg" }); + Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListName, Resource::String::SourceListArg }); for (const auto& source : sources) { table.OutputLine({ source.Name, source.Arg }); @@ -144,15 +152,15 @@ namespace AppInstaller::CLI::Workflow { if (!context.Args.Contains(Args::Type::SourceName)) { - context.Reporter.Info() << "Updating all sources..." << std::endl; + context.Reporter.Info() << Resource::String::SourceUpdateAll << std::endl; } const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); for (const auto& sd : sources) { - context.Reporter.Info() << "Updating source: " << sd.Name << "..." << std::endl; + context.Reporter.Info() << Resource::String::SourceUpdateOne << ' ' << sd.Name << "..."_liv << std::endl; context.Reporter.ExecuteWithProgress(std::bind(Repository::UpdateSource, sd.Name, std::placeholders::_1)); - context.Reporter.Info() << "Done." << std::endl; + context.Reporter.Info() << Resource::String::Done << std::endl; } } @@ -160,15 +168,15 @@ namespace AppInstaller::CLI::Workflow { if (!context.Args.Contains(Args::Type::SourceName)) { - context.Reporter.Info() << "Removing all sources..." << std::endl; + context.Reporter.Info() << Resource::String::SourceRemoveAll << std::endl; } const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); for (const auto& sd : sources) { - context.Reporter.Info() << "Removing source: " << sd.Name << "..." << std::endl; + context.Reporter.Info() << Resource::String::SourceRemoveOne << ' ' << sd.Name << "..."_liv << std::endl; context.Reporter.ExecuteWithProgress(std::bind(Repository::RemoveSource, sd.Name, std::placeholders::_1)); - context.Reporter.Info() << "Done." << std::endl; + context.Reporter.Info() << Resource::String::Done << std::endl; } } @@ -181,14 +189,10 @@ namespace AppInstaller::CLI::Workflow if (!sources.empty()) { - context.Reporter.Info() << "The following sources will be reset:" << std::endl; + context.Reporter.Info() << Resource::String::SourceResetListAndOverridePreamble << std::endl; context << ListSources; - - if (!context.Reporter.PromptForBoolResponse("Do you wish to continue?")) - { - AICLI_TERMINATE_CONTEXT(E_ABORT); - } + AICLI_TERMINATE_CONTEXT(E_ABORT); } } } @@ -199,16 +203,16 @@ namespace AppInstaller::CLI::Workflow for (const auto& source : sources) { - context.Reporter.Info() << "Resetting source: " << source.Name << " ..."; + context.Reporter.Info() << Resource::String::SourceResetOne << ' ' << source.Name << "..."_liv; Repository::DropSource(source.Name); - context.Reporter.Info() << " Done." << std::endl; + context.Reporter.Info() << Resource::String::Done << std::endl; } } void ResetAllSources(Execution::Context& context) { - context.Reporter.Info() << "Resetting all sources ..."; + context.Reporter.Info() << Resource::String::SourceResetAll; Repository::DropSource({}); - context.Reporter.Info() << " Done." << std::endl; + context.Reporter.Info() << Resource::String::Done << std::endl; } } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -167,7 +167,7 @@ namespace AppInstaller::CLI::Workflow auto& searchResult = context.Get<Execution::Data::SearchResult>(); Logging::Telemetry().LogSearchResultCount(searchResult.Matches.size()); - Execution::TableOutput<4> table(context.Reporter, { "Name", "Id", "Version", "Matched" }); + Execution::TableOutput<4> table(context.Reporter, { Resource::String::SearchName, Resource::String::SearchId, Resource::String::SearchVersion, Resource::String::SearchMatch }); for (size_t i = 0; i < searchResult.Matches.size(); ++i) { @@ -181,7 +181,7 @@ namespace AppInstaller::CLI::Workflow if (searchResult.Truncated) { - context.Reporter.Info() << "<additional entries truncated due to result limit>" << std::endl; + context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; } } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -153,6 +153,9 @@ <data name="CountArgumentDescription" xml:space="preserve"> <value>Show no more than specified number of results</value> </data> + <data name="Done" xml:space="preserve"> + <value>Done</value> + </data> <data name="ExactArgumentDescription" xml:space="preserve"> <value>Find app using exact match</value> </data> @@ -180,20 +183,35 @@ <data name="FeaturesCommandShortDescription" xml:space="preserve"> <value>Shows the status of experimental features</value> </data> + <data name="FeaturesDisabled" xml:space="preserve"> + <value>Disabled</value> + </data> + <data name="FeaturesEnabled" xml:space="preserve"> + <value>Enabled</value> + </data> + <data name="FeaturesFeature" xml:space="preserve"> + <value>Feature</value> + </data> + <data name="FeaturesLink" xml:space="preserve"> + <value>Link</value> + </data> <data name="FeaturesMessage" xml:space="preserve"> <value>The following experimental features are in progress. They can be configured through the settings file 'winget settings'.</value> <comment>{Locked="winget settings"}</comment> </data> + <data name="FeaturesProperty" xml:space="preserve"> + <value>Property</value> + </data> + <data name="FeaturesStatus" xml:space="preserve"> + <value>Status</value> + </data> <data name="FileArgumentDescription" xml:space="preserve"> <value>File to be hashed</value> </data> <data name="FlagContainAdjoinedError" xml:space="preserve"> <value>Flag argument cannot contain adjoined value</value> </data> - <data name="ForceArgumentDescription" xml:space="preserve"> - <value>Execute the command without prompts</value> - </data> <data name="HashCommandLongDescription" xml:space="preserve"> <value>Computes the hash of a local file, appropriate for entry into a manifest. It can also compute the hash of the signature file of an MSIX package to enable streaming installations.</value> </data> @@ -227,6 +245,23 @@ They can be configured through the settings file 'winget settings'.</value> <data name="InstallCommandShortDescription" xml:space="preserve"> <value>Installs the given application</value> </data> + <data name="InstallerHashMismatchAdminBlock" xml:space="preserve"> + <value>Installer hash does not match; this cannot be overridden when running as admin</value> + </data> + <data name="InstallerHashMismatchOverridden" xml:space="preserve"> + <value>Installer hash does not match; proceeding due to --force</value> + <comment>{Locked="--force"}</comment> + </data> + <data name="InstallerHashMismatchOverrideRequired" xml:space="preserve"> + <value>Installer hash does not match; to override this check use --force</value> + <comment>{Locked="--force"}</comment> + </data> + <data name="InstallerHashVerified" xml:space="preserve"> + <value>Successfully verified installer hash</value> + </data> + <data name="InstallForceArgumentDescription" xml:space="preserve"> + <value>Override the installer hash check</value> + </data> <data name="InteractiveArgumentDescription" xml:space="preserve"> <value>Request interactive installation; user input may be needed</value> </data> @@ -337,6 +372,22 @@ They can be configured through the settings file 'winget settings'.</value> <data name="SearchCommandShortDescription" xml:space="preserve"> <value>Find and show basic info of apps</value> </data> + <data name="SearchId" xml:space="preserve"> + <value>Id</value> + <comment>Abbreviation of Identifier.</comment> + </data> + <data name="SearchMatch" xml:space="preserve"> + <value>Match</value> + </data> + <data name="SearchName" xml:space="preserve"> + <value>Name</value> + </data> + <data name="SearchTruncated" xml:space="preserve"> + <value>additional entries truncated due to result limit</value> + </data> + <data name="SearchVersion" xml:space="preserve"> + <value>Version</value> + </data> <data name="SettingLoadFailure" xml:space="preserve"> <value>The following failures were found validating the settings:</value> </data> @@ -349,18 +400,36 @@ They can be configured through the settings file 'winget settings'.</value> <data name="SettingsWarnings" xml:space="preserve"> <value>Unexpected error while loading settings. Please verify your settings by running the settings command.</value> </data> + <data name="ShowChannel" xml:space="preserve"> + <value>Channel</value> + </data> <data name="ShowCommandLongDescription" xml:space="preserve"> <value>Shows information on a specific application.</value> </data> <data name="ShowCommandShortDescription" xml:space="preserve"> <value>Shows info about an application</value> </data> + <data name="ShowVersion" xml:space="preserve"> + <value>Version</value> + </data> <data name="SilentArgumentDescription" xml:space="preserve"> <value>Request silent installation</value> </data> <data name="SingleCharAfterDashError" xml:space="preserve"> <value>Only the single character alias can occur after a single -</value> </data> + <data name="SourceAddAlreadyExistsDifferentArg" xml:space="preserve"> + <value>A source with the given name already exists and refers to a different location:</value> + </data> + <data name="SourceAddAlreadyExistsDifferentName" xml:space="preserve"> + <value>A source with a different name already refers to this location:</value> + </data> + <data name="SourceAddAlreadyExistsMatch" xml:space="preserve"> + <value>A source with the given name already exists and refers to the same location:</value> + </data> + <data name="SourceAddBegin" xml:space="preserve"> + <value>Adding source:</value> + </data> <data name="SourceAddCommandLongDescription" xml:space="preserve"> <value>Add a new source. A source provides the data for you to discover and install applications. Only add a new source if you trust it as a secure location.</value> </data> @@ -379,36 +448,99 @@ They can be configured through the settings file 'winget settings'.</value> <data name="SourceCommandShortDescription" xml:space="preserve"> <value>Manage sources of applications</value> </data> + <data name="SourceListArg" xml:space="preserve"> + <value>Argument</value> + <comment>Value given to source.</comment> + </data> <data name="SourceListCommandLongDescription" xml:space="preserve"> <value>List all current sources, or full details of a specific source.</value> </data> <data name="SourceListCommandShortDescription" xml:space="preserve"> <value>List current sources</value> </data> + <data name="SourceListData" xml:space="preserve"> + <value>Data</value> + <comment>Data stored by the source.</comment> + </data> + <data name="SourceListField" xml:space="preserve"> + <value>Field</value> + <comment>The name of a peice of information about a source.</comment> + </data> + <data name="SourceListName" xml:space="preserve"> + <value>Name</value> + <comment>The name of the source.</comment> + </data> + <data name="SourceListNoneFound" xml:space="preserve"> + <value>Did not find a source named:</value> + </data> + <data name="SourceListNoSources" xml:space="preserve"> + <value>There are no sources configured.</value> + </data> + <data name="SourceListType" xml:space="preserve"> + <value>Type</value> + <comment>The kind of source.</comment> + </data> + <data name="SourceListUpdated" xml:space="preserve"> + <value>Updated</value> + <comment>The last time the source was updated.</comment> + </data> + <data name="SourceListUpdatedNever" xml:space="preserve"> + <value>never</value> + <comment>The soure has never been updated.</comment> + </data> + <data name="SourceListValue" xml:space="preserve"> + <value>Value</value> + <comment>The value of information about a source.</comment> + </data> <data name="SourceNameArgumentDescription" xml:space="preserve"> <value>Name of the source</value> </data> + <data name="SourceRemoveAll" xml:space="preserve"> + <value>Removing all sources...</value> + </data> <data name="SourceRemoveCommandLongDescription" xml:space="preserve"> <value>Remove a specific source.</value> </data> <data name="SourceRemoveCommandShortDescription" xml:space="preserve"> <value>Remove current sources</value> </data> + <data name="SourceRemoveOne" xml:space="preserve"> + <value>Removing source:</value> + </data> + <data name="SourceResetAll" xml:space="preserve"> + <value>Resetting all sources...</value> + </data> <data name="SourceResetCommandLongDescription" xml:space="preserve"> <value>This command drops existing sources, potentially leaving any local data behind. Without any argument, it will drop all sources and add the defaults. If a named source is provided, only that source will be dropped.</value> </data> <data name="SourceResetCommandShortDescription" xml:space="preserve"> <value>Reset sources</value> </data> + <data name="SourceResetForceArgumentDescription" xml:space="preserve"> + <value>Forces the reset of the sources</value> + </data> + <data name="SourceResetListAndOverridePreamble" xml:space="preserve"> + <value>The following sources will be reset if the --force option is given:</value> + <comment>{Locked="--force"}</comment> + </data> + <data name="SourceResetOne" xml:space="preserve"> + <value>Resetting source:</value> + </data> <data name="SourceTypeArgumentDescription" xml:space="preserve"> <value>Type of the source</value> </data> + <data name="SourceUpdateAll" xml:space="preserve"> + <value>Updating all sources...</value> + </data> <data name="SourceUpdateCommandLongDescription" xml:space="preserve"> <value>Update all sources, or only a specific source.</value> </data> <data name="SourceUpdateCommandShortDescription" xml:space="preserve"> <value>Update current sources</value> </data> + <data name="SourceUpdateOne" xml:space="preserve"> + <value>Updating source:</value> + </data> <data name="TagArgumentDescription" xml:space="preserve"> <value>Filter results by tag</value> </data> diff --git a/src/AppInstallerCLITests/Command.cpp b/src/AppInstallerCLITests/Command.cpp @@ -25,7 +25,7 @@ std::string GetArgumentName(const Argument& arg) std::string GetArgumentAlias(const Argument& arg) { - if (arg.Alias() == APPINSTALLER_CLI_ARGUMENT_NO_SHORT_VER) + if (arg.Alias() == Argument::NoAlias) { return {}; } diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -326,7 +326,13 @@ namespace AppInstaller::Logging } } - 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) + 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) { if (g_IsTelemetryProviderEnabled) { @@ -339,6 +345,7 @@ namespace AppInstaller::Logging AICLI_TraceLoggingStringView(channel, "Channel"), TraceLoggingBinary(expected.data(), static_cast<ULONG>(expected.size()), "Expected"), TraceLoggingBinary(actual.data(), static_cast<ULONG>(actual.size()), "Actual"), + TraceLoggingValue(overrideHashMismatch, "Override"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h b/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h @@ -73,7 +73,13 @@ namespace AppInstaller::Logging void LogSearchResultCount(uint64_t resultCount) noexcept; // Logs a mismatch between the expected and actual hash values. - void 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); + void 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); // Logs a faild installation attempt. void LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode);