diff --git a/src/chocolatey.resources/helpers/chocolateyScriptRunner.ps1 b/src/chocolatey.resources/helpers/chocolateyScriptRunner.ps1 index cbb96c7e21..a27e771fb3 100644 --- a/src/chocolatey.resources/helpers/chocolateyScriptRunner.ps1 +++ b/src/chocolatey.resources/helpers/chocolateyScriptRunner.ps1 @@ -46,10 +46,22 @@ $checksumExe = Join-Path $chocoTools 'checksum.exe' Write-Debug "Running `'$packageScript`'"; & "$packageScript" - $scriptSuccess = $? +$lastExecutableExitCode = $LASTEXITCODE + +if ($lastExecutableExitCode -ne $null -and $lastExecutableExitCode -ne '') { + Write-Debug "The last executable that ran had an exit code of '$lastExecutableExitCode'." +} + +if (-not $scriptSuccess) { + Write-Debug "The script exited with a failure." +} + +$exitCode = 0 +if ($env:ChocolateyCheckLastExitCode -ne $null -and $env:ChocolateyCheckLastExitCode -eq 'true' -and $lastExecutableExitCode -ne $null -and $lastExecutableExitCode -ne '') { + $exitCode = $lastExecutableExitCode +} -$exitCode = $LASTEXITCODE if ($exitCode -eq 0 -and -not $scriptSuccess) { $exitCode = 1 } diff --git a/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs b/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs index 93e71a31da..9f4bc254f1 100644 --- a/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs @@ -1258,7 +1258,6 @@ public void should_have_a_version_of_one_dot_zero() } } - [Concern(typeof(ChocolateyInstallCommand))] public class when_installing_a_package_that_has_nonterminating_errors_with_fail_on_stderr : ScenariosBase { diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index 7e0fee610b..42813e947e 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -93,6 +93,7 @@ public static class Environment public static readonly string ChocolateyIgnoreChecksums = "ChocolateyIgnoreChecksums"; public static readonly string ChocolateyAllowEmptyChecksums = "ChocolateyAllowEmptyChecksums"; public static readonly string ChocolateyAllowEmptyChecksumsSecure = "ChocolateyAllowEmptyChecksumsSecure"; + public static readonly string ChocolateyCheckLastExitCode = "ChocolateyCheckLastExitCode"; public static readonly string ChocolateyPowerShellHost = "ChocolateyPowerShellHost"; public static readonly string ChocolateyForce = "ChocolateyForce"; } @@ -154,6 +155,7 @@ public static class Features public static readonly string IgnoreInvalidOptionsSwitches = "ignoreInvalidOptionsSwitches"; public static readonly string UsePackageExitCodes = "usePackageExitCodes"; public static readonly string UseFipsCompliantChecksums = "useFipsCompliantChecksums"; + public static readonly string ScriptsCheckLastExitCode = "scriptsCheckLastExitCode"; } public static class Messages diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index f31cebca7a..d852f0d7a0 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -272,6 +272,7 @@ private static void set_feature_flags(ChocolateyConfiguration config, ConfigFile config.Features.IgnoreInvalidOptionsSwitches = set_feature_flag(ApplicationParameters.Features.IgnoreInvalidOptionsSwitches, configFileSettings, defaultEnabled: true, description: "Ignore Invalid Options/Switches - If a switch or option is passed that is not recognized, should choco fail? Available in 0.9.10+."); config.Features.UsePackageExitCodes = set_feature_flag(ApplicationParameters.Features.UsePackageExitCodes, configFileSettings, defaultEnabled: true, description: "Use Package Exit Codes - Package scripts can provide exit codes. With this on, package exit codes will be what choco uses for exit when non-zero (this value can come from a dependency package). Chocolatey defines valid exit codes as 0, 1605, 1614, 1641, 3010. With this feature off, choco will exit with a 0 or a 1 (matching previous behavior). Available in 0.9.10+."); config.Features.UseFipsCompliantChecksums = set_feature_flag(ApplicationParameters.Features.UseFipsCompliantChecksums, configFileSettings, defaultEnabled: false, description: "Use FIPS Compliant Checksums - Ensure checksumming done by choco uses FIPS compliant algorithms. Not recommended unless required by FIPS Mode. Enabling on an existing installation could have unintended consequences related to upgrades/uninstalls. Available in 0.9.10+."); + config.Features.ScriptsCheckLastExitCode = set_feature_flag(ApplicationParameters.Features.ScriptsCheckLastExitCode, configFileSettings, defaultEnabled: false, description: "Scripts Check $LastExitCode (external commands) - Leave this off unless you absolutely need it while you fix your package scripts to use `throw 'error message'` or `Set-PowerShellExitCode #` instead of `exit #`. This behavior started in 0.9.10 and produced hard to find bugs. If the last external process exits successfully but with an exit code of not zero, this could cause hard to detect package failures. Available in 0.10.3+. Will be removed in 0.11.0."); config.PromptForConfirmation = !set_feature_flag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass."); } diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index b6706cc773..0af28bc32d 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -354,6 +354,8 @@ public sealed class FeaturesConfiguration public bool IgnoreInvalidOptionsSwitches { get; set; } public bool UsePackageExitCodes { get; set; } public bool UseFipsCompliantChecksums { get; set; } + //todo remove in 0.11.0 + public bool ScriptsCheckLastExitCode { get; set; } } //todo: retrofit other command configs this way diff --git a/src/chocolatey/infrastructure.app/configuration/EnvironmentSettings.cs b/src/chocolatey/infrastructure.app/configuration/EnvironmentSettings.cs index 86554656ab..e9bd9f91fd 100644 --- a/src/chocolatey/infrastructure.app/configuration/EnvironmentSettings.cs +++ b/src/chocolatey/infrastructure.app/configuration/EnvironmentSettings.cs @@ -79,6 +79,7 @@ public static void set_environment_variables(ChocolateyConfiguration config) if (!config.Features.ChecksumFiles) Environment.SetEnvironmentVariable(ApplicationParameters.Environment.ChocolateyIgnoreChecksums, "true"); if (config.Features.AllowEmptyChecksums) Environment.SetEnvironmentVariable(ApplicationParameters.Environment.ChocolateyAllowEmptyChecksums, "true"); if (config.Features.AllowEmptyChecksumsSecure) Environment.SetEnvironmentVariable(ApplicationParameters.Environment.ChocolateyAllowEmptyChecksumsSecure, "true"); + if (config.Features.ScriptsCheckLastExitCode) Environment.SetEnvironmentVariable(ApplicationParameters.Environment.ChocolateyCheckLastExitCode, "true"); Environment.SetEnvironmentVariable("chocolateyRequestTimeout", config.WebRequestTimeoutSeconds.to_string() + "000"); Environment.SetEnvironmentVariable("chocolateyResponseTimeout", config.CommandExecutionTimeoutSeconds.to_string() + "000"); diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 1db1471f42..31f7e88c79 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -690,8 +690,7 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu get_environment_before(config, allowLogging: true); - var beforeUpgradeAction = new Action(packageResult => before_package_upgrade(packageResult, config)); - + var beforeUpgradeAction = new Action(packageResult => before_package_modify(packageResult, config)); var packageUpgrades = perform_source_runner_function(config, r => r.upgrade_run(config, action, beforeUpgradeAction)); var upgradeFailures = report_action_summary(packageUpgrades, "upgraded"); @@ -705,9 +704,12 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu return packageUpgrades; } - private void before_package_upgrade(PackageResult packageResult, ChocolateyConfiguration config) + private void before_package_modify(PackageResult packageResult, ChocolateyConfiguration config) { - _powershellService.before_modify(config, packageResult); + if (!config.SkipPackageInstallProvider) + { + _powershellService.before_modify(config, packageResult); + } } public void uninstall_noop(ChocolateyConfiguration config) @@ -743,8 +745,8 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi } var environmentBefore = get_environment_before(config); - - var packageUninstalls = perform_source_runner_function(config, r => r.uninstall_run(config, action)); + var beforeUninstallAction = new Action(packageResult => before_package_modify(packageResult, config)); + var packageUninstalls = perform_source_runner_function(config, r => r.uninstall_run(config, action, beforeUninstallAction)); // not handled in the uninstall handler IEnumerable environmentChanges; @@ -860,11 +862,6 @@ public void handle_package_uninstall(PackageResult packageResult, ChocolateyConf packageResult.InstallLocation += ".{0}".format_with(packageResult.Package.Version.to_string()); } - if (!config.SkipPackageInstallProvider) - { - _powershellService.before_modify(config, packageResult); - } - _shimgenService.uninstall(config, packageResult); if (!config.SkipPackageInstallProvider) diff --git a/src/chocolatey/infrastructure.app/services/CygwinService.cs b/src/chocolatey/infrastructure.app/services/CygwinService.cs index 02b48531a4..50aa719bb7 100644 --- a/src/chocolatey/infrastructure.app/services/CygwinService.cs +++ b/src/chocolatey/infrastructure.app/services/CygwinService.cs @@ -263,7 +263,7 @@ public ConcurrentDictionary upgrade_noop(ChocolateyConfig return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); } - public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction) + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction = null) { throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); } @@ -273,7 +273,7 @@ public void uninstall_noop(ChocolateyConfiguration config, Action this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); } - public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, Action beforeUninstallAction = null) { throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); } diff --git a/src/chocolatey/infrastructure.app/services/ISourceRunner.cs b/src/chocolatey/infrastructure.app/services/ISourceRunner.cs index b8aaaa271b..bd55da09cb 100644 --- a/src/chocolatey/infrastructure.app/services/ISourceRunner.cs +++ b/src/chocolatey/infrastructure.app/services/ISourceRunner.cs @@ -102,7 +102,8 @@ public interface ISourceRunner /// /// The configuration. /// The action to continue with when upgrade is successful. + /// The action (if any) to run on any currently installed package before triggering the uninstall. /// results of installs - ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction); + ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, Action beforeUninstallAction = null); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index e7d8c3003b..67a450260b 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -389,9 +389,6 @@ public ConcurrentDictionary install_run(ChocolateyConfigu foreach (string packageName in packageNames.or_empty_list_if_null()) { //todo: get smarter about realizing multiple versions have been installed before and allowing that - - remove_rollback_directory_if_exists(packageName); - IPackage installedPackage = packageManager.LocalRepository.FindPackage(packageName); if (installedPackage != null && (version == null || version == installedPackage.Version) && !config.Force) @@ -433,7 +430,8 @@ public ConcurrentDictionary install_run(ChocolateyConfigu { var forcedResult = packageInstalls.GetOrAdd(packageName, new PackageResult(availablePackage, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, availablePackage.Id))); forcedResult.Messages.Add(new ResultMessage(ResultType.Note, "Backing up and removing old version")); - + + remove_rollback_directory_if_exists(packageName); backup_existing_version(config, installedPackage, _packageInfoService.get_package_information(installedPackage)); try @@ -543,8 +541,6 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu foreach (string packageName in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null()) { - remove_rollback_directory_if_exists(packageName); - IPackage installedPackage = packageManager.LocalRepository.FindPackage(packageName); if (installedPackage == null) @@ -717,14 +713,15 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu packageName, version == null ? null : version.ToString())) { - ensure_package_files_have_compatible_attributes(config, installedPackage, pkgInfo); - rename_legacy_package_version(config, installedPackage, pkgInfo); if (beforeUpgradeAction != null) { var currentPackageResult = new PackageResult(installedPackage, get_install_directory(config, installedPackage)); beforeUpgradeAction(currentPackageResult); } + remove_rollback_directory_if_exists(packageName); + ensure_package_files_have_compatible_attributes(config, installedPackage, pkgInfo); + rename_legacy_package_version(config, installedPackage, pkgInfo); backup_existing_version(config, installedPackage, pkgInfo); remove_shim_directors(config, installedPackage, pkgInfo); if (config.Force && (installedPackage.Version == availablePackage.Version)) @@ -965,12 +962,12 @@ public void uninstall_noop(ChocolateyConfiguration config, Action } } - public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, Action beforeUninstallAction = null) { - return uninstall_run(config, continueAction, performAction: true); + return uninstall_run(config, continueAction, performAction: true, beforeUninstallAction: beforeUninstallAction); } - public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, bool performAction) + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, bool performAction, Action beforeUninstallAction = null) { var packageUninstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); @@ -1089,8 +1086,6 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi foreach (string packageName in config.PackageNames.Split(new[] { ApplicationParameters.PackageNamesSeparator }, StringSplitOptions.RemoveEmptyEntries).or_empty_list_if_null()) { - remove_rollback_directory_if_exists(packageName); - IList installedPackageVersions = new List(); if (string.IsNullOrWhiteSpace(config.Version)) { @@ -1177,8 +1172,16 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi packageVersion.Id, packageVersion.Version.to_string()) ) { + if (beforeUninstallAction != null) + { + // guessing this is not added so that it doesn't fail the action if an error is recorded? + //var currentPackageResult = packageUninstalls.GetOrAdd(packageName, new PackageResult(packageVersion, get_install_directory(config, packageVersion))); + var currentPackageResult = new PackageResult(packageVersion, get_install_directory(config, packageVersion)); + beforeUninstallAction(currentPackageResult); + } ensure_package_files_have_compatible_attributes(config, packageVersion, pkgInfo); rename_legacy_package_version(config, packageVersion, pkgInfo); + remove_rollback_directory_if_exists(packageName); backup_existing_version(config, packageVersion, pkgInfo); packageManager.UninstallPackage(packageVersion.Id, forceRemove: config.Force, removeDependencies: config.ForceDependencies, version: packageVersion.Version); ensure_nupkg_is_removed(packageVersion, pkgInfo); diff --git a/src/chocolatey/infrastructure.app/services/PythonService.cs b/src/chocolatey/infrastructure.app/services/PythonService.cs index ef4a562c33..dbf5566841 100644 --- a/src/chocolatey/infrastructure.app/services/PythonService.cs +++ b/src/chocolatey/infrastructure.app/services/PythonService.cs @@ -392,7 +392,7 @@ public ConcurrentDictionary upgrade_noop(ChocolateyConfig return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); } - public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction) + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction = null) { set_executable_path_if_not_set(); var args = build_args(config, _upgradeArguments); @@ -465,7 +465,7 @@ public void uninstall_noop(ChocolateyConfiguration config, Action this.Log().Info("Would have run '{0} {1}'".format_with(_exePath.escape_curly_braces(), args.escape_curly_braces())); } - public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, Action beforeUninstallAction = null) { set_executable_path_if_not_set(); var args = build_args(config, _uninstallArguments); diff --git a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs index 6e67470842..b9a523b388 100644 --- a/src/chocolatey/infrastructure.app/services/RubyGemsService.cs +++ b/src/chocolatey/infrastructure.app/services/RubyGemsService.cs @@ -246,7 +246,7 @@ public ConcurrentDictionary upgrade_noop(ChocolateyConfig return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); } - public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction) + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction = null) { throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); } @@ -256,7 +256,7 @@ public void uninstall_noop(ChocolateyConfiguration config, Action this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); } - public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, Action beforeUninstallAction = null) { throw new NotImplementedException("{0} does not implement uninstall".format_with(APP_NAME)); } diff --git a/src/chocolatey/infrastructure.app/services/WebPiService.cs b/src/chocolatey/infrastructure.app/services/WebPiService.cs index 3a0e580833..c2291ac111 100644 --- a/src/chocolatey/infrastructure.app/services/WebPiService.cs +++ b/src/chocolatey/infrastructure.app/services/WebPiService.cs @@ -254,7 +254,7 @@ public ConcurrentDictionary upgrade_noop(ChocolateyConfig return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); } - public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction) + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction = null) { throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); } @@ -264,7 +264,7 @@ public void uninstall_noop(ChocolateyConfiguration config, Action this.Log().Warn(ChocolateyLoggers.Important, "{0} does not implement uninstall".format_with(APP_NAME)); } - public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, Action beforeUninstallAction = null) { throw new NotImplementedException("{0} does not implement uninstall".format_with(APP_NAME)); } diff --git a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs index c9d9048798..7f6aa19df3 100644 --- a/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs +++ b/src/chocolatey/infrastructure.app/services/WindowsFeatureService.cs @@ -324,7 +324,7 @@ public ConcurrentDictionary upgrade_noop(ChocolateyConfig return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); } - public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction) + public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config, Action continueAction, Action beforeUpgradeAction = null) { set_executable_path_if_not_set(); throw new NotImplementedException("{0} does not implement upgrade".format_with(APP_NAME)); @@ -337,7 +337,7 @@ public void uninstall_noop(ChocolateyConfiguration config, Action this.Log().Info("Would have run '{0} {1}'".format_with(_exePath.escape_curly_braces(), args.escape_curly_braces())); } - public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction) + public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config, Action continueAction, Action beforeUninstallAction = null) { set_executable_path_if_not_set(); var args = build_args(config, _uninstallArguments);