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

Add expected installer return codes #1421

Merged
merged 21 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ pdb
PEVENT
pfp
PGP
php
PII
pipssource
placeholders
Expand Down Expand Up @@ -408,6 +409,7 @@ SERVICEPACKMAJOR
SERVICEPACKMINOR
setfill
setschemaversion
setupexitcodes
setvariable
setw
shcore
Expand Down Expand Up @@ -536,6 +538,7 @@ URegular
uri
url
urlmon
USEREXIT
userguide
USERPROFILE
usersources
Expand Down
54 changes: 48 additions & 6 deletions schemas/JSON/manifests/v1.1.0/manifest.installer.1.1.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,56 @@
}
}
},
"InstallerReturnCode": {
"type": "integer",
"not": {
"enum": [ 0 ]
},
"minimum": -2147483648,
"maximum": 429496729,
florelis marked this conversation as resolved.
Show resolved Hide resolved
"description": "An exit code that can be returned by the installer after execution"
},
"InstallerSuccessCodes": {
"type": [ "array", "null" ],
"items": {
"type": "integer",
"not": {
"enum": [ 0 ]
},
"minimum": -2147483648,
"maximum": 4294967295
"$ref": "#/definitions/InstallerReturnCode"
},
"maxItems": 16,
"uniqueItems": true,
"description": "List of additional non-zero installer success exit codes other than known default values by winget"
},
"ExpectedReturnCodes": {
"type": [ "array", "null" ],
"items": {
"type": "object",
"properties": {
"InstallerReturnCode": {
"$ref": "#/definitions/InstallerReturnCode"
},
"ReturnResponse": {
"type": [ "string", "null" ],
florelis marked this conversation as resolved.
Show resolved Hide resolved
"enum": [
"PackageInUse",
florelis marked this conversation as resolved.
Show resolved Hide resolved
"InstallInProgress",
"FileInUse",
"MissingDependency",
"DiskFull",
"InsufficientMemory",
"NoNetwork",
"ContactSupport",
"RebootRequiredToFinish",
"RebootRequiredForInstall",
"RebootInitiated",
"CancelledByUser",
"AlreadyInstalled",
"Downgrade",
"BlockedByPolicy"
]
}
}
},
"description": "Installer exit codes for common errors"
},
"UpgradeBehavior": {
"type": [ "string", "null" ],
"enum": [
Expand Down Expand Up @@ -449,6 +485,9 @@
"InstallerSuccessCodes": {
"$ref": "#/definitions/InstallerSuccessCodes"
},
"ExpectedReturnCodes": {
"$ref": "#/definitions/ExpectedReturnCodes"
},
"UpgradeBehavior": {
"$ref": "#/definitions/UpgradeBehavior"
},
Expand Down Expand Up @@ -543,6 +582,9 @@
"InstallerSuccessCodes": {
"$ref": "#/definitions/InstallerSuccessCodes"
},
"ExpectedReturnCodes": {
"$ref": "#/definitions/ExpectedReturnCodes"
},
"UpgradeBehavior": {
"$ref": "#/definitions/UpgradeBehavior"
},
Expand Down
55 changes: 49 additions & 6 deletions schemas/JSON/manifests/v1.1.0/manifest.singleton.1.1.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,20 +165,57 @@
}
}
},
"InstallerReturnCode": {
"type": "integer",
"not": {
"enum": [ 0 ]
},
"minimum": -2147483648,
"maximum": 4294967295,
"description": "An exit code that can be returned by the installer after execution"
},
"InstallerSuccessCodes": {
"type": [ "array", "null" ],
"items": {
"type": "integer",
"not": {
"enum": [ 0 ]
},
"minimum": -2147483648,
"maximum": 4294967295
"$ref": "#/definitions/InstallerReturnCode"
},
"maxItems": 16,
"uniqueItems": true,
"description": "List of additional non-zero installer success exit codes other than known default values by winget"
},
"ExpectedReturnCodes": {
"type": [ "array", "null" ],
"items": {
"type": "object",
"properties": {
"InstallerReturnCode": {
"$ref": "#/definitions/InstallerReturnCode"
},
"ReturnResponse": {
"type": [ "string", "null" ],
"enum": [
"PackageInUse",
"InstallInProgress",
"FileInUse",
"MissingDependency",
"DiskFull",
"InsufficientMemory",
"NoNetwork",
"ContactSupport",
"RebootRequiredToFinish",
"RebootRequiredForInstall",
"RebootInitiated",
"CancelledByUser",
"AlreadyInstalled",
"Downgrade",
"BlockedByPolicy"
]
}
}
},
"maxItems": 16,
florelis marked this conversation as resolved.
Show resolved Hide resolved
"description": "Installer exit codes for common errors"
},
"UpgradeBehavior": {
"type": [ "string", "null" ],
"enum": [
Expand Down Expand Up @@ -481,6 +518,9 @@
"InstallerSuccessCodes": {
"$ref": "#/definitions/InstallerSuccessCodes"
},
"ExpectedReturnCodes": {
"$ref": "#/definitions/ExpectedReturnCodes"
},
"UpgradeBehavior": {
"$ref": "#/definitions/UpgradeBehavior"
},
Expand Down Expand Up @@ -677,6 +717,9 @@
"InstallerSuccessCodes": {
"$ref": "#/definitions/InstallerSuccessCodes"
},
"ExpectedReturnCodes": {
"$ref": "#/definitions/ExpectedReturnCodes"
},
"UpgradeBehavior": {
"$ref": "#/definitions/UpgradeBehavior"
},
Expand Down
7 changes: 7 additions & 0 deletions src/AppInstallerCLICore/ExecutionContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace AppInstaller::CLI::Execution
InstallerPath,
LogPath,
InstallerArgs,
InstallerReturnCode,
CompletionData,
InstalledPackageVersion,
UninstallString,
Expand Down Expand Up @@ -152,6 +153,12 @@ namespace AppInstaller::CLI::Execution
using value_t = std::string;
};

template <>
struct DataMapping<Data::InstallerReturnCode>
{
using value_t = DWORD;
};

template <>
struct DataMapping<Data::CompletionData>
{
Expand Down
20 changes: 19 additions & 1 deletion src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,43 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(ImportPackageAlreadyInstalled);
WINGET_DEFINE_RESOURCE_STRINGID(ImportSearchFailed);
WINGET_DEFINE_RESOURCE_STRINGID(ImportSourceNotInstalled);
WINGET_DEFINE_RESOURCE_STRINGID(InstallAndUpgradeCommandsReportDependencies);
WINGET_DEFINE_RESOURCE_STRINGID(InstallationAbandoned);
WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer1);
WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer2);
WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimerMSStore);
WINGET_DEFINE_RESOURCE_STRINGID(InstallationRequiresHigherWindows);
WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(InstallAndUpgradeCommandsReportDependencies);
WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable);
WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerBlockedByPolicy);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedSecurityCheck);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedVirusScan);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedWithCode);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchAdminBlock);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchError);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverridden);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified);
WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeBlockedByPolicy);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCancelledByUser);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeContactSupport);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDiskFull);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDowngrade);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeFileInUse);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInstallInProgress);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInsufficientMemory);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeMissingDependency);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeNoNetwork);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUse);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootInitiated);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredForInstall);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredToFinish);
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowStartingPackageInstall);
WINGET_DEFINE_RESOURCE_STRINGID(InstallForceArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(InstallScopeDescription);
Expand Down
105 changes: 98 additions & 7 deletions src/AppInstallerCLICore/Workflows/InstallFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,55 @@ namespace AppInstaller::CLI::Workflow
return false;
}
}

