Skip to content

Commit

Permalink
Support --no-upgrade option for install flow (#2655)
Browse files Browse the repository at this point in the history
  • Loading branch information
yao-msft authored Nov 4, 2022
1 parent 58beda4 commit fb37675
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 29 deletions.
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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)
{
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
7 changes: 6 additions & 1 deletion src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit fb37675

Please sign in to comment.