forked from chocolatey/choco
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(chocolateyGH-1038) Stop execution if pending reboot
When executing Chocolatey commands, it is well known that a pending reboot requirement can prevent a succesful operation. This adds the ability to enable detection of a pending reboot (implemented as a global validation), which will then halt execution of the command. This addition doesn't require that the usePackageExitCodes feature be enabled, but rather, it inspecting the current state of the system, via the registry to determine if a reboot is required. These checks will only be performed when running on the Windows Operating System.
- Loading branch information
Showing
5 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
src/chocolatey/infrastructure.app/services/IPendingRebootService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
|
||
/// <summary> | ||
/// Test to see if there are any known situations that require | ||
/// a System reboot. | ||
/// </summary> | ||
/// <param name="config">The current Chocolatey Configuration</param> | ||
/// <returns><c>true</c> if reboot is required; otherwise <c>false</c>.</returns> | ||
public interface IPendingRebootService | ||
{ | ||
bool is_pending_reboot(ChocolateyConfiguration config); | ||
} | ||
} |
180 changes: 180 additions & 0 deletions
180
src/chocolatey/infrastructure.app/services/PendingRebootService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
// 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; | ||
|
||
/// <summary> | ||
/// Service to check for System level pending reboot request | ||
/// </summary> | ||
/// <remarks>Based on code from https://github.com/puppetlabs/puppetlabs-reboot</remarks> | ||
public class PendingRebootService : IPendingRebootService | ||
{ | ||
private readonly IRegistryService _registryService; | ||
|
||
public PendingRebootService(IRegistryService registryService) | ||
{ | ||
_registryService = registryService; | ||
} | ||
|
||
/// <summary> | ||
/// Test to see if there are any known situations that require | ||
/// a System reboot. | ||
/// </summary> | ||
/// <param name="config">The current Chocolatey Configuration</param> | ||
/// <returns><c>true</c> if reboot is required; otherwise <c>false</c>.</returns> | ||
public bool is_pending_reboot(ChocolateyConfiguration config) | ||
{ | ||
if (config.Information.PlatformType != PlatformType.Windows) | ||
{ | ||
return false; | ||
} | ||
|
||
this.Log().Debug("Performing reboot requirement 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(); | ||
} | ||
|
||
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("PendingComputerRename: {0}".format_with(result)); | ||
|
||
return result; | ||
} | ||
|
||
private bool is_pending_component_based_servicing() | ||
{ | ||
if (!is_vista_sp1_or_later()) | ||
{ | ||
this.Log().Debug("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("PendingComponentBasedServicing: {0}".format_with(result)); | ||
|
||
return result; | ||
} | ||
|
||
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("PendingWindowsAutoUpdate: {0}".format_with(result)); | ||
|
||
return result; | ||
} | ||
|
||
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("PendingFileRenameOperations: {0}".format_with(result)); | ||
|
||
return result; | ||
} | ||
|
||
private bool is_pending_package_installer() | ||
{ | ||
// http://support.microsoft.com/kb/832475 | ||
// 0x00000000 (0) No pending restart. | ||
var path = "SOFTWARE\\Microsoft\\Updates"; | ||
var value = get_registry_key_string_value(path, "UpdateExeVolatile"); | ||
|
||
var result = !string.IsNullOrWhiteSpace(value) && value != "0"; | ||
|
||
this.Log().Debug("PendingPackageInstaller: {0}".format_with(result)); | ||
|
||
return result; | ||
} | ||
|
||
private bool is_pending_package_installer_syswow64() | ||
{ | ||
// http://support.microsoft.com/kb/832475 | ||
// 0x00000000 (0) No pending restart. | ||
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("PendingPackageInstallerSysWow64: {0}".format_with(result)); | ||
|
||
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().Debug("Operating System Version Number: {0}".format_with(versionNumber)); | ||
|
||
return versionNumber.Build >= 6001; | ||
} | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
src/chocolatey/infrastructure.app/validations/SystemStateValidation.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// 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; | ||
|
||
/// <summary> | ||
/// Performs validation against the current System State. This | ||
/// includes things like pending reboot requirement. Any errors | ||
/// that are returned will halt the current operation. | ||
/// </summary> | ||
public class SystemStateValidation : IValidation | ||
{ | ||
private readonly IPendingRebootService _pendingRebootService; | ||
|
||
public SystemStateValidation(IPendingRebootService pendingRebootService) | ||
{ | ||
_pendingRebootService = pendingRebootService; | ||
} | ||
|
||
public ICollection<ValidationResult> validate(ChocolateyConfiguration config) | ||
{ | ||
var validationResults = new List<ValidationResult>(); | ||
|
||
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<ValidationResult> validationResults) | ||
{ | ||
var result = _pendingRebootService.is_pending_reboot(config); | ||
|
||
if (result) | ||
{ | ||
var commandsToErrorOn = new List<string> {"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 | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} |