commit fb376750c1441c5ac947fb45186b104c6c556fae
parent 58beda46d30db7ffc0a81818de2c9e049c1f29a1
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date: Fri, 4 Nov 2022 14:10:11 -0700
Support --no-upgrade option for install flow (#2655)
Diffstat:
12 files changed, 100 insertions(+), 29 deletions(-)
diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp
@@ -61,6 +61,8 @@ namespace AppInstaller::CLI
return Argument{ "ignore-security-hash"_liv, NoAlias, Args::Type::HashOverride, Resource::String::HashOverrideArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::HashOverride };
case Args::Type::AcceptPackageAgreements:
return Argument{ "accept-package-agreements"_liv, NoAlias, Args::Type::AcceptPackageAgreements, Resource::String::AcceptPackageAgreementsArgumentDescription, ArgumentType::Flag };
+ case Args::Type::NoUpgrade:
+ return Argument{ "no-upgrade"_liv, NoAlias, Args::Type::NoUpgrade, Resource::String::NoUpgradeArgumentDescription, ArgumentType::Flag };
case Args::Type::HashFile:
return Argument{ "file"_liv, 'f', Args::Type::HashFile, Resource::String::FileArgumentDescription, ArgumentType::Positional, true };
case Args::Type::Msix:
diff --git a/src/AppInstallerCLICore/Commands/ImportCommand.cpp b/src/AppInstallerCLICore/Commands/ImportCommand.cpp
@@ -17,6 +17,7 @@ namespace AppInstaller::CLI
Argument{ "import-file", 'i', Execution::Args::Type::ImportFile, Resource::String::ImportFileArgumentDescription, ArgumentType::Positional, true },
Argument{ "ignore-unavailable", Argument::NoAlias, Execution::Args::Type::IgnoreUnavailable, Resource::String::ImportIgnoreUnavailableArgumentDescription, ArgumentType::Flag },
Argument{ "ignore-versions", Argument::NoAlias, Execution::Args::Type::IgnoreVersions, Resource::String::ImportIgnorePackageVersionsArgumentDescription, ArgumentType::Flag },
+ Argument::ForType(Execution::Args::Type::NoUpgrade),
Argument::ForType(Execution::Args::Type::AcceptPackageAgreements),
Argument::ForType(Execution::Args::Type::AcceptSourceAgreements),
};
diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp
@@ -43,6 +43,7 @@ namespace AppInstaller::CLI
Argument::ForType(Args::Type::HashOverride),
Argument::ForType(Args::Type::DependencySource),
Argument::ForType(Args::Type::AcceptPackageAgreements),
+ Argument::ForType(Args::Type::NoUpgrade),
Argument::ForType(Args::Type::CustomHeader),
Argument::ForType(Args::Type::AcceptSourceAgreements),
Argument::ForType(Args::Type::Rename),
diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h
@@ -35,13 +35,14 @@ namespace AppInstaller::CLI::Execution
Silent,
Locale,
Log,
- Override, //Override args are (and the only args) directly passed to installer
+ Override, // Override args are (and the only args) directly passed to installer
InstallLocation,
InstallScope,
InstallArchitecture,
HashOverride, // Ignore hash mismatches
AcceptPackageAgreements, // Accept all license agreements for packages
Rename, // Renames the file of the executable. Only applies to the portable installerType
+ NoUpgrade, // Install flow should not try to convert to upgrade flow upon finding existing installed version
// Uninstall behavior
Purge, // Removes all files and directories related to a package during an uninstall. Only applies to the portable installerType.
diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h
@@ -219,6 +219,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(NoPackagesFoundInImportFile);
WINGET_DEFINE_RESOURCE_STRINGID(Notes);
WINGET_DEFINE_RESOURCE_STRINGID(NoUninstallInfoFound);
+ WINGET_DEFINE_RESOURCE_STRINGID(NoUpgradeArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(NoVTArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(OpenLogsArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatch);
@@ -231,6 +232,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(Package);
WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsNotAgreedTo);
WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsPrompt);
+ WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled);
WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies);
WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError);
WINGET_DEFINE_RESOURCE_STRINGID(PoliciesDisabled);
diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp
@@ -309,20 +309,25 @@ namespace AppInstaller::CLI::Workflow
context.Reporter.Info() << Resource::String::Cancelled << std::endl;
return;
}
- else if (searchContext.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE)
- {
- AICLI_LOG(CLI, Info, << "Package is already installed: [" << packageRequest.Id << "]");
- context.Reporter.Info() << Resource::String::ImportPackageAlreadyInstalled << ' ' << packageRequest.Id << std::endl;
- continue;
- }
else
{
- AICLI_LOG(CLI, Info, << "Package not found for import: [" << packageRequest.Id << "], Version " << packageRequest.VersionAndChannel.ToString());
- context.Reporter.Info() << Resource::String::ImportSearchFailed << ' ' << packageRequest.Id << std::endl;
-
- // Keep searching for the remaining packages and only fail at the end.
- foundAll = false;
- continue;
+ auto searchTerminationHR = searchContext.GetTerminationHR();
+ if (searchTerminationHR == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE ||
+ searchTerminationHR == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED)
+ {
+ AICLI_LOG(CLI, Info, << "Package is already installed: [" << packageRequest.Id << "]");
+ context.Reporter.Info() << Resource::String::ImportPackageAlreadyInstalled << ' ' << packageRequest.Id << std::endl;
+ continue;
+ }
+ else
+ {
+ AICLI_LOG(CLI, Info, << "Package not found for import: [" << packageRequest.Id << "], Version " << packageRequest.VersionAndChannel.ToString());
+ context.Reporter.Info() << Resource::String::ImportSearchFailed << ' ' << packageRequest.Id << std::endl;
+
+ // Keep searching for the remaining packages and only fail at the end.
+ foundAll = false;
+ continue;
+ }
}
}
diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp
@@ -246,10 +246,19 @@ namespace AppInstaller::CLI::Workflow
if (!m_isUpgrade && context.Contains(Execution::Data::InstalledPackageVersion) && context.Get<Execution::Data::InstalledPackageVersion>() != nullptr)
{
- AICLI_LOG(CLI, Info, << "Found installed package, converting to upgrade flow");
- context.Reporter.Info() << Execution::ConvertToUpgradeFlowEmphasis << Resource::String::ConvertInstallFlowToUpgrade << std::endl;
- context.SetFlags(Execution::ContextFlag::InstallerExecutionUseUpdate);
- m_isUpgrade = true;
+ if (context.Args.Contains(Execution::Args::Type::NoUpgrade))
+ {
+ AICLI_LOG(CLI, Warning, << "Found installed package, exiting installation.");
+ context.Reporter.Warn() << Resource::String::PackageAlreadyInstalled << std::endl;
+ AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED);
+ }
+ else
+ {
+ AICLI_LOG(CLI, Info, << "Found installed package, converting to upgrade flow");
+ context.Reporter.Info() << Execution::ConvertToUpgradeFlowEmphasis << Resource::String::ConvertInstallFlowToUpgrade << std::endl;
+ context.SetFlags(Execution::ContextFlag::InstallerExecutionUseUpdate);
+ m_isUpgrade = true;
+ }
}
if (context.Args.Contains(Execution::Args::Type::Version))
diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs
@@ -198,6 +198,8 @@ namespace AppInstallerCLIE2ETests
public const int ERROR_NESTEDINSTALLER_INVALID_PATH = unchecked((int)0x8A15005D);
public const int ERROR_PINNED_CERTIFICATE_MISMATCH = unchecked((int)0x8A15005E);
public const int ERROR_INSTALL_LOCATION_REQUIRED = unchecked((int)0x8A15005F);
+ public const int ERROR_ARCHIVE_SCAN_FAILED = unchecked((int)0x8A150060);
+ public const int ERROR_PACKAGE_ALREADY_INSTALLED = unchecked((int)0x8A150061);
public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101);
public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102);
@@ -215,6 +217,9 @@ namespace AppInstallerCLIE2ETests
public const int ERROR_INSTALL_DOWNGRADE = unchecked((int)0x8A15010E);
public const int ERROR_INSTALL_BLOCKED_BY_POLICY = unchecked((int)0x8A15010F);
public const int ERROR_INSTALL_DEPENDENCIES = unchecked((int)0x8A150110);
+ public const int ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION = unchecked((int)0x8A150111);
+ public const int ERROR_INSTALL_INVALID_PARAMETER = unchecked((int)0x8A150112);
+ public const int ERROR_INSTALL_SYSTEM_NOT_SUPPORTED = unchecked((int)0x8A150113);
public const int INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND = unchecked((int)0x8A150201);
public const int INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE = unchecked((int)0x0A150202);
diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
@@ -1349,7 +1349,6 @@ Please specify one of them using the `--source` option to proceed.</value>
</data>
<data name="PortableAliasAdded" xml:space="preserve">
<value>Command line alias added:</value>
-
</data>
<data name="PortableInstallFailed" xml:space="preserve">
<value>Portable install failed; Cleaning up...</value>
@@ -1467,4 +1466,10 @@ Please specify one of them using the `--source` option to proceed.</value>
<value>Archive scan detected malware; proceeding due to --force</value>
<comment>{Locked="--force"}</comment>
</data>
+ <data name="NoUpgradeArgumentDescription" xml:space="preserve">
+ <value>Skips upgrade if an installed version already exists</value>
+ </data>
+ <data name="PackageAlreadyInstalled" xml:space="preserve">
+ <value>A package version is already installed. Installation cancelled.</value>
+ </data>
</root>
\ No newline at end of file
diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp
@@ -3685,6 +3685,27 @@ TEST_CASE("InstallFlow_FoundInstalledAndUpgradeAvailable", "[UpdateFlow][workflo
REQUIRE(installResultStr.find("/ver3.0.0.0") != std::string::npos);
}
+TEST_CASE("InstallFlow_FoundInstalledAndUpgradeAvailable_WithNoUpgrade", "[UpdateFlow][workflow]")
+{
+ TestCommon::TempFile installResultPath("TestExeInstalled.txt");
+
+ std::ostringstream installOutput;
+ TestContext context{ installOutput, std::cin };
+ auto previousThreadGlobals = context.SetForCurrentThread();
+ OverrideForCompositeInstalledSource(context);
+ context.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller"sv);
+ context.Args.AddArg(Execution::Args::Type::NoUpgrade);
+
+ InstallCommand install({});
+ install.Execute(context);
+ INFO(installOutput.str());
+
+ // Verify Installer is not called.
+ REQUIRE(!std::filesystem::exists(installResultPath.GetPath()));
+ REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAlreadyInstalled).get()) != std::string::npos);
+ REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED);
+}
+
TEST_CASE("InstallFlow_FoundInstalledAndUpgradeNotAvailable", "[UpdateFlow][workflow]")
{
TestCommon::TempFile installResultPath("TestExeInstalled.txt");
diff --git a/src/AppInstallerCommonCore/Errors.cpp b/src/AppInstallerCommonCore/Errors.cpp
@@ -180,6 +180,8 @@ namespace AppInstaller
return "Failed to install portable package";
case APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED:
return "Volume does not support reparse points.";
+ case APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS:
+ return "Portable package from a different source already exists.";
case APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY:
return "Unable to create symlink, path points to a directory.";
case APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION:
@@ -188,12 +190,30 @@ 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_UNSUPPORTED_ARGUMENT:
+ return "One or more arguments are not supported.";
+ case APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL:
+ return "Embedded null characters are disallowed for SQLite";
+ case APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND:
+ return "Failed to find the nested installer in the archive.";
+ case APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED:
+ return "Failed to extract archive.";
+ case APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH:
+ return "Invalid relative file path to nested installer provided.";
+ case APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH:
+ return "The server certificate did not match any of the expected values.";
case APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED:
- return "Install location required but not provided";
+ return "Install location must be provided.";
+ case APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED:
+ return "Archive malware scan failed.";
+ case APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED:
+ return "Found at least one version of the package installed.";
+
+ // Install errors
case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE:
- return "Application is currently running.Exit the application then try again.";
+ return "Application is currently running. Exit the application then try again.";
case APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS:
- return "Another installation is already in progress.Try again later.";
+ return "Another installation is already in progress. Try again later.";
case APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE:
return "One or more file is being used. Exit the application then try again.";
case APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY:
@@ -222,14 +242,12 @@ namespace AppInstaller
return "Organization policies are preventing installation. Contact your admin.";
case APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES:
return "Failed to install package dependencies.";
- case APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL:
- return "Embedded null characters are disallowed for SQLite";
- case APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH:
- return "The server certificate did not match any of the expected values.";
- case APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND:
- return "Failed to find the nested installer in the archive.";
- case APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH:
- return "Invalid relative file path to nested installer provided.";
+ case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION:
+ return "Application is currently in use by another application.";
+ case APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER:
+ return "Invalid parameter.";
+ case APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED:
+ return "Package not supported by the system.";
default:
return "Unknown Error Code";
}
diff --git a/src/AppInstallerCommonCore/Public/AppInstallerErrors.h b/src/AppInstallerCommonCore/Public/AppInstallerErrors.h
@@ -109,6 +109,7 @@
#define APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH ((HRESULT)0x8A15005E)
#define APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED ((HRESULT)0x8A15005F)
#define APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED ((HRESULT)0x8A150060)
+#define APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED ((HRESULT)0x8A150061)
// Install errors.
#define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101)