struct ExpectedReturnCode
{
ExpectedReturnCode(ExpectedReturnCodeEnum installerReturnCode, HRESULT hr, Resource::StringId message) :
InstallerReturnCode(installerReturnCode), HResult(hr), Message(message) {}

static ExpectedReturnCode GetExpectedReturnCode(ExpectedReturnCodeEnum returnCode)
{
switch (returnCode)
{
case ExpectedReturnCodeEnum::PackageInUse:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, Resource::String::InstallFlowReturnCodePackageInUse);
case ExpectedReturnCodeEnum::InstallInProgress:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS, Resource::String::InstallFlowReturnCodeInstallInProgress);
case ExpectedReturnCodeEnum::FileInUse:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE, Resource::String::InstallFlowReturnCodeFileInUse);
case ExpectedReturnCodeEnum::MissingDependency:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY, Resource::String::InstallFlowReturnCodeMissingDependency);
case ExpectedReturnCodeEnum::DiskFull:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL, Resource::String::InstallFlowReturnCodeDiskFull);
case ExpectedReturnCodeEnum::InsufficientMemory:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY, Resource::String::InstallFlowReturnCodeInsufficientMemory);
case ExpectedReturnCodeEnum::NoNetwork:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK, Resource::String::InstallFlowReturnCodeNoNetwork);
case ExpectedReturnCodeEnum::ContactSupport:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT, Resource::String::InstallFlowReturnCodeContactSupport);
case ExpectedReturnCodeEnum::RebootRequiredToFinish:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH, Resource::String::InstallFlowReturnCodeRebootRequiredToFinish);
case ExpectedReturnCodeEnum::RebootRequiredForInstall:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_INSTALL, Resource::String::InstallFlowReturnCodeRebootRequiredForInstall);
case ExpectedReturnCodeEnum::RebootInitiated:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED, Resource::String::InstallFlowReturnCodeRebootInitiated);
case ExpectedReturnCodeEnum::CancelledByUser:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER, Resource::String::InstallFlowReturnCodeCancelledByUser);
case ExpectedReturnCodeEnum::AlreadyInstalled:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED, Resource::String::InstallFlowReturnCodeAlreadyInstalled);
case ExpectedReturnCodeEnum::Downgrade:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE, Resource::String::InstallFlowReturnCodeDowngrade);
case ExpectedReturnCodeEnum::BlockedByPolicy:
return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY, Resource::String::InstallFlowReturnCodeBlockedByPolicy);
default:
THROW_HR(E_UNEXPECTED);
}
}

