diff --git a/src/chocolatey.console/Program.cs b/src/chocolatey.console/Program.cs index 3ee2541cb9..b1f715155b 100644 --- a/src/chocolatey.console/Program.cs +++ b/src/chocolatey.console/Program.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -158,7 +158,7 @@ private static void Main(string[] args) "chocolatey".Log().Error(ChocolateyLoggers.Important, () => "{0}".format_with(ex.Message)); } - Environment.ExitCode = 1; + if (Environment.ExitCode == 0) Environment.ExitCode = 1; } finally { @@ -188,7 +188,7 @@ private static void add_assembly_resolver() { var requestedAssembly = new AssemblyName(args.Name); - // There are things that are ILMerged into Chocolatey. Anything with + // There are things that are ILMerged into Chocolatey. Anything with // the right public key except licensed should use the choco/chocolatey assembly if (requestedAssembly.get_public_key_token().is_equal_to(ApplicationParameters.OfficialChocolateyPublicKey) && !requestedAssembly.Name.is_equal_to(ApplicationParameters.LicensedChocolateyAssemblySimpleName) diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 7eb772368a..97dd3a9447 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -102,6 +102,7 @@ + @@ -120,10 +121,13 @@ + + + @@ -317,6 +321,9 @@ + + + diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index 808a0f3c6b..87f51a308b 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -33,7 +33,7 @@ public static class ApplicationParameters #if DEBUG public static readonly string InstallLocation = _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); public static readonly string LicensedAssemblyLocation = _fileSystem.file_exists(_fileSystem.combine_paths(InstallLocation, "chocolatey.licensed.dll")) ? _fileSystem.combine_paths(InstallLocation, "chocolatey.licensed.dll") : _fileSystem.combine_paths(InstallLocation, "extensions", "chocolatey", "chocolatey.licensed.dll"); - + #else public static readonly string InstallLocation = System.Environment.GetEnvironmentVariable(ChocolateyInstallEnvironmentVariableName) ?? _fileSystem.get_directory_name(_fileSystem.get_current_assembly_path()); public static readonly string LicensedAssemblyLocation = _fileSystem.combine_paths(InstallLocation, "extensions", "chocolatey", "chocolatey.licensed.dll"); @@ -112,10 +112,10 @@ public static class Environment public static readonly string[] ConfigFileExtensions = new string[] {".autoconf",".config",".conf",".cfg",".jsc",".json",".jsonp",".ini",".xml",".yaml"}; public static readonly string ConfigFileTransformExtension = ".install.xdt"; public static readonly string[] ShimDirectorFileExtensions = new string[] {".gui",".ignore"}; - + public static readonly string HashProviderFileTooBig = "UnableToDetectChanges_FileTooBig"; public static readonly string HashProviderFileLocked = "UnableToDetectChanges_FileLocked"; - + /// /// This is a readonly bool set to true. It is only shifted for specs. /// @@ -127,6 +127,12 @@ public static class Environment /// public static readonly bool AllowPrompts = true; + public static class ExitCodes + { + public static readonly int ErrorFailNoActionReboot = 350; + public static readonly int ErrorInstallSuspend = 1604; + } + public static class Tools { //public static readonly string WebPiCmdExe = _fileSystem.combine_paths(InstallLocation, "nuget.exe"); @@ -145,7 +151,7 @@ public static class ConfigSettings public static readonly string ProxyBypassOnLocal = "proxyBypassOnLocal"; public static readonly string WebRequestTimeoutSeconds = "webRequestTimeoutSeconds"; } - + public static class Features { public static readonly string ChecksumFiles = "checksumFiles"; @@ -171,6 +177,7 @@ public static class Features public static readonly string SkipPackageUpgradesWhenNotInstalled = "skipPackageUpgradesWhenNotInstalled"; public static readonly string RemovePackageInformationOnUninstall = "removePackageInformationOnUninstall"; public static readonly string LogWithoutColor = "logWithoutColor"; + public static readonly string ExitOnRebootDetected = "exitOnRebootDetected"; } public static class Messages diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index 7534f27cd9..59a9e3ec3b 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -210,7 +210,7 @@ private static void set_config_items(ChocolateyConfiguration config, ConfigFileS config.CacheLocation = Environment.ExpandEnvironmentVariables(set_config_item(ApplicationParameters.ConfigSettings.CacheLocation, configFileSettings, string.IsNullOrWhiteSpace(configFileSettings.CacheLocation) ? string.Empty : configFileSettings.CacheLocation, "Cache location if not TEMP folder. Replaces `$env:TEMP` value.")); if (string.IsNullOrWhiteSpace(config.CacheLocation)) { config.CacheLocation = fileSystem.get_temp_path(); // System.Environment.GetEnvironmentVariable("TEMP"); - // TEMP gets set in EnvironmentSettings, so it may already have + // TEMP gets set in EnvironmentSettings, so it may already have // chocolatey in the path when it installs the next package from // the API. if(!String.Equals(fileSystem.get_directory_info_for(config.CacheLocation).Name, "chocolatey", StringComparison.OrdinalIgnoreCase)) { @@ -220,7 +220,7 @@ private static void set_config_items(ChocolateyConfiguration config, ConfigFileS // if it is still empty, use temp in the Chocolatey install directory. if (string.IsNullOrWhiteSpace(config.CacheLocation)) config.CacheLocation = fileSystem.combine_paths(ApplicationParameters.InstallLocation, "temp"); - + var commandExecutionTimeoutSeconds = 0; var commandExecutionTimeout = set_config_item(ApplicationParameters.ConfigSettings.CommandExecutionTimeoutSeconds, configFileSettings, string.IsNullOrWhiteSpace(configFileSettings.CommandExecutionTimeoutSeconds.to_string()) ? ApplicationParameters.DefaultWaitForExitInSeconds.to_string() : configFileSettings.CommandExecutionTimeoutSeconds.to_string(), "Default timeout for command execution. '0' for infinite (starting in 0.10.4)."); int.TryParse(commandExecutionTimeout, out commandExecutionTimeoutSeconds); @@ -304,6 +304,7 @@ private static void set_feature_flags(ChocolateyConfiguration config, ConfigFile 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."); + config.Features.ExitOnRebootDetected = set_feature_flag(ApplicationParameters.Features.ExitOnRebootDetected, configFileSettings, defaultEnabled: false, description: "Exit On Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either {1} or {2}. When it exits with {1}, it means pending reboot discovered prior to running operation. When it exits with {2}, it means some work completed prior to reboot request being detected. As this will affect upgrade all, it is normally recommended to leave this off. Available in 0.10.12+.".format_with(ApplicationParameters.Features.ExitOnRebootDetected, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend)); } private static bool set_feature_flag(string featureName, ConfigFileSettings configFileSettings, bool defaultEnabled, string description) @@ -350,7 +351,7 @@ private static void set_global_options(IList args, ChocolateyConfigurati option => config.Verbose = option != null) .Add("trace", "Trace - Show trace messaging. Very, very verbose trace messaging. Avoid except when needing super low-level .NET Framework debugging. Available in 0.10.4+.", - option => config.Trace = option != null) + option => config.Trace = option != null) .Add("nocolor|no-color", "No Color - Do not show colorization in logging output. This overrides the feature '{0}', set to '{1}'. Available in 0.10.9+.".format_with(ApplicationParameters.Features.LogWithoutColor, config.Features.LogWithoutColor), option => config.Features.LogWithoutColor = option != null) @@ -378,8 +379,9 @@ private static void set_global_options(IList args, ChocolateyConfigurati option => { int timeout = 0; - int.TryParse(option.remove_surrounding_quotes(), out timeout); - if (timeout > 0) + var timeoutString = option.remove_surrounding_quotes(); + int.TryParse(timeoutString, out timeout); + if (timeout > 0 || timeoutString.is_equal_to("0")) { config.CommandExecutionTimeoutSeconds = timeout; } @@ -413,7 +415,7 @@ private static void set_global_options(IList args, ChocolateyConfigurati option => config.Proxy.BypassList = option.remove_surrounding_quotes()) .Add("proxy-bypass-on-local", "Proxy Bypass On Local - Bypass proxy for local connections. Requires explicity proxy (`--proxy` or config setting). Overrides the default proxy bypass on local setting of '{0}'. Available in 0.10.4+.".format_with(config.Proxy.BypassOnLocal), - option => config.Proxy.BypassOnLocal = option != null) + option => config.Proxy.BypassOnLocal = option != null) .Add("log-file=", "Log File to output to in addition to regular loggers. Available in 0.10.8+.", option => config.AdditionalLogFileLocation= option.remove_surrounding_quotes()) @@ -472,7 +474,7 @@ the local options are parsed. (`""value""`) but in powershell.exe you should use backticks (`` `""value`"" ``) or apostrophes (`'value'`). Using the combination allows for both shells to work without issue, except for when the next - section applies. + section applies. * **Pass quotes in arguments**: When you need to pass quoted values to to something like a native installer, you are in for a world of fun. In cmd.exe you must pass it like this: `-ia ""/yo=""""Spaces spaces""""""`. In @@ -480,8 +482,8 @@ section applies. No other combination will work. In PowerShell.exe if you are on version v3+, you can try `--%` before `-ia` to just pass the args through as is, which means it should not require any special workarounds. - * **Periods in PowerShell**: If you need to pass a period as part of a - value or a path, PowerShell doesn't always handle it well. Please + * **Periods in PowerShell**: If you need to pass a period as part of a + value or a path, PowerShell doesn't always handle it well. Please quote those values using ""Quote Values"" section above. * Options and switches apply to all items passed, so if you are installing multiple packages, and you use `--version=1.0.0`, choco @@ -614,7 +616,7 @@ private static void set_environment_options(ChocolateyConfiguration config) config.Information.IsUserRemoteDesktop = ProcessInformation.user_is_terminal_services(); config.Information.IsUserRemote = ProcessInformation.user_is_remote(); config.Information.IsProcessElevated = ProcessInformation.process_is_elevated(); - + if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("https_proxy")) && string.IsNullOrWhiteSpace(config.Proxy.Location)) { config.Proxy.Location = Environment.GetEnvironmentVariable("https_proxy"); @@ -643,7 +645,7 @@ private static void set_licensed_options(ChocolateyConfiguration config, Chocola if (licensedConfigBuilder == null) { if (config.RegularOutput) "chocolatey".Log().Warn(ChocolateyLoggers.Important, - @"Unable to set licensed configuration. Please upgrade to a newer + @"Unable to set licensed configuration. Please upgrade to a newer licensed version (choco upgrade chocolatey.extension)."); return; } @@ -697,7 +699,7 @@ private static void set_hash_provider(ChocolateyConfiguration config, Container if (ex.InnerException != null && ex.InnerException.Message.contains("FIPS")) { "chocolatey".Log().Warn(ChocolateyLoggers.Important, @" -FIPS Mode detected - run 'choco feature enable -n {0}' +FIPS Mode detected - run 'choco feature enable -n {0}' to use Chocolatey.".format_with(ApplicationParameters.Features.UseFipsCompliantChecksums)); var errorMessage = "When FIPS Mode is enabled, Chocolatey requires {0} feature also be enabled.".format_with(ApplicationParameters.Features.UseFipsCompliantChecksums); diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs index a6694a5ef6..66ddfa45e0 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -150,11 +150,26 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon .Add("usepackagecodes|usepackageexitcodes|use-package-codes|use-package-exit-codes", "UsePackageExitCodes - Package scripts can provide exit codes. Use those for choco's exit code when non-zero (this value can come from a dependency package). Chocolatey defines valid exit codes as 0, 1605, 1614, 1641, 3010. Overrides the default feature '{0}' set to '{1}'. Available in 0.9.10+.".format_with(ApplicationParameters.Features.UsePackageExitCodes, configuration.Features.UsePackageExitCodes.to_string()), option => configuration.Features.UsePackageExitCodes = option != null - ) + ) .Add("stoponfirstfailure|stop-on-first-failure|stop-on-first-package-failure", "Stop On First Package Failure - stop running install, upgrade or uninstall on first package failure instead of continuing with others. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.4+.".format_with(ApplicationParameters.Features.StopOnFirstPackageFailure, configuration.Features.StopOnFirstPackageFailure.to_string()), option => configuration.Features.StopOnFirstPackageFailure = option != null ) + .Add("exitwhenrebootdetected|exit-when-reboot-detected", + "Exit When Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with + (ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), + option => configuration.Features.ExitOnRebootDetected = option != null + ) + .Add("ignoredetectedreboot|ignore-detected-reboot", + "Ignore Detected Reboot - Ignore any detected reboots if found. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with + (ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), + option => + { + if (option != null) + { + configuration.Features.ExitOnRebootDetected = false; + } + }) ; //todo: package name can be a url / installertype @@ -234,13 +249,13 @@ feed. THIS IS NOT YET REIMPLEMENTED. NOTE: Any package name ending with .config is considered a 'packages.config' file. Please see https://bit.ly/packages_config -NOTE: Chocolatey Pro / Business builds on top of a great open source - experience with quite a few features that enhance the your use of the +NOTE: Chocolatey Pro / Business builds on top of a great open source + experience with quite a few features that enhance the your use of the community package repository (when using Pro), and really enhance the Chocolatey experience all around. If you are an organization looking for a better ROI, look no further than Business - automatic package creation from installer files, automatic recompile support, runtime - malware protection, private CDN download cache, synchronize with + malware protection, private CDN download cache, synchronize with Programs and Features, etc - https://chocolatey.org/compare. "); diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs index 7174ba3e95..75a0dacd3d 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -113,11 +113,26 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon { configuration.Features.FailOnAutoUninstaller = false; } - }) + }) .Add("stoponfirstfailure|stop-on-first-failure|stop-on-first-package-failure", "Stop On First Package Failure - stop running install, upgrade or uninstall on first package failure instead of continuing with others. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.4+.".format_with(ApplicationParameters.Features.StopOnFirstPackageFailure, configuration.Features.StopOnFirstPackageFailure.to_string()), option => configuration.Features.StopOnFirstPackageFailure = option != null ) + .Add("exitwhenrebootdetected|exit-when-reboot-detected", + "Exit When Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with + (ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), + option => configuration.Features.ExitOnRebootDetected = option != null + ) + .Add("ignoredetectedreboot|ignore-detected-reboot", + "Ignore Detected Reboot - Ignore any detected reboots if found. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with + (ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), + option => + { + if (option != null) + { + configuration.Features.ExitOnRebootDetected = false; + } + }) ; } @@ -177,14 +192,14 @@ chocolatey without attempting to uninstall the program. choco feature disable -n autoUninstaller -NOTE: Chocolatey Pro / Business automatically synchronizes with - Programs and Features, ensuring manually removed apps are +NOTE: Chocolatey Pro / Business automatically synchronizes with + Programs and Features, ensuring manually removed apps are automatically removed from Chocolatey's repository. -NOTE: Synchronizer and AutoUninstaller enhancements in licensed - versions of Chocolatey ensure that Autouninstaller is up to 95% - effective at removing software without an uninstall script. This is - because synchronizer ensures the registry snapshot stays up to date +NOTE: Synchronizer and AutoUninstaller enhancements in licensed + versions of Chocolatey ensure that Autouninstaller is up to 95% + effective at removing software without an uninstall script. This is + because synchronizer ensures the registry snapshot stays up to date and licensed enhancements have the ability to inspect more locations to determine how to automatically uninstall software. "); diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index 8f3daaedd9..1ba3212e4d 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -195,6 +195,21 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon { if (option != null) configuration.Features.UseRememberedArgumentsForUpgrades = false; }) + .Add("exitwhenrebootdetected|exit-when-reboot-detected", + "Exit When Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with + (ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), + option => configuration.Features.ExitOnRebootDetected = option != null + ) + .Add("ignoredetectedreboot|ignore-detected-reboot", + "Ignore Detected Reboot - Ignore any detected reboots if found. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with + (ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()), + option => + { + if (option != null) + { + configuration.Features.ExitOnRebootDetected = false; + } + }) ; } @@ -265,9 +280,9 @@ all currently installed packages. Skip upgrading certain packages with `choco pin` or with the option `--except`. -NOTE: Chocolatey Pro / Business automatically synchronizes with +NOTE: Chocolatey Pro / Business automatically synchronizes with Programs and Features, ensuring automatically updating apps' versions - (like Chrome) are up to date in Chocolatey's repository. + (like Chrome) are up to date in Chocolatey's repository. "); "chocolatey".Log().Info(ChocolateyLoggers.Important, "Examples"); diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 9a129413fb..aaf876ca2e 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -61,8 +61,8 @@ public override string ToString() var properties = new StringBuilder(); this.Log().Debug(ChocolateyLoggers.Important, @" -NOTE: Hiding sensitive configuration data! Please double and triple - check to be sure no sensitive data is shown, especially if copying +NOTE: Hiding sensitive configuration data! Please double and triple + check to be sure no sensitive data is shown, especially if copying output to a gist for review."); output_tostring(properties, GetType().GetProperties(), this, ""); return properties.ToString(); @@ -174,7 +174,7 @@ private void append_output(StringBuilder propertyValues, string append) /// true for regular output; false for limited output. public bool RegularOutput { get; set; } /// - /// Gets or sets a value indicating whether console logging should be supressed. + /// Gets or sets a value indicating whether console logging should be supressed. /// This is for use by API calls which surface results in alternate forms. /// /// true for no output; false for regular or limited output. @@ -221,7 +221,7 @@ private void append_output(StringBuilder propertyValues, string append) public string DownloadChecksum64 { get; set; } public string DownloadChecksumType { get; set; } public string DownloadChecksumType64 { get; set; } - + /// /// Configuration values provided by choco. /// @@ -268,8 +268,8 @@ private void append_output(StringBuilder propertyValues, string append) /// /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 /// - public SourcesCommandConfiguration SourceCommand { get; set; } - + public SourcesCommandConfiguration SourceCommand { get; set; } + /// /// Default Machine Sources Configuration /// @@ -391,6 +391,7 @@ public sealed class FeaturesConfiguration public bool IgnoreUnfoundPackagesOnUpgradeOutdated { get; set; } public bool SkipPackageUpgradesWhenNotInstalled { get; set; } public bool RemovePackageInformationOnUninstall { get; set; } + public bool ExitOnRebootDetected { get; set; } //todo remove in 0.11.0 public bool ScriptsCheckLastExitCode { get; set; } @@ -483,8 +484,8 @@ public sealed class FeatureCommandConfiguration { public string Name { get; set; } public FeatureCommandType Command { get; set; } - } - + } + [Serializable] public sealed class ConfigCommandConfiguration { @@ -529,8 +530,8 @@ public sealed class PushCommandConfiguration { public string Key { get; set; } //DisableBuffering? - } - + } + [Serializable] public sealed class ProxyConfiguration { diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index c801a2cd86..67ffc551b5 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,9 +27,11 @@ namespace chocolatey.infrastructure.app.registration using infrastructure.commands; using infrastructure.configuration; using infrastructure.services; + using infrastructure.validations; using nuget; using services; using tasks; + using validations; using CryptoHashProvider = cryptography.CryptoHashProvider; using IFileSystem = filesystem.IFileSystem; using IHashProvider = cryptography.IHashProvider; @@ -61,6 +63,7 @@ public void RegisterComponents(Container container) container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); + container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(() => new CryptoHashProvider(container.GetInstance()), Lifestyle.Singleton); @@ -126,6 +129,19 @@ public void RegisterComponents(Container container) return list.AsReadOnly(); }, Lifestyle.Singleton); + + container.Register>( + () => + { + var list = new List + { + new GlobalConfigurationValidation(), + new SystemStateValidation(container.GetInstance()) + }; + + return list.AsReadOnly(); + }, + Lifestyle.Singleton); } } diff --git a/src/chocolatey/infrastructure.app/runners/ConsoleApplication.cs b/src/chocolatey/infrastructure.app/runners/ConsoleApplication.cs index c3565f5ea4..32a240c8e7 100644 --- a/src/chocolatey/infrastructure.app/runners/ConsoleApplication.cs +++ b/src/chocolatey/infrastructure.app/runners/ConsoleApplication.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,10 +18,13 @@ namespace chocolatey.infrastructure.app.runners { using System; using System.Collections.Generic; + using System.Linq; using SimpleInjector; using configuration; + using infrastructure.validations; using logging; using utility; + using validations; /// /// Console application responsible for running chocolatey @@ -39,11 +42,11 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain else { this.Log().Debug(() => "Command line: {0}".format_with(commandLine)); - this.Log().Debug(() => "Received arguments: {0}".format_with(string.Join(" ", args))); + this.Log().Debug(() => "Received arguments: {0}".format_with(string.Join(" ", args))); } - + IList commandArgs = new List(); - //shift the first arg off + //shift the first arg off int count = 0; foreach (var arg in args) { @@ -55,7 +58,7 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain commandArgs.Add(arg); } - + var runner = new GenericRunner(); runner.run(config, container, isConsole: true, parseArgs: command => { @@ -64,7 +67,7 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain config, (optionSet) => command.configure_argument_parser(optionSet, config), (unparsedArgs) => { - // if debug is bundled with local options, it may not get picked up when global + // if debug is bundled with local options, it may not get picked up when global // options are parsed. Attempt to set it again once local options are set. // This does mean some output from debug will be missed (but not much) if (config.Debug) Log4NetAppenderConfiguration.set_logging_level_debug_when_debug(config.Debug, "{0}LoggingColoredConsoleAppender".format_with(ChocolateyLoggers.Verbose.to_string()), "{0}LoggingColoredConsoleAppender".format_with(ChocolateyLoggers.Trace.to_string())); @@ -73,7 +76,7 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain if (!config.Features.IgnoreInvalidOptionsSwitches) { - // all options / switches should be parsed, + // all options / switches should be parsed, // so show help menu if there are any left foreach (var unparsedArg in unparsedArgs.or_empty_list_if_null()) { @@ -85,9 +88,67 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain } } }, - () => command.handle_validation(config), + () => { + this.Log().Debug(() => "Performing validation checks."); + command.handle_validation(config); + + var validationResults = new List(); + var validationChecks = container.GetAllInstances(); + foreach (var validationCheck in validationChecks) + { + validationResults.AddRange(validationCheck.validate(config)); + } + + var validationErrors = report_validation_summary(validationResults, config); + + if (validationErrors != 0) + { + // NOTE: This is intentionally left blank, as the reason for throwing is + // documented in the report_validation_summary above, and a duplication + // is not required in the exception. + throw new ApplicationException(""); + } + }, () => command.help_message(config)); }); } + + private int report_validation_summary(IList validationResults, ChocolateyConfiguration config) + { + var successes = validationResults.Count(v => v.Status == ValidationStatus.Success); + var warnings = validationResults.Count(v => v.Status == ValidationStatus.Warning); + var errors = validationResults.Count(v => v.Status == ValidationStatus.Error); + + if (config.RegularOutput) + { + this.Log().Info(errors + warnings == 0 ? ChocolateyLoggers.LogFileOnly : ChocolateyLoggers.Important, () => "{0} validations performed. {1} success(es), {2} warning(s), and {3} error(s).".format_with( + validationResults.Count, + successes, + warnings, + errors)); + + if (warnings != 0) + { + this.Log().Info(""); + this.Log().Warn("Validation Warnings:"); + foreach (var warning in validationResults.Where(p => p.Status == ValidationStatus.Warning).or_empty_list_if_null()) + { + this.Log().Warn(" - {0}".format_with(warning.Message)); + } + } + } + + if (errors != 0) + { + this.Log().Info(""); + this.Log().Error("Validation Errors:"); + foreach (var error in validationResults.Where(p => p.Status == ValidationStatus.Error).or_empty_list_if_null()) + { + this.Log().Error(" - {0}".format_with(error.Message)); + } + } + + return errors; + } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index c86313c3ea..3ef31c147a 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -98,6 +98,11 @@ Did you know Pro / Business automatically syncs with Programs and private readonly string _shutdownExe = Environment.ExpandEnvironmentVariables("%systemroot%\\System32\\shutdown.exe"); + // Hold a list of exit codes that are known to be related to reboots + // 1641 - restart initiated + // 3010 - restart required + private readonly List _rebootExitCodes = new List { 1641, 3010 }; + public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, IEnumerable sourceRunners, IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, @@ -118,12 +123,12 @@ public ChocolateyPackageService(INugetService nugetService, IPowershellService p _configTransformService = configTransformService; } - public void ensure_source_app_installed(ChocolateyConfiguration config) + public virtual void ensure_source_app_installed(ChocolateyConfiguration config) { perform_source_runner_action(config, r => r.ensure_source_app_installed(config, (packageResult) => handle_package_result(packageResult, config, CommandNameType.install))); } - public int count_run(ChocolateyConfiguration config) + public virtual int count_run(ChocolateyConfiguration config) { return perform_source_runner_function(config, r => r.count_run(config)); } @@ -159,7 +164,7 @@ public void list_noop(ChocolateyConfiguration config) randomly_notify_about_pro_business(config, PRO_BUSINESS_LIST_MESSAGE); } - public IEnumerable list_run(ChocolateyConfiguration config) + public virtual IEnumerable list_run(ChocolateyConfiguration config) { if (string.IsNullOrWhiteSpace(config.Sources) && !config.ListCommand.LocalOnly) { @@ -246,7 +251,7 @@ public void pack_noop(ChocolateyConfiguration config) randomly_notify_about_pro_business(config); } - public void pack_run(ChocolateyConfiguration config) + public virtual void pack_run(ChocolateyConfiguration config) { if (config.SourceType != SourceType.normal) { @@ -270,7 +275,7 @@ public void push_noop(ChocolateyConfiguration config) randomly_notify_about_pro_business(config); } - public void push_run(ChocolateyConfiguration config) + public virtual void push_run(ChocolateyConfiguration config) { if (config.SourceType != SourceType.normal) { @@ -328,7 +333,7 @@ public void randomly_notify_about_pro_business(ChocolateyConfiguration config, s } } - public void handle_package_result(PackageResult packageResult, ChocolateyConfiguration config, CommandNameType commandName) + public virtual void handle_package_result(PackageResult packageResult, ChocolateyConfiguration config, CommandNameType commandName) { EnvironmentSettings.reset_environment_variables(config); set_pending(packageResult, config); @@ -420,6 +425,18 @@ public void handle_package_result(PackageResult packageResult, ChocolateyConfigu remove_pending(packageResult, config); + if(_rebootExitCodes.Contains(packageResult.ExitCode)) + { + if(config.Features.ExitOnRebootDetected) + { + Environment.ExitCode = ApplicationParameters.ExitCodes.ErrorInstallSuspend; + this.Log().Warn(ChocolateyLoggers.Important, @"Chocolatey has detected a pending reboot after installing/upgrading +package '{0}' - stopping further execution".format_with(packageResult.Name)); + + throw new ApplicationException("Reboot required before continuing. Reboot and run same command again."); + } + } + if (!packageResult.Success) { this.Log().Error(ChocolateyLoggers.Important, "The {0} of {1} was NOT successful.".format_with(commandName.to_string(), packageResult.Name)); @@ -541,7 +558,7 @@ private string capture_arguments(ChocolateyConfiguration config, PackageResult p return NugetEncryptionUtility.EncryptString(arguments.to_string()); } - public ConcurrentDictionary install_run(ChocolateyConfiguration config) + public virtual ConcurrentDictionary install_run(ChocolateyConfiguration config) { this.Log().Info(is_packages_config_file(config.PackageNames) ? @"Installing from config file:" : @"Installing the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); @@ -560,29 +577,34 @@ public ConcurrentDictionary install_run(ChocolateyConfigu get_environment_before(config, allowLogging: true); - foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null()) + try { - Action action = null; - if (packageConfig.SourceType == SourceType.normal) + foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null()) { - action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install); - } + Action action = null; + if (packageConfig.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install); + } - var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action)); + var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action)); - foreach (var result in results) - { - packageInstalls.GetOrAdd(result.Key, result.Value); + foreach (var result in results) + { + packageInstalls.GetOrAdd(result.Key, result.Value); + } } } - - var installFailures = report_action_summary(packageInstalls, "installed"); - if (installFailures != 0 && Environment.ExitCode == 0) + finally { - Environment.ExitCode = 1; - } + var installFailures = report_action_summary(packageInstalls, "installed"); + if (installFailures != 0 && Environment.ExitCode == 0) + { + Environment.ExitCode = 1; + } - randomly_notify_about_pro_business(config); + randomly_notify_about_pro_business(config); + } return packageInstalls; } @@ -594,7 +616,7 @@ Would have determined packages that are out of date based on what is installed and what versions are available for upgrade."); } - public void outdated_run(ChocolateyConfiguration config) + public virtual void outdated_run(ChocolateyConfiguration config) { if (config.SourceType != SourceType.normal) { @@ -720,7 +742,7 @@ public void upgrade_noop(ChocolateyConfiguration config) randomly_notify_about_pro_business(config); } - public ConcurrentDictionary upgrade_run(ChocolateyConfiguration config) + public virtual ConcurrentDictionary upgrade_run(ChocolateyConfiguration config) { this.Log().Info(@"Upgrading the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); @@ -740,24 +762,36 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu throw new ApplicationException("A packages.config file is only used with installs."); } - Action action = null; - if (config.SourceType == SourceType.normal) + var packageUpgrades = new ConcurrentDictionary(); + + try { - action = (packageResult) => handle_package_result(packageResult, config, CommandNameType.upgrade); - } + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_result(packageResult, config, CommandNameType.upgrade); + } - get_environment_before(config, allowLogging: true); + get_environment_before(config, allowLogging: true); - 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 beforeUpgradeAction = new Action(packageResult => before_package_modify(packageResult, config)); + var results = perform_source_runner_function(config, r => r.upgrade_run(config, action, beforeUpgradeAction)); - var upgradeFailures = report_action_summary(packageUpgrades, "upgraded"); - if (upgradeFailures != 0 && Environment.ExitCode == 0) - { - Environment.ExitCode = 1; + foreach (var result in results) + { + packageUpgrades.GetOrAdd(result.Key, result.Value); + } } + finally + { + var upgradeFailures = report_action_summary(packageUpgrades, "upgraded"); + if (upgradeFailures != 0 && Environment.ExitCode == 0) + { + Environment.ExitCode = 1; + } - randomly_notify_about_pro_business(config); + randomly_notify_about_pro_business(config); + } return packageUpgrades; } @@ -786,7 +820,7 @@ public void uninstall_noop(ChocolateyConfiguration config) randomly_notify_about_pro_business(config); } - public ConcurrentDictionary uninstall_run(ChocolateyConfiguration config) + public virtual ConcurrentDictionary uninstall_run(ChocolateyConfiguration config) { this.Log().Info(@"Uninstalling the following packages:"); this.Log().Info(ChocolateyLoggers.Important, @"{0}".format_with(config.PackageNames)); @@ -796,30 +830,41 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi throw new ApplicationException("A packages.config file is only used with installs."); } - Action action = null; - if (config.SourceType == SourceType.normal) + var packageUninstalls = new ConcurrentDictionary(); + + try { - action = (packageResult) => handle_package_uninstall(packageResult, config); - } + Action action = null; + if (config.SourceType == SourceType.normal) + { + action = (packageResult) => handle_package_uninstall(packageResult, config); + } - var environmentBefore = get_environment_before(config); - var beforeUninstallAction = new Action(packageResult => before_package_modify(packageResult, config)); - var packageUninstalls = perform_source_runner_function(config, r => r.uninstall_run(config, action, beforeUninstallAction)); + var environmentBefore = get_environment_before(config); + var beforeUninstallAction = new Action(packageResult => before_package_modify(packageResult, config)); + var results = perform_source_runner_function(config, r => r.uninstall_run(config, action, beforeUninstallAction)); - // not handled in the uninstall handler - IEnumerable environmentChanges; - IEnumerable environmentRemovals; - get_log_environment_changes(config, environmentBefore, out environmentChanges, out environmentRemovals); + foreach (var result in results) + { + packageUninstalls.GetOrAdd(result.Key, result.Value); + } - var uninstallFailures = report_action_summary(packageUninstalls, "uninstalled"); - if (uninstallFailures != 0 && Environment.ExitCode == 0) - { - Environment.ExitCode = 1; + // not handled in the uninstall handler + IEnumerable environmentChanges; + IEnumerable environmentRemovals; + get_log_environment_changes(config, environmentBefore, out environmentChanges, out environmentRemovals); } - - if (uninstallFailures != 0) + finally { - this.Log().Warn(@" + var uninstallFailures = report_action_summary(packageUninstalls, "uninstalled"); + if (uninstallFailures != 0 && Environment.ExitCode == 0) + { + Environment.ExitCode = 1; + } + + if (uninstallFailures != 0) + { + this.Log().Warn(@" If a package uninstall is failing and/or you've already uninstalled the software outside of Chocolatey, you can attempt to run the command with `-n` to skip running a chocolateyUninstall script, additionally @@ -835,9 +880,10 @@ If a package is failing because it is a dependency of another package removed. Then delete the folder for the package. This option should only be used as a last resort. "); - } + } - randomly_notify_about_pro_business(config); + randomly_notify_about_pro_business(config); + } return packageUninstalls; } @@ -913,7 +959,7 @@ The recent package changes indicate a reboot is necessary. return failures; } - public void handle_package_uninstall(PackageResult packageResult, ChocolateyConfiguration config) + public virtual void handle_package_uninstall(PackageResult packageResult, ChocolateyConfiguration config) { if (!_fileSystem.directory_exists(packageResult.InstallLocation)) { @@ -946,6 +992,18 @@ public void handle_package_uninstall(PackageResult packageResult, ChocolateyConf handle_unsuccessful_operation(config, packageResult, movePackageToFailureLocation: false, attemptRollback: false); } + if(_rebootExitCodes.Contains(packageResult.ExitCode)) + { + if(config.Features.ExitOnRebootDetected) + { + Environment.ExitCode = ApplicationParameters.ExitCodes.ErrorInstallSuspend; + this.Log().Warn(ChocolateyLoggers.Important, @"Chocolatey has detected a pending reboot after uninstalling +package '{0}' - stopping further execution".format_with(packageResult.Name)); + + throw new ApplicationException("Reboot required before continuing. Reboot and run same command again."); + } + } + if (!packageResult.Success) { // throw an error so that NuGet Service doesn't attempt to continue with package removal @@ -1232,7 +1290,7 @@ private void remove_rollback_if_exists(PackageResult packageResult) _nugetService.remove_rollback_directory_if_exists(packageResult.Name); } - public void set_pending(PackageResult packageResult, ChocolateyConfiguration config) + public virtual void set_pending(PackageResult packageResult, ChocolateyConfiguration config) { var packageDirectory = packageResult.InstallLocation; if (string.IsNullOrWhiteSpace(packageDirectory)) return; @@ -1256,7 +1314,7 @@ public void set_pending(PackageResult packageResult, ChocolateyConfiguration con } } - public void remove_pending(PackageResult packageResult, ChocolateyConfiguration config) + public virtual void remove_pending(PackageResult packageResult, ChocolateyConfiguration config) { var packageDirectory = packageResult.InstallLocation; if (string.IsNullOrWhiteSpace(packageDirectory)) return; diff --git a/src/chocolatey/infrastructure.app/services/IPendingRebootService.cs b/src/chocolatey/infrastructure.app/services/IPendingRebootService.cs new file mode 100644 index 0000000000..f5cb4eeab2 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/IPendingRebootService.cs @@ -0,0 +1,31 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.services +{ + using configuration; + + /// + /// Test to see if there are any known situations that require + /// a System reboot. + /// + /// The current Chocolatey Configuration + /// true if reboot is required; otherwise false. + public interface IPendingRebootService + { + bool is_pending_reboot(ChocolateyConfiguration config); + } +} diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 5682a22540..63159fd59d 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -872,7 +872,7 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate return packageInstalls; } - public ConcurrentDictionary get_outdated(ChocolateyConfiguration config) + public virtual ConcurrentDictionary get_outdated(ChocolateyConfiguration config) { var packageManager = NugetCommon.GetPackageManager( config, diff --git a/src/chocolatey/infrastructure.app/services/PendingRebootService.cs b/src/chocolatey/infrastructure.app/services/PendingRebootService.cs new file mode 100644 index 0000000000..57f88bd1c9 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/PendingRebootService.cs @@ -0,0 +1,225 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.services +{ + using configuration; + using Microsoft.Win32; + using platforms; + + /// + /// Service to check for System level pending reboot request + /// + /// Based on code from https://github.com/puppetlabs/puppetlabs-reboot + public class PendingRebootService : IPendingRebootService + { + private readonly IRegistryService _registryService; + + public PendingRebootService(IRegistryService registryService) + { + _registryService = registryService; + } + + /// + /// Test to see if there are any known situations that require a system reboot. + /// + /// The current Chocolatey Configuration + /// true if reboot is required; otherwise false. + public bool is_pending_reboot(ChocolateyConfiguration config) + { + if (config.Information.PlatformType != PlatformType.Windows) + { + return false; + } + + this.Log().Debug(" Reboot Requirement Checks:"); + + // note this is short-circuited, if one trips, it won't continue the checks + return is_pending_computer_rename() || + is_pending_component_based_servicing() || + is_pending_windows_auto_update() || + is_pending_file_rename_operations() || + is_pending_package_installer() || + is_pending_package_installer_syswow64(); + } + + /// + /// Determines whether Windows is waiting on a reboot to rename the computer. + /// + /// + /// true if [is pending computer rename]; otherwise, false. + /// + private bool is_pending_computer_rename() + { + var path = "SYSTEM\\CurrentControlSet\\Control\\ComputerName\\{0}"; + var activeName = get_registry_key_string_value(path.format_with("ActiveComputerName"), "ComputerName"); + var pendingName = get_registry_key_string_value(path.format_with("ComputerName"), "ComputerName"); + + bool result = !string.IsNullOrWhiteSpace(activeName) && + !string.IsNullOrWhiteSpace(pendingName) && + activeName != pendingName; + + this.Log().Debug(" - Pending Computer Rename = {0}".format_with(result ? "Flagged" : "Checked")); + + return result; + } + + /// + /// Determines whether Windows is waiting on a reboot for component based servicing. + /// CBS (Component Based Servicing) is also known as trusted installer. This could also be where + /// pending reboots related to MSI installs are recorded as of newer versions of Windows. + /// + /// + /// true if [is pending component based servicing]; otherwise, false. + /// + /// + /// https://blogs.technet.microsoft.com/askperf/2008/04/23/understanding-component-based-servicing/ + /// + private bool is_pending_component_based_servicing() + { + if (!is_vista_sp1_or_later()) + { + this.Log().Trace("Not using Windows Vista SP1 or earlier, so no check for Component Based Servicing can be made."); + return false; + } + + var path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending"; + var key = _registryService.get_key(RegistryHive.LocalMachine, path); + var result = key != null; + + this.Log().Debug(" - Pending Component Based Servicing = {0}".format_with(result ? "Flagged" : "Checked")); + + return result; + } + + /// + /// Determines whether Windows Automatic Update is waiting on a reboot. + /// + /// + /// true if is pending windows automatic update; otherwise, false. + /// + private bool is_pending_windows_auto_update() + { + var path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired"; + var key = _registryService.get_key(RegistryHive.LocalMachine, path); + var result = key != null; + + this.Log().Debug(" - Pending Windows Auto Update = {0}".format_with(result ? "Flagged" : "Checked")); + + return result; + } + + /// + /// Determines whether there is a pending file rename operation waiting on reboot for lock releases. + /// + /// + /// true if is pending file rename operations; otherwise, false. + /// + private bool is_pending_file_rename_operations() + { + var path = "SYSTEM\\CurrentControlSet\\Control\\Session Manager"; + var value = get_registry_key_value(path, "PendingFileRenameOperations"); + + var result = false; + + if (value != null && value is string[]) + { + result = (value as string[]).Length != 0; + } + + this.Log().Debug(" - Pending File Rename Operations = {0}".format_with(result ? "Flagged" : "Checked")); + + return result; + } + + /// + /// Determines whether Windows has a pending software install waiting for reboot (MSIs typically). + /// + /// + /// true if [is pending package installer]; otherwise, false. + /// + /// + /// https://support.microsoft.com/kb/832475 + /// 0x00000000 (0) No pending restart. + /// + private bool is_pending_package_installer() + { + var path = "SOFTWARE\\Microsoft\\Updates"; + var value = get_registry_key_string_value(path, "UpdateExeVolatile"); + + var result = !string.IsNullOrWhiteSpace(value) && value != "0"; + + this.Log().Debug(" - Pending Windows Package Installer = {0}".format_with(result ? "Flagged" : "Checked")); + + return result; + } + + /// + /// Determines whether Windows has a pending 32-bit software install waiting for reboot (MSIs typically). + /// + /// + /// true if [is pending package installer syswow64]; otherwise, false. + /// + /// + /// https://support.microsoft.com/kb/832475 + /// 0x00000000 (0) No pending restart. + /// + private bool is_pending_package_installer_syswow64() + { + var path = "SOFTWARE\\Wow6432Node\\Microsoft\\Updates"; + var value = get_registry_key_string_value(path, "UpdateExeVolatile"); + + var result = !string.IsNullOrWhiteSpace(value) && value != "0"; + + this.Log().Debug(" - Pending Windows Package Installer SysWow64 = {0}".format_with(result ? "Flagged" : "Checked")); + + return result; + } + + private string get_registry_key_string_value(string path, string value) + { + var key = _registryService.get_key(RegistryHive.LocalMachine, path); + + if (key == null) + { + return string.Empty; + } + + return key.GetValue(value, string.Empty).to_string(); + } + + private object get_registry_key_value(string path, string value) + { + var key = _registryService.get_key(RegistryHive.LocalMachine, path); + + if (key == null) + { + return null; + } + + return key.GetValue(value, null); + } + + private bool is_vista_sp1_or_later() + { + var versionNumber = Platform.get_version(); + + this.Log().Trace(" Operating System Version Number: {0}".format_with(versionNumber)); + + return versionNumber.Build >= 6001; + } + } +} diff --git a/src/chocolatey/infrastructure.app/validations/GlobalConfigurationValidation.cs b/src/chocolatey/infrastructure.app/validations/GlobalConfigurationValidation.cs new file mode 100644 index 0000000000..6d5f4393d4 --- /dev/null +++ b/src/chocolatey/infrastructure.app/validations/GlobalConfigurationValidation.cs @@ -0,0 +1,86 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.validations +{ + using System.Collections.Generic; + using configuration; + using infrastructure.validations; + + /// + /// Performs top level validation checks against the current + /// Chocolatey Configuration object to ensure that everything is + /// set up properly. Any errors that are returned will halt + /// the current operation. + /// + public class GlobalConfigurationValidation : IValidation + { + public ICollection validate(ChocolateyConfiguration config) + { + this.Log().Debug("Global Configuration Validation Checks:"); + var validationResults = new List(); + + check_usage_of_package_exit_code(config, validationResults); + + if (validationResults.Count == 0) + { + validationResults.Add(new ValidationResult + { + Message = "Global Chocolatey Configuration is valid.", + Status = ValidationStatus.Success, + ExitCode = 0 + }); + } + + return validationResults; + } + + private void check_usage_of_package_exit_code(ChocolateyConfiguration config, ICollection validationResults) + { + var validationStatusResult = ValidationStatus.Checked; + // In order for a Chocolatey execution to correctly halt + // on a detected reboot, it is necessary for package exit + // codes to be used. Otherwise, the codes (1641, and 3010) + // can't be detected. + if (config.Features.ExitOnRebootDetected && + !config.Features.UsePackageExitCodes) + { + var validationResult = new ValidationResult + { + Message = @"When attempting to halt execution of a Chocolatey command based on a + request for a system reboot, it is necessary to have the + usePackageExitCodes feature enabled. Use the following command: + choco feature enable -name={0} + to enable this feature (exit code 1). +".format_with(ApplicationParameters.Features.UsePackageExitCodes), + Status = ValidationStatus.Error, + ExitCode = 1 + }; + + var commandsToErrorOn = new List {"install", "uninstall", "upgrade"}; + if (!commandsToErrorOn.Contains(config.CommandName.ToLowerInvariant())) + { + validationResult.Status = ValidationStatus.Warning; + } + + validationStatusResult = validationResult.Status; + validationResults.Add(validationResult); + } + + this.Log().Debug(" - Package Exit Code / Exit On Reboot = {0}".format_with(validationStatusResult.to_string())); + } + } +} diff --git a/src/chocolatey/infrastructure.app/validations/SystemStateValidation.cs b/src/chocolatey/infrastructure.app/validations/SystemStateValidation.cs new file mode 100644 index 0000000000..5a8bf1c37e --- /dev/null +++ b/src/chocolatey/infrastructure.app/validations/SystemStateValidation.cs @@ -0,0 +1,108 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.validations +{ + using System; + using System.Collections.Generic; + using configuration; + using infrastructure.validations; + using services; + + /// + /// Performs validation against the current System State. This + /// includes things like pending reboot requirement. Any errors + /// that are returned will halt the current operation. + /// + public class SystemStateValidation : IValidation + { + private readonly IPendingRebootService _pendingRebootService; + + public SystemStateValidation(IPendingRebootService pendingRebootService) + { + _pendingRebootService = pendingRebootService; + } + + public ICollection validate(ChocolateyConfiguration config) + { + this.Log().Debug("System State Validation Checks:"); + var validationResults = new List(); + + check_system_pending_reboot(config, validationResults); + + if (validationResults.Count == 0) + { + validationResults.Add(new ValidationResult + { + Message = "System State is valid", + Status = ValidationStatus.Success, + ExitCode = 0 + }); + } + + return validationResults; + } + + private void check_system_pending_reboot(ChocolateyConfiguration config, ICollection validationResults) + { + var result = _pendingRebootService.is_pending_reboot(config); + + if (result) + { + var commandsToErrorOn = new List {"install", "uninstall", "upgrade"}; + + if (!commandsToErrorOn.Contains(config.CommandName.ToLowerInvariant())) + { + validationResults.Add(new ValidationResult + { + Message = @"A pending system reboot request has been detected, however, this is + being ignored due to the current command being used '{0}'. + It is recommended that you reboot at your earliest convenience. +".format_with(config.CommandName), + Status = ValidationStatus.Warning, + ExitCode = 0 + }); + } + else if (!config.Features.ExitOnRebootDetected) + { + validationResults.Add(new ValidationResult + { + Message = @"A pending system reboot request has been detected, however, this is + being ignored due to the current Chocolatey configuration. If you + want to halt when this occurs, then either set the global feature + using: + choco feature enable -name={0} + or, pass the option --exit-when-reboot-detected. +".format_with(ApplicationParameters.Features.ExitOnRebootDetected), + Status = ValidationStatus.Warning, + ExitCode = 0 + }); + } + else + { + Environment.ExitCode = ApplicationParameters.ExitCodes.ErrorFailNoActionReboot; + + validationResults.Add(new ValidationResult + { + Message = "A pending system reboot has been detected (exit code {0}).".format_with(ApplicationParameters.ExitCodes.ErrorFailNoActionReboot), + Status = ValidationStatus.Error, + ExitCode = ApplicationParameters.ExitCodes.ErrorFailNoActionReboot + }); + } + } + } + } +} diff --git a/src/chocolatey/infrastructure/validations/IValidation.cs b/src/chocolatey/infrastructure/validations/IValidation.cs new file mode 100644 index 0000000000..130ddfad7d --- /dev/null +++ b/src/chocolatey/infrastructure/validations/IValidation.cs @@ -0,0 +1,34 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.validations +{ + using System.Collections.Generic; + using app.configuration; + + /// + /// Interface for all validations + /// + public interface IValidation + { + /// + /// Performs the required validation logic + /// + /// The current Chocolatey Configuration + /// The validation results + ICollection validate(ChocolateyConfiguration config); + } +} diff --git a/src/chocolatey/infrastructure/validations/ValidationResult.cs b/src/chocolatey/infrastructure/validations/ValidationResult.cs new file mode 100644 index 0000000000..f56653f284 --- /dev/null +++ b/src/chocolatey/infrastructure/validations/ValidationResult.cs @@ -0,0 +1,30 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.validations +{ + /// + /// Outcome of validation + /// + public sealed class ValidationResult + { + public ValidationStatus Status { get; set; } + + public string Message { get; set; } + + public int ExitCode { get; set; } + } +} diff --git a/src/chocolatey/infrastructure/validations/ValidationStatus.cs b/src/chocolatey/infrastructure/validations/ValidationStatus.cs new file mode 100644 index 0000000000..5802a77f4f --- /dev/null +++ b/src/chocolatey/infrastructure/validations/ValidationStatus.cs @@ -0,0 +1,27 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.validations +{ + public enum ValidationStatus + { + Unknown, + Checked, + Success, + Error, + Warning + } +}