commit 2f0ef1f20ba2d8524f80f006fcf600c156ececbc
parent 6f6610e8bd9cff40ccc8f198b1d0be218ead9f5f
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date: Tue, 9 Mar 2021 15:12:15 -0800
Support custom installer success exit codes (#778)
Diffstat:
11 files changed, 74 insertions(+), 13 deletions(-)
diff --git a/schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json b/schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json
@@ -135,7 +135,7 @@
},
"maxItems": 16,
"uniqueItems": true,
- "description": "List of non-zero installer success exit codes"
+ "description": "List of additional non-zero installer success exit codes other than known default values by winget"
},
"UpgradeBehavior": {
"type": [ "string", "null" ],
diff --git a/schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json b/schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json
@@ -148,7 +148,7 @@
},
"maxItems": 16,
"uniqueItems": true,
- "description": "List of non-zero installer success exit codes"
+ "description": "List of additional non-zero installer success exit codes other than known default values by winget"
},
"UpgradeBehavior": {
"type": [ "string", "null" ],
diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp
@@ -193,6 +193,7 @@ namespace AppInstaller::CLI::Workflow
context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl;
const std::string& installerArgs = context.Get<Execution::Data::InstallerArgs>();
+ const auto& additionalSuccessCodes = context.Get<Execution::Data::Installer>()->InstallerSuccessCodes;
auto installResult = context.Reporter.ExecuteWithProgress(
std::bind(InvokeShellExecute,
@@ -205,7 +206,7 @@ namespace AppInstaller::CLI::Workflow
context.Reporter.Warn() << "Installation abandoned" << std::endl;
AICLI_TERMINATE_CONTEXT(E_ABORT);
}
- else if (installResult.value() != 0)
+ else if (installResult.value() != 0 && (std::find(additionalSuccessCodes.begin(), additionalSuccessCodes.end(), installResult.value()) == additionalSuccessCodes.end()))
{
const auto& manifest = context.Get<Execution::Data::Manifest>();
Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "ShellExecute", installResult.value());
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
@@ -237,6 +237,9 @@
<CopyFileToFolders Include="TestData\InstallFlowTest_NoApplicableArchitecture.yaml">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
+ <CopyFileToFolders Include="TestData\InstallFlowTest_NonZeroExitCode.yaml">
+ <DeploymentContent>true</DeploymentContent>
+ </CopyFileToFolders>
<CopyFileToFolders Include="TestData\ImportFile-Bad-Invalid.json">
<DeploymentContent>true</DeploymentContent>
</CopyFileToFolders>
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters
@@ -279,6 +279,9 @@
<CopyFileToFolders Include="TestData\InstallFlowTest_NoApplicableArchitecture.yaml">
<Filter>TestData</Filter>
</CopyFileToFolders>
+ <CopyFileToFolders Include="TestData\InstallFlowTest_NonZeroExitCode.yaml">
+ <Filter>TestData</Filter>
+ </CopyFileToFolders>
<CopyFileToFolders Include="TestData\InstallFlowTest_Msix_StreamingFlow.yaml">
<Filter>TestData</Filter>
</CopyFileToFolders>
diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NonZeroExitCode.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NonZeroExitCode.yaml
@@ -0,0 +1,19 @@
+PackageIdentifier: AppInstallerCliTest.TestInstaller
+PackageVersion: 1.0.0.0
+PackageName: AppInstaller Test Installer
+Publisher: Microsoft Corporation
+Moniker: AICLITestExe
+License: Test
+InstallerSwitches:
+ Custom: /ExitCode 0x80070005
+ SilentWithProgress: /silentwithprogress
+ Silent: /silence
+Installers:
+ - Architecture: x86
+ InstallerUrl: https://ThisIsNotUsed
+ InstallerType: exe
+ InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B
+ InstallerSuccessCodes:
+ - -2147024891
+ManifestType: singleton
+ManifestVersion: 1.0.0
diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp
@@ -445,6 +445,30 @@ TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow][workflow]")
REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos);
}
+TEST_CASE("InstallFlowNonZeroExitCode", "[InstallFlow][workflow]")
+{
+ TestCommon::TempFile installResultPath("TestExeInstalled.txt");
+
+ std::ostringstream installOutput;
+ TestContext context{ installOutput, std::cin };
+ OverrideForShellExecute(context);
+ context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NonZeroExitCode.yaml").GetPath().u8string());
+
+ InstallCommand install({});
+ install.Execute(context);
+ INFO(installOutput.str());
+
+ // Verify Installer is called and parameters are passed in.
+ REQUIRE(context.GetTerminationHR() == S_OK);
+ REQUIRE(std::filesystem::exists(installResultPath.GetPath()));
+ std::ifstream installResultFile(installResultPath.GetPath());
+ REQUIRE(installResultFile.is_open());
+ std::string installResultStr;
+ std::getline(installResultFile, installResultStr);
+ REQUIRE(installResultStr.find("/ExitCode 0x80070005") != std::string::npos);
+ REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos);
+}
+
TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow][workflow]")
{
TestCommon::TempFile installResultPath("TestExeInstalled.txt");
diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp
@@ -366,7 +366,7 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton)
REQUIRE(defaultSwitches.at(InstallerSwitchType::InstallLocation) == "/dir=<INSTALLPATH>");
REQUIRE(defaultSwitches.at(InstallerSwitchType::Update) == "/upgrade");
- REQUIRE(manifest.DefaultInstallerInfo.InstallerSuccessCodes == std::vector<int>{ 1, static_cast<int>(0x80070005) });
+ REQUIRE(manifest.DefaultInstallerInfo.InstallerSuccessCodes == std::vector<DWORD>{ 1, static_cast<DWORD>(0x80070005) });
REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious);
REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" });
REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" });
diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp
@@ -60,15 +60,15 @@ namespace AppInstaller::Manifest
return result;
}
- std::vector<int> ProcessIntSequenceNode(const YAML::Node& node)
+ std::vector<DWORD> ProcessInstallerSuccessCodeSequenceNode(const YAML::Node& node)
{
THROW_HR_IF(E_INVALIDARG, !node.IsSequence());
- std::vector<int> result;
+ std::vector<DWORD> result;
for (auto const& entry : node.Sequence())
{
- result.emplace_back(entry.as<int>());
+ result.emplace_back(static_cast<DWORD>(entry.as<int>()));
}
return result;
@@ -214,7 +214,7 @@ namespace AppInstaller::Manifest
// Starting v1, we should be only adding new fields for each minor version increase
if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 })
{
- // Root level and Localization node level
+ // Root level and Installer node level
std::vector<FieldProcessInfo> v1CommonFields =
{
{ "InstallerLocale", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Locale = value.as<std::string>(); return {}; } },
@@ -223,7 +223,7 @@ namespace AppInstaller::Manifest
{ "Scope", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Scope = ConvertToScopeEnum(value.as<std::string>()); return {}; } },
{ "InstallModes", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->InstallModes = ProcessScopeSequenceNode(value); return {}; } },
{ "InstallerSwitches", [this](const YAML::Node& value)->ValidationErrors { m_p_switches = &(m_p_installer->Switches); return ValidateAndProcessFields(value, SwitchesFieldInfos); } },
- { "InstallerSuccessCodes", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->InstallerSuccessCodes = ProcessIntSequenceNode(value); return {}; } },
+ { "InstallerSuccessCodes", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->InstallerSuccessCodes = ProcessInstallerSuccessCodeSequenceNode(value); return {}; } },
{ "UpgradeBehavior", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->UpdateBehavior = ConvertToUpdateBehaviorEnum(value.as<std::string>()); return {}; } },
{ "Commands", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Commands = ProcessStringSequenceNode(value); return {}; } },
{ "Protocols", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Protocols = ProcessStringSequenceNode(value); return {}; } },
diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h
@@ -49,7 +49,7 @@ namespace AppInstaller::Manifest
// If present, has more precedence than root
std::map<InstallerSwitchType, string_t> Switches;
- std::vector<int> InstallerSuccessCodes;
+ std::vector<DWORD> InstallerSuccessCodes;
UpdateBehaviorEnum UpdateBehavior = UpdateBehaviorEnum::Install;
diff --git a/src/AppInstallerTestExeInstaller/main.cpp b/src/AppInstallerTestExeInstaller/main.cpp
@@ -131,6 +131,7 @@ int wmain(int argc, const wchar_t** argv)
std::wstringstream outContent;
std::wstring productCode;
std::wstring version;
+ int exitCode = 0;
// Output to cout by default, but swap to a file if requested
std::wostream* out = &std::wcout;
@@ -149,7 +150,17 @@ int wmain(int argc, const wchar_t** argv)
outContent << argv[i] << ' ';
}
}
-
+
+ // Supports custom exit code
+ else if (_wcsicmp(argv[i], L"/ExitCode") == 0)
+ {
+ if (++i < argc)
+ {
+ exitCode = static_cast<int>(std::stoll(argv[i], 0, 0));
+ outContent << argv[i] << ' ';
+ }
+ }
+
// Supports custom product code ID
else if (_wcsicmp(argv[i], L"/ProductID") == 0)
{
@@ -195,6 +206,6 @@ int wmain(int argc, const wchar_t** argv)
path uninstallerPath = GenerateUninstaller(*out, installDirectory, productCode);
WriteToUninstallRegistry(*out, productCode, uninstallerPath, version);
-
- return 0;
+
+ return exitCode;
}