diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 70e9397d89..ad1a036a96 100644 --- 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 index 05863f8cc2..ab6dfdac2d 100644 --- 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 index f1fa7c8462..55e7edeccb 100644 --- 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 index 2c2b7768f3..8b3351ab92 100644 --- 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 index ff27dddf85..d42508bc20 100644 --- 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 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 index b926633fd7..056bb09da2 100644 --- 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 index 1ff285892d..98d60d8ef2 100644 --- 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 index b6e3fae8e4..06ce22302d 100644 --- 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 index 7b37d4ba25..d2d4e53eb7 100644 --- 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 index c01ef96952..74986e4d19 100644 --- 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 index 5b7d4cfe66..214bab05b9 100644 --- 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 #include @@ -32,15 +33,16 @@ namespace AppInstaller::CLI::Execution template struct TableOutput { + using header_t = std::array; using line_t = std::array; - 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 index 5e5d3e0b5d..355ea0a778 100644 --- 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(); - 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 index 198df0c9e5..0982ff445a 100644 --- 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::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().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 index a19efd835c..e204e8bf82 100644 --- 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() << "" << 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& sources = context.Get(); 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& sources = context.Get(); 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 index 52f9829304..e6ff9eb160 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -167,7 +167,7 @@ namespace AppInstaller::CLI::Workflow auto& searchResult = context.Get(); 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() << "" << 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 index 00a9af632b..c38882cc64 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -153,6 +153,9 @@ Show no more than specified number of results + + Done + Find app using exact match @@ -180,20 +183,35 @@ Shows the status of experimental features + + Disabled + + + Enabled + + + Feature + + + Link + The following experimental features are in progress. They can be configured through the settings file 'winget settings'. {Locked="winget settings"} + + Property + + + Status + File to be hashed Flag argument cannot contain adjoined value - - Execute the command without prompts - 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. @@ -227,6 +245,23 @@ They can be configured through the settings file 'winget settings'. Installs the given application + + Installer hash does not match; this cannot be overridden when running as admin + + + Installer hash does not match; proceeding due to --force + {Locked="--force"} + + + Installer hash does not match; to override this check use --force + {Locked="--force"} + + + Successfully verified installer hash + + + Override the installer hash check + Request interactive installation; user input may be needed @@ -337,6 +372,22 @@ They can be configured through the settings file 'winget settings'. Find and show basic info of apps + + Id + Abbreviation of Identifier. + + + Match + + + Name + + + additional entries truncated due to result limit + + + Version + The following failures were found validating the settings: @@ -349,18 +400,36 @@ They can be configured through the settings file 'winget settings'. Unexpected error while loading settings. Please verify your settings by running the settings command. + + Channel + Shows information on a specific application. Shows info about an application + + Version + Request silent installation Only the single character alias can occur after a single - + + A source with the given name already exists and refers to a different location: + + + A source with a different name already refers to this location: + + + A source with the given name already exists and refers to the same location: + + + Adding source: + 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. @@ -379,36 +448,99 @@ They can be configured through the settings file 'winget settings'. Manage sources of applications + + Argument + Value given to source. + List all current sources, or full details of a specific source. List current sources + + Data + Data stored by the source. + + + Field + The name of a peice of information about a source. + + + Name + The name of the source. + + + Did not find a source named: + + + There are no sources configured. + + + Type + The kind of source. + + + Updated + The last time the source was updated. + + + never + The soure has never been updated. + + + Value + The value of information about a source. + Name of the source + + Removing all sources... + Remove a specific source. Remove current sources + + Removing source: + + + Resetting all sources... + 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. Reset sources + + Forces the reset of the sources + + + The following sources will be reset if the --force option is given: + {Locked="--force"} + + + Resetting source: + Type of the source + + Updating all sources... + Update all sources, or only a specific source. Update current sources + + Updating source: + Filter results by tag diff --git a/src/AppInstallerCLITests/Command.cpp b/src/AppInstallerCLITests/Command.cpp index 188f916fa2..d29ed129d3 100644 --- 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 index ca74189c5f..ae3b376bfa 100644 --- 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& expected, const std::vector& actual) + void TelemetryTraceLogger::LogInstallerHashMismatch( + std::string_view id, + std::string_view version, + std::string_view channel, + const std::vector& expected, + const std::vector& actual, + bool overrideHashMismatch) { if (g_IsTelemetryProviderEnabled) { @@ -339,6 +345,7 @@ namespace AppInstaller::Logging AICLI_TraceLoggingStringView(channel, "Channel"), TraceLoggingBinary(expected.data(), static_cast(expected.size()), "Expected"), TraceLoggingBinary(actual.data(), static_cast(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 index 0cbc43c4cf..95d51b45b1 100644 --- 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& expected, const std::vector& actual); + void LogInstallerHashMismatch( + std::string_view id, + std::string_view version, + std::string_view channel, + const std::vector& expected, + const std::vector& 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);