win32-taskscheduler 0.2.0 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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