Set-SCCMReboot: Do reboots using the SCCM client

#region Function Set-SCCMReboot
Function Set-SCCMReboot {
<#
.SYNOPSIS
	Makes SCCM Client initiate, cancel, modify a pending SCCM reboot.
.DESCRIPTION
	Makes SCCM Client initiate, cancel, modify a pending SCCM reboot.
	It takes about 15-300 seconds before the SCCM GUI reacts to the changes (Creating a mandatory reboot takes the longest)
	Cancelling a reboot is also not announced, the green Restart Icon in the system tool tray just disappears.
	
	NOTE: Once a reboot is initiated, SCCM will not initiate/allow additional Software installations
	and gracefully handles the reboot process, user logged in or not.
.PARAMETER Action
	Valid Actions are:
		'Check'			Return $true if a SCCM reboot is set. Also writes the RebootData registry values in the PSADT log
		'Reboot'		Triggers a reboot by SCCM client. Default is non-mandatory reboot
		'CancelReboot'	Cancels an impending reboot by SCCM client
		'ChgToNonMandatoryReboot' 	Changes the Reboot GUI to Non-Mandatory, where the client CAN defer the reboot
		'ChgToMandatoryReboot'		Changes the Reboot GUI to Mandatory, where the client CANNOT defer the reboot    **** Not Working at this time ****
		'ResetRebootCountdownTimer'	Resets the countdown clock to give user more time before.
.PARAMETER HardReboot
	$true makes the reboot or shutdown a TRUE reboot (a-la Windows 7). 	(This is an educated guess)
	Otherwise in Windows 10, the default $false just hibernates (This is why Windows 10 is 'Fast' on boot up.)
	Default is False.
	If MandatoryReboot=$true we change HardReboot to $true to match SCCM Console behaviour
	NOTE: this does seem to be recognized in both registry and WMI checks
.PARAMETER MandatoryReboot
	$True makes the reboot non-deferrable. 
	The user cannot stop the reboot or delay it. 
	Default is False.

.PARAMETER ContinueOnError
    Continue if an error is encountered. Default is: $true. (Currently not used)
.EXAMPLE
	Set-SCCMReboot -Action Check
.EXAMPLE
	Set-SCCMReboot -Action Reboot 
	Triggers a reboot with default parameters (Non-mandatory)
	Currently seems to take a good 5 minutes for the GUI to decide to process the changes.
.EXAMPLE
	Set-SCCMReboot -Action Reboot -MandatoryReboot $true
	Causes a mandatory reboot to occur in 8 hours. Sometimes longer (like 3 Days?!?) on some computers (no explanation as to why)
	Can take upto 5 minutes for the GUI to decide to process the changes.
.EXAMPLE
	Set-SCCMReboot -Action CancelReboot
.EXAMPLE
	Set-SCCMReboot -Action chgToNonMandatoryReboot 
.EXAMPLE
	Set-SCCMReboot -Action ChgToMandatoryReboot     **** Not Working at this time ****
	(I Suspect it's because OverrideRebootWindowTime and/or RebootBy are not set by just using Set-SCCMReboot -Action Reboot)
.EXAMPLE
	Set-SCCMReboot -Action ResetRebootCountdownTimer
	
.NOTES
	Author: Denis St-Pierre (Ottawa, Canada)
	Only tested on Windows 10.
	-Based on https://www.reddit.com/r/SCCM/comments/bj7br8/sccm_reboot_decoded_how_to_make_a_pc_cancel_start/
	-shutdown /a (/a = abort) will not stop a pending SCCM reboot
	-Current limitations: Unable to set the amount of time before the reboot occurs.
	
	NOTE: In modern versions of windows, having an elevated process display something like a reboot message in the unelevated user context is cumbersome or problematic. 
	This function makes the SCCM client handles reboots so that we do not have to recreate the solution. 
	Fun fact: Shutdown.exe does not display a GUI until the last 10 minutes before the reboot.

	Set-SCCMReboot DEV TIP #1: To trigger a Mandatory reboot via the SCCM console, right-click on the target device and select client message -> Reboot
	Set-SCCMReboot DEV TIP #2: To Trigger a non-Mandatory reboot, create a bogus SCCM application that only exits with exitcode 1641 and launch it on target pc
	SCCM TIP: If an SCCM application exits with exitcode 1641, SCCM will re-launch that SCCM application again BY ITSELF and not run the detection script.
			  It seems only exitcode 0 and 3010 will make SCCM client run the detection script.
	
	It is suspected that the true method of initiating a reboot via SCCM is to edit the ROOT\ccm\RebootManagement:CCM_RebootData=@ WMI instance.
	By sheer luck while using WMI Explorer 2.0, I once saw it populated with the same values as the registry key in this script but forgot to take a printscreen.
#>
	[CmdletBinding()]
	param (
		[Parameter(Mandatory=$true,HelpMessage='Valid choices are: Check,Reboot,CancelReboot,ChgToNonMandatoryReboot,ChgToMandatoryReboot,ResetRebootCountdownTimer')]
		[ValidateSet('Check','Reboot','CancelReboot','ChgToNonMandatoryReboot','ChgToMandatoryReboot','ResetRebootCountdownTimer')]
		[string]$Action,
		[Parameter(Mandatory=$False,HelpMessage='In Windows 10, a shutdown using the GUI is actually a hibernation. If True, we get a true shutdown. Default is False.')]
		[ValidateNotNullorEmpty()]
		[Bool]$HardReboot=$false,
		[Parameter(Mandatory=$False,HelpMessage='If True, the reboot non-deferrable. Default is False.')]
		[ValidateNotNullorEmpty()]
		[Bool]$MandatoryReboot=$false,
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[boolean]$ContinueOnError = $true
	)
	Begin {
        ## Get the name of this function and write header
        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
    }
    Process {
		Try {
			## Make sure SCCM client is installed and running
			Write-Log -Message 'Check to see if SCCM Client service [ccmexec] is installed and running.' -Source ${CmdletName}
			If (Test-ServiceExists -Name 'ccmexec') {
				If ($(Get-Service -Name 'ccmexec' -ErrorAction 'SilentlyContinue').Status -ne 'Running') {
					Write-Log "SCCM Client Service [ccmexec] exists but it is not in a 'Running' state." -Source ${CmdletName} -Severity 3
					Throw "SCCM Client Service [ccmexec] exists but it is not in a 'Running' state."
				}
			} Else {
				Write-Log 'SCCM Client Service [ccmexec] does not exist. The SCCM Client may not be installed.' -Source ${CmdletName} -Severity 3
				Throw 'SCCM Client Service [ccmexec] does not exist. The SCCM Client may not be installed.'
			}
			
			
			[string]$SCCMRebootDataKey = 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData'
			Write-Log "performing [$Action].." -Source ${CmdletName}
			switch ($Action) {
				'Check' {
					$results = Get-RegistryKey -Key $SCCMRebootDataKey 
					If ($results) {
						write-log $($results | Out-String) -Source ${CmdletName} -Severity 4
					} Else {
						write-log "No SCCM Reboots pending (source: Registry)" -Source ${CmdletName} -Severity 5
					}
					##Detect pending SCCM reboot via WMI:
					Try {
						If ( $(Get-Service -Name ccmexec).status -eq 'Running' ) { #Make sure service is running or else we get access denied
							$results = Invoke-CimMethod -Namespace root/ccm/ClientSDK -ClassName CCM_ClientUtilities -MethodName DetermineIfRebootPending -ErrorAction SilentlyContinue
							##ISSUE: Invoke-CimMethod fails if the ccmexec service is being restarted but not in a way that will trigger try/catch. using SilentlyContinue for now
							##According to http://sms-hints-tricks.blogspot.com/2015/05/override-sccm-reboot-time.html
							##	The OverrideRebootWindowTime is the Epoch time for the Forced box that cannot be closed to appear.  
							##	Rebootby is the Epoch time that will cause the machine to restart.
							If ($results) {
								write-log $($results | out-string) -Source ${CmdletName} -Severity 4
							} Else {
								write-log "Unable to read WMI or No SCCM Reboots pending (source: WMI)" -Source ${CmdletName} -Severity 3
							}
						} Else {
							Write-Log "[ccmexec] is not running. Cannot check WMI without it." -Source ${CmdletName} -Severity 2
						}
					} Catch {
						write-log $(Resolve-Error) -Source ${CmdletName} #try to locate root cause...
						##service is too busy or not in a functional state. trying again in 4 seconds..
						Start-sleep -Seconds 4
						Try {
							$results = Invoke-CimMethod -Namespace root/ccm/ClientSDK -ClassName CCM_ClientUtilities -MethodName DetermineIfRebootPending -ErrorAction SilentlyContinue
							If ($results) {
								write-log $($results | Out-String) -Source ${CmdletName} -Severity 4
							} Else {
								write-log "Unable to read WMI or No SCCM Reboots pending (source: WMI try#2)" -Source ${CmdletName} -Severity 3
							}
						} Catch {
							write-log $(Resolve-Error) -Source ${CmdletName}
						}
					}
					break
				}
				'Reboot' {
					[Int64]$time = [DateTimeOffset]::Now.ToUnixTimeSeconds()
					Write-Log "UTC time [$time]" -Source ${CmdletName}
					If ($MandatoryReboot) {
						write-log "Reboot is mandatory" -Source ${CmdletName} -Severity 2
						##CAVEAT: RebootBy and OverrideRebootWindowTime *must* match for mandatory reboots to work
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'RebootBy' -Value $time -Type QWord -ContinueOnError $true
						##As per http://sms-hints-tricks.blogspot.com/2015/05/override-sccm-reboot-time.html
						##	The OverrideRebootWindowTime is the Epoch time for the Forced box that cannot be closed to appear.  
						##	Rebootby is the Epoch time that will cause the machine to restart.
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'OverrideRebootWindowTime' -Value $time -Type QWord -ContinueOnError $true
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'PreferredRebootWindowTypes' -Value @("3") -Type MultiString -ContinueOnError $true
						$HardReboot = $true
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'OverrideRebootWindow' -Value 1 -Type Dword -ContinueOnError $true
					} Else {
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'RebootBy' -Value 0 -Type QWord -ContinueOnError $true
						#As per http://sms-hints-tricks.blogspot.com/2015/05/override-sccm-reboot-time.html
						#	The OverrideRebootWindowTime is the Epoch time for the Forced box that cannot be closed to appear.  
						#	Rebootby is the Epoch time that will cause the machine to restart.
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'OverrideRebootWindowTime' -Value 0 -Type QWord -ContinueOnError $true
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'PreferredRebootWindowTypes' -Value @("4") -Type MultiString -ContinueOnError $true
						##is OverrideRebootWindow Needed for non-Mandatory reboots?
						##Set-RegistryKey -Key $SCCMRebootDataKey -Name 'OverrideRebootWindow' -Value 0 -Type Dword -ContinueOnError $true
					}
					Set-RegistryKey -Key $SCCMRebootDataKey -Name 'RebootValueInUTC' -Value 1 -Type DWord -ContinueOnError $true
					Set-RegistryKey -Key $SCCMRebootDataKey -Name 'NotifyUI' -Value 1 -Type DWord -ContinueOnError $true
					If ($HardReboot) {
						write-log "Performing HARD reboot" -Source ${CmdletName} -Severity 2
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'HardReboot' -Value 1 -Type DWord -ContinueOnError $true
					} Else {
						#write-log "Performing soft reboot" -Source ${CmdletName} -Severity 2
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'HardReboot' -Value 0 -Type DWord -ContinueOnError $true
					}
					Set-RegistryKey -Key $SCCMRebootDataKey -Name 'GraceSeconds' -Value 0 -Type DWord -ContinueOnError $true
					Write-Log "restarting [ccmexec] service.." -Source ${CmdletName}
					[ScriptBlock]$PSJobSB = {Restart-Service ccmexec -force -Verbose}
					Start-Job -ScriptBlock $PSJobSB -Name "RestartCCMEXECJob" -Verbose | Out-String | Write-Log -Source ${CmdletName}
					break
				}
				'CancelReboot' { ##CANCEL a pending reboot, forcibly
					Remove-RegistryKey -Key $SCCMRebootDataKey
					Remove-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\Handler\UpdatesRebootStatus\*' -Name '*'
					Remove-RegistryKey -key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' -Name '*'
					Execute-Process -Path "Shutdown.exe" -Parameters "/a" -NoWait #No such thing as Stop-Reboot
					Write-Log "restarting [ccmexec] service.." -Source ${CmdletName}
					[ScriptBlock]$PSJobSB = {Restart-Service ccmexec -force -Verbose}
					Start-Job -ScriptBlock $PSJobSB -Name "RestartCCMEXECJob" -Verbose | Out-String | Write-Log -Source ${CmdletName}
					break
				}
				'ChgToNonMandatoryReboot' { ##change mandatory reboot to  non-mandatory reboot
					Write-Log "Checking if a Reboot Is Pending." -Source ${CmdletName}
					[Int64]$UtcTimeRebootBy = Get-RegistryKey -Key $SCCMRebootDataKey -Value 'RebootBy' -ContinueOnError $true
					If ([Bool]$UtcTimeRebootBy) {
						Write-Log "Found RebootBy = [$UtcTimeRebootBy]. An SCCM Reboot is pending.(good)" -Source ${CmdletName}
						Set-RegistryKey -Key $SCCMRebootDataKey -name 'RebootBy' -value 0
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'PreferredRebootWindowTypes' -Value @("4") -Type MultiString -ContinueOnError $true
						Write-Log "restarting [ccmexec] service.." -Source ${CmdletName}
						[ScriptBlock]$PSJobSB = {Restart-Service ccmexec -force -Verbose}
						Start-Job -ScriptBlock $PSJobSB -Name "RestartCCMEXECJob" -Verbose | Out-String | Write-Log -Source ${CmdletName}
					} Else {
						Write-Log "Reg value RebootBy was not found in [$SCCMRebootDataKey]" -Source ${CmdletName} -Severity 2
						Write-Log "No SCCM reboot to change. Nothing to do." -Source ${CmdletName} -Severity 2
					}
					break
				}
				'ChgToMandatoryReboot' { ##change non-mandatory reboot to mandatory reboot (NOT WORKING AT THIS TIME)
					##CAVEAT: RebootBy and OverrideRebootWindowTime *must* match for mandatory reboots to work
					##(I Suspect it's because OverrideRebootWindowTime and/or RebootBy are not set by just using Set-SCCMReboot -Action Reboot)
					Write-Log "Checking if a Reboot Is Pending." -Source ${CmdletName}
					[Int64]$UtcTimeOverrideRebootWindowTime = Get-RegistryKey -Key $SCCMRebootDataKey -Value 'OverrideRebootWindowTime' -ContinueOnError $true
					If ([Bool]$UtcTimeOverrideRebootWindowTime) {
						Write-Log "Found OverrideRebootWindowTime = [$UtcTimeOverrideRebootWindowTime]. An SCCM Reboot is pending.(good)" -Source ${CmdletName}
						Set-RegistryKey -Key $SCCMRebootDataKey -name 'RebootBy' -value [Int64]$UtcTimeOverrideRebootWindowTime
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'PreferredRebootWindowTypes' -Value @("3") -Type MultiString -ContinueOnError $true
						write-log "Performing HARD reboot" -Source ${CmdletName} -Severity 2
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'HardReboot' -Value 1 -Type DWord -ContinueOnError $true
						Write-Log "restarting [ccmexec] service.." -Source ${CmdletName}
						[ScriptBlock]$PSJobSB = {Restart-Service ccmexec -force -Verbose}
						Start-Job -ScriptBlock $PSJobSB -Name "RestartCCMEXECJob" -Verbose | Out-String | Write-Log -Source ${CmdletName}
					} Else {
						Write-Log "Reg value OverrideRebootWindowTime was not found in [$SCCMRebootDataKey]" -Source ${CmdletName} -Severity 2
						Write-Log "No SCCM reboot to change. Nothing to do." -Source ${CmdletName} -Severity 2
					}
					break
				}
				'ResetRebootCountdownTimer' { ##restarts the countdown clock for the reboot
					##What if we just want to extend the time of a mandatory reboot?
					##Example: In your client settings you have your reboot countdown set to 10 hours.... 
					##the user calls and says it's going to reboot in 10 min and needs it extended...
					##This will reset that user's countdown timer back to 10 hours.
					Write-Log "Checking if a Reboot Is Pending." -Source ${CmdletName}
					[Int64]$UtcTimeRebootBy = Get-RegistryKey -Key $SCCMRebootDataKey -Value 'RebootBy' -ContinueOnError $true
					If ([Bool]$UtcTimeRebootBy) {
						Write-Log "Found RebootBy = [$UtcTimeRebootBy]. An SCCM Reboot is pending.(good)" -Source ${CmdletName}
						[Int64]$time = [DateTimeOffset]::Now.ToUnixTimeSeconds()
						Set-RegistryKey -Key $SCCMRebootDataKey -Name 'RebootBy' -Value $time -Type QWord -ContinueOnError $true
						Write-Log "restarting [ccmexec] service.."  -Source ${CmdletName}
						[ScriptBlock]$PSJobSB = {Restart-Service ccmexec -force -Verbose}
						Start-Job -ScriptBlock $PSJobSB -Name "RestartCCMEXECJob" -Verbose | Out-String | Write-Log -Source ${CmdletName}
					} Else {
						Write-Log "Reg value RebootBy was not found in [$SCCMRebootDataKey]" -Severity 2
						Write-Log "No SCCM reboot to change. Nothing to do." -Source ${CmdletName} -Severity 2
					}
					break
				}
				default {
					Write-Log "Action [$Action] no supported" -Source ${CmdletName} -Severity 3
					Start-Sleep -Seconds 5
					break
				}
			}

		} Catch {
			write-log $(Resolve-Error) -Source ${CmdletName} -Severity 3
		}
	}
	End {
        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
    }
}
#endregion Function Set-SCCMReboot

1 Like

You mean with that script and the computer is in pending reboot state?

yes. depending what you choose.
If a mandatory reboot, the computer will after some time as if the SCCM console requested it.

It’s not perfect but it works.