Skip to content

Commit

Permalink
(chocolateyGH-1038) Stop execution if pending reboot
Browse files Browse the repository at this point in the history
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
gep13 committed Mar 9, 2019
1 parent 2025f89 commit 36af792
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/chocolatey/chocolatey.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,13 @@
<Compile Include="infrastructure.app\domain\RegistryValueExtensions.cs" />
<Compile Include="infrastructure.app\domain\RegistryValueKindType.cs" />
<Compile Include="infrastructure.app\events\HandlePackageResultCompletedMessage.cs" />
<Compile Include="infrastructure.app\services\IPendingRebootService.cs" />
<Compile Include="infrastructure.app\services\PendingRebootService.cs" />
<Compile Include="infrastructure.app\templates\ChocolateyTodoTemplate.cs" />
<Compile Include="infrastructure.app\utility\ArgumentsUtility.cs" />
<Compile Include="infrastructure.app\utility\HashCode.cs" />
<Compile Include="infrastructure.app\utility\PackageUtility.cs" />
<Compile Include="infrastructure.app\validations\SystemStateValidation.cs" />
<Compile Include="infrastructure\filesystem\FileSystem.cs" />
<Compile Include="infrastructure\logging\AggregateLog.cs" />
<Compile Include="infrastructure\logging\LogLevelType.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public void RegisterComponents(Container container)
container.Register<IChocolateyPackageInformationService, ChocolateyPackageInformationService>(Lifestyle.Singleton);
container.Register<IShimGenerationService, ShimGenerationService>(Lifestyle.Singleton);
container.Register<IRegistryService, RegistryService>(Lifestyle.Singleton);
container.Register<IPendingRebootService, PendingRebootService>(Lifestyle.Singleton);
container.Register<IFilesService, FilesService>(Lifestyle.Singleton);
container.Register<IConfigTransformService, ConfigTransformService>(Lifestyle.Singleton);
container.Register<IHashProvider>(() => new CryptoHashProvider(container.GetInstance<IFileSystem>()), Lifestyle.Singleton);
Expand Down Expand Up @@ -135,6 +136,7 @@ public void RegisterComponents(Container container)
var list = new List<IValidation>
{
new GlobalConfigurationValidation(),
new SystemStateValidation(container.GetInstance<IPendingRebootService>())
};
return list.AsReadOnly();
Expand Down
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 src/chocolatey/infrastructure.app/services/PendingRebootService.cs
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 src/chocolatey/infrastructure.app/validations/SystemStateValidation.cs
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
});
}
}
}
}
}

0 comments on commit 36af792

Please sign in to comment.