winget-cli

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

commit c854548d8889f78a4c8582d065bac2f768017a4d
parent c5d0af525225ecc6871dc797cf0f303a8266d2c4
Author: Ryan Fu <69221034+ryfu-msft@users.noreply.github.com>
Date:   Tue, 21 Jun 2022 15:59:23 -0700

Add support for UnsupportedArguments (#2216)


Diffstat:
Mschemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json | 6++++++
Mschemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json | 6++++++
Msrc/AppInstallerCLICore/Argument.cpp | 11+++++++++++
Msrc/AppInstallerCLICore/Argument.h | 3+++
Msrc/AppInstallerCLICore/Command.cpp | 12+-----------
Msrc/AppInstallerCLICore/Commands/InstallCommand.cpp | 1+
Msrc/AppInstallerCLICore/Commands/UpgradeCommand.cpp | 3++-
Msrc/AppInstallerCLICore/Core.cpp | 2+-
Msrc/AppInstallerCLICore/ExecutionContext.cpp | 1+
Msrc/AppInstallerCLICore/ExecutionContext.h | 12++++++++++++
Msrc/AppInstallerCLICore/Resources.h | 1+
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/Workflows/InstallFlow.h | 6++++++
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 3+++
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 6++++++
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters | 6++++++
Asrc/AppInstallerCLITests/TestData/InstallFlowTest_UnsupportedArguments.yaml | 19+++++++++++++++++++
Msrc/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml | 2--
Msrc/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Installer.yaml | 3++-
Asrc/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_UnsupportedArgs.yaml | 24++++++++++++++++++++++++
Msrc/AppInstallerCLITests/WorkFlow.cpp | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/YamlManifest.cpp | 7+++++--
Msrc/AppInstallerCommonCore/Public/AppInstallerErrors.h | 1+
23 files changed, 298 insertions(+), 18 deletions(-)

diff --git a/schemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json @@ -571,6 +571,9 @@ "UnsupportedOSArchitectures": { "$ref": "#/definitions/UnsupportedOSArchitectures" }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, "AppsAndFeaturesEntries": { "$ref": "#/definitions/AppsAndFeaturesEntries" }, @@ -668,6 +671,9 @@ "UnsupportedOSArchitectures": { "$ref": "#/definitions/UnsupportedOSArchitectures" }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, "AppsAndFeaturesEntries": { "$ref": "#/definitions/AppsAndFeaturesEntries" }, diff --git a/schemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json @@ -609,6 +609,9 @@ "UnsupportedOSArchitectures": { "$ref": "#/definitions/UnsupportedOSArchitectures" }, + "UnsupportedArguments": { + "$ref": " #/definitions/UnsupportedArguments" + }, "AppsAndFeaturesEntries": { "$ref": "#/definitions/AppsAndFeaturesEntries" }, @@ -822,6 +825,9 @@ "UnsupportedOSArchitectures": { "$ref": "#/definitions/UnsupportedOSArchitectures" }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, "AppsAndFeaturesEntries": { "$ref": "#/definitions/AppsAndFeaturesEntries" }, diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -111,6 +111,17 @@ namespace AppInstaller::CLI args.push_back(ForType(Args::Type::VerboseLogs)); } + std::string Argument::GetUsageString() const + { + std::ostringstream strstr; + if (m_alias != Argument::NoAlias) + { + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << m_alias << ','; + } + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << m_name; + return strstr.str(); + } + void Argument::ValidatePackageSelectionArgumentSupplied(const Execution::Args& args) { for (Args::Type type : { Args::Type::Query, Args::Type::Manifest, Args::Type::Id, Args::Type::Name, Args::Type::Moniker, Args::Type::ProductCode, Args::Type::Tag, Args::Type::Command }) diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h @@ -107,6 +107,9 @@ namespace AppInstaller::CLI // Requires that some form of package selection argument is present static void ValidatePackageSelectionArgumentSupplied(const Execution::Args& args); + // Gets the argument usage string in the format of "-alias,--name". + std::string GetUsageString() const; + // Arguments are not localized at this time. Utility::LocIndView Name() const { return Utility::LocIndView{ m_name }; } char Alias() const { return m_alias; } diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -223,17 +223,7 @@ namespace AppInstaller::CLI size_t maxArgNameLength = 0; for (const auto& arg : arguments) { - std::ostringstream strstr; - if (arg.Alias() != Argument::NoAlias) - { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias() << ','; - } - if (arg.AlternateName() != Argument::NoAlternateName) { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.AlternateName() << ','; - } - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name(); - - argNames.emplace_back(strstr.str()); + argNames.emplace_back(arg.GetUsageString()); maxArgNameLength = std::max(maxArgNameLength, argNames.back().length()); } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -148,6 +148,7 @@ namespace AppInstaller::CLI Workflow::GetManifest << Workflow::SelectInstaller << Workflow::EnsureApplicableInstaller << + Workflow::CheckForUnsupportedArgs << Workflow::InstallSinglePackage; } } diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -252,7 +252,8 @@ namespace AppInstaller::CLI context << SelectLatestApplicableUpdate(true); } - context << InstallSinglePackage; + context << + InstallSinglePackage; } } } diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp @@ -121,7 +121,7 @@ namespace AppInstaller::CLI } context.UpdateForArgs(); - + context.SetExecutingCommand(command.get()); command->ValidateArguments(context.Args); } // Exceptions specific to parsing the arguments of a command diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -122,6 +122,7 @@ namespace AppInstaller::CLI::Execution { auto clone = std::make_unique<Context>(Reporter, m_threadGlobals); clone->m_flags = m_flags; + clone->m_executingCommand = m_executingCommand; // If the parent is hooked up to the CTRL signal, have the clone be as well if (m_disableCtrlHandlerOnExit) { diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h @@ -39,6 +39,11 @@ // Also returns the specified value from the current function. #define AICLI_TERMINATE_CONTEXT_RETURN(_hr_,_ret_) AICLI_TERMINATE_CONTEXT_ARGS(context,_hr_,_ret_) +namespace AppInstaller::CLI +{ + struct Command; +} + namespace AppInstaller::CLI::Workflow { struct WorkflowTask; @@ -137,6 +142,12 @@ namespace AppInstaller::CLI::Execution std::unique_ptr<AppInstaller::ThreadLocalStorage::PreviousThreadGlobals> SetForCurrentThread(); + // Gets the executing command + AppInstaller::CLI::Command* GetExecutingCommand() { return m_executingCommand; } + + // Sets the executing command + void SetExecutingCommand(AppInstaller::CLI::Command* command) { m_executingCommand = command; } + #ifndef AICLI_DISABLE_TEST_HOOKS // Enable tests to override behavior bool ShouldExecuteWorkflowTask(const Workflow::WorkflowTask& task); @@ -156,5 +167,6 @@ namespace AppInstaller::CLI::Execution ContextFlag m_flags = ContextFlag::None; Workflow::ExecutionStage m_executionStage = Workflow::ExecutionStage::Initial; AppInstaller::ThreadLocalStorage::ThreadGlobals m_threadGlobals; + AppInstaller::CLI::Command* m_executingCommand = nullptr; }; } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -362,6 +362,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowStartingPackageUninstall); WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowUninstallSuccess); WINGET_DEFINE_RESOURCE_STRINGID(UnrecognizedCommand); + WINGET_DEFINE_RESOURCE_STRINGID(UnsupportedArgument); WINGET_DEFINE_RESOURCE_STRINGID(UpdateAllArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicable); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandLongDescription); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -14,6 +14,8 @@ #include "Workflows/DependenciesFlow.h" #include <AppInstallerDeployment.h> #include <winget/ARPCorrelation.h> +#include <Argument.h> +#include <Command.h> using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; using namespace winrt::Windows::Foundation; @@ -57,6 +59,36 @@ namespace AppInstaller::CLI::Workflow } } + bool ShouldErrorForUnsupportedArgument(UnsupportedArgumentEnum arg) + { + switch (arg) + { + case UnsupportedArgumentEnum::Location: + return true; + default: + return false; + } + } + + Execution::Args::Type GetUnsupportedArgumentType(UnsupportedArgumentEnum unsupportedArgument) + { + Execution::Args::Type execArg; + + switch (unsupportedArgument) + { + case UnsupportedArgumentEnum::Log: + execArg = Execution::Args::Type::Log; + break; + case UnsupportedArgumentEnum::Location: + execArg = Execution::Args::Type::InstallLocation; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return execArg; + } + struct ExpectedReturnCode { ExpectedReturnCode(ExpectedReturnCodeEnum installerReturnCode, HRESULT hr, Resource::StringId message) : @@ -128,6 +160,47 @@ namespace AppInstaller::CLI::Workflow } } + void CheckForUnsupportedArgs(Execution::Context& context) + { + bool messageDisplayed = false; + const auto& unsupportedArgs = context.Get<Execution::Data::Installer>()->UnsupportedArguments; + for (auto unsupportedArg : unsupportedArgs) + { + const auto& unsupportedArgType = GetUnsupportedArgumentType(unsupportedArg); + if (context.Args.Contains(unsupportedArgType)) + { + if (!messageDisplayed) + { + context.Reporter.Warn() << Resource::String::UnsupportedArgument << std::endl; + messageDisplayed = true; + } + + const auto& executingCommand = context.GetExecutingCommand(); + if (executingCommand != nullptr) + { + const auto& commandArguments = executingCommand->GetArguments(); + for (const auto& argument : commandArguments) + { + if (unsupportedArgType == argument.ExecArgType()) + { + const auto& usageString = argument.GetUsageString(); + if (ShouldErrorForUnsupportedArgument(unsupportedArg)) + { + context.Reporter.Error() << usageString << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); + } + else + { + context.Reporter.Warn() << usageString << std::endl; + break; + } + } + } + } + } + } + } + void ShowInstallationDisclaimer(Execution::Context& context) { auto installerType = context.Get<Execution::Data::Installer>().value().InstallerType; @@ -443,6 +516,7 @@ namespace AppInstaller::CLI::Workflow void InstallSinglePackage(Execution::Context& context) { context << + Workflow::CheckForUnsupportedArgs << Workflow::DownloadSinglePackage << Workflow::InstallPackageInstaller; } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -28,6 +28,12 @@ namespace AppInstaller::CLI::Workflow // Inputs: InstallationNotes // Outputs: None void DisplayInstallationNotes(Execution::Context& context); + + // Checks if there are any included arguments that are not supported for the package. + // Required Args: None + // Inputs: Installer + // Outputs: None + void CheckForUnsupportedArgs(Execution::Context& context); // Shows the license agreements if the application has them. // Required Args: None diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1355,4 +1355,7 @@ Please specify one of them using the `--source` option to proceed.</value> <data name="SymlinkModified" xml:space="preserve"> <value>Portable symlink not deleted as it was modified and points to a different target exe</value> </data> + <data name="UnsupportedArgument" xml:space="preserve"> + <value>A provided argument is not supported for this package</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -276,6 +276,9 @@ <CopyFileToFolders Include="TestData\InstallFlowTest_Portable_WithCommand.yaml"> <DeploymentContent>true</DeploymentContent> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallFlowTest_UnsupportedArguments.yaml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> <CopyFileToFolders Include="TestData\ImportFile-Bad-Invalid.json"> <DeploymentContent>true</DeploymentContent> </CopyFileToFolders> @@ -567,6 +570,9 @@ <CopyFileToFolders Include="TestData\UpdateFlowTest_Exe_2_LicenseAgreement.yaml"> <DeploymentContent>true</DeploymentContent> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\UpdateFlowTest_Exe_UnsupportedArgs.yaml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> <CopyFileToFolders Include="TestData\UpdateFlowTest_ExeDependencies.yaml"> <DeploymentContent>true</DeploymentContent> </CopyFileToFolders> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -468,6 +468,9 @@ <CopyFileToFolders Include="TestData\InstallFlowTest_Portable_WithCommand.yaml"> <Filter>TestData</Filter> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\InstallFlowTest_UnsupportedArguments.yaml"> + <Filter>TestData</Filter> + </CopyFileToFolders> <CopyFileToFolders Include="TestData\InstallerArgTest_Msi_WithSwitches.yaml"> <Filter>TestData</Filter> </CopyFileToFolders> @@ -492,6 +495,9 @@ <CopyFileToFolders Include="TestData\UpdateFlowTest_Exe_2_LicenseAgreement.yaml"> <Filter>TestData</Filter> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\UpdateFlowTest_Exe_UnsupportedArgs.yaml"> + <Filter>TestData</Filter> + </CopyFileToFolders> <CopyFileToFolders Include="TestData\UpdateFlowTest_ExeDependencies.yaml"> <Filter>TestData</Filter> </CopyFileToFolders> diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_UnsupportedArguments.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_UnsupportedArguments.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 +UnsupportedArguments: + - log + - location +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml @@ -103,7 +103,6 @@ ExpectedReturnCodes: ReturnResponseUrl: https://DefaultReturnResponseUrl.com UnsupportedArguments: - log - - location Installers: - Architecture: x86 @@ -159,7 +158,6 @@ Installers: DisplayInstallWarnings: false ElevationRequirement: elevationRequired UnsupportedArguments: - - log - location UnsupportedOSArchitectures: - arm64 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Installer.yaml @@ -74,7 +74,6 @@ ExpectedReturnCodes: ReturnResponseUrl: https://DefaultReturnResponseUrl.com UnsupportedArguments: - log - - location Installers: - Architecture: x86 @@ -156,5 +155,7 @@ Installers: - InstallerReturnCode: 11 ReturnResponse: custom ReturnResponseUrl: https://defaultReturnResponseUrl.com + UnsupportedArguments: + - location ManifestType: installer ManifestVersion: 1.2.0 \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_UnsupportedArgs.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_UnsupportedArgs.yaml @@ -0,0 +1,23 @@ +# Same content with UpdateFlowTest_Exe.yaml but with higher version and UnsupportedArguments +PackageIdentifier: AppInstallerCliTest.TestExeInstaller +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +Switches: + Custom: /custom /ver3.0.0.0 + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +UnsupportedArguments: + - log + - location +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -244,6 +244,21 @@ namespace PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } + if (input == "TestExeInstallerWithUnsupportedArguments") + { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_UnsupportedArgs.yaml")); + result.Matches.emplace_back( + ResultMatch( + TestPackage::Make( + manifest, + TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector<Manifest>{ manifest2, manifest }, + shared_from_this() + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + } + if (input == "TestExeInstallerWithNothingInstalled") { auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); @@ -818,6 +833,75 @@ TEST_CASE("InstallFlow_InstallationNotes", "[InstallFlow][workflow]") REQUIRE(installOutput.str().find("testInstallationNotes") != std::string::npos); } +TEST_CASE("InstallFlow_UnsupportedArguments_Warn", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TempDirectory tempDirectory("TempDirectory", false); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Log, tempDirectory); + + InstallCommand install({}); + context.SetExecutingCommand(&install); + install.Execute(context); + INFO(installOutput.str()); + + // Verify unsupported arguments warn message is shown + REQUIRE(context.GetTerminationHR() == S_OK); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); + REQUIRE(installOutput.str().find("-o,--log") != std::string::npos); +} + +TEST_CASE("InstallFlow_UnsupportedArguments_Error", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TempDirectory tempDirectory("TempDirectory", false); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + + InstallCommand install({}); + context.SetExecutingCommand(&install); + install.Execute(context); + INFO(installOutput.str()); + + // Verify unsupported arguments error message is shown + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); + REQUIRE(installOutput.str().find("-l,--location") != std::string::npos); +} + +TEST_CASE("InstallFlow_UnsupportedArguments_NotProvided") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); + + InstallCommand install({}); + context.SetExecutingCommand(&install); + install.Execute(context); + INFO(installOutput.str()); + + // Verify unsupported arguments error message is not shown when not provided + REQUIRE(context.GetTerminationHR() == S_OK); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get() + " -o,--log") == std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get() + " -l,--location") == std::string::npos); +} + TEST_CASE("InstallFlow_ExpectedReturnCodes", "[InstallFlow][workflow]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); @@ -1533,6 +1617,29 @@ TEST_CASE("UpdateFlow_UpdatePortable", "[UpdateFlow][workflow]") REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); } +TEST_CASE("UpdateFlow_UpdateExeWithUnsupportedArgs", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + TestCommon::TempDirectory tempDirectory("TempDirectory", false); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context); + context.Args.AddArg(Execution::Args::Type::Query, "TestExeInstallerWithUnsupportedArguments"sv); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + + UpgradeCommand update({}); + context.SetExecutingCommand(&update); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify unsupported arguments error message is shown + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get() + " -l,--location") != std::string::npos); +} + TEST_CASE("UpdateFlow_UpdatePortableWithManifest", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestPortableInstalled.txt"); diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp @@ -468,9 +468,8 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) { REQUIRE(manifest.DefaultInstallerInfo.DisplayInstallWarnings); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.size() == 2); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.size() == 1); REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.at(1) == UnsupportedArgumentEnum::Location); } if (isSingleton) @@ -547,6 +546,8 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE_FALSE(installer1.DisplayInstallWarnings); REQUIRE(installer1.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); REQUIRE(installer1.ExpectedReturnCodes.at(3).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); + REQUIRE(installer1.UnsupportedArguments.size() == 1); + REQUIRE(installer1.UnsupportedArguments.at(1) == UnsupportedArgumentEnum::Location); } if (!isSingleton) @@ -592,6 +593,8 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); REQUIRE_FALSE(installer3.DisplayInstallWarnings); + REQUIRE(installer3.UnsupportedArguments.size() == 1); + REQUIRE(installer3.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Location); } // Localization diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h @@ -101,6 +101,7 @@ #define APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION ((HRESULT)0x8A150056) #define APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED ((HRESULT)0x8A150057) #define APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED ((HRESULT)0x8A150058) +#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT ((HRESULT)0x8A150059) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101)