Skip to content

Commit bde8a9d

Browse files
committed
(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.
1 parent 9c0da09 commit bde8a9d

File tree

5 files changed

+323
-0
lines changed

5 files changed

+323
-0
lines changed

src/chocolatey/chocolatey.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,13 @@
121121
<Compile Include="infrastructure.app\domain\RegistryValueExtensions.cs" />
122122
<Compile Include="infrastructure.app\domain\RegistryValueKindType.cs" />
123123
<Compile Include="infrastructure.app\events\HandlePackageResultCompletedMessage.cs" />
124+
<Compile Include="infrastructure.app\services\IPendingRebootService.cs" />
125+
<Compile Include="infrastructure.app\services\PendingRebootService.cs" />
124126
<Compile Include="infrastructure.app\templates\ChocolateyTodoTemplate.cs" />
125127
<Compile Include="infrastructure.app\utility\ArgumentsUtility.cs" />
126128
<Compile Include="infrastructure.app\utility\HashCode.cs" />
127129
<Compile Include="infrastructure.app\utility\PackageUtility.cs" />
130+
<Compile Include="infrastructure.app\validations\SystemStateValidation.cs" />
128131
<Compile Include="infrastructure\filesystem\FileSystem.cs" />
129132
<Compile Include="infrastructure\logging\AggregateLog.cs" />
130133
<Compile Include="infrastructure\logging\LogLevelType.cs" />

src/chocolatey/infrastructure.app/registration/ContainerBinding.cs

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public void RegisterComponents(Container container)
6363
container.Register<IChocolateyPackageInformationService, ChocolateyPackageInformationService>(Lifestyle.Singleton);
6464
container.Register<IShimGenerationService, ShimGenerationService>(Lifestyle.Singleton);
6565
container.Register<IRegistryService, RegistryService>(Lifestyle.Singleton);
66+
container.Register<IPendingRebootService, PendingRebootService>(Lifestyle.Singleton);
6667
container.Register<IFilesService, FilesService>(Lifestyle.Singleton);
6768
container.Register<IConfigTransformService, ConfigTransformService>(Lifestyle.Singleton);
6869
container.Register<IHashProvider>(() => new CryptoHashProvider(container.GetInstance<IFileSystem>()), Lifestyle.Singleton);
@@ -135,6 +136,7 @@ public void RegisterComponents(Container container)
135136
var list = new List<IValidation>
136137
{
137138
new GlobalConfigurationValidation(),
139+
new SystemStateValidation(container.GetInstance<IPendingRebootService>())
138140
};
139141

140142
return list.AsReadOnly();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright © 2017 - 2018 Chocolatey Software, Inc
2+
// Copyright © 2011 - 2017 RealDimensions Software, LLC
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
//
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
namespace chocolatey.infrastructure.app.services
18+
{
19+
using configuration;
20+
21+
/// <summary>
22+
/// Test to see if there are any known situations that require
23+
/// a System reboot.
24+
/// </summary>
25+
/// <param name="config">The current Chocolatey Configuration</param>
26+
/// <returns><c>true</c> if reboot is required; otherwise <c>false</c>.</returns>
27+
public interface IPendingRebootService
28+
{
29+
bool is_pending_reboot(ChocolateyConfiguration config);
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright © 2017 - 2018 Chocolatey Software, Inc
2+
// Copyright © 2011 - 2017 RealDimensions Software, LLC
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
//
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
namespace chocolatey.infrastructure.app.services
18+
{
19+
using configuration;
20+
using Microsoft.Win32;
21+
using platforms;
22+
23+
/// <summary>
24+
/// Service to check for System level pending reboot request
25+
/// </summary>
26+
/// <remarks>Based on code from https://github.com/puppetlabs/puppetlabs-reboot</remarks>
27+
public class PendingRebootService : IPendingRebootService
28+
{
29+
private readonly IRegistryService _registryService;
30+
31+
public PendingRebootService(IRegistryService registryService)
32+
{
33+
_registryService = registryService;
34+
}
35+
36+
/// <summary>
37+
/// Test to see if there are any known situations that require
38+
/// a System reboot.
39+
/// </summary>
40+
/// <param name="config">The current Chocolatey Configuration</param>
41+
/// <returns><c>true</c> if reboot is required; otherwise <c>false</c>.</returns>
42+
public bool is_pending_reboot(ChocolateyConfiguration config)
43+
{
44+
if (config.Information.PlatformType != PlatformType.Windows)
45+
{
46+
return false;
47+
}
48+
49+
this.Log().Debug("Performing reboot requirement checks...");
50+
51+
return is_pending_computer_rename() ||
52+
is_pending_component_based_servicing() ||
53+
is_pending_windows_auto_update() ||
54+
is_pending_file_rename_operations() ||
55+
is_pending_package_installer() ||
56+
is_pending_package_installer_syswow64();
57+
}
58+
59+
private bool is_pending_computer_rename()
60+
{
61+
var path = "SYSTEM\\CurrentControlSet\\Control\\ComputerName\\{0}";
62+
var activeName = get_registry_key_string_value(path.format_with("ActiveComputerName"), "ComputerName");
63+
var pendingName = get_registry_key_string_value(path.format_with("ComputerName"), "ComputerName");
64+
65+
bool result = !string.IsNullOrWhiteSpace(activeName) &&
66+
!string.IsNullOrWhiteSpace(pendingName) &&
67+
activeName != pendingName;
68+
69+
this.Log().Debug("PendingComputerRename: {0}".format_with(result));
70+
71+
return result;
72+
}
73+
74+
private bool is_pending_component_based_servicing()
75+
{
76+
if (!is_vista_sp1_or_later())
77+
{
78+
this.Log().Debug("Not using Windows Vista SP1 or earlier, so no check for Component Based Servicing can be made.");
79+
return false;
80+
}
81+
82+
var path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending";
83+
var key = _registryService.get_key(RegistryHive.LocalMachine, path);
84+
var result = key != null;
85+
86+
this.Log().Debug("PendingComponentBasedServicing: {0}".format_with(result));
87+
88+
return result;
89+
}
90+
91+
private bool is_pending_windows_auto_update()
92+
{
93+
var path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired";
94+
var key = _registryService.get_key(RegistryHive.LocalMachine, path);
95+
var result = key != null;
96+
97+
this.Log().Debug("PendingWindowsAutoUpdate: {0}".format_with(result));
98+
99+
return result;
100+
}
101+
102+
private bool is_pending_file_rename_operations()
103+
{
104+
var path = "SYSTEM\\CurrentControlSet\\Control\\Session Manager";
105+
var value = get_registry_key_value(path, "PendingFileRenameOperations");
106+
107+
var result = false;
108+
109+
if (value != null && value is string[])
110+
{
111+
result = (value as string[]).Length != 0;
112+
}
113+
114+
this.Log().Debug("PendingFileRenameOperations: {0}".format_with(result));
115+
116+
return result;
117+
}
118+
119+
private bool is_pending_package_installer()
120+
{
121+
// http://support.microsoft.com/kb/832475
122+
// 0x00000000 (0) No pending restart.
123+
var path = "SOFTWARE\\Microsoft\\Updates";
124+
var value = get_registry_key_string_value(path, "UpdateExeVolatile");
125+
126+
var result = !string.IsNullOrWhiteSpace(value) && value != "0";
127+
128+
this.Log().Debug("PendingPackageInstaller: {0}".format_with(result));
129+
130+
return result;
131+
}
132+
133+
private bool is_pending_package_installer_syswow64()
134+
{
135+
// http://support.microsoft.com/kb/832475
136+
// 0x00000000 (0) No pending restart.
137+
var path = "SOFTWARE\\Wow6432Node\\Microsoft\\Updates";
138+
var value = get_registry_key_string_value(path, "UpdateExeVolatile");
139+
140+
var result = !string.IsNullOrWhiteSpace(value) && value != "0";
141+
142+
this.Log().Debug("PendingPackageInstallerSysWow64: {0}".format_with(result));
143+
144+
return result;
145+
}
146+
147+
private string get_registry_key_string_value(string path, string value)
148+
{
149+
var key = _registryService.get_key(RegistryHive.LocalMachine, path);
150+
151+
if (key == null)
152+
{
153+
return string.Empty;
154+
}
155+
156+
return key.GetValue(value, string.Empty).to_string();
157+
}
158+
159+
private object get_registry_key_value(string path, string value)
160+
{
161+
var key = _registryService.get_key(RegistryHive.LocalMachine, path);
162+
163+
if (key == null)
164+
{
165+
return null;
166+
}
167+
168+
return key.GetValue(value, null);
169+
}
170+
171+
private bool is_vista_sp1_or_later()
172+
{
173+
var versionNumber = Platform.get_version();
174+
175+
this.Log().Debug("Operating System Version Number: {0}".format_with(versionNumber));
176+
177+
return versionNumber.Build >= 6001;
178+
}
179+
}
180+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright © 2017 - 2018 Chocolatey Software, Inc
2+
// Copyright © 2011 - 2017 RealDimensions Software, LLC
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
//
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
namespace chocolatey.infrastructure.app.validations
18+
{
19+
using System;
20+
using System.Collections.Generic;
21+
using configuration;
22+
using infrastructure.validations;
23+
using services;
24+
25+
/// <summary>
26+
/// Performs validation against the current System State. This
27+
/// includes things like pending reboot requirement. Any errors
28+
/// that are returned will halt the current operation.
29+
/// </summary>
30+
public class SystemStateValidation : IValidation
31+
{
32+
private readonly IPendingRebootService _pendingRebootService;
33+
34+
public SystemStateValidation(IPendingRebootService pendingRebootService)
35+
{
36+
_pendingRebootService = pendingRebootService;
37+
}
38+
39+
public ICollection<ValidationResult> validate(ChocolateyConfiguration config)
40+
{
41+
var validationResults = new List<ValidationResult>();
42+
43+
check_system_pending_reboot(config, validationResults);
44+
45+
if (validationResults.Count == 0)
46+
{
47+
validationResults.Add(new ValidationResult
48+
{
49+
Message = "System State is valid",
50+
Status = ValidationStatus.Success,
51+
ExitCode = 0
52+
});
53+
}
54+
55+
return validationResults;
56+
}
57+
58+
private void check_system_pending_reboot(ChocolateyConfiguration config, ICollection<ValidationResult> validationResults)
59+
{
60+
var result = _pendingRebootService.is_pending_reboot(config);
61+
62+
if (result)
63+
{
64+
var commandsToErrorOn = new List<string> {"install", "uninstall", "upgrade"};
65+
66+
if (!commandsToErrorOn.Contains(config.CommandName.ToLowerInvariant()))
67+
{
68+
validationResults.Add(new ValidationResult
69+
{
70+
Message = @"A pending system reboot request has been detected, however, this is
71+
being ignored due to the current command being used '{0}'.
72+
It is recommended that you reboot at your earliest convenience.
73+
".format_with(config.CommandName),
74+
Status = ValidationStatus.Warning,
75+
ExitCode = 0
76+
});
77+
}
78+
else if (!config.Features.ExitOnRebootDetected)
79+
{
80+
validationResults.Add(new ValidationResult
81+
{
82+
Message = @"A pending system reboot request has been detected, however, this is
83+
being ignored due to the current Chocolatey configuration. If you
84+
want to halt when this occurs, then either set the global feature
85+
using:
86+
choco feature enable -name={0}
87+
or, pass the option --exit-when-reboot-detected.
88+
".format_with(ApplicationParameters.Features.ExitOnRebootDetected),
89+
Status = ValidationStatus.Warning,
90+
ExitCode = 0
91+
});
92+
}
93+
else
94+
{
95+
Environment.ExitCode = ApplicationParameters.ExitCodes.ErrorFailNoActionReboot;
96+
97+
validationResults.Add(new ValidationResult
98+
{
99+
Message = "A pending system reboot has been detected (exit code {0}).".format_with(ApplicationParameters.ExitCodes.ErrorFailNoActionReboot),
100+
Status = ValidationStatus.Error,
101+
ExitCode = ApplicationParameters.ExitCodes.ErrorFailNoActionReboot
102+
});
103+
}
104+
}
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)