Scheduled Tasks Inconsistencies

Scheduled Tasks are inconsistently applied at login triggers, and do not cover cases where long-running process like gpg-agent may hang. The remedy for this is to trigger the Scheduled Task on an Event. Event Triggers cannot be created using the current powershell cmdlet – and can only be created interactively or via a com object.

This will run through configuring a gpg-agent to refresh on screen unlock (including initial login) using both GUI and using a powershell script options, and assumes GPG agents have been added to the user’s PATH environment variable already.

Enable Logon/Logoff Events

Logon/Logoff events are not configured to log by default. When enabled, successful unlock events will have an ID of 4801, and event login failures will have an ID of 4800. The unlock event will trigger at screen unlock as well as logging into the machine.

Manually Adding Event Triggered Scheduled Task

Setup triggered events to refresh GPG agent on screen unlocks.

See Powershell to Create Event Triggered Scheduled Task for a powershell script that does this for you.

This can be verified to work by restarting your machine or killing the current agent with gpgconf --kill gpg-agent and locking/unlocking your screen then attempting to use Putty. It is registered in Task Scheduler Library as GPGAgentRefreshUnlock.

You may noticed occasionsally that command windows pop-up while the scheduled task is executing. If this should be completely hidden, see Hiding Command Windows.

Powershell to Create Event Triggered Scheduled Task

Use a Windows COM object to directly interact with Task Scheduler to create the Event Trigger and the task. A password is required when adding the task.

This re-creates the same task as manually specified above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
$Hostname = $Env:computername
$UserDomain = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$Service = new-object -ComObject ("Schedule.Service")
$Service.Connect($Hostname)
$TaskFolder = $Service.GetFolder("\")
$TaskDefinition = $Service.NewTask(0)
$RegistrationInfo = $TaskDefinition.RegistrationInfo
$RegistrationInfo.Description = 'Restarts GPG agent on windows unlock'
$RegistrationInfo.Author = $UserDomain

$Principal = $TaskDefinition.Principal
$Principal.LogonType = 3
$Principal.UserId = $UserDomain

$Settings = $TaskDefinition.Settings
$Settings.Enabled = $true
$Settings.Hidden = $true
$Settings.Compatibility = 2
$Settings.MultipleInstances = 2
$Settings.DisallowStartIfOnBatteries = $false
$Settings.StopIfGoingOnBatteries = $false
$Settings.AllowHardTerminate = $false
$Settings.StartWhenAvailable = $false
$Settings.RunOnlyIfNetworkAvailable = $false
$Settings.AllowDemandStart = $true
$Settings.RunOnlyIfIdle = $false
$Settings.DisallowStartOnRemoteAppSession = $false
$Settings.UseUnifiedSchedulingEngine = $true
$Settings.WakeToRun = $false
$Settings.ExecutionTimeLimit = 'PT72H'
$Settings.Priority = 7

$Triggers = $TaskDefinition.Triggers
$Trigger = $Triggers.Create(0)
$Trigger.Subscription = "<QueryList><Query Id='0' Path='Security'><Select Path='Security'>*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4801]]</Select></Query></QueryList>"
$Trigger.Enabled = $true

$GpgKillAction = $TaskDefinition.Actions.Create(0)
$GpgKillAction.Path = 'gpgconf'
$GpgKillAction.Arguments = '--kill gpg-agent'
$GpgRestartAction = $TaskDefinition.Actions.Create(0)
$GpgRestartAction.Path = 'gpg-connect-agent'
$GpgRestartAction.Arguments = '/bye'

$Credentials = Get-Credential
$TaskFolder.RegisterTaskDefinition('GpgAgentRefreshUnlock',$TaskDefinition,6,$UserDomain,[System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credentials.password)),3)

gpg-unlock.ps1

  • Execution Policy: Unrestricted (see: Setting Execution Policy).

  • This is registered in Task Scheduler Library as GPGAgentRefreshUnlock.

  • The Subscription query on line 35 is extracted from the manually created scheduled task, instead of manually generating it. Just RMB › export and look in the XML file for Subscription.

This can be verified to work by restarting your machine or killing the current agent with gpgconf --kill gpg-agent and locking/unlocking your screen then attempting to use Putty. It is registered in Task Scheduler Library as GPGAgentUnlockRestart.

You may noticed occasionsally that command windows pop-up while the scheduled task is executing. If this should be completely hidden, see Hiding Command Windows.

(optional) Prompt on Terminal, Instead of Window

You may directly query for a password to use in the terminal if a pop-up authentication prompt is too much. Just insert this and replace the secure string marshaller on line 46 RegisterTaskDefinition with $password; though this will leave a unencrypted password in memory. Remember to overwrite or delete this.

$password = Read-Host -assecurestring "Please enter your password"
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))

Hiding Command Windows

Windows may appear during the execution of actions involving cmd. However the built in method to hide windowsstart /b – will fail as this is not the first thing that executed in the scheduled task (it runs a command shell, then executes start), or fails with the error:

The operator or administrator has refused the request.

The solution to this is to create a small visual basic wrapper that calls your command with no window defined. The command will execute in the wrapper, thus hiding the window creation.

It is absolutely imperative that this script be signed and trusted for use, and that unsigned vbs scripts are NOT trusted. Otherwise you’ve just created a way for someone to potentially execute anything they want as you whenever you login. If you don’t know what the above means, then don’t do this.

quiet_launcher.vbs
CreateObject("Wscript.Shell").Run "" & WScript.Arguments(0) & "", 0, False

Demonstration of Scheduled Task at Login Failure

As GPG agent can hangup occasionally, only restarting on initial login will produce problems with long-running systems, or surface scheduling errors within Task Scheduler itself. This task will work, but will sporadically fail. This is only kept as an example of the failure condition and should not be used.

This task will eventually fail on long running systems (powershell as admin).
$job = Register-ScheduledJob `
   -Name GpgAgent `
   -ScriptBlock { gpg-connect-agent.exe /bye } `
   -Trigger (New-JobTrigger -AtLogOn -User $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)) `
   -ScheduledJobOption (New-ScheduledJobOption -StartIfOnBattery -ContinueIfGoingOnBattery) `
   -RunNow

# Change principal to run only on interactive logon instead of S4U (Service for user login).
$principal = New-ScheduledTaskPrincipal -LogonType Interactive -UserId $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
Set-ScheduledTask -TaskPath \Microsoft\Windows\PowerShell\ScheduledJobs\ -TaskName $job.Name -Principal $principal

Note

This job will appear in Task Scheduler as GpgAgent under Task Scheduler Library › Microsoft › Windows › PowerShell › ScheduledJobs

References

  1. Create schedule task with event log trigger

  2. Task scheduler VB scripting

  3. Powershell password prompt without readhost