From ba0c74a47302c4d7c487c203c6aee4e285148f7e Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 7 Jul 2022 09:53:44 -0700 Subject: [PATCH 01/15] Squash all old commits --- doc/Settings.md | 10 + .../JSON/settings/settings.schema.0.2.json | 5 + .../AppInstallerCLICore.vcxproj | 2 + .../AppInstallerCLICore.vcxproj.filters | 8 +- src/AppInstallerCLICore/COMContext.h | 2 + src/AppInstallerCLICore/ExecutionContext.h | 1 + src/AppInstallerCLICore/ExecutionReporter.cpp | 23 +- src/AppInstallerCLICore/ExecutionReporter.h | 3 + src/AppInstallerCLICore/Resources.h | 12 + .../Workflows/InstallFlow.cpp | 17 +- .../Workflows/PromptFlow.cpp | 332 ++++++++++++++++++ .../Workflows/PromptFlow.h | 37 ++ .../Workflows/WorkflowBase.cpp | 31 +- .../Workflows/WorkflowBase.h | 15 +- src/AppInstallerCLIE2ETests/Constants.cs | 3 + .../Shared/Strings/en-us/winget.resw | 25 ++ src/AppInstallerCommonCore/Errors.cpp | 4 + .../Public/AppInstallerErrors.h | 1 + .../Public/winget/UserSettings.h | 13 +- src/AppInstallerCommonCore/UserSettings.cpp | 9 +- 20 files changed, 524 insertions(+), 29 deletions(-) create mode 100644 src/AppInstallerCLICore/Workflows/PromptFlow.cpp create mode 100644 src/AppInstallerCLICore/Workflows/PromptFlow.h diff --git a/doc/Settings.md b/doc/Settings.md index aecbdeda8e..d231149518 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -135,6 +135,16 @@ The `purgePortablePackage` behavior affects the default behavior for uninstallin }, ``` +### Default install root + +The `defaultInstallRoot` affects the install location when a package requires one. This can be overridden by the `--location` parameter. This setting is only used when a package manifest includes `InstallLocationRequired`, and the actual location is obtained by appending the package ID to the root. + +```json + "installBehavior": { + "defaultInstallRoot": "C:\installRoot" + }, +``` + ## Telemetry The `telemetry` settings control whether winget writes ETW events that may be sent to Microsoft on a default installation of Windows. diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json index 6f9d1aa0b8..528a492cf4 100644 --- a/schemas/JSON/settings/settings.schema.0.2.json +++ b/schemas/JSON/settings/settings.schema.0.2.json @@ -116,6 +116,11 @@ "description": "The default root directory where packages are installed to under Machine scope. Applies to the portable installer type.", "type": "string", "default": "%PROGRAMFILES%/WinGet/Packages/" + }, + "defaultInstallRoot": { + "description": "Default install location to use for packages that require it when not specified", + "type": "string", + "maxLength": "1024" } } }, diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index f09cd3c3ab..77631a9c0b 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -283,6 +283,7 @@ + @@ -336,6 +337,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index f0925e10b9..c92147bd96 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -181,7 +181,10 @@ Workflows - + + + Workflows + @@ -327,6 +330,9 @@ Workflows + + + Workflows Workflows diff --git a/src/AppInstallerCLICore/COMContext.h b/src/AppInstallerCLICore/COMContext.h index 5cd9cde1e8..fa282661cc 100644 --- a/src/AppInstallerCLICore/COMContext.h +++ b/src/AppInstallerCLICore/COMContext.h @@ -41,12 +41,14 @@ namespace AppInstaller::CLI::Execution Reporter.SetChannel(Reporter::Channel::Disabled); Reporter.SetProgressSink(this); SetFlags(CLI::Execution::ContextFlag::AgreementsAcceptedByCaller); + SetFlags(CLI::Execution::ContextFlag::DisableInteractivity); } COMContext(std::ostream& out, std::istream& in) : CLI::Execution::Context(out, in) { Reporter.SetProgressSink(this); SetFlags(CLI::Execution::ContextFlag::AgreementsAcceptedByCaller); + SetFlags(CLI::Execution::ContextFlag::DisableInteractivity); } ~COMContext() = default; diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h index d2f0deb301..6fe4d8c444 100644 --- a/src/AppInstallerCLICore/ExecutionContext.h +++ b/src/AppInstallerCLICore/ExecutionContext.h @@ -64,6 +64,7 @@ namespace AppInstaller::CLI::Execution // TODO: Remove when the source interface is refactored. TreatSourceFailuresAsWarning = 0x10, ShowSearchResultsOnPartialFailure = 0x20, + DisableInteractivity = 0x40, }; DEFINE_ENUM_FLAG_OPERATORS(ContextFlag); diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index c70e78a858..0c2a4ec483 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -163,6 +163,27 @@ namespace AppInstaller::CLI::Execution m_in.get(); } + std::filesystem::path Reporter::PromptForPath(Resource::LocString message, Level level) + { + auto out = GetOutputStream(level); + out << message << ' '; + + // Try prompting until we get a valid answer + for (;;) + { + // Read the response + std::string response; + if (!std::getline(m_in, response)) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR); + } + + // TODO: Check + return response; + } + + } + void Reporter::ShowIndefiniteProgress(bool running) { if (m_spinner) @@ -186,7 +207,7 @@ namespace AppInstaller::CLI::Execution m_progressBar->ShowProgress(current, maximum, type); } } - + void Reporter::BeginProgress() { GetBasicOutputStream() << VirtualTerminal::Cursor::Visibility::DisableShow; diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index e262f44a6a..f31c1469b0 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -102,6 +102,9 @@ namespace AppInstaller::CLI::Execution // Prompts the user, continues when Enter is pressed void PromptForEnter(Level level = Level::Info); + // Prompts the user for a path. + std::filesystem::path PromptForPath(Resource::LocString message, 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 20b807a135..ecc0a85258 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -119,6 +119,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable); WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerAbortsTerminal); WINGET_DEFINE_RESOURCE_STRINGID(InstallerElevationExpected); WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy); WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedSecurityCheck); @@ -131,6 +132,9 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable); WINGET_DEFINE_RESOURCE_STRINGID(InstallerProhibitsElevation); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerRequiresInstallLocation); + WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal); + WINGET_DEFINE_RESOURCE_STRINGID(InstallersRequireInstallLocation); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled); @@ -150,6 +154,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredToFinish); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowStartingPackageInstall); WINGET_DEFINE_RESOURCE_STRINGID(InstallForceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallLocationNotProvided); WINGET_DEFINE_RESOURCE_STRINGID(InstallScopeDescription); WINGET_DEFINE_RESOURCE_STRINGID(InteractiveArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(InvalidAliasError); @@ -228,15 +233,22 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PressEnterToContinue); WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); WINGET_DEFINE_RESOURCE_STRINGID(ProductCodeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PromptForInstallRoot); WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); + WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); + WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); + WINGET_DEFINE_RESOURCE_STRINGID(PromptToProceed); WINGET_DEFINE_RESOURCE_STRINGID(PurgeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PurgeInstallDirectory); WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RelatedLink); WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ReparsePointsNotSupportedError); + WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityForAgreements); WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityFound); WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); WINGET_DEFINE_RESOURCE_STRINGID(ReservedFilenameError); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 2260d28750..5439eb94d6 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -12,7 +12,8 @@ #include "ArchiveFlow.h" #include "PortableFlow.h" #include "WorkflowBase.h" -#include "Workflows/DependenciesFlow.h" +#include "DependenciesFlow.h" +#include "PromptFlow.h" #include #include #include @@ -28,6 +29,7 @@ using namespace AppInstaller::Manifest; using namespace AppInstaller::Repository; using namespace AppInstaller::Settings; using namespace AppInstaller::Utility; +using namespace AppInstaller::Utility::literals; namespace AppInstaller::CLI::Workflow { @@ -513,7 +515,7 @@ namespace AppInstaller::CLI::Workflow void ReportIdentityAndInstallationDisclaimer(Execution::Context& context) { context << - Workflow::ReportManifestIdentityWithVersion << + Workflow::ReportManifestIdentityWithVersion() << Workflow::ShowInstallationDisclaimer; } @@ -535,7 +537,7 @@ namespace AppInstaller::CLI::Workflow { context << Workflow::ReportIdentityAndInstallationDisclaimer << - Workflow::ShowPackageAgreements(/* ensureAcceptance */ true) << + Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << Workflow::GetDependenciesFromInstaller << Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << Workflow::ManagePackageDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << @@ -561,11 +563,8 @@ namespace AppInstaller::CLI::Workflow void InstallMultiplePackages::operator()(Execution::Context& context) const { - if (m_ensurePackageAgreements) - { - // Show all license agreements before installing anything - context << Workflow::EnsurePackageAgreementsAcceptanceForMultipleInstallers; - } + // Show all prompts needed for every package before installing anything + context << Workflow::ShowPromptsForMultiplePackages(m_ensurePackageAgreements); if (context.IsTerminated()) { @@ -592,7 +591,7 @@ namespace AppInstaller::CLI::Workflow for (auto& packageContext : context.Get()) { packagesProgress++; - context.Reporter.Info() << "(" << packagesProgress << "/" << packagesCount << ") "; + context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; // We want to do best effort to install all packages regardless of previous failures Execution::Context& installContext = *packageContext; diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp new file mode 100644 index 0000000000..59bb2f5850 --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp @@ -0,0 +1,332 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PromptFlow.h" +#include "ShowFlow.h" +#include + +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + // An interface for defining prompts to the user regarding a package. + // Note that each prompt may behave differently when running non-interactively + // (e.g. failing if it is needed vs. continuing silently), and they may + // do some work while checking if the prompt is needed even if no prompt is shown, + // so they need to always run. + struct PackagePrompt + { + virtual ~PackagePrompt() = default; + + // Determines whether a package needs this prompt. + // Inputs: Manifest, Installer + // Outputs: None + virtual bool PackageNeedsPrompt(Execution::Context& context) = 0; + + // Prompts for the information needed for a single package. + // Inputs: Manifest, Installer + // Outputs: None + virtual void PromptForSinglePackage(Execution::Context& context) = 0; + + // Prompts for the information needed for multiple packages. + // Inputs: Manifest, Installer (for each sub context) + // Outputs: None + virtual void PromptForMultiplePackages(Execution::Context& context, std::vector& packagesToPrompt) = 0; + }; + + // Prompt for accepting package agreements. + struct PackageAgreementsPrompt : public PackagePrompt + { + PackageAgreementsPrompt(bool ensureAgreementsAcceptance) : m_ensureAgreementsAcceptance(ensureAgreementsAcceptance) {} + + bool PackageNeedsPrompt(Execution::Context& context) override + { + const auto& agreements = context.Get().CurrentLocalization.Get(); + return !agreements.empty(); + } + + void PromptForSinglePackage(Execution::Context& context) override + { + ShowPackageAgreements(context); + EnsurePackageAgreementsAcceptance(context, /* showPrompt */ true); + } + + void PromptForMultiplePackages(Execution::Context& context, std::vector& packagesToPrompt) override + { + for (auto packageContext : packagesToPrompt) + { + // Show agreements for each package + Execution::Context& showContext = *packageContext; + auto previousThreadGlobals = showContext.SetForCurrentThread(); + + ShowPackageAgreements(showContext); + if (showContext.IsTerminated()) + { + AICLI_TERMINATE_CONTEXT(showContext.GetTerminationHR()); + } + } + + EnsurePackageAgreementsAcceptance(context, /* showPrompt */ false); + } + + private: + void ShowPackageAgreements(Execution::Context& context) + { + const auto& manifest = context.Get(); + auto agreements = manifest.CurrentLocalization.Get(); + + if (agreements.empty()) + { + // Nothing to do + return; + } + + context << Workflow::ReportManifestIdentityWithVersion(Resource::String::ReportIdentityForAgreements) << Workflow::ShowPackageInfo; + context.Reporter.EmptyLine(); + } + + void EnsurePackageAgreementsAcceptance(Execution::Context& context, bool showPrompt) const + { + if (!m_ensureAgreementsAcceptance) + { + return; + } + + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::AgreementsAcceptedByCaller)) + { + AICLI_LOG(CLI, Info, << "Skipping package agreements acceptance check because AgreementsAcceptedByCaller flag is set."); + return; + } + + if (context.Args.Contains(Execution::Args::Type::AcceptPackageAgreements)) + { + AICLI_LOG(CLI, Info, << "Package agreements accepted by CLI flag"); + return; + } + + if (showPrompt && WI_IsFlagClear(context.GetFlags(), Execution::ContextFlag::DisableInteractivity)) + { + bool accepted = context.Reporter.PromptForBoolResponse(Resource::String::PackageAgreementsPrompt); + if (accepted) + { + AICLI_LOG(CLI, Info, << "Package agreements accepted in prompt"); + return; + } + else + { + AICLI_LOG(CLI, Info, << "Package agreements not accepted in prompt"); + } + } + + AICLI_LOG(CLI, Error, << "Package agreements were not agreed to."); + context.Reporter.Error() << Resource::String::PackageAgreementsNotAgreedTo << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); + } + + bool m_ensureAgreementsAcceptance; + }; + + // Prompt for getting the install root when a package requires it and it is not + // specified by the settings. + struct InstallRootPrompt : public PackagePrompt + { + InstallRootPrompt() : m_installLocation(User().Get()) {} + + bool PackageNeedsPrompt(Execution::Context& context) override + { + if (context.Get()->InstallLocationRequired && + !context.Args.Contains(Execution::Args::Type::InstallLocation)) + { + AICLI_LOG(CLI, Info, << "Package [" << context.Get().Id << "] requires an install location."); + + // An install location is required but one wasn't provided. + // Check if there is a default one from settings. + if (m_installLocation.empty()) + { + // We need to prompt + return true; + } + else + { + // Use the default + SetInstallLocation(context); + } + } + + return false; + } + + void PromptForSinglePackage(Execution::Context& context) override + { + context.Reporter.Info() << Resource::String::InstallerRequiresInstallLocation << std::endl; + PromptForInstallRoot(context); + + // When prompting for a single package, we use the provided location directly. + // This is different from when we prompt for multiple packages or use the root in the settings. + context.Args.AddArg(Execution::Args::Type::InstallLocation, m_installLocation.string()); + } + + void PromptForMultiplePackages(Execution::Context& context, std::vector& packagesToPrompt) override + { + // Report packages that will be affected. + context.Reporter.Info() << Resource::String::InstallersRequireInstallLocation << std::endl; + for (auto packageContext : packagesToPrompt) + { + *packageContext << ReportManifestIdentityWithVersion(" -"_liv, Execution::Reporter::Level::Warning); + if (packageContext->IsTerminated()) + { + AICLI_TERMINATE_CONTEXT(packageContext->GetTerminationHR()); + } + } + + PromptForInstallRoot(context); + + // Set the install location for each package. + for (auto packageContext : packagesToPrompt) + { + SetInstallLocation(*packageContext); + } + } + + private: + void PromptForInstallRoot(Execution::Context& context) + { + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::DisableInteractivity)) + { + AICLI_LOG(CLI, Error, << "Install location is required but was not provided."); + context.Reporter.Error() << Resource::String::InstallLocationNotProvided << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED); + } + + AICLI_LOG(CLI, Info, << "Prompting for install root."); + m_installLocation = context.Reporter.PromptForPath(Resource::String::PromptForInstallRoot); + AICLI_LOG(CLI, Info, << "Proceeding with installation using install root: " << m_installLocation); + } + + // Sets the install location for an execution context. + // The install location is obtained by appending the package ID to the install root. + // This function assumes that m_installLocation is set, either from settings or from the prompt, + // and that the context does not already have an install location. + void SetInstallLocation(Execution::Context& context) + { + auto packageId = context.Get().Id; + auto installLocation = m_installLocation; + installLocation += "\\" + packageId; + AICLI_LOG(CLI, Info, << "Setting install location for package [" << packageId << "] to: " << installLocation); + context.Args.AddArg(Execution::Args::Type::InstallLocation, installLocation.string()); + } + + std::filesystem::path m_installLocation; + }; + + // Prompt asking whether to continue when an installer will abort the terminal. + struct InstallerAbortsTerminalPrompt : public PackagePrompt + { + bool PackageNeedsPrompt(Execution::Context& context) override + { + return context.Get()->InstallerAbortsTerminal; + } + + void PromptForSinglePackage(Execution::Context& context) override + { + AICLI_LOG(CLI, Info, << "This installer may abort the terminal"); + context.Reporter.Warn() << Resource::String::InstallerAbortsTerminal << std::endl; + PromptToProceed(context); + } + + void PromptForMultiplePackages(Execution::Context& context, std::vector& packagesToPrompt) override + { + AICLI_LOG(CLI, Info, << "One or more installers may abort the terminal"); + context.Reporter.Warn() << Resource::String::InstallersAbortTerminal << std::endl; + for (auto packageContext : packagesToPrompt) + { + *packageContext << ReportManifestIdentityWithVersion(" -"_liv, Execution::Reporter::Level::Warning); + if (packageContext->IsTerminated()) + { + AICLI_TERMINATE_CONTEXT(packageContext->GetTerminationHR()); + } + } + + PromptToProceed(context); + } + + private: + void PromptToProceed(Execution::Context& context) + { + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::DisableInteractivity)) + { + return; + } + + AICLI_LOG(CLI, Info, << "Prompting before proceeding."); + bool accepted = context.Reporter.PromptForBoolResponse(Resource::String::PromptToProceed, Execution::Reporter::Level::Warning); + if (accepted) + { + AICLI_LOG(CLI, Info, << "Proceeding with installation"); + } + else + { + AICLI_LOG(CLI, Error, << "Aborting installation"); + context.Reporter.Error() << Resource::String::Cancelled << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + } + }; + + // Gets all the prompts that may be displayed, in order of appearance + std::vector> GetPackagePrompts(bool ensureAgreementsAcceptance = true) + { + std::vector> result; + result.push_back(std::make_unique(ensureAgreementsAcceptance)); + result.push_back(std::make_unique()); + result.push_back(std::make_unique()); + return result; + } + } + + void ShowPromptsForSinglePackage::operator()(Execution::Context& context) const + { + for (auto& prompt : GetPackagePrompts()) + { + // Show the prompt if needed + if (prompt->PackageNeedsPrompt(context)) + { + prompt->PromptForSinglePackage(context); + } + + if (context.IsTerminated()) + { + return; + } + } + } + + void ShowPromptsForMultiplePackages::operator()(Execution::Context& context) const + { + for (auto& prompt : GetPackagePrompts(m_ensureAgreementsAcceptance)) + { + // Find which packages need this prompt + std::vector packagesToPrompt; + for (auto& packageContext : context.Get()) + { + if (prompt->PackageNeedsPrompt(*packageContext)) + { + packagesToPrompt.push_back(packageContext.get()); + } + } + + // Prompt only if needed + if (!packagesToPrompt.empty()) + { + prompt->PromptForMultiplePackages(context, packagesToPrompt); + if (context.IsTerminated()) + { + return; + } + } + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.h b/src/AppInstallerCLICore/Workflows/PromptFlow.h new file mode 100644 index 0000000000..24fb465908 --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.h @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Shows all the prompts required for a single package, e.g. for package agreements + // Required Args: None + // Inputs: Manifest, Installer + // Outputs: None + struct ShowPromptsForSinglePackage : public WorkflowTask + { + ShowPromptsForSinglePackage(bool ensureAgreementsAcceptance) : + WorkflowTask("ShowPromptsForSinglePackage"), m_ensureAgreementsAcceptance(ensureAgreementsAcceptance) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_ensureAgreementsAcceptance; + }; + + // Shows all the prompts required for multiple package, e.g. for package agreements + // Required Args: None + // Inputs: PackagesToInstall + // Outputs: None + struct ShowPromptsForMultiplePackages : public WorkflowTask + { + ShowPromptsForMultiplePackages(bool ensureAgreementsAcceptance) : + WorkflowTask("ShowPromptsForMultiplePackages"), m_ensureAgreementsAcceptance(ensureAgreementsAcceptance) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_ensureAgreementsAcceptance; + }; +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index b13e612a33..f049d2abb2 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -31,14 +31,23 @@ namespace AppInstaller::CLI::Workflow } } - void ReportIdentity(Execution::Context& context, std::string_view name, std::string_view id) - { - context.Reporter.Info() << Resource::String::ReportIdentityFound << ' ' << Execution::NameEmphasis << name << " [" << Execution::IdEmphasis << id << ']' << std::endl; - } + void ReportIdentity( + Execution::Context& context, + Utility::LocIndString label, + std::string_view name, + std::string_view id, + std::string_view version = {}, + Execution::Reporter::Level level = Execution::Reporter::Level::Info) + { + auto out = context.Reporter.GetOutputStream(level); + out << label << ' ' << Execution::NameEmphasis << name << " ["_liv << Execution::IdEmphasis << id << ']'; + + if (!version.empty()) + { + out << ' ' << Resource::String::ShowVersion << ' ' << version; + } - void ReportIdentity(Execution::Context& context, std::string_view name, std::string_view id, std::string_view version) - { - context.Reporter.Info() << Resource::String::ReportIdentityFound << ' ' << Execution::NameEmphasis << name << " [" << Execution::IdEmphasis << id << "] " << Resource::String::ShowVersion << ' ' << version << std::endl; + out << std::endl; } Repository::Source OpenNamedSource(Execution::Context& context, std::string_view sourceName) @@ -945,19 +954,19 @@ namespace AppInstaller::CLI::Workflow void ReportPackageIdentity(Execution::Context& context) { auto package = context.Get(); - ReportIdentity(context, package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); + ReportIdentity(context, Resource::LocString{ Resource::String::ReportIdentityFound }, package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); } void ReportManifestIdentity(Execution::Context& context) { const auto& manifest = context.Get(); - ReportIdentity(context, manifest.CurrentLocalization.Get(), manifest.Id); + ReportIdentity(context, Resource::LocString{ Resource::String::ReportIdentityFound }, manifest.CurrentLocalization.Get(), manifest.Id); } - void ReportManifestIdentityWithVersion(Execution::Context& context) + void ReportManifestIdentityWithVersion::operator()(Execution::Context& context) const { const auto& manifest = context.Get(); - ReportIdentity(context, manifest.CurrentLocalization.Get(), manifest.Id, manifest.Version); + ReportIdentity(context, m_label, manifest.CurrentLocalization.Get(), manifest.Id, manifest.Version, m_level); } void GetManifest(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index ab526554db..b879bda3a3 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include "ExecutionArgs.h" +#include "ExecutionReporter.h" #include #include @@ -300,7 +301,19 @@ namespace AppInstaller::CLI::Workflow // Required Args: None // Inputs: Manifest // Outputs: None - void ReportManifestIdentityWithVersion(Execution::Context& context); + struct ReportManifestIdentityWithVersion : public WorkflowTask + { + ReportManifestIdentityWithVersion(Utility::LocIndView label, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : + WorkflowTask("ReportManifestIdentityWithVersion"), m_label(label), m_level(level) {} + ReportManifestIdentityWithVersion(Resource::LocString label = Resource::String::ReportIdentityFound, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : + WorkflowTask("ReportManifestIdentityWithVersion"), m_label(label), m_level(level) {} + + void operator()(Execution::Context& context) const override; + + private: + Utility::LocIndString m_label; + Execution::Reporter::Level m_level; + }; // Composite flow that produces a manifest; either from one given on the command line or by searching. // Required Args: None diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index b17fea0df1..1aedd577a6 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -178,6 +178,9 @@ public class ErrorCode public const int ERROR_INSTALLER_PROHIBITS_ELEVATION = unchecked((int)0x8A150056); public const int ERROR_PORTABLE_UNINSTALL_FAILED = unchecked((int)0x8A150057); public const int ERROR_ARP_VERSION_VALIDATION_FAILED = unchecked((int)0x8A150058); + public const int ERROR_UNSUPPORTED_ARGUMENT = unchecked((int)0x8A150059); + public const int ERROR_BIND_WITH_EMBEDDED_NULL = unchecked((int)0x8A15005A); + public const int ERROR_INSTALL_LOCATION_REQUIRED = unchecked((int)0x8A15005B); 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/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 583db2ef73..5f0a9ec497 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1379,4 +1379,29 @@ Please specify one of them using the `--source` option to proceed. Only one non-portable nested installer can be specified for an archive installer + + This installer is known to restart the terminal or shell + + + This package requires an install location + + + The following installers are known to restart the terminal or shell: + + + The following installers require an install location: + + + Specify the install root: + + + Do you want to proceed? + + + Agreements for + This will be followed by a package name, and then a list of license agreements + + + Install location is required by the package but it was not provided + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp index 6b2a08c622..093f16396f 100644 --- a/src/AppInstallerCommonCore/Errors.cpp +++ b/src/AppInstallerCommonCore/Errors.cpp @@ -156,6 +156,8 @@ namespace AppInstaller return "Source agreements were not agreed to"; case APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH: return "Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again."; + case APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE: + return "Missing resource file"; case APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED: return "Running MSI install failed"; case APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT: @@ -188,6 +190,8 @@ namespace AppInstaller return "Failed to uninstall portable package"; case APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED: return "Failed to validate DisplayVersion values against index."; + case APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED: + return "Install location required but not provided"; case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: return "Application is currently running.Exit the application then try again."; case APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS: diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h index abd3e49970..3eac6e22ef 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -106,6 +106,7 @@ #define APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND ((HRESULT)0x8A15005B) #define APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED ((HRESULT)0x8A15005C) #define APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH ((HRESULT)0x8A15005D) +#define APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED ((HRESULT)0x8A15005E) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index ace3ab64da..d9a910a678 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -73,17 +73,18 @@ namespace AppInstaller::Settings EFExperimentalCmd, EFExperimentalArg, EFDependencies, + EFDirectMSI, EFZipInstall, TelemetryDisable, InstallScopePreference, InstallScopeRequirement, - NetworkDownloader, - NetworkDOProgressTimeoutInSeconds, InstallArchitecturePreference, InstallArchitectureRequirement, InstallLocalePreference, InstallLocaleRequirement, - EFDirectMSI, + InstallDefaultRoot, + NetworkDownloader, + NetworkDOProgressTimeoutInSeconds, EnableSelfInitiatedMinidump, LoggingLevelPreference, InstallIgnoreWarnings, @@ -129,14 +130,13 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalCmd, bool, bool, false, ".experimentalFeatures.experimentalCmd"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalArg, bool, bool, false, ".experimentalFeatures.experimentalArg"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFDependencies, bool, bool, false, ".experimentalFeatures.dependencies"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFZipInstall, bool, bool, false, ".experimentalFeatures.zipInstall"sv); SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitecturePreference, std::vector, std::vector, {}, ".installBehavior.preferences.architectures"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitectureRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.architectures"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopePreference, std::string, ScopePreference, ScopePreference::User, ".installBehavior.preferences.scope"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopeRequirement, std::string, ScopePreference, ScopePreference::None, ".installBehavior.requirements.scope"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDownloader, std::string, InstallerDownloader, InstallerDownloader::Default, ".network.downloader"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDOProgressTimeoutInSeconds, uint32_t, std::chrono::seconds, 60s, ".network.doProgressTimeoutInSeconds"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocalePreference, std::vector, std::vector, {}, ".installBehavior.preferences.locale"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocaleRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.locale"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallIgnoreWarnings, bool, bool, false, ".installBehavior.ignoreWarnings"sv); @@ -145,6 +145,9 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::PortableAppMachineRoot, std::string, std::filesystem::path, {}, ".installBehavior.portableAppMachineRoot"sv); SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortablePackage, bool, bool, false, ".uninstallBehavior.purgePortablePackage"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallDefaultRoot, std::string, std::filesystem::path, {}, ".installBehavior.defaultInstallRoot"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDownloader, std::string, InstallerDownloader, InstallerDownloader::Default, ".network.downloader"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDOProgressTimeoutInSeconds, uint32_t, std::chrono::seconds, 60s, ".network.doProgressTimeoutInSeconds"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EnableSelfInitiatedMinidump, bool, bool, false, ".debugging.enableSelfInitiatedMinidump"sv); SETTINGMAPPING_SPECIALIZATION(Setting::LoggingLevelPreference, std::string, Logging::Level, Logging::Level::Info, ".logging.level"sv); diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index da01087de2..096f9e2641 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -235,9 +235,9 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFExperimentalCmd) WINGET_VALIDATE_PASS_THROUGH(EFExperimentalArg) WINGET_VALIDATE_PASS_THROUGH(EFDependencies) + WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) WINGET_VALIDATE_PASS_THROUGH(EFZipInstall) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) - WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) WINGET_VALIDATE_PASS_THROUGH(InstallIgnoreWarnings) WINGET_VALIDATE_PASS_THROUGH(DisableInstallNotes) @@ -318,6 +318,13 @@ namespace AppInstaller::Settings return SettingMapping::Validate(value); } + WINGET_VALIDATE_SIGNATURE(InstallDefaultRoot) + { + // TODO: Check valid + // TODO: Allow environment variables? + return std::filesystem::path{ value }; + } + WINGET_VALIDATE_SIGNATURE(NetworkDownloader) { static constexpr std::string_view s_downloader_default = "default"; From 867adf2512acc3a8edfd0fc23c73d3d3312465d8 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 7 Jul 2022 10:33:13 -0700 Subject: [PATCH 02/15] Add setting to disable prompts --- doc/Settings.md | 14 ++++++++ .../JSON/settings/settings.schema.0.2.json | 17 +++++++++ .../Public/winget/UserSettings.h | 35 +++++++++++++++---- src/AppInstallerCommonCore/UserSettings.cpp | 1 + 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/doc/Settings.md b/doc/Settings.md index d231149518..a4c50bc4d7 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -193,6 +193,20 @@ The `doProgressTimeoutInSeconds` setting updates the number of seconds to wait w } ``` +## Interactivity + +The `interactivity` settings control whether winget may show interactive prompts during execution. Note that this refers only to prompts shown by winget itself and not to those shown by package installers. + +### disable + +```json + "interactivity": { + "disable": true + }, +``` + +If set to true, the `interactivity.disable` setting will prevent any interactive prompt from being shown. + ## Experimental Features To allow work to be done and distributed to early adopters for feedback, settings can be used to enable "experimental" features. diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json index 528a492cf4..69362752a1 100644 --- a/schemas/JSON/settings/settings.schema.0.2.json +++ b/schemas/JSON/settings/settings.schema.0.2.json @@ -169,6 +169,17 @@ } } }, + "Interactivity": { + "description": "Interactivity settings", + "type": "object", + "properties": { + "disable": { + "description": "Controls whether interactive prompts are shown by the Windows Package Manager client", + "type": "boolean", + "default": false + } + } + }, "Experimental": { "description": "Experimental Features", "type": "object", @@ -238,6 +249,12 @@ }, "additionalItems": true }, + { + "properties": { + "interactivity": { "$ref": "#/definitions/Interactivity" } + }, + "additionalItems": true + }, { "properties": { "experimentalFeatures": { "$ref": "#/definitions/Experimental" } diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index d9a910a678..602db390c9 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -68,14 +68,19 @@ namespace AppInstaller::Settings // Validate will be called by ValidateAll without any more changes. enum class Setting : size_t { + // Visual ProgressBarVisualStyle, + // Source AutoUpdateTimeInMinutes, + // Experimental EFExperimentalCmd, EFExperimentalArg, EFDependencies, EFDirectMSI, EFZipInstall, + // Telemetry TelemetryDisable, + // Install behavior InstallScopePreference, InstallScopeRequirement, InstallArchitecturePreference, @@ -83,15 +88,21 @@ namespace AppInstaller::Settings InstallLocalePreference, InstallLocaleRequirement, InstallDefaultRoot, - NetworkDownloader, - NetworkDOProgressTimeoutInSeconds, - EnableSelfInitiatedMinidump, - LoggingLevelPreference, InstallIgnoreWarnings, DisableInstallNotes, PortableAppUserRoot, PortableAppMachineRoot, + // Network + NetworkDownloader, + NetworkDOProgressTimeoutInSeconds, + // Logging + LoggingLevelPreference, + // Uninstall behavior UninstallPurgePortablePackage, + // Interactivity + InteractivityDisable, + // Debug + EnableSelfInitiatedMinidump, Max }; @@ -125,14 +136,19 @@ namespace AppInstaller::Settings #define SETTINGMAPPING_SPECIALIZATION(_setting_, _json_, _value_, _default_, _path_) \ SETTINGMAPPING_SPECIALIZATION_POLICY(_setting_, _json_, _value_, _default_, _path_, ValuePolicy::None) + // Visual SETTINGMAPPING_SPECIALIZATION(Setting::ProgressBarVisualStyle, std::string, VisualStyle, VisualStyle::Accent, ".visual.progressBar"sv); + // Source SETTINGMAPPING_SPECIALIZATION_POLICY(Setting::AutoUpdateTimeInMinutes, uint32_t, std::chrono::minutes, 5min, ".source.autoUpdateIntervalInMinutes"sv, ValuePolicy::SourceAutoUpdateIntervalInMinutes); + // Experimental SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalCmd, bool, bool, false, ".experimentalFeatures.experimentalCmd"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalArg, bool, bool, false, ".experimentalFeatures.experimentalArg"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFDependencies, bool, bool, false, ".experimentalFeatures.dependencies"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFZipInstall, bool, bool, false, ".experimentalFeatures.zipInstall"sv); + // Telemetry SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); + // Install behavior SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitecturePreference, std::vector, std::vector, {}, ".installBehavior.preferences.architectures"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitectureRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.architectures"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopePreference, std::string, ScopePreference, ScopePreference::User, ".installBehavior.preferences.scope"sv); @@ -143,14 +159,19 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::DisableInstallNotes, bool, bool, false, ".installBehavior.disableInstallNotes"sv); SETTINGMAPPING_SPECIALIZATION(Setting::PortableAppUserRoot, std::string, std::filesystem::path, {}, ".installBehavior.portableAppUserRoot"sv); SETTINGMAPPING_SPECIALIZATION(Setting::PortableAppMachineRoot, std::string, std::filesystem::path, {}, ".installBehavior.portableAppMachineRoot"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortablePackage, bool, bool, false, ".uninstallBehavior.purgePortablePackage"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallDefaultRoot, std::string, std::filesystem::path, {}, ".installBehavior.defaultInstallRoot"sv); + // Uninstall behavior + SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortablePackage, bool, bool, false, ".uninstallBehavior.purgePortablePackage"sv); + // Network SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDownloader, std::string, InstallerDownloader, InstallerDownloader::Default, ".network.downloader"sv); SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDOProgressTimeoutInSeconds, uint32_t, std::chrono::seconds, 60s, ".network.doProgressTimeoutInSeconds"sv); + // Debug SETTINGMAPPING_SPECIALIZATION(Setting::EnableSelfInitiatedMinidump, bool, bool, false, ".debugging.enableSelfInitiatedMinidump"sv); + // Logging SETTINGMAPPING_SPECIALIZATION(Setting::LoggingLevelPreference, std::string, Logging::Level, Logging::Level::Info, ".logging.level"sv); - + // Interactivity + SETTINGMAPPING_SPECIALIZATION(Setting::InteractivityDisable, bool, bool, false, ".interactivity.disable"sv); + // Used to deduce the SettingVariant type; making a variant that includes std::monostate and all SettingMapping types. template inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index 096f9e2641..27d2899be7 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -238,6 +238,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) WINGET_VALIDATE_PASS_THROUGH(EFZipInstall) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) + WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) WINGET_VALIDATE_PASS_THROUGH(InstallIgnoreWarnings) WINGET_VALIDATE_PASS_THROUGH(DisableInstallNotes) From 2911e29b3dfc321621e459d9cded266ed8f6aff7 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 7 Jul 2022 12:48:08 -0700 Subject: [PATCH 03/15] Validate path --- src/AppInstallerCommonCore/UserSettings.cpp | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index 27d2899be7..2e1afc5fab 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -189,6 +189,17 @@ namespace AppInstaller::Settings // Use folding to call each setting validate function. (FoldHelper{}, ..., Validate(S)>(root, settings, warnings)); } + + std::optional ValidatePathValue(std::string_view value) + { + std::filesystem::path path = ConvertToUTF16(value); + if (!path.is_absolute()) + { + return {}; + } + + return path; + } } namespace details @@ -246,18 +257,12 @@ namespace AppInstaller::Settings WINGET_VALIDATE_SIGNATURE(PortableAppUserRoot) { - std::filesystem::path root = ConvertToUTF16(value); - if (!root.is_absolute()) - { - return {}; - } - - return root; + return ValidatePathValue(value); } WINGET_VALIDATE_SIGNATURE(PortableAppMachineRoot) { - return SettingMapping::Validate(value); + return ValidatePathValue(value); } WINGET_VALIDATE_SIGNATURE(InstallArchitecturePreference) @@ -321,9 +326,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_SIGNATURE(InstallDefaultRoot) { - // TODO: Check valid - // TODO: Allow environment variables? - return std::filesystem::path{ value }; + return ValidatePathValue(value); } WINGET_VALIDATE_SIGNATURE(NetworkDownloader) From de14e974788f6d3c7e189c58c232cb7704baa160 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 7 Jul 2022 12:51:25 -0700 Subject: [PATCH 04/15] Respect interactivity settings --- .../Workflows/PromptFlow.cpp | 153 ++++++++++++++++-- .../Workflows/PromptFlow.h | 14 ++ .../Workflows/WorkflowBase.h | 14 -- 3 files changed, 155 insertions(+), 26 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp index 59bb2f5850..5918d7b986 100644 --- a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp @@ -12,6 +12,99 @@ namespace AppInstaller::CLI::Workflow { namespace { + bool IsInteractivityAllowed(Execution::Context& context) + { + // Interactivity can be disabled for several reasons: + // * We are running in a non-interactive context (e.g., COM call) + // * It is disabled in the settings + // * It was disabled from the command line + + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::DisableInteractivity)) + { + AICLI_LOG(CLI, Debug, << "Skipping prompt. Interactivity is disabled due to non-interactive context."); + return false; + } + + if (context.Args.Contains(Execution::Args::Type::DisableInteractivity)) + { + AICLI_LOG(CLI, Debug, << "Skipping prompt. Interactivity is disabled by command line argument."); + return false; + } + + if (Settings::User().Get()) + { + AICLI_LOG(CLI, Debug, << "Skipping prompt. Interactivity is disabled in settings."); + return false; + } + + return true; + } + + bool HandleSourceAgreementsForOneSource(Execution::Context& context, const Source& source) + { + auto details = source.GetDetails(); + AICLI_LOG(CLI, Verbose, << "Checking Source agreements for source: " << details.Name); + + if (source.CheckSourceAgreements()) + { + AICLI_LOG(CLI, Verbose, << "Source agreements satisfied. Source: " << details.Name); + return true; + } + + // Show source agreements + std::string agreementsTitleMessage = Resource::LocString{ Resource::String::SourceAgreementsTitle }; + context.Reporter.Info() << Execution::SourceInfoEmphasis << + Utility::LocIndString{ Utility::FindAndReplaceMessageToken(agreementsTitleMessage, details.Name) } << std::endl; + + const auto& agreements = source.GetInformation().SourceAgreements; + + for (const auto& agreement : agreements) + { + if (!agreement.Label.empty()) + { + context.Reporter.Info() << Execution::SourceInfoEmphasis << Utility::LocIndString{ agreement.Label } << ": "_liv; + } + + if (!agreement.Text.empty()) + { + context.Reporter.Info() << Utility::LocIndString{ agreement.Text } << std::endl; + } + + if (!agreement.Url.empty()) + { + context.Reporter.Info() << Utility::LocIndString{ agreement.Url } << std::endl; + } + } + + // Show message for each individual implicit agreement field + auto fields = source.GetAgreementFieldsFromSourceInformation(); + if (WI_IsFlagSet(fields, ImplicitAgreementFieldEnum::Market)) + { + context.Reporter.Info() << Resource::String::SourceAgreementsMarketMessage << std::endl; + } + + context.Reporter.Info() << std::endl; + + bool accepted = context.Args.Contains(Execution::Args::Type::AcceptSourceAgreements); + + if (!accepted && IsInteractivityAllowed(context)) + { + accepted = context.Reporter.PromptForBoolResponse(Resource::String::SourceAgreementsPrompt); + } + + if (accepted) + { + AICLI_LOG(CLI, Verbose, << "Source agreements accepted. Source: " << details.Name); + source.SaveAcceptedSourceAgreements(); + } + else + { + AICLI_LOG(CLI, Verbose, << "Source agreements not accepted. Source: " << details.Name); + } + + return accepted; + } + // An interface for defining prompts to the user regarding a package. // Note that each prompt may behave differently when running non-interactively // (e.g. failing if it is needed vs. continuing silently), and they may @@ -107,17 +200,21 @@ namespace AppInstaller::CLI::Workflow return; } - if (showPrompt && WI_IsFlagClear(context.GetFlags(), Execution::ContextFlag::DisableInteractivity)) + if (showPrompt) { - bool accepted = context.Reporter.PromptForBoolResponse(Resource::String::PackageAgreementsPrompt); - if (accepted) + AICLI_LOG(CLI, Debug, << "Prompting to accept package agreements"); + if (IsInteractivityAllowed(context)) { - AICLI_LOG(CLI, Info, << "Package agreements accepted in prompt"); - return; - } - else - { - AICLI_LOG(CLI, Info, << "Package agreements not accepted in prompt"); + bool accepted = context.Reporter.PromptForBoolResponse(Resource::String::PackageAgreementsPrompt); + if (accepted) + { + AICLI_LOG(CLI, Info, << "Package agreements accepted in prompt"); + return; + } + else + { + AICLI_LOG(CLI, Info, << "Package agreements not accepted in prompt"); + } } } @@ -194,7 +291,7 @@ namespace AppInstaller::CLI::Workflow private: void PromptForInstallRoot(Execution::Context& context) { - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::DisableInteractivity)) + if (!IsInteractivityAllowed(context)) { AICLI_LOG(CLI, Error, << "Install location is required but was not provided."); context.Reporter.Error() << Resource::String::InstallLocationNotProvided << std::endl; @@ -256,12 +353,12 @@ namespace AppInstaller::CLI::Workflow private: void PromptToProceed(Execution::Context& context) { - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::DisableInteractivity)) + AICLI_LOG(CLI, Info, << "Prompting before proceeding with installer that aborts terminal."); + if (!IsInteractivityAllowed(context)) { return; } - AICLI_LOG(CLI, Info, << "Prompting before proceeding."); bool accepted = context.Reporter.PromptForBoolResponse(Resource::String::PromptToProceed, Execution::Reporter::Level::Warning); if (accepted) { @@ -287,6 +384,38 @@ namespace AppInstaller::CLI::Workflow } } + void HandleSourceAgreements::operator()(Execution::Context& context) const + { + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::AgreementsAcceptedByCaller)) + { + AICLI_LOG(CLI, Info, << "Skipping source agreements acceptance check because AgreementsAcceptedByCaller flag is set."); + return; + } + + bool allAccepted = true; + + if (m_source.IsComposite()) + { + for (auto const& source : m_source.GetAvailableSources()) + { + if (!HandleSourceAgreementsForOneSource(context, source)) + { + allAccepted = false; + } + } + } + else + { + allAccepted = HandleSourceAgreementsForOneSource(context, m_source); + } + + if (!allAccepted) + { + context.Reporter.Error() << Resource::String::SourceAgreementsNotAgreedTo << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED); + } + } + void ShowPromptsForSinglePackage::operator()(Execution::Context& context) const { for (auto& prompt : GetPackagePrompts()) diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.h b/src/AppInstallerCLICore/Workflows/PromptFlow.h index 24fb465908..0c79df84b6 100644 --- a/src/AppInstallerCLICore/Workflows/PromptFlow.h +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.h @@ -5,6 +5,20 @@ namespace AppInstaller::CLI::Workflow { + // Handles all opened source(s) agreements if needed. + // Required Args: The source to be checked for agreements + // Inputs: None + // Outputs: None + struct HandleSourceAgreements : public WorkflowTask + { + HandleSourceAgreements(Repository::Source source) : WorkflowTask("HandleSourceAgreements"), m_source(std::move(source)) {} + + void operator()(Execution::Context& context) const override; + + private: + Repository::Source m_source; + }; + // Shows all the prompts required for a single package, e.g. for package agreements // Required Args: None // Inputs: Manifest, Installer diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index b879bda3a3..32976f06c1 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -372,20 +372,6 @@ namespace AppInstaller::CLI::Workflow private: ExecutionStage m_stage; }; - - // Handles all opened source(s) agreements if needed. - // Required Args: The source to be checked for agreements - // Inputs: None - // Outputs: None - struct HandleSourceAgreements : public WorkflowTask - { - HandleSourceAgreements(Repository::Source source) : WorkflowTask("HandleSourceAgreements"), m_source(std::move(source)) {} - - void operator()(Execution::Context& context) const override; - - private: - Repository::Source m_source; - }; } // Passes the context to the function if it has not been terminated; returns the context. From 586c01575c5efdc8e1a8150845efc5b4e10214a3 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 7 Jul 2022 13:01:59 -0700 Subject: [PATCH 05/15] Add argument --- src/AppInstallerCLICore/Argument.cpp | 1 + src/AppInstallerCLICore/ExecutionArgs.h | 12 +++++++++--- .../Shared/Strings/en-us/winget.resw | 4 ++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index a871a3b387..e748a22ed1 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -110,6 +110,7 @@ namespace AppInstaller::CLI args.push_back(ForType(Args::Type::RainbowStyle)); args.push_back(ForType(Args::Type::RetroStyle)); args.push_back(ForType(Args::Type::VerboseLogs)); + args.emplace_back("disable-interactivity", NoAlias, Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Standard, false); } std::string Argument::GetUsageString() const diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index c160cb1688..bafda0b0cf 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -79,20 +79,26 @@ namespace AppInstaller::CLI::Execution AdminSettingEnable, AdminSettingDisable, - // Other + // Upgrade command All, // Used in Update command to update all installed packages to latest + IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions + + // Show command ListVersions, // Used in Show command to list all available versions of an app + + // Common arguments NoVT, // Disable VirtualTerminal outputs RetroStyle, // Makes progress display as retro RainbowStyle, // Makes progress display as a rainbow Help, // Show command usage Info, // Show general info about WinGet VerboseLogs, // Increases winget logging level to verbose + DisableInteractivity, // Disable interactive prompts + Wait, // Prompts the user to press any key before exiting + DependencySource, // Index source to be queried against for finding dependencies CustomHeader, // Optional Rest source header AcceptSourceAgreements, // Accept all source agreements - IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions - Wait, // Prompts the user to press any key before exiting // Used for demonstration purposes ExperimentalArg, diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 5f0a9ec497..8660053a8a 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1404,4 +1404,8 @@ Please specify one of them using the `--source` option to proceed. Install location is required by the package but it was not provided + + Disable interactive prompts + Description for a command line argument, shown next to it in the help + \ No newline at end of file From f9e128e8519877d53fd03ad2304717115cfeee92 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Fri, 8 Jul 2022 13:29:18 -0700 Subject: [PATCH 06/15] Remove from bad rebase --- src/AppInstallerCLICore/Command.cpp | 1 + .../Workflows/InstallFlow.cpp | 85 ------------------- .../Workflows/InstallFlow.h | 39 +-------- 3 files changed, 2 insertions(+), 123 deletions(-) diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp index aa5f993dc8..a41e364cd0 100644 --- a/src/AppInstallerCLICore/Command.cpp +++ b/src/AppInstallerCLICore/Command.cpp @@ -808,6 +808,7 @@ namespace AppInstaller::CLI { ExecuteInternal(context); } + if (context.Args.Contains(Execution::Args::Type::Wait)) { context.Reporter.PromptForEnter(); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 5439eb94d6..95d159de48 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -247,91 +247,6 @@ namespace AppInstaller::CLI::Workflow } } - void ShowPackageAgreements::operator()(Execution::Context& context) const - { - const auto& manifest = context.Get(); - auto agreements = manifest.CurrentLocalization.Get(); - - if (agreements.empty()) - { - // Nothing to do - return; - } - - context << Workflow::ShowPackageInfo; - context.Reporter.Info() << std::endl; - - if (m_ensureAcceptance) - { - context << Workflow::EnsurePackageAgreementsAcceptance(/* showPrompt */ true); - } - } - - void EnsurePackageAgreementsAcceptance::operator()(Execution::Context& context) const - { - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::AgreementsAcceptedByCaller)) - { - AICLI_LOG(CLI, Info, << "Skipping package agreements acceptance check because AgreementsAcceptedByCaller flag is set."); - return; - } - - if (context.Args.Contains(Execution::Args::Type::AcceptPackageAgreements)) - { - AICLI_LOG(CLI, Info, << "Package agreements accepted by CLI flag"); - return; - } - - if (m_showPrompt) - { - bool accepted = context.Reporter.PromptForBoolResponse(Resource::String::PackageAgreementsPrompt); - if (accepted) - { - AICLI_LOG(CLI, Info, << "Package agreements accepted in prompt"); - return; - } - else - { - AICLI_LOG(CLI, Info, << "Package agreements not accepted in prompt"); - } - } - - AICLI_LOG(CLI, Error, << "Package agreements were not agreed to."); - context.Reporter.Error() << Resource::String::PackageAgreementsNotAgreedTo << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); - } - - void EnsurePackageAgreementsAcceptanceForMultipleInstallers(Execution::Context& context) - { - bool hasPackageAgreements = false; - for (auto& packageContext : context.Get()) - { - // Show agreements for each package that has one - auto agreements = packageContext->Get().CurrentLocalization.Get(); - if (agreements.empty()) - { - continue; - } - Execution::Context& showContext = *packageContext; - auto previousThreadGlobals = showContext.SetForCurrentThread(); - - showContext << - Workflow::ReportManifestIdentityWithVersion << - Workflow::ShowPackageAgreements(/* ensureAcceptance */ false); - if (showContext.IsTerminated()) - { - AICLI_TERMINATE_CONTEXT(showContext.GetTerminationHR()); - } - - hasPackageAgreements |= true; - } - - // If any package has agreements, ensure they are accepted - if (hasPackageAgreements) - { - context << Workflow::EnsurePackageAgreementsAcceptance(/* showPrompt */ false); - } - } - void ExecuteInstallerForType::operator()(Execution::Context& context) const { bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index 62136565de..7e62becb94 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -35,44 +35,7 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void CheckForUnsupportedArgs(Execution::Context& context); - // Shows the license agreements if the application has them. - // Required Args: None - // Inputs: Manifest - // Outputs: None - struct ShowPackageAgreements : public WorkflowTask - { - ShowPackageAgreements(bool ensureAcceptance) : WorkflowTask("ShowPackageAgreements"), m_ensureAcceptance(ensureAcceptance) {} - - void operator()(Execution::Context& context) const override; - - private: - // Whether we need to ensure that the agreements are accepted, or only show them. - bool m_ensureAcceptance; - }; - - // Ensure the user accepted the license agreements. - // Required Args: None - // Inputs: None - // Outputs: None - struct EnsurePackageAgreementsAcceptance : public WorkflowTask - { - EnsurePackageAgreementsAcceptance(bool showPrompt) : WorkflowTask("EnsurePackageAgreementsAcceptance"), m_showPrompt(showPrompt) {} - - void operator()(Execution::Context& context) const override; - - private: - // Whether to show an interactive prompt - bool m_showPrompt; - }; - - // Ensure that the user accepted all the license agreements when there are - // multiple installers. - // Required Args: None - // Inputs: PackagesToInstall - // Outputs: None - void EnsurePackageAgreementsAcceptanceForMultipleInstallers(Execution::Context& context); - - // Starts execution of the installer. + // Composite flow that chooses what to do based on the installer type. // Required Args: None // Inputs: Installer, InstallerPath // Outputs: None From 844b14c8299f09b8073bb271e3739d69271af0b1 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 12 Jul 2022 13:04:23 -0700 Subject: [PATCH 07/15] Fix errors --- src/AppInstallerCLICore/Resources.h | 4 ---- src/AppInstallerCLICore/Workflows/PromptFlow.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index ecc0a85258..7c5cb74b09 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -235,15 +235,11 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ProductCodeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PromptForInstallRoot); WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); - WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); - WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); WINGET_DEFINE_RESOURCE_STRINGID(PromptToProceed); WINGET_DEFINE_RESOURCE_STRINGID(PurgeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PurgeInstallDirectory); WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RelatedLink); WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription); diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp index 5918d7b986..0947b78978 100644 --- a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp @@ -21,19 +21,19 @@ namespace AppInstaller::CLI::Workflow if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::DisableInteractivity)) { - AICLI_LOG(CLI, Debug, << "Skipping prompt. Interactivity is disabled due to non-interactive context."); + AICLI_LOG(CLI, Verbose, << "Skipping prompt. Interactivity is disabled due to non-interactive context."); return false; } if (context.Args.Contains(Execution::Args::Type::DisableInteractivity)) { - AICLI_LOG(CLI, Debug, << "Skipping prompt. Interactivity is disabled by command line argument."); + AICLI_LOG(CLI, Verbose, << "Skipping prompt. Interactivity is disabled by command line argument."); return false; } if (Settings::User().Get()) { - AICLI_LOG(CLI, Debug, << "Skipping prompt. Interactivity is disabled in settings."); + AICLI_LOG(CLI, Verbose, << "Skipping prompt. Interactivity is disabled in settings."); return false; } @@ -202,7 +202,7 @@ namespace AppInstaller::CLI::Workflow if (showPrompt) { - AICLI_LOG(CLI, Debug, << "Prompting to accept package agreements"); + AICLI_LOG(CLI, Verbose, << "Prompting to accept package agreements"); if (IsInteractivityAllowed(context)) { bool accepted = context.Reporter.PromptForBoolResponse(Resource::String::PackageAgreementsPrompt); From 5f2a52449ec954c5a7b2438fd592e18b34e2ac5c Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Wed, 27 Jul 2022 14:50:33 -0700 Subject: [PATCH 08/15] Fix build errors --- .../AppInstallerCLICore.vcxproj.filters | 2 +- src/AppInstallerCLICore/Resources.h | 3 +- .../Workflows/PromptFlow.cpp | 4 +-- .../Workflows/SourceFlow.cpp | 1 + .../Workflows/WorkflowBase.cpp | 33 +------------------ src/AppInstallerCLITests/WorkFlow.cpp | 15 +++++---- 6 files changed, 15 insertions(+), 43 deletions(-) diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index c92147bd96..6f203a8902 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -330,7 +330,7 @@ Workflows - + Workflows diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 7c5cb74b09..0176541340 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -44,6 +44,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CountOutOfBoundsError); WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowInstall); WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceNotFound); WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceTooManyMatches); @@ -55,8 +56,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowContainsLoop); WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementError); WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementExitMessage); - WINGET_DEFINE_RESOURCE_STRINGID(CountOutOfBoundsError); WINGET_DEFINE_RESOURCE_STRINGID(DisabledByGroupPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(DisableInteractivityArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(Done); WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalArgumentDescription); diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp index 0947b78978..72341f0f9e 100644 --- a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp @@ -40,7 +40,7 @@ namespace AppInstaller::CLI::Workflow return true; } - bool HandleSourceAgreementsForOneSource(Execution::Context& context, const Source& source) + bool HandleSourceAgreementsForOneSource(Execution::Context& context, const Repository::Source& source) { auto details = source.GetDetails(); AICLI_LOG(CLI, Verbose, << "Checking Source agreements for source: " << details.Name); @@ -78,7 +78,7 @@ namespace AppInstaller::CLI::Workflow // Show message for each individual implicit agreement field auto fields = source.GetAgreementFieldsFromSourceInformation(); - if (WI_IsFlagSet(fields, ImplicitAgreementFieldEnum::Market)) + if (WI_IsFlagSet(fields, Repository::ImplicitAgreementFieldEnum::Market)) { context.Reporter.Info() << Resource::String::SourceAgreementsMarketMessage << std::endl; } diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp index 9dbb3c2e91..d6c45556df 100644 --- a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "Resources.h" #include "SourceFlow.h" +#include "PromptFlow.h" #include "TableOutput.h" #include "WorkflowBase.h" diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index f049d2abb2..2d3a6b2e64 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -4,6 +4,7 @@ #include "WorkflowBase.h" #include "ExecutionContext.h" #include "ManifestComparator.h" +#include "PromptFlow.h" #include "TableOutput.h" #include @@ -1127,38 +1128,6 @@ namespace AppInstaller::CLI::Workflow { context.SetExecutionStage(m_stage); } - - void HandleSourceAgreements::operator()(Execution::Context& context) const - { - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::AgreementsAcceptedByCaller)) - { - AICLI_LOG(CLI, Info, << "Skipping source agreements acceptance check because AgreementsAcceptedByCaller flag is set."); - return; - } - - bool allAccepted = true; - - if (m_source.IsComposite()) - { - for (auto const& source : m_source.GetAvailableSources()) - { - if (!HandleSourceAgreementsForOneSource(context, source)) - { - allAccepted = false; - } - } - } - else - { - allAccepted = HandleSourceAgreementsForOneSource(context, m_source); - } - - if (!allAccepted) - { - context.Reporter.Error() << Resource::String::SourceAgreementsNotAgreedTo << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED); - } - } } AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, AppInstaller::CLI::Workflow::WorkflowTask::Func f) diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 8107d80d5c..6e2c416584 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -10,19 +10,20 @@ #include #include #include -#include +#include +#include #include +#include #include #include -#include -#include -#include -#include -#include #include +#include +#include +#include #include #include -#include +#include +#include #include #include #include From bea291a113d1de1a3eba26fb67d973a9b2c0a24e Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Wed, 27 Jul 2022 16:36:23 -0700 Subject: [PATCH 09/15] Add tests --- .../AppInstallerCLITests.vcxproj | 10 +- .../AppInstallerCLITests.vcxproj.filters | 6 + .../InstallFlowTest_AbortsTerminal.yaml | 16 ++ ...stallFlowTest_InstallLocationRequired.yaml | 18 +++ src/AppInstallerCLITests/WorkFlow.cpp | 153 ++++++++++++++++++ 5 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 src/AppInstallerCLITests/TestData/InstallFlowTest_AbortsTerminal.yaml create mode 100644 src/AppInstallerCLITests/TestData/InstallFlowTest_InstallLocationRequired.yaml diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 5bd30e611c..d59f5f5a01 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -263,6 +263,12 @@ true + + true + + + true + true @@ -295,7 +301,7 @@ true - + true @@ -622,7 +628,7 @@ true - + true diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 9e45f86551..8f42a3cbdc 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -504,12 +504,18 @@ TestData + + TestData + TestData TestData + + TestData + TestData diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_AbortsTerminal.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_AbortsTerminal.yaml new file mode 100644 index 0000000000..4d7980b285 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_AbortsTerminal.yaml @@ -0,0 +1,16 @@ +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +InstallerAbortsTerminal: true +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallLocationRequired.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallLocationRequired.yaml new file mode 100644 index 0000000000..4c0e987253 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallLocationRequired.yaml @@ -0,0 +1,18 @@ +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +InstallLocationRequired: true +InstallerSwitches: + InstallLocation: /InstallDir +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 6e2c416584..c4b9fc6e6d 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -3146,3 +3146,156 @@ TEST_CASE("AdminSetting_LocalManifestFiles", "[LocalManifests][workflow]") REQUIRE_THROWS(installCommand3.ValidateArguments(args3)); } } + +TEST_CASE("PromptFlow_InteractivityDisabled", "[PromptFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TestUserSettings testSettings; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); + + SECTION("Disabled by setting") + { + testSettings.Set(true); + } + SECTION("Disabled by arg") + { + context.Args.AddArg(Execution::Args::Type::DisableInteractivity); + } + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt is not shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsPrompt).get()) == std::string::npos); + + // Verify installation failed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); + REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); +} + +TEST_CASE("PromptFlow_InstallerAbortsTerminal_Proceed", "[PromptFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + // Accept that the installer may abort the terminal by saying "Yes" at the prompt + std::istringstream installInput{ "y" }; + + std::ostringstream installOutput; + TestContext context{ installOutput, installInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_AbortsTerminal.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt is shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerAbortsTerminal).get()) != std::string::npos); + + // Verify Installer is called. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("PromptFlow_InstallerAbortsTerminal_Cancel", "[PromptFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + // Cancel the installation by saying "No" at the prompt that the installer may abort the terminal + std::istringstream installInput{ "n" }; + + std::ostringstream installOutput; + TestContext context{ installOutput, installInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_AbortsTerminal.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt is shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerAbortsTerminal).get()) != std::string::npos); + + // Verify installation failed + REQUIRE_TERMINATED_WITH(context, E_ABORT); + REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("PromptFlow_InstallLocationRequired", "[PromptFlow][workflow]") +{ + TestCommon::TempDirectory installLocation("TempDirectory"); + TestCommon::TestUserSettings testSettings; + + std::istringstream installInput; + std::ostringstream installOutput; + TestContext context{ installOutput, installInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallLocationRequired.yaml").GetPath().u8string()); + + bool shouldShowPrompt = false; + std::filesystem::path installResultPath = installLocation.GetPath() / "TestExeInstalled.txt"; + SECTION("From argument") + { + context.Args.AddArg(Execution::Args::Type::InstallLocation, installLocation.GetPath().string()); + } + SECTION("From settings") + { + testSettings.Set(installLocation.GetPath().string()); + + // When using the default location from settings, the Package ID is appended to the root + auto installLocationWithPackageId = installLocation.GetPath() / "AppInstallerCliTest.TestInstaller"; + std::filesystem::create_directory(installLocationWithPackageId); + installResultPath = installLocationWithPackageId / "TestExeInstalled.txt"; + } + SECTION("From prompt") + { + installInput.str(installLocation.GetPath().string()); + shouldShowPrompt = true; + } + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + bool promptShown = installOutput.str().find(Resource::LocString(Resource::String::InstallerRequiresInstallLocation).get()) != std::string::npos; + REQUIRE(shouldShowPrompt == promptShown); + + // Verify Installer is called with the right parameters + REQUIRE(std::filesystem::exists(installResultPath)); + std::ifstream installResultFile(installResultPath); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + const auto installDirArgument = "/InstallDir " + installLocation.GetPath().string(); + REQUIRE(installResultStr.find(installDirArgument) != std::string::npos); +} + +TEST_CASE("PromptFlow_InstallLocationRequired_Missing", "[PromptFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallLocationRequired.yaml").GetPath().u8string()); + // Disable interactivity so that there is not prompt and we cannot get the required location + context.Args.AddArg(Execution::Args::Type::DisableInteractivity); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt is shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerRequiresInstallLocation).get()) != std::string::npos); + + // Verify installation failed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED); + REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); +} \ No newline at end of file From 89a560d163cd3dbdf3a7164131d502fec4d1455b Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 28 Jul 2022 10:56:36 -0700 Subject: [PATCH 10/15] Validate path --- src/AppInstallerCLICore/ExecutionReporter.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index 0c2a4ec483..88808bc50a 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -166,11 +166,12 @@ namespace AppInstaller::CLI::Execution std::filesystem::path Reporter::PromptForPath(Resource::LocString message, Level level) { auto out = GetOutputStream(level); - out << message << ' '; // Try prompting until we get a valid answer for (;;) { + out << message << ' '; + // Read the response std::string response; if (!std::getline(m_in, response)) @@ -178,8 +179,12 @@ namespace AppInstaller::CLI::Execution THROW_HR(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR); } - // TODO: Check - return response; + // Validate the path + std::filesystem::path path{ response }; + if (path.is_absolute()) + { + return path; + } } } From c052303759ee8a4a9945a4bc5a2a2a74476514a0 Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 28 Jul 2022 12:21:09 -0700 Subject: [PATCH 11/15] Update error constants --- src/AppInstallerCLIE2ETests/Constants.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index 1aedd577a6..f0d3eed914 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -180,7 +180,10 @@ public class ErrorCode public const int ERROR_ARP_VERSION_VALIDATION_FAILED = unchecked((int)0x8A150058); public const int ERROR_UNSUPPORTED_ARGUMENT = unchecked((int)0x8A150059); public const int ERROR_BIND_WITH_EMBEDDED_NULL = unchecked((int)0x8A15005A); - public const int ERROR_INSTALL_LOCATION_REQUIRED = unchecked((int)0x8A15005B); + public const int ERROR_NESTEDINSTALLER_NOT_FOUND = unchecked((int)0x8A15005B); + public const int ERROR_EXTRACT_ARCHIVE_FAILED = unchecked((int)0x8A15005C); + public const int ERROR_NESTEDINSTALLER_INVALID_PATH = unchecked((int)0x8A15005D); + public const int ERROR_INSTALL_LOCATION_REQUIRED = unchecked((int)0x8A15005E); public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); From 91ac067ad4e83b818686e47f952d8882f674cc3d Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 9 Aug 2022 16:10:30 -0700 Subject: [PATCH 12/15] PR comments --- schemas/JSON/settings/settings.schema.0.2.json | 2 +- src/AppInstallerCLICore/Argument.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json index 69362752a1..1dc29fe633 100644 --- a/schemas/JSON/settings/settings.schema.0.2.json +++ b/schemas/JSON/settings/settings.schema.0.2.json @@ -120,7 +120,7 @@ "defaultInstallRoot": { "description": "Default install location to use for packages that require it when not specified", "type": "string", - "maxLength": "1024" + "maxLength": "32767" } } }, diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index e748a22ed1..6af4585099 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -110,7 +110,7 @@ namespace AppInstaller::CLI args.push_back(ForType(Args::Type::RainbowStyle)); args.push_back(ForType(Args::Type::RetroStyle)); args.push_back(ForType(Args::Type::VerboseLogs)); - args.emplace_back("disable-interactivity", NoAlias, Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Standard, false); + args.emplace_back("disable-interactivity", NoAlias, Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Flag, false); } std::string Argument::GetUsageString() const From 90bc3fe6ee5f0271a4382ffcdb6c5024e59316ba Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Tue, 9 Aug 2022 16:40:04 -0700 Subject: [PATCH 13/15] Fix bad merge --- src/AppInstallerCLICore/ExecutionArgs.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index fa81ef528f..bafda0b0cf 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -99,7 +99,6 @@ namespace AppInstaller::CLI::Execution DependencySource, // Index source to be queried against for finding dependencies CustomHeader, // Optional Rest source header AcceptSourceAgreements, // Accept all source agreements - Wait, // Prompts the user to press any key before exiting // Used for demonstration purposes ExperimentalArg, From 3870fc445b7592171027cd36b454892e2e14fb8a Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 11 Aug 2022 13:48:28 -0700 Subject: [PATCH 14/15] Fix bad merge --- .../AppInstallerCLICore.vcxproj | 2 +- .../AppInstallerCLICore.vcxproj.filters | 4 +- .../Workflows/WorkflowBase.cpp | 65 ------------------- 3 files changed, 3 insertions(+), 68 deletions(-) diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 77631a9c0b..4cc8f5d9da 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -260,7 +260,7 @@ - + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index 6f203a8902..4904eab23d 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -173,7 +173,7 @@ Workflows - + Public @@ -336,7 +336,7 @@ Workflows - + diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index a73a74a437..12cc39ec09 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -164,71 +164,6 @@ namespace AppInstaller::CLI::Workflow } } - bool HandleSourceAgreementsForOneSource(Execution::Context& context, const Source& source) - { - auto details = source.GetDetails(); - AICLI_LOG(CLI, Verbose, << "Checking Source agreements for source: " << details.Name); - - if (source.CheckSourceAgreements()) - { - AICLI_LOG(CLI, Verbose, << "Source agreements satisfied. Source: " << details.Name); - return true; - } - - // Show source agreements - std::string agreementsTitleMessage = Resource::LocString{ Resource::String::SourceAgreementsTitle }; - context.Reporter.Info() << Execution::SourceInfoEmphasis << - Utility::LocIndString{ Utility::FindAndReplaceMessageToken(agreementsTitleMessage, details.Name) } << std::endl; - - const auto& agreements = source.GetInformation().SourceAgreements; - - for (const auto& agreement : agreements) - { - if (!agreement.Label.empty()) - { - context.Reporter.Info() << Execution::SourceInfoEmphasis << Utility::LocIndString{ agreement.Label } << ": "_liv; - } - - if (!agreement.Text.empty()) - { - context.Reporter.Info() << Utility::LocIndString{ agreement.Text } << std::endl; - } - - if (!agreement.Url.empty()) - { - context.Reporter.Info() << Utility::LocIndString{ agreement.Url } << std::endl; - } - } - - // Show message for each individual implicit agreement field - auto fields = source.GetAgreementFieldsFromSourceInformation(); - if (WI_IsFlagSet(fields, ImplicitAgreementFieldEnum::Market)) - { - context.Reporter.Info() << Resource::String::SourceAgreementsMarketMessage << std::endl; - } - - context.Reporter.Info() << std::endl; - - bool accepted = context.Args.Contains(Execution::Args::Type::AcceptSourceAgreements); - - if (!accepted) - { - accepted = context.Reporter.PromptForBoolResponse(Resource::String::SourceAgreementsPrompt); - } - - if (accepted) - { - AICLI_LOG(CLI, Verbose, << "Source agreements accepted. Source: " << details.Name); - source.SaveAcceptedSourceAgreements(); - } - else - { - AICLI_LOG(CLI, Verbose, << "Source agreements rejected. Source: " << details.Name); - } - - return accepted; - } - // Data shown on a line of a table displaying installed packages struct InstalledPackagesTableLine { From eac56774060d4898ecee4be121799cbc70005c9a Mon Sep 17 00:00:00 2001 From: Flor Elisa Chacon Ochoa Date: Thu, 11 Aug 2022 15:53:08 -0700 Subject: [PATCH 15/15] Remove new use of ResourceLoader in COM --- src/AppInstallerCLICore/Workflows/PromptFlow.cpp | 4 ++-- .../Workflows/WorkflowBase.cpp | 16 +++++++++++----- src/AppInstallerCLICore/Workflows/WorkflowBase.h | 9 +++++---- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp index 72341f0f9e..1df6f3ea17 100644 --- a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp @@ -272,7 +272,7 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Info() << Resource::String::InstallersRequireInstallLocation << std::endl; for (auto packageContext : packagesToPrompt) { - *packageContext << ReportManifestIdentityWithVersion(" -"_liv, Execution::Reporter::Level::Warning); + *packageContext << ReportManifestIdentityWithVersion(" - "_liv, Execution::Reporter::Level::Warning); if (packageContext->IsTerminated()) { AICLI_TERMINATE_CONTEXT(packageContext->GetTerminationHR()); @@ -340,7 +340,7 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Warn() << Resource::String::InstallersAbortTerminal << std::endl; for (auto packageContext : packagesToPrompt) { - *packageContext << ReportManifestIdentityWithVersion(" -"_liv, Execution::Reporter::Level::Warning); + *packageContext << ReportManifestIdentityWithVersion(" - "_liv, Execution::Reporter::Level::Warning); if (packageContext->IsTerminated()) { AICLI_TERMINATE_CONTEXT(packageContext->GetTerminationHR()); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 12cc39ec09..dd5ba94a57 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -34,14 +34,20 @@ namespace AppInstaller::CLI::Workflow void ReportIdentity( Execution::Context& context, - Utility::LocIndString label, + Utility::LocIndView prefix, + std::optional label, std::string_view name, std::string_view id, std::string_view version = {}, Execution::Reporter::Level level = Execution::Reporter::Level::Info) { auto out = context.Reporter.GetOutputStream(level); - out << label << ' ' << Execution::NameEmphasis << name << " ["_liv << Execution::IdEmphasis << id << ']'; + out << prefix; + if (label) + { + out << *label << ' '; + } + out << Execution::NameEmphasis << name << " ["_liv << Execution::IdEmphasis << id << ']'; if (!version.empty()) { @@ -941,19 +947,19 @@ namespace AppInstaller::CLI::Workflow void ReportPackageIdentity(Execution::Context& context) { auto package = context.Get(); - ReportIdentity(context, Resource::LocString{ Resource::String::ReportIdentityFound }, package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); + ReportIdentity(context, {}, Resource::String::ReportIdentityFound, package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); } void ReportManifestIdentity(Execution::Context& context) { const auto& manifest = context.Get(); - ReportIdentity(context, Resource::LocString{ Resource::String::ReportIdentityFound }, manifest.CurrentLocalization.Get(), manifest.Id); + ReportIdentity(context, {}, Resource::String::ReportIdentityFound, manifest.CurrentLocalization.Get(), manifest.Id); } void ReportManifestIdentityWithVersion::operator()(Execution::Context& context) const { const auto& manifest = context.Get(); - ReportIdentity(context, m_label, manifest.CurrentLocalization.Get(), manifest.Id, manifest.Version, m_level); + ReportIdentity(context, m_prefix, m_label, manifest.CurrentLocalization.Get(), manifest.Id, manifest.Version, m_level); } void GetManifest(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index 32976f06c1..03f81d0d47 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -303,15 +303,16 @@ namespace AppInstaller::CLI::Workflow // Outputs: None struct ReportManifestIdentityWithVersion : public WorkflowTask { - ReportManifestIdentityWithVersion(Utility::LocIndView label, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : - WorkflowTask("ReportManifestIdentityWithVersion"), m_label(label), m_level(level) {} - ReportManifestIdentityWithVersion(Resource::LocString label = Resource::String::ReportIdentityFound, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : + ReportManifestIdentityWithVersion(Utility::LocIndView prefix, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : + WorkflowTask("ReportManifestIdentityWithVersion"), m_prefix(prefix), m_level(level) {} + ReportManifestIdentityWithVersion(Resource::StringId label = Resource::String::ReportIdentityFound, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : WorkflowTask("ReportManifestIdentityWithVersion"), m_label(label), m_level(level) {} void operator()(Execution::Context& context) const override; private: - Utility::LocIndString m_label; + Utility::LocIndView m_prefix; + std::optional m_label; Execution::Reporter::Level m_level; };