Continue Your Automation To Run Once After Restarting a Headless Windows System

There are times when you cannot avoid having to reboot a system and continue with an automation task.

When you hit one of these, you start down the road of finding the built-in ways that Windows allows you to stage a task to start when the system restarts.

There is just one small catch - most of them require a user logon in order to process. On the many headless servers that make up the cloud - this simply isn’t an option. Finding out how to fulfill this requirement can be a little hard to unravel at first because some of these locations are in the machine registry - which can give the first blush impression that they process without a user logon.

Run and RunOnce Registry Keys

There are four registry location where you can configure something to run on reboot, two of which only run one time:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce

You can find out how to configure these locations by searching the internet, however, if your scenario requires automation to restart without the need for a user logon, then you are out of luck with all of them. The The HKEY_LOCAL_MACHINE locations still require a user to logon - but they simply process for EVERY user who logs on.

Active Setup Registry Keys

Active Setup keys are another Windows mechanism that allows a command to be staged. To make this work you populate they key HKEY_LOCAL_MACHINE\Sofwtare\Microsoft\Active Setup\Installed Components<UID> with a properly formatted command. When a user logs on, if they have not run that key, the command is run and then the key is copied to HKEY_CURRENT_USER\Sofwtare\Microsoft\Active Setup\Installed Components<UID> as a done marker.

Once again, a user must logon for these keys to be run.

Unique Requirements

Here are the unique requirements, as I know them, that might cause you to be seeking a solution to this problem:

  1. You need to run or continue an automation process after a Windows machine reboots.

  2. You need this to happen whether or not a user ever logs on (common for servers).

  3. You need the process to run with full local administrator rights (including no UAC prompts).

  4. You need it to run WITHOUT providing a user password to the task for one or more of these reasons:

    • you don’t want to code passwords into automation code (especially not ones with administrator privileges)

    • you don’t or can’t know an admin password for an existing user id

    • you don’t want to create a user id with admin rights and delete it afterward just to accomplish this task

Solution: Self-Deleting Scheduled Task That Runs As The System Account (Runs Only Once)

By the time you resort to the task scheduler, you are still in for some adventures discovering what commands will give you the gold. Especially if you are using PowerShell.

This is because:

  • PowerShell scheduled jobs require a username and password and are not available on older versions.

  • PowerShell task scheduling CMDLets are capable of doing this, but are only available on Server 2012 R2 / Windows 8.1 and later and are more complex to workout.

  • Many of the sample configurations you will find on the Internet automatically include both a username and password as the person writing them assumed that would be the only way to gain access to admin rights on a headless reboot.

So the magic sauce is to use schtasks.exe to schedule a run once task that deletes itself.

Caution About schtasks /Z

schtasks.exe has a “/Z” switch that is supposed to self-delete a task. On newer OSes you have also add /V1 to avoid a red herring error that you have malformed XML. However, in my testing with these switches the task would never self-delete - it kept running on every startup. (only tested on Server 2012 R2)

The below code is written in PowerShell but uses schtasks.exe - which is actually more straight forward than the ScheduledTask CMDLets.

Updated Code

The below code’s primary home is on the following repository (where it might be improved upon compared to the below):

Example 1: Write Out A Script To Schedule

This method works well when the code to run after a restart is fairly brief and it is not practical to schedule the pre-restart code to run itself again.

Code to Write A Script To Be Called On Restart (Including self-deletion of the task)

The first bit of code writes out a scheduled task script.

#Code to write out scheduled task script that self-deletes
$scriptlocation = "$env:windir\temp\afterreboot.ps1"
"Write-EventLog -Message 'HeadlessRestartTask: hi I ran on reboot' -LogName System -Source EventLog -EventId 333"| out-file $scriptlocation
start-sleep -s 2" | out-file $scriptlocation -append
"schtasks.exe /delete /f /tn HeadlessRestartTask" | out-file $scriptlocation -append

Code to Schedule The Above Script

#This code schedules the above script
schtasks.exe /create /f /tn HeadlessRestartTask /ru SYSTEM /sc ONSTART /tr "powershell.exe -file $scriptlocation"
Write-Host "`"$scriptlocation`" is scheduled to run once after reboot."

Example 2: Script Scheduling Itself for Post-Restart Resuming

If a script needs to schedule itself to run once on a headless restart then the script itself must reside on the local disk.

An easy way to handle the need to restart the script at a certain checkpoint is to have an optional switch built-in to the script that forwards execution to the restart position in the code. (This is “-SkipToPhaseTwo” in the below example.)

In the below code the special $MyInvocation object is used to find the full path to the currently executing script so you don’t have to customize it on each use.

If (!$SkipToPhaseTwo)
  #A bunch of code to do the pre-restart portion of the automation goes here
  #This code schedules the currently running script to restart and forward to a given checkpoint:
  schtasks.exe /create /tn "HeadlessRestartTask" /ru SYSTEM /sc ONSTART /tr "powershell.exe -file $($MyInvocation.MyCommand.Definition) -SkipToPhaseTwo"
  Write-Host "`"$scriptlocation`" is scheduled to run once after reboot."
  Restart-Computer -Force
Write-Output "The system has restarted, continuing..."
#self-delete the scheduled task
"schtasks.exe /delete /f /tn HeadlessRestartTask"

#Additional post-restart code goes here.

Example 3: Oneliner Self-Deleting With No Script File

I give this example with trepidation because if your code is not super simple, it quickly becomes quotation parsing hell. You’ll notice I’ve gone to lengths to avoid an extra quotes even just for this simple example.

schtasks.exe /create /f /tn HeadlessRestartTask /ru SYSTEM /sc ONSTART /tr "powershell.exe -executionpolicy remotesigned -command 'Write-EventLog -Message HeadlessRestartTask_hi_I_ran_on_reboot -LogName System -Source EventLog -EventId 333 ; schtasks.exe /delete /f /tn HeadlessRestartTask'"

Example 4: PowerShell Code for Comparison to Schtasks.exe

#PowerShell to schedule the above script at $scriptlocation
$TaskTrigger = (New-ScheduledTaskTrigger -atstartup)
$TaskAction = New-ScheduledTaskAction -Execute Powershell.exe -argument "-ExecutionPolicy Bypass -File $scriptlocation"
$TaskUserID = New-ScheduledTaskPrincipal -UserId System -RunLevel Highest -LogonType ServiceAccount
Register-ScheduledTask -Force -TaskName HeadlessRestartTask -Action $TaskAction -Principal $TaskUserID -Trigger $TaskTrigger

This Technique In Production

I am a co-maintainer of the Chocolatey package for installing PowerShell. The technique discussed, as well as very similar code, are in the current PowerShell Chocolatey package - it is used to fix up the PowerShell PSModulePath when PowerShell / WMF 5.1 is installed directly over PowerShell WMF 3.0.

In this scenario the damaging alterations to the PSModulePath are done sometime during the restart by the post-boot finish up processing of the WMF 5.1 Windows Update - so the fix must run after Windows Updates completes processing during the restart.

Here is the code

Share Comments
comments powered by Disqus