diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp
index 7162c98b89..0a804bdf30 100644
--- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp
+++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp
@@ -45,8 +45,7 @@ namespace AppInstaller::CLI
// either for upgrading or for listing available upgrades.
bool HasArgumentsForMultiplePackages(Execution::Args& execArgs)
{
- return execArgs.Contains(Args::Type::All) ||
- execArgs.Contains(Args::Type::IncludeUnknown);
+ return execArgs.Contains(Args::Type::All);
}
// Determines whether there are any arguments only used as options during an upgrade,
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
index 490582234b..300c14991a 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
@@ -303,6 +303,9 @@
true
+
+ true
+
true
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
index c63d10d997..2eb85c90d6 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
@@ -552,6 +552,9 @@
TestData
+
+ TestData
+
TestData
diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_UnknownVersion.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_UnknownVersion.yaml
new file mode 100644
index 0000000000..ffc46c0991
--- /dev/null
+++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_UnknownVersion.yaml
@@ -0,0 +1,19 @@
+# Test manifest for upgrading a package with an unknown version.
+PackageIdentifier: AppInstallerCliTest.TestExeUnknownVersion
+PackageVersion: 1.0.0.0
+PackageLocale: en-US
+PackageName: AppInstaller Test Exe Unknown Version
+Publisher: Microsoft Corporation
+AppMoniker: AICLITestExe
+License: Test
+Switches:
+ SilentWithProgress: /silentwithprogress
+ Silent: /silence
+ Update: /update
+Installers:
+ - Architecture: x64
+ InstallerUrl: https://ThisIsNotUsed
+ InstallerType: exe
+ InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B
+ManifestType: singleton
+ManifestVersion: 1.3.0
\ No newline at end of file
diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp
index b8638ad5bc..6c1bf02fa2 100644
--- a/src/AppInstallerCLITests/WorkFlow.cpp
+++ b/src/AppInstallerCLITests/WorkFlow.cpp
@@ -258,6 +258,23 @@ namespace
PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMSStoreInstaller")));
}
+ if (input.empty() || input == "TestExeInstallerWithUnknownVersion")
+ {
+ auto installed = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_UnknownVersion.yaml"));
+ auto available = installed;
+ // Override the installed version to be unknown.
+ installed.Version = "unknown";
+ result.Matches.emplace_back(
+ ResultMatch(
+ TestPackage::Make(
+ installed,
+ TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } },
+ std::vector{ available },
+ shared_from_this()
+ ),
+ PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeUnknownVersion")));
+ }
+
if (input == "TestExeInstallerWithLatestInstalled")
{
auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml"));
@@ -2191,6 +2208,84 @@ TEST_CASE("UpdateFlow_UpdateExeWithUnsupportedArgs", "[UpdateFlow][workflow]")
REQUIRE(updateOutput.str().find("-l,--location") != std::string::npos);
}
+TEST_CASE("UpdateFlow_UnknownVersion", "[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, "TestExeInstallerWithUnknownVersion"sv);
+ context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory);
+
+ UpgradeCommand update({});
+ context.SetExecutingCommand(&update);
+ update.Execute(context);
+ INFO(updateOutput.str());
+
+ // Verify help message is shown the user to use --include-unknown
+ REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE);
+ REQUIRE(!std::filesystem::exists(updateResultPath.GetPath()));
+ REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionExplanation).get()) != std::string::npos);
+}
+
+TEST_CASE("UpdateFlow_UnknownVersion_IncludeUnknownArg", "[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);
+ OverrideForShellExecute(context);
+ context.Args.AddArg(Execution::Args::Type::Query, "TestExeInstallerWithUnknownVersion"sv);
+ context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory);
+ context.Args.AddArg(Execution::Args::Type::IncludeUnknown);
+
+ UpgradeCommand update({});
+ context.SetExecutingCommand(&update);
+ update.Execute(context);
+ INFO(updateOutput.str());
+ REQUIRE(std::filesystem::exists(updateResultPath.GetPath()));
+}
+
+TEST_CASE("UpdateFlow_NoArgs_UnknownVersion", "[UpdateFlow][workflow]")
+{
+ std::ostringstream updateOutput;
+ TestContext context{ updateOutput, std::cin };
+ auto previousThreadGlobals = context.SetForCurrentThread();
+ OverrideForCompositeInstalledSource(context);
+
+ UpgradeCommand update({});
+ context.SetExecutingCommand(&update);
+ update.Execute(context);
+ INFO(updateOutput.str());
+
+ // Verify --include-unknown help text is displayed if update is executed with no args and an unknown version package is available for upgrade.
+ REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) != std::string::npos);
+}
+
+TEST_CASE("UpdateFlow_IncludeUnknown", "[UpdateFlow][workflow]")
+{
+ std::ostringstream updateOutput;
+ TestContext context{ updateOutput, std::cin };
+ auto previousThreadGlobals = context.SetForCurrentThread();
+ OverrideForCompositeInstalledSource(context);
+ context.Args.AddArg(Execution::Args::Type::IncludeUnknown);
+
+ UpgradeCommand update({});
+ context.SetExecutingCommand(&update);
+ update.Execute(context);
+ INFO(updateOutput.str());
+
+ // Verify unknown version package is displayed available for upgrade.
+ REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) == std::string::npos);
+ REQUIRE(updateOutput.str().find("unknown") != std::string::npos);
+}
+
TEST_CASE("UpdateFlow_UpdatePortableWithManifest", "[UpdateFlow][workflow]")
{
TestCommon::TempFile updateResultPath("TestPortableInstalled.txt");
@@ -2396,6 +2491,43 @@ TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]")
update.Execute(context);
INFO(updateOutput.str());
+ // Verify that --include-unknown help message is displayed.
+ REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) != std::string::npos);
+ REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") == std::string::npos);
+
+ // Verify installers are called.
+ REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath()));
+ REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath()));
+ REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath()));
+ REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath()));
+}
+
+TEST_CASE("UpdateFlow_UpdateAll_IncludeUnknown", "[UpdateFlow][workflow]")
+{
+ TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt");
+ TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt");
+ TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt");
+ TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt");
+
+ std::ostringstream updateOutput;
+ TestContext context{ updateOutput, std::cin };
+ auto previousThreadGlobals = context.SetForCurrentThread();
+ OverrideForCompositeInstalledSource(context);
+ OverrideForShellExecute(context);
+ OverrideForMSIX(context);
+ OverrideForMSStore(context, true);
+ OverrideForPortableInstall(context);
+ context.Args.AddArg(Execution::Args::Type::All);
+ context.Args.AddArg(Execution::Args::Type::IncludeUnknown);
+
+ UpgradeCommand update({});
+ update.Execute(context);
+ INFO(updateOutput.str());
+
+ // Verify that --include-unknown help message is NOT displayed and unknown version package is upgraded.
+ REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) == std::string::npos);
+ REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") != std::string::npos);
+
// Verify installers are called.
REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath()));
REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath()));
@@ -2767,7 +2899,7 @@ TEST_CASE("ExportFlow_ExportAll", "[ExportFlow][workflow]")
REQUIRE(exportedCollection.Sources[0].Details.Identifier == "*TestSource");
const auto& exportedPackages = exportedCollection.Sources[0].Packages;
- REQUIRE(exportedPackages.size() == 5);
+ REQUIRE(exportedPackages.size() == 6);
REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
{
return p.Id == "AppInstallerCliTest.TestExeInstaller" && p.VersionAndChannel.GetVersion().ToString().empty();
@@ -2788,6 +2920,10 @@ TEST_CASE("ExportFlow_ExportAll", "[ExportFlow][workflow]")
{
return p.Id == "AppInstallerCliTest.TestZipInstaller" && p.VersionAndChannel.GetVersion().ToString().empty();
}));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestExeUnknownVersion" && p.VersionAndChannel.GetVersion().ToString().empty();
+ }));
}
TEST_CASE("ExportFlow_ExportAll_WithVersions", "[ExportFlow][workflow]")
@@ -2811,7 +2947,7 @@ TEST_CASE("ExportFlow_ExportAll_WithVersions", "[ExportFlow][workflow]")
REQUIRE(exportedCollection.Sources[0].Details.Identifier == "*TestSource");
const auto& exportedPackages = exportedCollection.Sources[0].Packages;
- REQUIRE(exportedPackages.size() == 5);
+ REQUIRE(exportedPackages.size() == 6);
REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
{
return p.Id == "AppInstallerCliTest.TestExeInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0";
@@ -2832,6 +2968,10 @@ TEST_CASE("ExportFlow_ExportAll_WithVersions", "[ExportFlow][workflow]")
{
return p.Id == "AppInstallerCliTest.TestZipInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0";
}));
+ REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p)
+ {
+ return p.Id == "AppInstallerCliTest.TestExeUnknownVersion" && p.VersionAndChannel.GetVersion().ToString() == "unknown";
+ }));
}
TEST_CASE("ImportFlow_Successful", "[ImportFlow][workflow]")