win32-taskscheduler 0.2.0 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,266 @@
1
+ require_relative "sid"
2
+
3
+ module Win32
4
+ class TaskScheduler
5
+ module TaskSchedulerConstants
6
+ SERVICE_ACCOUNT_USERS = SID::SERVICE_ACCOUNT_USERS
7
+
8
+ BUILT_IN_GROUPS = SID::BUILT_IN_GROUPS
9
+
10
+ SYSTEM_USERS = SERVICE_ACCOUNT_USERS + BUILT_IN_GROUPS
11
+
12
+ # Triggers
13
+
14
+ # Trigger is set to run the task a single time
15
+ TASK_TIME_TRIGGER_ONCE = 1
16
+
17
+ # Trigger is set to run the task on a daily interval
18
+ TASK_TIME_TRIGGER_DAILY = 2
19
+
20
+ # Trigger is set to run the task on specific days of a specific week & month
21
+ TASK_TIME_TRIGGER_WEEKLY = 3
22
+
23
+ # Trigger is set to run the task on specific day(s) of the month
24
+ TASK_TIME_TRIGGER_MONTHLYDATE = 4
25
+
26
+ # Trigger is set to run the task on specific day(s) of the month
27
+ TASK_TIME_TRIGGER_MONTHLYDOW = 5
28
+
29
+ # Trigger is set to run the task if the system remains idle for the amount
30
+ # of time specified by the idle wait time of the task
31
+ TASK_EVENT_TRIGGER_ON_IDLE = 6
32
+
33
+ TASK_TRIGGER_REGISTRATION = 7
34
+
35
+ # Trigger is set to run the task at system startup
36
+ TASK_EVENT_TRIGGER_AT_SYSTEMSTART = 8
37
+
38
+ # Trigger is set to run the task when a user logs on
39
+ TASK_EVENT_TRIGGER_AT_LOGON = 9
40
+
41
+ TASK_TRIGGER_SESSION_STATE_CHANGE = 11
42
+
43
+ # Daily Tasks
44
+
45
+ # The task will run on Sunday
46
+ TASK_SUNDAY = 0x1
47
+
48
+ # The task will run on Monday
49
+ TASK_MONDAY = 0x2
50
+
51
+ # The task will run on Tuesday
52
+ TASK_TUESDAY = 0x4
53
+
54
+ # The task will run on Wednesday
55
+ TASK_WEDNESDAY = 0x8
56
+
57
+ # The task will run on Thursday
58
+ TASK_THURSDAY = 0x10
59
+
60
+ # The task will run on Friday
61
+ TASK_FRIDAY = 0x20
62
+
63
+ # The task will run on Saturday
64
+ TASK_SATURDAY = 0x40
65
+
66
+ # Weekly tasks
67
+
68
+ # The task will run between the 1st and 7th day of the month
69
+ TASK_FIRST_WEEK = 0x01
70
+
71
+ # The task will run between the 8th and 14th day of the month
72
+ TASK_SECOND_WEEK = 0x02
73
+
74
+ # The task will run between the 15th and 21st day of the month
75
+ TASK_THIRD_WEEK = 0x04
76
+
77
+ # The task will run between the 22nd and 28th day of the month
78
+ TASK_FOURTH_WEEK = 0x08
79
+
80
+ # The task will run the last seven days of the month
81
+ TASK_LAST_WEEK = 0x10
82
+
83
+ # Monthly tasks
84
+
85
+ # The task will run in January
86
+ TASK_JANUARY = 0x1
87
+
88
+ # The task will run in February
89
+ TASK_FEBRUARY = 0x2
90
+
91
+ # The task will run in March
92
+ TASK_MARCH = 0x4
93
+
94
+ # The task will run in April
95
+ TASK_APRIL = 0x8
96
+
97
+ # The task will run in May
98
+ TASK_MAY = 0x10
99
+
100
+ # The task will run in June
101
+ TASK_JUNE = 0x20
102
+
103
+ # The task will run in July
104
+ TASK_JULY = 0x40
105
+
106
+ # The task will run in August
107
+ TASK_AUGUST = 0x80
108
+
109
+ # The task will run in September
110
+ TASK_SEPTEMBER = 0x100
111
+
112
+ # The task will run in October
113
+ TASK_OCTOBER = 0x200
114
+
115
+ # The task will run in November
116
+ TASK_NOVEMBER = 0x400
117
+
118
+ # The task will run in December
119
+ TASK_DECEMBER = 0x800
120
+
121
+ # Flags
122
+
123
+ # Used when converting AT service jobs into work items
124
+ TASK_FLAG_INTERACTIVE = 0x1
125
+
126
+ # The work item will be deleted when there are no more scheduled run times
127
+ TASK_FLAG_DELETE_WHEN_DONE = 0x2
128
+
129
+ # The work item is disabled. Useful for temporarily disabling a task
130
+ TASK_FLAG_DISABLED = 0x4
131
+
132
+ # The work item begins only if the computer is not in use at the scheduled
133
+ # start time
134
+ TASK_FLAG_START_ONLY_IF_IDLE = 0x10
135
+
136
+ # The work item terminates if the computer makes an idle to non-idle
137
+ # transition while the work item is running
138
+ TASK_FLAG_KILL_ON_IDLE_END = 0x20
139
+
140
+ # The work item does not start if the computer is running on battery power
141
+ TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40
142
+
143
+ # The work item ends, and the associated application quits, if the computer
144
+ # switches to battery power
145
+ TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80
146
+
147
+ # The work item starts only if the computer is in a docking station
148
+ TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100
149
+
150
+ # The work item created will be hidden
151
+ TASK_FLAG_HIDDEN = 0x200
152
+
153
+ # The work item runs only if there is a valid internet connection
154
+ TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400
155
+
156
+ # The work item starts again if the computer makes a non-idle to idle
157
+ # transition
158
+ TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800
159
+
160
+ # The work item causes the system to be resumed, or awakened, if the
161
+ # system is running on batter power
162
+ TASK_FLAG_SYSTEM_REQUIRED = 0x1000
163
+
164
+ # The work item runs only if a specified account is logged on interactively
165
+ TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000
166
+
167
+ # Triggers
168
+
169
+ # The task will stop at some point in time
170
+ TASK_TRIGGER_FLAG_HAS_END_DATE = 0x1
171
+
172
+ # The task can be stopped at the end of the repetition period
173
+ TASK_TRIGGER_FLAG_KILL_AT_DURATION_END = 0x2
174
+
175
+ # The task trigger is disabled
176
+ TASK_TRIGGER_FLAG_DISABLED = 0x4
177
+
178
+ # Run Level Types
179
+ # Tasks will be run with the least privileges
180
+ TASK_RUNLEVEL_LUA = 0
181
+ # Tasks will be run with the highest privileges
182
+ TASK_RUNLEVEL_HIGHEST = 1
183
+
184
+ # Logon Types
185
+ # Used for non-NT credentials
186
+ TASK_LOGON_NONE = 0
187
+ # Use a password for logging on the user
188
+ TASK_LOGON_PASSWORD = 1
189
+ # The service will log the user on using Service For User
190
+ TASK_LOGON_S4U = 2
191
+ # Task will be run only in an existing interactive session
192
+ TASK_LOGON_INTERACTIVE_TOKEN = 3
193
+ # Group activation. The groupId field specifies the group
194
+ TASK_LOGON_GROUP = 4
195
+ # When Local System, Local Service, or Network Service account is
196
+ # being used as a security context to run the task
197
+ TASK_LOGON_SERVICE_ACCOUNT = 5
198
+ # Not in use; currently identical to TASK_LOGON_PASSWORD
199
+ TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD = 6
200
+
201
+ TASK_MAX_RUN_TIMES = 1440
202
+ TASKS_TO_RETRIEVE = 5
203
+
204
+ # Task creation
205
+
206
+ TASK_VALIDATE_ONLY = 0x1
207
+ TASK_CREATE = 0x2
208
+ TASK_UPDATE = 0x4
209
+ TASK_CREATE_OR_UPDATE = 0x6
210
+ TASK_DISABLE = 0x8
211
+ TASK_DONT_ADD_PRINCIPAL_ACE = 0x10
212
+ TASK_IGNORE_REGISTRATION_TRIGGERS = 0x20
213
+
214
+ # Priority classes
215
+
216
+ REALTIME_PRIORITY_CLASS = 0
217
+ HIGH_PRIORITY_CLASS = 1
218
+ ABOVE_NORMAL_PRIORITY_CLASS = 2 # Or 3
219
+ NORMAL_PRIORITY_CLASS = 4 # Or 5, 6
220
+ BELOW_NORMAL_PRIORITY_CLASS = 7 # Or 8
221
+ IDLE_PRIORITY_CLASS = 9 # Or 10
222
+
223
+ CLSCTX_INPROC_SERVER = 0x1
224
+ CLSID_CTask = [0x148BD520, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack("LSSC8")
225
+ CLSID_CTaskScheduler = [0x148BD52A, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack("LSSC8")
226
+ IID_ITaskScheduler = [0x148BD527, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack("LSSC8")
227
+ IID_ITask = [0x148BD524, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack("LSSC8")
228
+ IID_IPersistFile = [0x0000010b, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46].pack("LSSC8")
229
+
230
+ # Days of month
231
+
232
+ TASK_FIRST = 0x01
233
+ TASK_SECOND = 0x02
234
+ TASK_THIRD = 0x04
235
+ TASK_FOURTH = 0x08
236
+ TASK_FIFTH = 0x10
237
+ TASK_SIXTH = 0x20
238
+ TASK_SEVENTH = 0x40
239
+ TASK_EIGHTH = 0x80
240
+ TASK_NINETH = 0x100
241
+ TASK_TENTH = 0x200
242
+ TASK_ELEVENTH = 0x400
243
+ TASK_TWELFTH = 0x800
244
+ TASK_THIRTEENTH = 0x1000
245
+ TASK_FOURTEENTH = 0x2000
246
+ TASK_FIFTEENTH = 0x4000
247
+ TASK_SIXTEENTH = 0x8000
248
+ TASK_SEVENTEENTH = 0x10000
249
+ TASK_EIGHTEENTH = 0x20000
250
+ TASK_NINETEENTH = 0x40000
251
+ TASK_TWENTIETH = 0x80000
252
+ TASK_TWENTY_FIRST = 0x100000
253
+ TASK_TWENTY_SECOND = 0x200000
254
+ TASK_TWENTY_THIRD = 0x400000
255
+ TASK_TWENTY_FOURTH = 0x800000
256
+ TASK_TWENTY_FIFTH = 0x1000000
257
+ TASK_TWENTY_SIXTH = 0x2000000
258
+ TASK_TWENTY_SEVENTH = 0x4000000
259
+ TASK_TWENTY_EIGHTH = 0x8000000
260
+ TASK_TWENTY_NINTH = 0x10000000
261
+ TASK_THIRTYETH = 0x20000000
262
+ TASK_THIRTY_FIRST = 0x40000000
263
+ TASK_LAST = 0x80000000
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,51 @@
1
+ require "ffi"
2
+
3
+ module Win32
4
+ class TaskScheduler
5
+ module Helper
6
+ extend FFI::Library
7
+
8
+ FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
9
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
10
+ FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF
11
+
12
+ ffi_lib :kernel32, :advapi32
13
+
14
+ attach_function :FormatMessage, :FormatMessageA,
15
+ %i{ulong pointer ulong ulong pointer ulong pointer}, :ulong
16
+
17
+ attach_function :ConvertStringSidToSidW, %i{pointer pointer}, :bool
18
+ attach_function :LookupAccountSidW, %i{pointer pointer pointer pointer pointer pointer pointer}, :bool
19
+ attach_function :LocalFree, [:pointer], :pointer
20
+
21
+ def win_error(function, err = FFI.errno)
22
+ err_msg = ""
23
+ flags = FORMAT_MESSAGE_IGNORE_INSERTS |
24
+ FORMAT_MESSAGE_FROM_SYSTEM |
25
+ FORMAT_MESSAGE_MAX_WIDTH_MASK
26
+
27
+ # 0x0409 == MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)
28
+ # We use English for errors because Ruby uses English for errors.
29
+
30
+ FFI::MemoryPointer.new(:char, 1024) do |buf|
31
+ len = FormatMessage(flags, nil, err, 0x0409, buf, buf.size, nil)
32
+ err_msg = function + ": " + buf.read_string(len).strip
33
+ end
34
+
35
+ err_msg
36
+ end
37
+
38
+ def ole_error(function, err)
39
+ regex = /OLE error code:(.*?)\sin/
40
+ match = regex.match(err.to_s)
41
+
42
+ if match
43
+ error = match.captures.first.hex
44
+ win_error(function, error)
45
+ else
46
+ "#{function}: #{err}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,132 @@
1
+ require_relative "helper"
2
+
3
+ module FFI
4
+ class Pointer
5
+ def read_wstring(num_wchars = nil)
6
+ if num_wchars.nil?
7
+ # Find the length of the string
8
+ length = 0
9
+ last_char = nil
10
+ while last_char != "\000\000"
11
+ length += 1
12
+ last_char = get_bytes(0, length * 2)[-2..-1]
13
+ end
14
+
15
+ num_wchars = length
16
+ end
17
+
18
+ wide_to_utf8(get_bytes(0, num_wchars * 2))
19
+ end
20
+
21
+ def wide_to_utf8(wstring)
22
+ # ensure it is actually UTF-16LE
23
+ # Ruby likes to mark binary data as ASCII-8BIT
24
+ wstring = wstring.force_encoding("UTF-16LE")
25
+
26
+ # encode it all as UTF-8 and remove trailing CRLF and NULL characters
27
+ wstring.encode("UTF-8").strip
28
+ end
29
+ end
30
+ end
31
+
32
+ module Win32
33
+ class TaskScheduler
34
+ module SID
35
+ extend Win32::TaskScheduler::Helper
36
+ ERROR_INSUFFICIENT_BUFFER = 122
37
+
38
+ def self.LocalSystem
39
+ from_string_sid("S-1-5-18")
40
+ end
41
+
42
+ def self.NtLocal
43
+ from_string_sid("S-1-5-19")
44
+ end
45
+
46
+ def self.NtNetwork
47
+ from_string_sid("S-1-5-20")
48
+ end
49
+
50
+ def self.BuiltinAdministrators
51
+ from_string_sid("S-1-5-32-544")
52
+ end
53
+
54
+ def self.BuiltinUsers
55
+ from_string_sid("S-1-5-32-545")
56
+ end
57
+
58
+ def self.Guests
59
+ from_string_sid("S-1-5-32-546")
60
+ end
61
+
62
+ # Converts a string-format security identifier (SID) into a valid, functional SID
63
+ # and returns a hash with account_name and account_simple_name
64
+ # @see https://docs.microsoft.com/en-us/windows/desktop/api/sddl/nf-sddl-convertstringsidtosidw
65
+ #
66
+ def self.from_string_sid(string_sid)
67
+ result = FFI::MemoryPointer.new :pointer
68
+ unless ConvertStringSidToSidW(utf8_to_wide(string_sid), result)
69
+ raise FFI::LastError.error
70
+ end
71
+ result_pointer = result.read_pointer
72
+ domain, name, use = account(result_pointer)
73
+ LocalFree(result_pointer)
74
+ account_names(domain, name, use)
75
+ end
76
+
77
+ def self.utf8_to_wide(ustring)
78
+ # ensure it is actually UTF-8
79
+ # Ruby likes to mark binary data as ASCII-8BIT
80
+ ustring = (ustring + "").force_encoding("UTF-8")
81
+
82
+ # ensure we have the double-null termination Windows Wide likes
83
+ ustring += "\000\000" if ustring.empty? || ustring[-1].chr != "\000"
84
+
85
+ # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
86
+ ustring.encode("UTF-16LE")
87
+ end
88
+
89
+ # Accepts a security identifier (SID) as input.
90
+ # It retrieves the name of the account for this SID and the name of the
91
+ # first domain on which this SID is found
92
+ # @see https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountsidw
93
+ #
94
+ def self.account(sid)
95
+ sid = sid.pointer if sid.respond_to?(:pointer)
96
+ name_size = FFI::Buffer.new(:long).write_long(0)
97
+ referenced_domain_name_size = FFI::Buffer.new(:long).write_long(0)
98
+
99
+ if LookupAccountSidW(nil, sid, nil, name_size, nil, referenced_domain_name_size, nil)
100
+ raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupAccountSid, and got no error!"
101
+ elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
102
+ raise FFI::LastError.error
103
+ end
104
+
105
+ name = FFI::MemoryPointer.new :char, (name_size.read_long * 2)
106
+ referenced_domain_name = FFI::MemoryPointer.new :char, (referenced_domain_name_size.read_long * 2)
107
+ use = FFI::Buffer.new(:long).write_long(0)
108
+ unless LookupAccountSidW(nil, sid, name, name_size, referenced_domain_name, referenced_domain_name_size, use)
109
+ raise FFI::LastError.error
110
+ end
111
+ [referenced_domain_name.read_wstring(referenced_domain_name_size.read_long), name.read_wstring(name_size.read_long), use.read_long]
112
+ end
113
+
114
+ # Formats domain, name and returns a hash with
115
+ # account_name and account_simple_name
116
+ #
117
+ def self.account_names(domain, name, _use)
118
+ account_name = !domain.to_s.empty? ? "#{domain}\\#{name}" : name
119
+ account_simple_name = name
120
+ { account_name: account_name, account_simple_name: account_simple_name }
121
+ end
122
+
123
+ SERVICE_ACCOUNT_USERS = [self.LocalSystem, self.NtLocal, self.NtNetwork].map do |user|
124
+ [user[:account_simple_name].upcase, user[:account_name].upcase]
125
+ end.flatten.freeze
126
+
127
+ BUILT_IN_GROUPS = [self.BuiltinAdministrators, self.BuiltinUsers, self.Guests].map do |user|
128
+ [user[:account_simple_name].upcase, user[:account_name].upcase]
129
+ end.flatten.freeze
130
+ end
131
+ end
132
+ end