25. 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.
25.1. 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.
Enable logon logoff events policy
Computer Configuration › Windows Settings › Security Settings › Advanced Audit Policy Configuration › System Audit Policies - Local Group Policy Object › Logon/Logoff › Audit Other Login/Logoff Events
☑
Configure the following audit events
☑
SUCCESS
☑
FAILURE
Updated: 2021-02-19
25.2. 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.
Manaually add event triggered scheduled task
Manually add general section schedule
⌘ › Task Scheduler › Task Scheduler Library › Action › Create Task › General
Name
GpgAgentRefreshUnlock
Description
Restarts GPG agent on windows unlock
Check
Run only when user is logged on
Configure for
Windows 10
☑
Hidden
Updated: 2021-02-19
Manually add triggers section schedule
Triggers › New
Begin the task
On an event
Check
Basic
Log
Security
Source
Microsoft Windows security auditing
Event ID
4801
☑
Hidden
Updated: 2021-02-19
Manually add actions section gpg kill schedule
Actions › New
Action
Start a program
Program/Script
gpgconf
Add arguments (optional)
–kill gpg-agent
Updated: 2021-02-19
Manually add actions section gpg agent schedule
Note
gpgconf --kill gpg-agent
action should always be executed before
restarting the connect agent.
Actions › New
Action
Start a program
Program/Script
gpg-connect-agent
Add arguments (optional)
/bye
Updated: 2021-02-19
Manually add conditions section schedule
Conditions
☐
Updated: 2021-02-19
Manually add settings section schedule
Settings
Allow task to be run on demand
☑
Stop the task if it runs longer than
☑
Stop the task if it runs longer than
3 days
All Remaining
☐
Updated: 2021-02-19
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.
25.3. 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$Hostname = $Env:computername
2$UserDomain = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
3$Service = new-object -ComObject ("Schedule.Service")
4$Service.Connect($Hostname)
5$TaskFolder = $Service.GetFolder("\")
6$TaskDefinition = $Service.NewTask(0)
7$RegistrationInfo = $TaskDefinition.RegistrationInfo
8$RegistrationInfo.Description = 'Restarts GPG agent on windows unlock'
9$RegistrationInfo.Author = $UserDomain
10
11$Principal = $TaskDefinition.Principal
12$Principal.LogonType = 3
13$Principal.UserId = $UserDomain
14
15$Settings = $TaskDefinition.Settings
16$Settings.Enabled = $true
17$Settings.Hidden = $true
18$Settings.Compatibility = 2
19$Settings.MultipleInstances = 2
20$Settings.DisallowStartIfOnBatteries = $false
21$Settings.StopIfGoingOnBatteries = $false
22$Settings.AllowHardTerminate = $false
23$Settings.StartWhenAvailable = $false
24$Settings.RunOnlyIfNetworkAvailable = $false
25$Settings.AllowDemandStart = $true
26$Settings.RunOnlyIfIdle = $false
27$Settings.DisallowStartOnRemoteAppSession = $false
28$Settings.UseUnifiedSchedulingEngine = $true
29$Settings.WakeToRun = $false
30$Settings.ExecutionTimeLimit = 'PT72H'
31$Settings.Priority = 7
32
33$Triggers = $TaskDefinition.Triggers
34$Trigger = $Triggers.Create(0)
35$Trigger.Subscription = "<QueryList><Query Id='0' Path='Security'><Select Path='Security'>*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4801]]</Select></Query></QueryList>"
36$Trigger.Enabled = $true
37
38$GpgKillAction = $TaskDefinition.Actions.Create(0)
39$GpgKillAction.Path = 'gpgconf'
40$GpgKillAction.Arguments = '--kill gpg-agent'
41$GpgRestartAction = $TaskDefinition.Actions.Create(0)
42$GpgRestartAction.Path = 'gpg-connect-agent'
43$GpgRestartAction.Arguments = '/bye'
44
45$Credentials = Get-Credential
46$TaskFolder.RegisterTaskDefinition('GpgAgentRefreshUnlock',$TaskDefinition,6,$UserDomain,[System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credentials.password)),3)
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 forSubscription
.
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.
25.4. (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))
25.5. Hiding Command Windows
Windows may appear during the execution of actions involving cmd
. However
the built in method to hide windows – start /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.
CreateObject("Wscript.Shell").Run "" & WScript.Arguments(0) & "", 0, False
Use wrapper script for GPG conf task
⌘ › Task Scheduler › Task Scheduler Library › GPGAgentRefreshUnlock › Actions › Edit › Start a Program
Program/Script
quiet_launcher.vbs
Add arguments
gpgconf –kill gpg-agent
Updated: 2021-02-19
Use wrapper script for GPG connect task
⌘ › Task Scheduler › Task Scheduler Library › GPGAgentRefreshUnlock › actions › edit › Start a program
Program/Script
quiet_launcher.vbs
Add arguments
gpg-connect-agent /bye
Updated: 2021-02-19
25.6. 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.
$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