Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support --no-upgrade option for install flow #2655

Merged
merged 2 commits into from Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Argument.cpp
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Commands/ImportCommand.cpp
Expand Up @@ -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),
};
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Commands/InstallCommand.cpp
Expand Up @@ -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),
Expand Down
3 changes: 2 additions & 1 deletion src/AppInstallerCLICore/ExecutionArgs.h
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
29 changes: 17 additions & 12 deletions src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searchContext.GetTerminationHR()

Nit: should this be assigned to a variable so that it is not called 3 times within this if-else block.

{
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;
}
}
}

Expand Down
17 changes: 13 additions & 4 deletions src/AppInstallerCLICore/Workflows/UpdateFlow.cpp
Expand Up @@ -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))
Expand Down
5 changes: 5 additions & 0 deletions src/AppInstallerCLIE2ETests/Constants.cs
Expand Up @@ -198,6 +198,8 @@ public class ErrorCode
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);
Expand All @@ -215,6 +217,9 @@ public class ErrorCode
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);
Expand Down
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
21 changes: 21 additions & 0 deletions src/AppInstallerCLITests/WorkFlow.cpp
Expand Up @@ -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");
Expand Down
40 changes: 29 additions & 11 deletions src/AppInstallerCommonCore/Errors.cpp
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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";
}
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCommonCore/Public/AppInstallerErrors.h
Expand Up @@ -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)
Expand Down