ExpectedReturnCodeEnum InstallerReturnCode;
HRESULT HResult;
Resource::StringId Message;
};
}

void EnsureApplicableInstaller(Execution::Context& context)
Expand Down Expand Up @@ -432,15 +481,17 @@ namespace AppInstaller::CLI::Workflow
context <<
GetInstallerArgs <<
RenameDownloadedInstaller <<
ShellExecuteInstallImpl;
ShellExecuteInstallImpl <<
ReportInstallerResult("ShellExecute"sv, APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED);
}

void DirectMSIInstall(Execution::Context& context)
{
context <<
GetInstallerArgs <<
RenameDownloadedInstaller <<
DirectMSIInstallImpl;
DirectMSIInstallImpl <<
ReportInstallerResult("MsiInstallProduct"sv, APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED, /* isHResult */ true);
florelis marked this conversation as resolved.
Show resolved Hide resolved
}

void MsixInstall(Execution::Context& context)
Expand Down Expand Up @@ -468,11 +519,9 @@ namespace AppInstaller::CLI::Workflow
}
catch (const wil::ResultException& re)
{
const auto& manifest = context.Get<Execution::Data::Manifest>();
Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "MSIX", re.GetErrorCode());

context.Reporter.Error() << GetUserPresentableMessage(re) << std::endl;
AICLI_TERMINATE_CONTEXT(re.GetErrorCode());
context.Add<Execution::Data::InstallerReturnCode>(re.GetErrorCode());
context << ReportInstallerResult("MSIX", re.GetErrorCode(), /* isHResult */ true);
florelis marked this conversation as resolved.
Show resolved Hide resolved
return;
}

if (registrationDeferred)
Expand All @@ -485,6 +534,48 @@ namespace AppInstaller::CLI::Workflow
}
}

void ReportInstallerResult::operator()(Execution::Context& context) const
{
DWORD installResult = context.Get<Execution::Data::InstallerReturnCode>();
const auto& additionalSuccessCodes = context.Get<Execution::Data::Installer>()->InstallerSuccessCodes;
if (installResult != 0 && (std::find(additionalSuccessCodes.begin(), additionalSuccessCodes.end(), installResult) == additionalSuccessCodes.end()))
{
const auto& manifest = context.Get<Execution::Data::Manifest>();
Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, m_installerType, installResult);

if (m_isHResult)
{
context.Reporter.Error() << Resource::String::InstallerFailedWithCode << ' ' << GetUserPresentableMessage(installResult) << std::endl;
}
else
{
context.Reporter.Error() << Resource::String::InstallerFailedWithCode << ' ' << installResult << std::endl;
}

// Show installer log path if exists
if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get<Execution::Data::LogPath>()))
{
context.Reporter.Info() << Resource::String::InstallerLogAvailable << ' ' << context.Get<Execution::Data::LogPath>().u8string() << std::endl;
}

// Show a specific message if we can identify the return code
const auto& expectedReturnCodes = context.Get<Execution::Data::Installer>()->ExpectedReturnCodes;
auto expectedReturnCodeItr = expectedReturnCodes.find(installResult);
if (expectedReturnCodeItr != expectedReturnCodes.end() && expectedReturnCodeItr->second != ExpectedReturnCodeEnum::Unknown)
{
auto returnCode = ExpectedReturnCode::GetExpectedReturnCode(expectedReturnCodeItr->second);
context.Reporter.Error() << returnCode.Message << std::endl;
AICLI_TERMINATE_CONTEXT(returnCode.HResult);
}

AICLI_TERMINATE_CONTEXT(m_hr);
}
else
{
context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl;
}
}

void RemoveInstaller(Execution::Context& context)
{
// Path may not be present if installed from a URL for MSIX
Expand Down
Loading