win32-taskscheduler 0.1.0 → 0.2.0

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,56 @@
1
+ #######################################################################
2
+ # taskscheduler_example.rb
3
+ #
4
+ # A test script for general futzing. You can run this example via the
5
+ # 'example' rake task.
6
+ #
7
+ # Modify as you see fit.
8
+ #######################################################################
9
+ require 'win32/taskscheduler'
10
+ require 'fileutils'
11
+ require 'pp'
12
+ include Win32
13
+
14
+ puts 'VERSION: ' + TaskScheduler::VERSION
15
+
16
+ ts = TaskScheduler.new
17
+
18
+ trigger = {
19
+ "start_year" => 2009,
20
+ "start_month" => 4,
21
+ "start_day" => 11,
22
+ "start_hour" => 7,
23
+ "start_minute" => 14,
24
+ "trigger_type" => TaskScheduler::DAILY,
25
+ "type" => { "days_interval" => 1 }
26
+ }
27
+
28
+ unless ts.enum.grep(/foo/).length > 0
29
+ ts.new_work_item("foo", trigger)
30
+ ts.application_name = "notepad.exe"
31
+ ts.save
32
+ puts "Task Added"
33
+ end
34
+
35
+ ts.activate("foo")
36
+ ts.priority = TaskScheduler::IDLE
37
+ ts.working_directory = "C:\\"
38
+
39
+ puts "App name: " + ts.application_name
40
+ puts "Creator: " + ts.creator
41
+ puts "Exit code: " + ts.exit_code.to_s
42
+ puts "Flags: " + ts.flags.to_s
43
+ puts "Max run time: " + ts.max_run_time.to_s
44
+ puts "Next run time: " + ts.next_run_time.to_s
45
+ puts "Parameters: " + ts.parameters
46
+ puts "Priority: " + ts.priority.to_s
47
+ puts "Status: " + ts.status
48
+ puts "Trigger count: " + ts.trigger_count.to_s
49
+ puts "Trigger string: " + ts.trigger_string(0)
50
+ puts "Working directory: " + ts.working_directory
51
+ puts "Trigger: "
52
+
53
+ pp ts.trigger(0)
54
+
55
+ ts.delete("foo")
56
+ puts "Task deleted"
@@ -0,0 +1,1677 @@
1
+ require 'windows/com'
2
+ require 'windows/unicode'
3
+ require 'windows/error'
4
+ require 'windows/process'
5
+ require 'windows/msvcrt/buffer'
6
+ include Windows::COM
7
+ include Windows::Unicode
8
+ include Windows::Process
9
+ include Windows::Error
10
+ include Windows::MSVCRT::Buffer
11
+
12
+ module Win32
13
+ class TaskScheduler
14
+
15
+ # The version of the win32-taskscheduler library
16
+ VERSION = '0.2.0'
17
+
18
+ # The error class raised if any task scheduler specific calls fail.
19
+ class Error < StandardError; end
20
+
21
+ private
22
+
23
+ TASK_TIME_TRIGGER_ONCE = 0
24
+ TASK_TIME_TRIGGER_DAILY = 1
25
+ TASK_TIME_TRIGGER_WEEKLY = 2
26
+ TASK_TIME_TRIGGER_MONTHLYDATE = 3
27
+ TASK_TIME_TRIGGER_MONTHLYDOW = 4
28
+ TASK_EVENT_TRIGGER_ON_IDLE = 5
29
+ TASK_EVENT_TRIGGER_AT_SYSTEMSTART = 6
30
+ TASK_EVENT_TRIGGER_AT_LOGON = 7
31
+
32
+ TASK_SUNDAY = 0x1
33
+ TASK_MONDAY = 0x2
34
+ TASK_TUESDAY = 0x4
35
+ TASK_WEDNESDAY = 0x8
36
+ TASK_THURSDAY = 0x10
37
+ TASK_FRIDAY = 0x20
38
+ TASK_SATURDAY = 0x40
39
+ TASK_FIRST_WEEK = 1
40
+ TASK_SECOND_WEEK = 2
41
+ TASK_THIRD_WEEK = 3
42
+ TASK_FOURTH_WEEK = 4
43
+ TASK_LAST_WEEK = 5
44
+ TASK_JANUARY = 0x1
45
+ TASK_FEBRUARY = 0x2
46
+ TASK_MARCH = 0x4
47
+ TASK_APRIL = 0x8
48
+ TASK_MAY = 0x10
49
+ TASK_JUNE = 0x20
50
+ TASK_JULY = 0x40
51
+ TASK_AUGUST = 0x80
52
+ TASK_SEPTEMBER = 0x100
53
+ TASK_OCTOBER = 0x200
54
+ TASK_NOVEMBER = 0x400
55
+ TASK_DECEMBER = 0x800
56
+
57
+ TASK_FLAG_INTERACTIVE = 0x1
58
+ TASK_FLAG_DELETE_WHEN_DONE = 0x2
59
+ TASK_FLAG_DISABLED = 0x4
60
+ TASK_FLAG_START_ONLY_IF_IDLE = 0x10
61
+ TASK_FLAG_KILL_ON_IDLE_END = 0x20
62
+ TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40
63
+ TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80
64
+ TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100
65
+ TASK_FLAG_HIDDEN = 0x200
66
+ TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400
67
+ TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800
68
+ TASK_FLAG_SYSTEM_REQUIRED = 0x1000
69
+ TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000
70
+ TASK_TRIGGER_FLAG_HAS_END_DATE = 0x1
71
+ TASK_TRIGGER_FLAG_KILL_AT_DURATION_END = 0x2
72
+ TASK_TRIGGER_FLAG_DISABLED = 0x4
73
+
74
+ TASK_MAX_RUN_TIMES = 1440
75
+ TASKS_TO_RETRIEVE = 5
76
+
77
+ CLSCTX_INPROC_SERVER = 0x1
78
+ CLSID_CTask = [0x148BD520,0xA2AB,0x11CE,0xB1,0x1F,0x00,0xAA,0x00,0x53,0x05,0x03].pack('LSSC8')
79
+ CLSID_CTaskScheduler = [0x148BD52A,0xA2AB,0x11CE,0xB1,0x1F,0x00,0xAA,0x00,0x53,0x05,0x03].pack('LSSC8')
80
+ IID_ITaskScheduler = [0x148BD527,0xA2AB,0x11CE,0xB1,0x1F,0x00,0xAA,0x00,0x53,0x05,0x03].pack('LSSC8')
81
+ IID_ITask = [0x148BD524,0xA2AB,0x11CE,0xB1,0x1F,0x00,0xAA,0x00,0x53,0x05,0x03].pack('LSSC8')
82
+ IID_IPersistFile = [0x0000010b,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46].pack('LSSC8')
83
+
84
+ public
85
+
86
+ # Shorthand constants
87
+
88
+ IDLE = IDLE_PRIORITY_CLASS
89
+ NORMAL = NORMAL_PRIORITY_CLASS
90
+ HIGH = HIGH_PRIORITY_CLASS
91
+ REALTIME = REALTIME_PRIORITY_CLASS
92
+ BELOW_NORMAL = BELOW_NORMAL_PRIORITY_CLASS
93
+ ABOVE_NORMAL = ABOVE_NORMAL_PRIORITY_CLASS
94
+
95
+ ONCE = TASK_TIME_TRIGGER_ONCE
96
+ DAILY = TASK_TIME_TRIGGER_DAILY
97
+ WEEKLY = TASK_TIME_TRIGGER_WEEKLY
98
+ MONTHLYDATE = TASK_TIME_TRIGGER_MONTHLYDATE
99
+ MONTHLYDOW = TASK_TIME_TRIGGER_MONTHLYDOW
100
+
101
+ ON_IDLE = TASK_EVENT_TRIGGER_ON_IDLE
102
+ AT_SYSTEMSTART = TASK_EVENT_TRIGGER_AT_SYSTEMSTART
103
+ AT_LOGON = TASK_EVENT_TRIGGER_AT_LOGON
104
+ FIRST_WEEK = TASK_FIRST_WEEK
105
+ SECOND_WEEK = TASK_SECOND_WEEK
106
+ THIRD_WEEK = TASK_THIRD_WEEK
107
+ FOURTH_WEEK = TASK_FOURTH_WEEK
108
+ LAST_WEEK = TASK_LAST_WEEK
109
+ SUNDAY = TASK_SUNDAY
110
+ MONDAY = TASK_MONDAY
111
+ TUESDAY = TASK_TUESDAY
112
+ WEDNESDAY = TASK_WEDNESDAY
113
+ THURSDAY = TASK_THURSDAY
114
+ FRIDAY = TASK_FRIDAY
115
+ SATURDAY = TASK_SATURDAY
116
+ JANUARY = TASK_JANUARY
117
+ FEBRUARY = TASK_FEBRUARY
118
+ MARCH = TASK_MARCH
119
+ APRIL = TASK_APRIL
120
+ MAY = TASK_MAY
121
+ JUNE = TASK_JUNE
122
+ JULY = TASK_JULY
123
+ AUGUST = TASK_AUGUST
124
+ SEPTEMBER = TASK_SEPTEMBER
125
+ OCTOBER = TASK_OCTOBER
126
+ NOVEMBER = TASK_NOVEMBER
127
+ DECEMBER = TASK_DECEMBER
128
+
129
+ INTERACTIVE = TASK_FLAG_INTERACTIVE
130
+ DELETE_WHEN_DONE = TASK_FLAG_DELETE_WHEN_DONE
131
+ DISABLED = TASK_FLAG_DISABLED
132
+ START_ONLY_IF_IDLE = TASK_FLAG_START_ONLY_IF_IDLE
133
+ KILL_ON_IDLE_END = TASK_FLAG_KILL_ON_IDLE_END
134
+ DONT_START_IF_ON_BATTERIES = TASK_FLAG_DONT_START_IF_ON_BATTERIES
135
+ KILL_IF_GOING_ON_BATTERIES = TASK_FLAG_KILL_IF_GOING_ON_BATTERIES
136
+ RUN_ONLY_IF_DOCKED = TASK_FLAG_RUN_ONLY_IF_DOCKED
137
+ HIDDEN = TASK_FLAG_HIDDEN
138
+ RUN_IF_CONNECTED_TO_INTERNET = TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET
139
+ RESTART_ON_IDLE_RESUME = TASK_FLAG_RESTART_ON_IDLE_RESUME
140
+ SYSTEM_REQUIRED = TASK_FLAG_SYSTEM_REQUIRED
141
+ RUN_ONLY_IF_LOGGED_ON = TASK_FLAG_RUN_ONLY_IF_LOGGED_ON
142
+
143
+ FLAG_HAS_END_DATE = TASK_TRIGGER_FLAG_HAS_END_DATE
144
+ FLAG_KILL_AT_DURATION_END = TASK_TRIGGER_FLAG_KILL_AT_DURATION_END
145
+ FLAG_DISABLED = TASK_TRIGGER_FLAG_DISABLED
146
+
147
+ MAX_RUN_TIMES = TASK_MAX_RUN_TIMES
148
+
149
+ # Returns a new TaskScheduler object. If a work_item (and possibly the
150
+ # the trigger) are passed as arguments then a new work item is created and
151
+ # associated with that trigger, although you can still activate other tasks
152
+ # with the same handle.
153
+ #
154
+ # This is really just a bit of convenience. Passing arguments to the
155
+ # constructor is the same as calling TaskScheduler.new plus
156
+ # TaskScheduler#new_work_item.
157
+ #
158
+ def initialize(work_item=nil, trigger=nil)
159
+ @pITS = nil
160
+ @pITask = nil
161
+
162
+ hr = CoInitialize(0)
163
+
164
+ if SUCCEEDED(hr)
165
+ ptr = 0.chr * 4
166
+
167
+ hr = CoCreateInstance(
168
+ CLSID_CTaskScheduler,
169
+ nil,
170
+ CLSCTX_INPROC_SERVER,
171
+ IID_ITaskScheduler,
172
+ ptr
173
+ )
174
+
175
+ if FAILED(hr)
176
+ raise Error, get_last_error
177
+ end
178
+
179
+ @pITS = ptr.unpack('L').first
180
+ else
181
+ raise Error, get_last_error
182
+ end
183
+
184
+ if work_item
185
+ if trigger
186
+ raise TypeError unless trigger.is_a?(Hash)
187
+ new_work_item(work_item, trigger)
188
+ end
189
+ end
190
+
191
+ self
192
+ end
193
+
194
+ # Returns an array of scheduled task names.
195
+ #
196
+ def enum
197
+ raise Error, 'null pointer' if @pITS.nil?
198
+
199
+ lpVtbl = 0.chr * 4
200
+ table = 0.chr * 24
201
+
202
+ memcpy(lpVtbl, @pITS, 4)
203
+ memcpy(table, lpVtbl.unpack('L').first, 24)
204
+ table = table.unpack('L*')
205
+
206
+ enum = Win32::API::Function.new(table[5], 'PP', 'L')
207
+
208
+ ptr = 0.chr * 4
209
+ hr = enum.call(@pITS, ptr)
210
+
211
+ raise Error, get_last_error if hr != S_OK
212
+
213
+ pIEnum = ptr.unpack('L').first
214
+ lpVtbl = 0.chr * 4
215
+ table = 0.chr * 16
216
+
217
+ memcpy(lpVtbl, pIEnum, 4)
218
+ memcpy(table, lpVtbl.unpack('L').first, 16)
219
+ table = table.unpack('L*')
220
+
221
+ _next = Win32::API::Function.new(table[3], 'PLPP', 'L')
222
+ release = Win32::API::Function.new(table[2], 'P', 'L')
223
+
224
+ array = []
225
+ fetched_tasks = 0.chr * 4
226
+ pnames = 0.chr * 4
227
+
228
+ while (_next.call(pIEnum, TASKS_TO_RETRIEVE, pnames, fetched_tasks) >= S_OK) &&
229
+ (fetched_tasks.unpack('L').first != 0)
230
+
231
+ tasks = fetched_tasks.unpack('L').first
232
+ names = 0.chr * 4 * tasks
233
+ memcpy(names, pnames.unpack('L').first, 4 * tasks)
234
+
235
+ for i in 0 ... tasks
236
+ str = 0.chr * 256
237
+ wcscpy(str, names[i*4, 4].unpack('L').first)
238
+ array.push(wide_to_multi(str))
239
+ CoTaskMemFree(names[i*4, 4].unpack('L').first)
240
+ end
241
+
242
+ CoTaskMemFree(pnames.unpack('L').first)
243
+ end
244
+
245
+ release.call(pIEnum)
246
+
247
+ array
248
+ end
249
+
250
+ alias :tasks :enum
251
+
252
+ # Activate the specified task.
253
+ #
254
+ def activate(task)
255
+ raise Error, 'null pointer' if @pITS.nil?
256
+ raise TypeError unless task.is_a?(String)
257
+
258
+ task = multi_to_wide(task)
259
+
260
+ lpVtbl = 0.chr * 4
261
+ table = 0.chr * 28
262
+
263
+ memcpy(lpVtbl, @pITS, 4)
264
+ memcpy(table, lpVtbl.unpack('L').first, 28)
265
+ table = table.unpack('L*')
266
+
267
+ activate = Win32::API::Function.new(table[6], 'PPPP', 'L')
268
+
269
+ ptr = 0.chr * 4
270
+ hr = activate.call(@pITS, task, IID_ITask, ptr)
271
+
272
+ if hr != S_OK
273
+ raise Error, get_last_error
274
+ end
275
+
276
+ @pITask = ptr.unpack('L').first
277
+
278
+ self
279
+ end
280
+
281
+ # Delete the specified task name.
282
+ #
283
+ def delete(task)
284
+ raise Error, 'null pointer' if @pITS.nil?
285
+ raise TypeError unless task.is_a?(String)
286
+
287
+ task = multi_to_wide(task)
288
+
289
+ lpVtbl = 0.chr * 4
290
+ table = 0.chr * 32
291
+
292
+ memcpy(lpVtbl, @pITS, 4)
293
+ memcpy(table, lpVtbl.unpack('L').first, 32)
294
+ table = table.unpack('L*')
295
+
296
+ delete = Win32::API::Function.new(table[7], 'PP', 'L')
297
+
298
+ hr = delete.call(@pITS,task)
299
+
300
+ if hr != S_OK
301
+ raise Error, get_last_error
302
+ end
303
+
304
+ self
305
+ end
306
+
307
+ # Execute the current task.
308
+ #
309
+ def run
310
+ raise Error, 'null pointer' if @pITask.nil?
311
+
312
+ lpVtbl = 0.chr * 4
313
+ table = 0.chr * 52
314
+
315
+ memcpy(lpVtbl, @pITask, 4)
316
+ memcpy(table, lpVtbl.unpack('L').first, 52)
317
+ table = table.unpack('L*')
318
+
319
+ run = Win32::API::Function.new(table[12], 'P', 'L')
320
+
321
+ hr = run.call(@pITask)
322
+
323
+ if hr != S_OK
324
+ raise Error,get_last_error
325
+ end
326
+
327
+ self
328
+ end
329
+
330
+ # Saves the current task. Tasks must be saved before they can be activated.
331
+ # The .job file itself is typically stored in the C:\WINDOWS\Tasks folder.
332
+ #
333
+ # If +file+ (an absolute path) is specified then the job is saved to that
334
+ # file instead. A '.job' extension is recommended but not enforced.
335
+ #
336
+ # Note that calling TaskScheduler#save also resets the TaskScheduler object
337
+ # so that there is no currently active task.
338
+ #
339
+ def save(file = nil)
340
+ raise Error, 'null pointer' if @pITask.nil?
341
+ file = multi_to_wide(file) if file
342
+
343
+ lpVtbl = 0.chr * 4
344
+ table = 0.chr * 12
345
+
346
+ memcpy(lpVtbl, @pITask, 4)
347
+ memcpy(table, lpVtbl.unpack('L').first, 12)
348
+ table = table.unpack('L*')
349
+
350
+ queryinterface = Win32::API::Function.new(table[0],'PPP','L')
351
+ release = Win32::API::Function.new(table[2],'P','L')
352
+
353
+ ptr = 0.chr * 4
354
+
355
+ hr = queryinterface.call(@pITask, IID_IPersistFile, ptr)
356
+
357
+ if hr != S_OK
358
+ raise Error, get_last_error
359
+ end
360
+
361
+ pIPersistFile = ptr.unpack('L').first
362
+
363
+ lpVtbl = 0.chr * 4
364
+ table = 0.chr * 28
365
+
366
+ memcpy(lpVtbl, pIPersistFile,4)
367
+ memcpy(table, lpVtbl.unpack('L').first, 28)
368
+ table = table.unpack('L*')
369
+
370
+ save = Win32::API::Function.new(table[6],'PPL','L')
371
+ release = Win32::API::Function.new(table[2],'P','L')
372
+
373
+ hr = save.call(pIPersistFile,file,1)
374
+
375
+ if hr != S_OK
376
+ raise Error,get_last_error
377
+ end
378
+
379
+ release.call(pIPersistFile)
380
+
381
+ CoUninitialize()
382
+ hr = CoInitialize(nil)
383
+
384
+ if hr >= 0
385
+ ptr = 0.chr * 4
386
+
387
+ hr = CoCreateInstance(
388
+ CLSID_CTaskScheduler,
389
+ nil,
390
+ CLSCTX_INPROC_SERVER,
391
+ IID_ITaskScheduler,
392
+ ptr
393
+ )
394
+
395
+ if hr != S_OK
396
+ CoUninitialize()
397
+ raise Error, get_last_error
398
+ end
399
+
400
+ @pITS = ptr.unpack('L').first
401
+ else
402
+ raise Error,get_last_error
403
+ end
404
+
405
+ release.call(@pITask)
406
+ @pITask = nil
407
+
408
+ self
409
+ end
410
+
411
+ # Terminate the current task.
412
+ #
413
+ def terminate
414
+ raise Error, 'null pointer' if @pITask.nil?
415
+
416
+ lpVtbl = 0.chr * 4
417
+ table = 0.chr * 56
418
+
419
+ memcpy(lpVtbl,@pITask,4)
420
+ memcpy(table,lpVtbl.unpack('L').first,56)
421
+ table = table.unpack('L*')
422
+
423
+ teriminate = Win32::API::Function.new(table[13],'P','L')
424
+ hr = teriminate.call(@pITask)
425
+
426
+ if hr != S_OK
427
+ raise Error,get_last_error
428
+ end
429
+
430
+ self
431
+ end
432
+
433
+ # Set the host on which the various TaskScheduler methods will execute.
434
+ #
435
+ def machine=(host)
436
+ raise Error, 'null pointer' if @pITS.nil?
437
+ raise TypeError unless host.is_a?(String)
438
+
439
+ host_w = multi_to_wide(host)
440
+
441
+ lpVtbl = 0.chr * 4
442
+ table = 0.chr * 16
443
+
444
+ memcpy(lpVtbl, @pITS, 4)
445
+ memcpy(table, lpVtbl.unpack('L').first, 16)
446
+ table = table.unpack('L*')
447
+
448
+ setTargetComputer = Win32::API::Function.new(table[3], 'PP', 'L')
449
+
450
+ hr = setTargetComputer.call(@pITS, host_w)
451
+
452
+ if hr != S_OK
453
+ raise Error, get_last_error
454
+ end
455
+
456
+ host
457
+ end
458
+
459
+ alias :host= :machine=
460
+
461
+ # Sets the +user+ and +password+ for the given task. If the user and
462
+ # password are set properly then true is returned.
463
+ #
464
+ # In some cases the job may be created, but the account information was
465
+ # bad. In this case the task is created but a warning is generated and
466
+ # false is returned.
467
+ #
468
+ def set_account_information(user, password)
469
+ raise Error, 'null pointer' if @pITS.nil?
470
+ raise Error, 'No currently active task' if @pITask.nil?
471
+
472
+ lpVtbl = 0.chr * 4
473
+ table = 0.chr * 124
474
+
475
+ memcpy(lpVtbl, @pITask, 4)
476
+ memcpy(table, lpVtbl.unpack('L').first, 124)
477
+ table = table.unpack('L*')
478
+
479
+ setAccountInformation = Win32::API::Function.new(table[30],'PPP','L')
480
+
481
+ if (user.nil? || user=="") && (password.nil? || password=="")
482
+ hr = setAccountInformation.call(@pITask, "", nil)
483
+ else
484
+ user = multi_to_wide(user)
485
+ password = multi_to_wide(password)
486
+ hr = setAccountInformation.call(@pITask, user, password)
487
+ end
488
+
489
+ bool = true
490
+
491
+ case hr
492
+ when S_OK
493
+ return true
494
+ when 0x80070005 # E_ACCESSDENIED
495
+ raise Error, 'access denied'
496
+ when 0x80070057 # E_INVALIDARG
497
+ raise Error, 'invalid argument'
498
+ when 0x8007000E # E_OUTOFMEMORY
499
+ raise Error, 'out of memory'
500
+ when 0x80041312 # SCHED_E_NO_SECURITY_SERVICES
501
+ raise Error, 'no security services on this platform'
502
+ when 0x80041314 # SCHED_E_UNSUPPORTED_ACCOUNT_OPTION
503
+ raise Error, 'unsupported account option'
504
+ when 0x8004130F # SCHED_E_ACCOUNT_INFORMATION_NOT_SET
505
+ warn 'job created, but password was invalid'
506
+ bool = false
507
+ else
508
+ raise Error, 'unknown error'
509
+ end
510
+ bool
511
+ end
512
+
513
+ # Returns the user associated with the task or nil if no user has yet
514
+ # been associated with the task.
515
+ #
516
+ def account_information
517
+ raise Error, 'null pointer' if @pITS.nil?
518
+ raise Error, 'No currently active task' if @pITask.nil?
519
+
520
+ lpVtbl = 0.chr * 4
521
+ table = 0.chr * 128
522
+
523
+ memcpy(lpVtbl, @pITask, 4)
524
+ memcpy(table,lpVtbl.unpack('L').first, 128)
525
+ table = table.unpack('L*')
526
+
527
+ getAccountInformation = Win32::API::Function.new(table[31], 'PP', 'L')
528
+
529
+ ptr = 0.chr * 4
530
+ hr = getAccountInformation.call(@pITask, ptr)
531
+
532
+ if hr == 0x8004130F # SCHED_E_ACCOUNT_INFORMATION_NOT_SET
533
+ user = nil
534
+ elsif hr >= 0 && hr != 0x80041312 # SCHED_E_NO_SECURITY_SERVICES
535
+ str = 0.chr * 256
536
+ wcscpy(str, ptr.unpack('L').first)
537
+ CoTaskMemFree(ptr.unpack('L').first)
538
+ user = wide_to_multi(str)
539
+ else
540
+ CoTaskMemFree(p.unpack('L').first)
541
+ raise Error,get_last_error(hr)
542
+ end
543
+
544
+ user
545
+ end
546
+
547
+ # Returns the name of the application associated with the task.
548
+ #
549
+ def application_name
550
+ raise Error, 'null pointer' if @pITS.nil?
551
+ raise Error, 'No currently active task' if @pITask.nil?
552
+
553
+ lpVtbl = 0.chr * 4
554
+ table = 0.chr * 136
555
+
556
+ memcpy(lpVtbl, @pITask,4)
557
+ memcpy(table, lpVtbl.unpack('L').first, 136)
558
+ table = table.unpack('L*')
559
+
560
+ getApplicationName = Win32::API::Function.new(table[33],'PP','L')
561
+
562
+ ptr = 0.chr * 4
563
+ hr = getApplicationName.call(@pITask, ptr)
564
+
565
+ if hr >= S_OK
566
+ str = 0.chr * 256
567
+ wcscpy(str, ptr.unpack('L').first)
568
+ app = wide_to_multi(str)
569
+ CoTaskMemFree(ptr.unpack('L').first)
570
+ else
571
+ raise Error, get_last_error
572
+ end
573
+
574
+ app
575
+ end
576
+
577
+ # Sets the application name associated with the task.
578
+ #
579
+ def application_name=(app)
580
+ raise Error, 'null pointer' if @pITS.nil?
581
+ raise Error, 'No currently active task' if @pITask.nil?
582
+ raise TypeError unless app.is_a?(String)
583
+
584
+ app_w = multi_to_wide(app)
585
+
586
+ lpVtbl = 0.chr * 4
587
+ table = 0.chr * 132
588
+ memcpy(lpVtbl,@pITask,4)
589
+ memcpy(table,lpVtbl.unpack('L').first,132)
590
+ table = table.unpack('L*')
591
+ setApplicationName = Win32::API::Function.new(table[32],'PP','L')
592
+
593
+ hr = setApplicationName.call(@pITask,app_w)
594
+
595
+ if hr != S_OK
596
+ raise Error,get_last_error(hr)
597
+ end
598
+ app
599
+ end
600
+
601
+ # Returns the command line parameters for the task.
602
+ #
603
+ def parameters
604
+ raise Error, 'null pointer' if @pITS.nil?
605
+ raise Error, 'No currently active task' if @pITask.nil?
606
+
607
+ lpVtbl = 0.chr * 4
608
+ table = 0.chr * 144
609
+
610
+ memcpy(lpVtbl, @pITask, 4)
611
+ memcpy(table, lpVtbl.unpack('L').first, 144)
612
+ table = table.unpack('L*')
613
+
614
+ getParameters = Win32::API::Function.new(table[35], 'PP', 'L')
615
+ ptr = 0.chr * 4
616
+ hr = getParameters.call(@pITask, ptr)
617
+
618
+ if hr >= S_OK
619
+ str = 0.chr * 256
620
+ wcscpy(str, ptr.unpack('L').first)
621
+ param = wide_to_multi(str)
622
+ CoTaskMemFree(ptr.unpack('L').first)
623
+ else
624
+ raise Error, get_last_error
625
+ end
626
+
627
+ param
628
+ end
629
+
630
+ # Sets the parameters for the task. These parameters are passed as command
631
+ # line arguments to the application the task will run. To clear the command
632
+ # line parameters set it to an empty string.
633
+ #
634
+ def parameters=(param)
635
+ raise Error, 'null pointer(ts_set_parameters)' if @pITS.nil?
636
+ raise Error, 'No currently active task' if @pITask.nil?
637
+ raise TypeError unless param.is_a?(String)
638
+
639
+ param_w = multi_to_wide(param)
640
+
641
+ lpVtbl = 0.chr * 4
642
+ table = 0.chr * 140
643
+
644
+ memcpy(lpVtbl,@pITask,4)
645
+ memcpy(table,lpVtbl.unpack('L').first,140)
646
+ table = table.unpack('L*')
647
+
648
+ setParameters = Win32::API::Function.new(table[34],'PP','L')
649
+ hr = setParameters.call(@pITask,param_w)
650
+
651
+ if hr != S_OK
652
+ raise Error, get_last_error(hr)
653
+ end
654
+ param
655
+ end
656
+
657
+ # Returns the working directory for the task.
658
+ #
659
+ def working_directory
660
+ raise Error,"fatal error: null pointer(ts_get_parameters)" if @pITS.nil?
661
+ raise Error,"No currently active task" if @pITask.nil?
662
+
663
+ lpVtbl = 0.chr * 4
664
+ table = 0.chr * 152
665
+
666
+ memcpy(lpVtbl, @pITask,4)
667
+ memcpy(table, lpVtbl.unpack('L').first,152)
668
+ table = table.unpack('L*')
669
+
670
+ getWorkingDirectory = Win32::API::Function.new(table[37],'PP','L')
671
+
672
+ ptr = 0.chr * 4
673
+ hr = getWorkingDirectory.call(@pITask, ptr)
674
+
675
+ if hr >= S_OK
676
+ str = 0.chr * 256
677
+ wcscpy(str, ptr.unpack('L').first)
678
+ dir = wide_to_multi(str)
679
+ CoTaskMemFree(ptr.unpack('L').first)
680
+ else
681
+ raise Error, get_last_error
682
+ end
683
+
684
+ dir
685
+ end
686
+
687
+ # Sets the working directory for the task.
688
+ #
689
+ def working_directory=(dir)
690
+ raise Error, 'null pointer' if @pITS.nil?
691
+ raise Error, 'No currently active task' if @pITask.nil?
692
+ raise TypeError unless dir.is_a?(String)
693
+
694
+ dir_w = multi_to_wide(dir)
695
+
696
+ lpVtbl = 0.chr * 4
697
+ table = 0.chr * 148
698
+
699
+ memcpy(lpVtbl, @pITask,4)
700
+ memcpy(table, lpVtbl.unpack('L').first, 148)
701
+ table = table.unpack('L*')
702
+
703
+ setWorkingDirectory = Win32::API::Function.new(table[36], 'PP', 'L')
704
+ hr = setWorkingDirectory.call(@pITask, dir_w)
705
+
706
+ if hr != S_OK
707
+ raise Error, get_last_error(hr)
708
+ end
709
+
710
+ dir
711
+ end
712
+
713
+ # Returns the task's priority level. Possible values are 'idle',
714
+ # 'normal', 'high', 'realtime', 'below_normal', 'above_normal',
715
+ # and 'unknown'.
716
+ #
717
+ def priority
718
+ raise Error, 'null pointer(ts_get_priority)' if @pITS.nil?
719
+ raise Error, 'No currently active task' if @pITask.nil?
720
+
721
+ lpVtbl = 0.chr * 4
722
+ table = 0.chr * 160
723
+
724
+ memcpy(lpVtbl, @pITask, 4)
725
+ memcpy(table, lpVtbl.unpack('L').first, 160)
726
+ table = table.unpack('L*')
727
+
728
+ getPriority = Win32::API::Function.new(table[39], 'PP', 'L')
729
+
730
+ ptr = 0.chr * 4
731
+ hr = getPriority.call(@pITask, ptr)
732
+
733
+ if hr >= S_OK
734
+ pri = ptr.unpack('L').first
735
+ if (pri & IDLE_PRIORITY_CLASS) != 0
736
+ priority = 'idle'
737
+ elsif (pri & NORMAL_PRIORITY_CLASS) != 0
738
+ priority = 'normal'
739
+ elsif (pri & HIGH_PRIORITY_CLASS) != 0
740
+ priority = 'high'
741
+ elsif (pri & REALTIME_PRIORITY_CLASS) != 0
742
+ priority = 'realtime'
743
+ elsif (pri & BELOW_NORMAL_PRIORITY_CLASS) != 0
744
+ priority = 'below_normal'
745
+ elsif (pri & ABOVE_NORMAL_PRIORITY_CLASS) != 0
746
+ priority = 'above_normal'
747
+ else
748
+ priority = 'unknown'
749
+ end
750
+ else
751
+ raise Error, get_last_error
752
+ end
753
+
754
+ priority
755
+ end
756
+
757
+ # Sets the priority of the task. The +priority+ should be a numeric
758
+ # priority constant value.
759
+ #
760
+ def priority=(priority)
761
+ raise Error, 'null pointer' if @pITS.nil?
762
+ raise Error, 'No currently active task' if @pITask.nil?
763
+ raise TypeError unless priority.is_a?(Numeric)
764
+
765
+ lpVtbl = 0.chr * 4
766
+ table = 0.chr * 156
767
+
768
+ memcpy(lpVtbl, @pITask, 4)
769
+ memcpy(table, lpVtbl.unpack('L').first, 156)
770
+ table = table.unpack('L*')
771
+
772
+ setPriority = Win32::API::Function.new(table[38], 'PL', 'L')
773
+ hr = setPriority.call(@pITask, priority)
774
+
775
+ if hr != S_OK
776
+ raise Error, get_last_error(hr)
777
+ end
778
+
779
+ priority
780
+ end
781
+
782
+ # Creates a new work item (scheduled job) with the given +trigger+. The
783
+ # trigger variable is a hash of options that define when the scheduled
784
+ # job should run.
785
+ #
786
+ def new_work_item(task, trigger)
787
+ raise TypeError unless trigger.is_a?(Hash)
788
+ raise Error, 'null pointer' if @pITS.nil?
789
+
790
+ trigger = transform_and_validate(trigger)
791
+
792
+ if @pITask
793
+ lpVtbl = 0.chr * 4
794
+ table = 0.chr * 12
795
+
796
+ memcpy(lpVtbl, @pITask, 4)
797
+ memcpy(table, lpVtbl.unpack('L').first, 12)
798
+ table = table.unpack('L*')
799
+
800
+ release = Win32::API::Function.new(table[2], 'P', 'L')
801
+ release.call(@pITask)
802
+
803
+ @pITask = nil
804
+ end
805
+
806
+ task = multi_to_wide(task)
807
+ lpVtbl = 0.chr * 4
808
+ table = 0.chr * 36
809
+
810
+ memcpy(lpVtbl, @pITS, 4)
811
+ memcpy(table, lpVtbl.unpack('L').first, 36)
812
+ table = table.unpack('L*')
813
+
814
+ newWorkItem = Win32::API::Function.new(table[8], 'PPPPP', 'L')
815
+
816
+ ptr = 0.chr * 4
817
+
818
+ hr = newWorkItem.call(@pITS, task, CLSID_CTask, IID_ITask, ptr)
819
+
820
+ if FAILED(hr)
821
+ raise Error, get_last_error
822
+ end
823
+
824
+ @pITask = ptr.unpack('L').first
825
+ lpVtbl = 0.chr * 4
826
+ table = 0.chr * 16
827
+
828
+ memcpy(lpVtbl, @pITask, 4)
829
+ memcpy(table, lpVtbl.unpack('L').first, 16)
830
+ table = table.unpack('L*')
831
+
832
+ createTrigger = Win32::API::Function.new(table[3], 'PPP', 'L')
833
+ p1 = 0.chr * 4
834
+ p2 = 0.chr * 4
835
+
836
+ hr = createTrigger.call(@pITask, p1, p2)
837
+
838
+ if hr != S_OK
839
+ raise Error, get_last_error
840
+ end
841
+
842
+ pITaskTrigger = p2.unpack('L').first
843
+ lpVtbl = 0.chr * 4
844
+ table = 0.chr * 16
845
+
846
+ memcpy(lpVtbl, pITaskTrigger, 4)
847
+ memcpy(table, lpVtbl.unpack('L').first, 16)
848
+ table = table.unpack('L*')
849
+
850
+ release = Win32::API::Function.new(table[2], 'P', 'L')
851
+ setTrigger = Win32::API::Function.new(table[3], 'PP', 'L')
852
+
853
+ type1 = 0
854
+ type2 = 0
855
+ tmp = trigger['type']
856
+ tmp = nil unless tmp.is_a?(Hash)
857
+
858
+ case trigger['trigger_type']
859
+ when TASK_TIME_TRIGGER_DAILY
860
+ if tmp && tmp['days_interval']
861
+ type1 = [tmp['days_interval'],0].pack('SS').unpack('L').first
862
+ end
863
+ when TASK_TIME_TRIGGER_WEEKLY
864
+ if tmp && tmp['weeks_interval'] && tmp['days_of_week']
865
+ type1 = [tmp['weeks_interval'],tmp['days_of_week']].pack('SS').unpack('L').first
866
+ end
867
+ when TASK_TIME_TRIGGER_MONTHLYDATE
868
+ if tmp && tmp['months'] && tmp['days']
869
+ type2 = [tmp['months'],0].pack('SS').unpack('L').first
870
+ type1 = tmp['days']
871
+ end
872
+ when TASK_TIME_TRIGGER_MONTHLYDOW
873
+ if tmp && tmp['weeks'] && tmp['days_of_week'] && tmp['months']
874
+ type1 = [tmp['weeks'],tmp['days_of_week']].pack('SS').unpack('L').first
875
+ type2 = [tmp['months'],0].pack('SS').unpack('L').first
876
+ end
877
+ when TASK_TIME_TRIGGER_ONCE
878
+ # Do nothing. The Type member of the TASK_TRIGGER struct is ignored.
879
+ else
880
+ raise Error, 'Unknown trigger type'
881
+ end
882
+
883
+ pTrigger = [
884
+ 48,
885
+ 0,
886
+ trigger['start_year'] || 0,
887
+ trigger['start_month'] || 0,
888
+ trigger['start_day'] || 0,
889
+ trigger['end_year'] || 0,
890
+ trigger['end_month'] || 0,
891
+ trigger['end_day'] || 0,
892
+ trigger['start_hour'] || 0,
893
+ trigger['start_minute'] || 0,
894
+ trigger['minutes_duration'] || 0,
895
+ trigger['minutes_interval'] || 0,
896
+ trigger['flags'] || 0,
897
+ trigger['trigger_type'] || 0,
898
+ type1,
899
+ type2,
900
+ 0,
901
+ trigger['random_minutes_interval'] || 0
902
+ ].pack('S10L4LLSS')
903
+
904
+ hr = setTrigger.call(pITaskTrigger, pTrigger)
905
+
906
+ if hr != S_OK
907
+ raise Error, get_last_error
908
+ end
909
+
910
+ release.call(pITaskTrigger)
911
+
912
+ self
913
+ end
914
+
915
+ alias :new_task :new_work_item
916
+
917
+ # Returns the number of triggers associated with the active task.
918
+ #
919
+ def trigger_count
920
+ raise Error, "null pointer" if @pITS.nil?
921
+ raise Error, "No currently active task" if @pITask.nil?
922
+
923
+ lpVtbl = 0.chr * 4
924
+ table = 0.chr * 24
925
+
926
+ memcpy(lpVtbl, @pITask,4)
927
+ memcpy(table, lpVtbl.unpack('L').first, 24)
928
+ table = table.unpack('L*')
929
+
930
+ getTriggerCount = Win32::API::Function.new(table[5], 'PP', 'L')
931
+ ptr = 0.chr * 4
932
+ hr = getTriggerCount.call(@pITask, ptr)
933
+
934
+ if hr >= S_OK
935
+ count = ptr.unpack('L').first
936
+ else
937
+ raise Error, get_last_error
938
+ end
939
+
940
+ count
941
+ end
942
+
943
+ # Returns a string that describes the current trigger at the specified
944
+ # index for the active task.
945
+ #
946
+ # Example: "At 7:14 AM every day, starting 4/11/2009"
947
+ #
948
+ def trigger_string(index)
949
+ raise Error, 'null pointer' if @pITS.nil?
950
+ raise Error, 'No currently active task' if @pITask.nil?
951
+ raise TypeError unless index.is_a?(Numeric)
952
+
953
+ lpVtbl = 0.chr * 4
954
+ table = 0.chr * 32
955
+
956
+ memcpy(lpVtbl, @pITask, 4)
957
+ memcpy(table, lpVtbl.unpack('L').first, 32)
958
+ table = table.unpack('L*')
959
+
960
+ getTriggerString = Win32::API::Function.new(table[7], 'PLP', 'L')
961
+ ptr = 0.chr * 4
962
+ hr = getTriggerString.call(@pITask, index, ptr)
963
+
964
+ if hr == S_OK
965
+ str = 0.chr * 256
966
+ wcscpy(str, ptr.unpack('L').first)
967
+ trigger = wide_to_multi(str)
968
+ CoTaskMemFree(ptr.unpack('L').first)
969
+ else
970
+ raise Error, get_last_error
971
+ end
972
+
973
+ trigger
974
+ end
975
+
976
+ # Deletes the trigger at the specified index.
977
+ #
978
+ def delete_trigger(index)
979
+ raise Error, 'null pointer' if @pITS.nil?
980
+ raise Error, 'No currently active task' if @pITask.nil?
981
+
982
+ lpVtbl = 0.chr * 4
983
+ table = 0.chr * 20
984
+
985
+ memcpy(lpVtbl, @pITask, 4)
986
+ memcpy(table, lpVtbl.unpack('L').first, 20)
987
+ table = table.unpack('L*')
988
+
989
+ deleteTrigger = Win32::API::Function.new(table[4], 'PL', 'L')
990
+ hr = deleteTrigger.call(@pITask,index)
991
+
992
+ if hr != S_OK
993
+ raise Error, get_last_error
994
+ end
995
+
996
+ index
997
+ end
998
+
999
+ # Returns a hash that describes the trigger at the given index for the
1000
+ # current task.
1001
+ #
1002
+ def trigger(index)
1003
+ raise Error, 'null pointer' if @pITS.nil?
1004
+ raise Error, 'No currently active task' if @pITask.nil?
1005
+
1006
+ lpVtbl = 0.chr * 4
1007
+ table = 0.chr * 28
1008
+
1009
+ memcpy(lpVtbl, @pITask, 4)
1010
+ memcpy(table, lpVtbl.unpack('L').first, 28)
1011
+ table = table.unpack('L*')
1012
+
1013
+ getTrigger = Win32::API::Function.new(table[6], 'PLP', 'L')
1014
+ ptr = 0.chr * 4
1015
+ hr = getTrigger.call(@pITask, index, ptr)
1016
+
1017
+ if hr != S_OK
1018
+ raise Error, get_last_error
1019
+ end
1020
+
1021
+ pITaskTrigger = ptr.unpack('L').first
1022
+ lpVtbl = 0.chr * 4
1023
+ table = 0.chr * 20
1024
+
1025
+ memcpy(lpVtbl, pITaskTrigger, 4)
1026
+ memcpy(table, lpVtbl.unpack('L').first, 20)
1027
+ table = table.unpack('L*')
1028
+
1029
+ release = Win32::API::Function.new(table[2], 'P', 'L')
1030
+ getTrigger = Win32::API::Function.new(table[4], 'PP', 'L')
1031
+
1032
+ pTrigger = [48].pack('S') + 0.chr * 46
1033
+ hr = getTrigger.call(pITaskTrigger, pTrigger)
1034
+
1035
+ if hr != S_OK
1036
+ error = get_last_error
1037
+ release.call(pITaskTrigger)
1038
+ raise Error, error
1039
+ end
1040
+
1041
+ tr = pTrigger.unpack('S10L4LLSS')
1042
+
1043
+ trigger = {}
1044
+ trigger['start_year'] = tr[2]
1045
+ trigger['start_month'] = tr[3]
1046
+ trigger['start_day'] = tr[4]
1047
+ trigger['end_year'] = tr[5]
1048
+ trigger['end_month'] = tr[6]
1049
+ trigger['end_day'] = tr[7]
1050
+ trigger['start_hour'] = tr[8]
1051
+ trigger['start_minute'] = tr[9]
1052
+ trigger['minutes_duration'] = tr[10]
1053
+ trigger['minutes_interval'] = tr[11]
1054
+ trigger['flags'] = tr[12]
1055
+ trigger['trigger_type'] = tr[13]
1056
+ trigger['random_minutes_interval'] = tr[17]
1057
+
1058
+ case tr[13]
1059
+ when TASK_TIME_TRIGGER_DAILY
1060
+ tmp = {}
1061
+ tmp['days_interval'] = [tr[14]].pack('L').unpack('SS').first
1062
+ trigger['type'] = tmp
1063
+ when TASK_TIME_TRIGGER_WEEKLY
1064
+ tmp = {}
1065
+ tmp['weeks_interval'],tmp['days_of_week'] = [tr[14]].pack('L').unpack('SS')
1066
+ trigger['type'] = tmp
1067
+ when TASK_TIME_TRIGGER_MONTHLYDATE
1068
+ tmp = {}
1069
+ tmp['days'] = tr[14]
1070
+ tmp['months'] = [tr[15]].pack('L').unpack('SS').first
1071
+ trigger['type'] = tmp
1072
+ when TASK_TIME_TRIGGER_MONTHLYDOW
1073
+ tmp = {}
1074
+ tmp['weeks'],tmp['days_of_week'] = [tr[14]].pack('L').unpack('SS')
1075
+ tmp['months'] = [tr[15]].pack('L').unpack('SS').first
1076
+ trigger['type'] = tmp
1077
+ when TASK_TIME_TRIGGER_ONCE
1078
+ tmp = {}
1079
+ tmp['once'] = nil
1080
+ trigger['type'] = tmp
1081
+ else
1082
+ raise Error, 'Unknown trigger type'
1083
+ end
1084
+
1085
+ release.call(pITaskTrigger)
1086
+
1087
+ trigger
1088
+ end
1089
+
1090
+ # Sets the trigger for the currently active task.
1091
+ #
1092
+ def trigger=(trigger)
1093
+ raise Error, 'null pointer' if @pITS.nil?
1094
+ raise Error, 'No currently active task' if @pITask.nil?
1095
+ raise TypeError unless trigger.is_a?(Hash)
1096
+
1097
+ lpVtbl = 0.chr * 4
1098
+ table = 0.chr * 16
1099
+
1100
+ memcpy(lpVtbl, @pITask, 4)
1101
+ memcpy(table, lpVtbl.unpack('L').first, 16)
1102
+ table = table.unpack('L*')
1103
+
1104
+ createTrigger = Win32::API::Function.new(table[3], 'PPP', 'L')
1105
+
1106
+ p1 = 0.chr * 4
1107
+ p2 = 0.chr * 4
1108
+
1109
+ hr = createTrigger.call(@pITask, p1, p2)
1110
+
1111
+ if hr != S_OK
1112
+ raise Error, get_last_error
1113
+ end
1114
+
1115
+ pITaskTrigger = p2.unpack('L').first
1116
+ lpVtbl = 0.chr * 4
1117
+ table = 0.chr * 16
1118
+
1119
+ memcpy(lpVtbl, pITaskTrigger, 4)
1120
+ memcpy(table, lpVtbl.unpack('L').first, 16)
1121
+ table = table.unpack('L*')
1122
+
1123
+ release = Win32::API::Function.new(table[2], 'P', 'L')
1124
+ setTrigger = Win32::API::Function.new(table[3], 'PP', 'L')
1125
+
1126
+ type1 = 0
1127
+ type2 = 0
1128
+ tmp = trigger['type']
1129
+ tmp = nil unless tmp.is_a?(Hash)
1130
+
1131
+ case trigger['trigger_type']
1132
+ when TASK_TIME_TRIGGER_DAILY
1133
+ if tmp && tmp['days_interval']
1134
+ type1 = [tmp['days_interval'],0].pack('SS').unpack('L').first
1135
+ end
1136
+ when TASK_TIME_TRIGGER_WEEKLY
1137
+ if tmp && tmp['weeks_interval'] && tmp['days_of_week']
1138
+ type1 = [tmp['weeks_interval'],tmp['days_of_week']].pack('SS').unpack('L').first
1139
+ end
1140
+ when TASK_TIME_TRIGGER_MONTHLYDATE
1141
+ if tmp && tmp['months'] && tmp['days']
1142
+ type2 = [tmp['months'],0].pack('SS').unpack('L').first
1143
+ type1 = tmp['days']
1144
+ end
1145
+ when TASK_TIME_TRIGGER_MONTHLYDOW
1146
+ if tmp && tmp['weeks'] && tmp['days_of_week'] && tmp['months']
1147
+ type1 = [tmp['weeks'],tmp['days_of_week']].pack('SS').unpack('L').first
1148
+ type2 = [tmp['months'],0].pack('SS').unpack('L').first
1149
+ end
1150
+ when TASK_TIME_TRIGGER_ONCE
1151
+ # Do nothing. The Type member of the TASK_TRIGGER struct is ignored.
1152
+ else
1153
+ raise Error, 'Unknown trigger type'
1154
+ end
1155
+
1156
+ pTrigger = [
1157
+ 48,
1158
+ 0,
1159
+ trigger['start_year'] || 0,
1160
+ trigger['start_month'] || 0,
1161
+ trigger['start_day'] || 0,
1162
+ trigger['end_year'] || 0,
1163
+ trigger['end_month'] || 0,
1164
+ trigger['end_day'] || 0,
1165
+ trigger['start_hour'] || 0,
1166
+ trigger['start_minute'] || 0,
1167
+ trigger['minutes_duration'] || 0,
1168
+ trigger['minutes_interval'] || 0,
1169
+ trigger['flags'] || 0,
1170
+ trigger['trigger_type'] || 0,
1171
+ type1,
1172
+ type2,
1173
+ 0,
1174
+ trigger['random_minutes_interval'] || 0
1175
+ ].pack('S10L4LLSS')
1176
+
1177
+ hr = setTrigger.call(pITaskTrigger, pTrigger)
1178
+
1179
+ if hr != S_OK
1180
+ raise Error, get_last_error
1181
+ end
1182
+
1183
+ release.call(pITaskTrigger)
1184
+
1185
+ trigger
1186
+ end
1187
+
1188
+ # Adds a trigger at the specified index.
1189
+ #
1190
+ def add_trigger(index, trigger)
1191
+ raise Error, 'null pointer' if @pITS.nil?
1192
+ raise Error, 'No currently active task' if @pITask.nil?
1193
+ raise TypeError unless trigger.is_a?(Hash)
1194
+
1195
+ lpVtbl = 0.chr * 4
1196
+ table = 0.chr * 28
1197
+
1198
+ memcpy(lpVtbl, @pITask, 4)
1199
+ memcpy(table, lpVtbl.unpack('L').first, 28)
1200
+ table = table.unpack('L*')
1201
+
1202
+ getTrigger = Win32::API::Function.new(table[6], 'PLP', 'L')
1203
+ ptr = 0.chr * 4
1204
+ hr = getTrigger.call(@pITask, index, ptr)
1205
+
1206
+ if hr != S_OK
1207
+ raise Error, get_last_error
1208
+ end
1209
+
1210
+ pITaskTrigger = ptr.unpack('L').first
1211
+ lpVtbl = 0.chr * 4
1212
+ table = 0.chr * 16
1213
+
1214
+ memcpy(lpVtbl, pITaskTrigger,4)
1215
+ memcpy(table, lpVtbl.unpack('L').first,16)
1216
+ table = table.unpack('L*')
1217
+
1218
+ release = Win32::API::Function.new(table[2], 'P', 'L')
1219
+ setTrigger = Win32::API::Function.new(table[3], 'PP', 'L')
1220
+
1221
+ type1 = 0
1222
+ type2 = 0
1223
+ tmp = trigger['type']
1224
+ tmp = nil unless tmp.is_a?(Hash)
1225
+
1226
+ case trigger['trigger_type']
1227
+ when TASK_TIME_TRIGGER_DAILY
1228
+ if tmp && tmp['days_interval']
1229
+ type1 = [tmp['days_interval'],0].pack('SS').unpack('L').first
1230
+ end
1231
+ when TASK_TIME_TRIGGER_WEEKLY
1232
+ if tmp && tmp['weeks_interval'] && tmp['days_of_week']
1233
+ type1 = [tmp['weeks_interval'],tmp['days_of_week']].pack('SS').unpack('L').first
1234
+ end
1235
+ when TASK_TIME_TRIGGER_MONTHLYDATE
1236
+ if tmp && tmp['months'] && tmp['days']
1237
+ type2 = [tmp['months'],0].pack('SS').unpack('L').first
1238
+ type1 = tmp['days']
1239
+ end
1240
+ when TASK_TIME_TRIGGER_MONTHLYDOW
1241
+ if tmp && tmp['weeks'] && tmp['days_of_week'] && tmp['months']
1242
+ type1 = [tmp['weeks'],tmp['days_of_week']].pack('SS').unpack('L').first
1243
+ type2 = [tmp['months'],0].pack('SS').unpack('L').first
1244
+ end
1245
+ when TASK_TIME_TRIGGER_ONCE
1246
+ # Do nothing. The Type member of the TASK_TRIGGER struct is ignored.
1247
+ else
1248
+ raise Error, 'Unknown trigger type'
1249
+ end
1250
+
1251
+ pTrigger = [
1252
+ 48,
1253
+ 0,
1254
+ trigger['start_year'] || 0,
1255
+ trigger['start_month'] || 0,
1256
+ trigger['start_day'] || 0,
1257
+ trigger['end_year'] || 0,
1258
+ trigger['end_month'] || 0,
1259
+ trigger['end_day'] || 0,
1260
+ trigger['start_hour'] || 0,
1261
+ trigger['start_minute'] || 0,
1262
+ trigger['minutes_duration'] || 0,
1263
+ trigger['minutes_interval'] || 0,
1264
+ trigger['flags'] || 0,
1265
+ trigger['trigger_type'] || 0,
1266
+ type1,
1267
+ type2,
1268
+ 0,
1269
+ trigger['random_minutes_interval'] || 0
1270
+ ].pack('S10L4LLSS')
1271
+
1272
+ hr = setTrigger.call(pITaskTrigger, pTrigger)
1273
+
1274
+ if hr != S_OK
1275
+ raise Error, get_last_error
1276
+ end
1277
+
1278
+ release.call(pITaskTrigger)
1279
+ true
1280
+ end
1281
+
1282
+ # Returns the flags (integer) that modify the behavior of the work item. You
1283
+ # must OR the return value to determine the flags yourself.
1284
+ #
1285
+ def flags
1286
+ raise Error, 'null pointer' if @pITask.nil?
1287
+
1288
+ lpVtbl = 0.chr * 4
1289
+ table = 0.chr * 120
1290
+
1291
+ memcpy(lpVtbl, @pITask, 4)
1292
+ memcpy(table, lpVtbl.unpack('L').first, 120)
1293
+ table = table.unpack('L*')
1294
+
1295
+ getFlags = Win32::API::Function.new(table[29], 'PP', 'L')
1296
+ ptr = 0.chr * 4
1297
+ hr = getFlags.call(@pITask, ptr)
1298
+
1299
+ if hr != S_OK
1300
+ raise Error, get_last_error
1301
+ end
1302
+
1303
+ flags = ptr.unpack('L').first
1304
+ end
1305
+
1306
+ # Sets an OR'd value of flags that modify the behavior of the work item.
1307
+ #
1308
+ def flags=(flags)
1309
+ raise Error, 'null pointer' if @pITS.nil?
1310
+ raise Error, 'No currently active task' if @pITask.nil?
1311
+
1312
+ lpVtbl = 0.chr * 4
1313
+ table = 0.chr * 116
1314
+
1315
+ memcpy(lpVtbl, @pITask, 4)
1316
+ memcpy(table, lpVtbl.unpack('L').first, 116)
1317
+ table = table.unpack('L*')
1318
+
1319
+ setFlags = Win32::API::Function.new(table[28], 'PL', 'L')
1320
+ hr = setFlags.call(@pITask, flags)
1321
+
1322
+ if hr != S_OK
1323
+ raise Error, get_last_error
1324
+ end
1325
+
1326
+ flags
1327
+ end
1328
+
1329
+ # Returns the status of the currently active task. Possible values are
1330
+ # 'ready', 'running', 'not scheduled' or 'unknown'.
1331
+ #
1332
+ def status
1333
+ raise Error, 'null pointer' if @pITask.nil?
1334
+ raise Error, 'No currently active task' if @pITask.nil?
1335
+
1336
+ lpVtbl = 0.chr * 4
1337
+ table = 0.chr * 68
1338
+
1339
+ memcpy(lpVtbl,@pITask,4)
1340
+ memcpy(table,lpVtbl.unpack('L').first,68)
1341
+ table = table.unpack('L*')
1342
+
1343
+ getStatus = Win32::API::Function.new(table[16], 'PP', 'L')
1344
+ ptr = 0.chr * 4
1345
+ hr = getStatus.call(@pITask, ptr)
1346
+
1347
+ if hr != S_OK
1348
+ raise Error,get_last_error
1349
+ end
1350
+
1351
+ st = ptr.unpack('L').first
1352
+
1353
+ case st
1354
+ when 0x00041300 # SCHED_S_TASK_READY
1355
+ status = 'ready'
1356
+ when 0x00041301 # SCHED_S_TASK_RUNNING
1357
+ status = 'running'
1358
+ when 0x00041305 # SCHED_S_TASK_NOT_SCHEDULED
1359
+ status = 'not scheduled'
1360
+ else
1361
+ status = 'unknown'
1362
+ end
1363
+
1364
+ status
1365
+ end
1366
+
1367
+ # Returns the exit code from the last scheduled run.
1368
+ #
1369
+ def exit_code
1370
+ raise Error, 'null pointer' if @pITask.nil?
1371
+ raise Error, 'No currently active task' if @pITask.nil?
1372
+
1373
+ lpVtbl = 0.chr * 4
1374
+ table = 0.chr * 72
1375
+
1376
+ memcpy(lpVtbl, @pITask, 4)
1377
+ memcpy(table, lpVtbl.unpack('L').first, 72)
1378
+ table = table.unpack('L*')
1379
+
1380
+ getExitCode = Win32::API::Function.new(table[17], 'PP', 'L')
1381
+ ptr = 0.chr * 4
1382
+ hr = getExitCode.call(@pITask, ptr)
1383
+
1384
+ if hr > 0x80000000
1385
+ raise Error, get_last_error
1386
+ end
1387
+
1388
+ exit_code = ptr.unpack('L').first
1389
+ end
1390
+
1391
+ # Returns the comment associated with the task, if any.
1392
+ #
1393
+ def comment
1394
+ raise Error, 'null pointer' if @pITask.nil?
1395
+ raise Error, 'No currently active task' if @pITask.nil?
1396
+
1397
+ lpVtbl = 0.chr * 4
1398
+ table = 0.chr * 80
1399
+
1400
+ memcpy(lpVtbl, @pITask, 4)
1401
+ memcpy(table, lpVtbl.unpack('L').first, 80)
1402
+ table = table.unpack('L*')
1403
+
1404
+ getComment = Win32::API::Function.new(table[19], 'PP', 'L')
1405
+ ptr = 0.chr * 4
1406
+ hr = getComment.call(@pITask, ptr)
1407
+
1408
+ if hr != S_OK
1409
+ raise Error,get_last_error
1410
+ end
1411
+
1412
+ str = 0.chr * 256
1413
+ wcscpy(str, ptr.unpack('L').first)
1414
+ CoTaskMemFree(ptr.unpack('L').first)
1415
+ wide_to_multi(str)
1416
+ end
1417
+
1418
+ # Sets the comment for the task.
1419
+ #
1420
+ def comment=(comment)
1421
+ raise Error, 'null pointer' if @pITask.nil?
1422
+ raise Error, 'No currently active task' if @pITask.nil?
1423
+ raise TypeError unless comment.is_a?(String)
1424
+
1425
+ lpVtbl = 0.chr * 4
1426
+ table = 0.chr * 76
1427
+
1428
+ memcpy(lpVtbl, @pITask, 4)
1429
+ memcpy(table, lpVtbl.unpack('L').first, 76)
1430
+ table = table.unpack('L*')
1431
+
1432
+ setComment = Win32::API::Function.new(table[18], 'PP', 'L')
1433
+ comment_w = multi_to_wide(comment)
1434
+ hr = setComment.call(@pITask, comment_w)
1435
+
1436
+ if hr != S_OK
1437
+ raise Error, get_last_error
1438
+ end
1439
+
1440
+ comment
1441
+ end
1442
+
1443
+ # Returns the name of the user who created the task.
1444
+ #
1445
+ def creator
1446
+ raise Error, 'null pointer' if @pITask.nil?
1447
+ raise Error, 'No currently active task' if @pITask.nil?
1448
+
1449
+ lpVtbl = 0.chr * 4
1450
+ table = 0.chr * 88
1451
+
1452
+ memcpy(lpVtbl, @pITask, 4)
1453
+ memcpy(table, lpVtbl.unpack('L').first, 88)
1454
+ table = table.unpack('L*')
1455
+
1456
+ getCreator = Win32::API::Function.new(table[21], 'PP', 'L')
1457
+ ptr = 0.chr * 4
1458
+ hr = getCreator.call(@pITask, ptr)
1459
+
1460
+ if hr != S_OK
1461
+ raise Error, get_last_error
1462
+ end
1463
+
1464
+ str = 0.chr * 256
1465
+ wcscpy(str, ptr.unpack('L').first)
1466
+ CoTaskMemFree(ptr.unpack('L').first)
1467
+ wide_to_multi(str)
1468
+ end
1469
+
1470
+ # Sets the creator for the task.
1471
+ #
1472
+ def creator=(creator)
1473
+ raise Error, 'null pointer' if @pITask.nil?
1474
+ raise Error, 'No currently active task' if @pITask.nil?
1475
+ raise TypeError unless creator.is_a?(String)
1476
+
1477
+ lpVtbl = 0.chr * 4
1478
+ table = 0.chr * 84
1479
+
1480
+ memcpy(lpVtbl, @pITask, 4)
1481
+ memcpy(table, lpVtbl.unpack('L').first, 84)
1482
+ table = table.unpack('L*')
1483
+
1484
+ setCreator = Win32::API::Function.new(table[20], 'PP', 'L')
1485
+ creator_w = multi_to_wide(creator)
1486
+ hr = setCreator.call(@pITask, creator_w)
1487
+
1488
+ if hr != S_OK
1489
+ raise Error, get_last_error
1490
+ end
1491
+
1492
+ creator
1493
+ end
1494
+
1495
+ # Returns a Time object that indicates the next time the task will run.
1496
+ #
1497
+ def next_run_time
1498
+ raise Error, 'null pointer' if @pITask.nil?
1499
+ raise Error, 'No currently active task' if @pITask.nil?
1500
+
1501
+ lpVtbl = 0.chr * 4
1502
+ table = 0.chr * 40
1503
+
1504
+ memcpy(lpVtbl, @pITask, 4)
1505
+ memcpy(table, lpVtbl.unpack('L').first, 40)
1506
+ table = table.unpack('L*')
1507
+
1508
+ getNextRunTime = Win32::API::Function.new(table[9], 'PP', 'L')
1509
+ st = 0.chr * 16
1510
+ hr = getNextRunTime.call(@pITask, st)
1511
+
1512
+ if hr != S_OK
1513
+ raise Error, get_last_error
1514
+ end
1515
+
1516
+ a1,a2,_,a3,a4,a5,a6,a7 = st.unpack('S*')
1517
+ a7 *= 1000
1518
+
1519
+ Time.local(a1,a2,a3,a4,a5,a6,a7)
1520
+ end
1521
+
1522
+ # Returns a Time object indicating the most recent time the task ran or
1523
+ # nil if the task has never run.
1524
+ #
1525
+ def most_recent_run_time
1526
+ raise Error, 'null pointer' if @pITask.nil?
1527
+ raise Error, 'No currently active task' if @pITask.nil?
1528
+
1529
+ lpVtbl = 0.chr * 4
1530
+ table = 0.chr * 64
1531
+
1532
+ memcpy(lpVtbl, @pITask, 4)
1533
+ memcpy(table, lpVtbl.unpack('L').first, 64)
1534
+ table = table.unpack('L*')
1535
+
1536
+ getMostRecentRunTime = Win32::API::Function.new(table[15], 'PP', 'L')
1537
+ st = 0.chr * 16
1538
+ hr = getMostRecentRunTime.call(@pITask, st)
1539
+
1540
+ if hr == 0x00041303 # SCHED_S_TASK_HAS_NOT_RUN
1541
+ time = nil
1542
+ elsif hr == S_OK
1543
+ a1, a2, _, a3, a4, a5, a6, a7 = st.unpack('S*')
1544
+ a7 *= 1000
1545
+ time = Time.local(a1, a2, a3, a4, a5, a6, a7)
1546
+ else
1547
+ raise Error, get_last_error
1548
+ end
1549
+
1550
+ time
1551
+ end
1552
+
1553
+ # Returns the maximum length of time, in milliseconds, that the task
1554
+ # will run before terminating.
1555
+ #
1556
+ def max_run_time
1557
+ raise Error, 'null pointer' if @pITask.nil?
1558
+ raise Error, 'No currently active task' if @pITask.nil?
1559
+
1560
+ lpVtbl = 0.chr * 4
1561
+ table = 0.chr * 176
1562
+
1563
+ memcpy(lpVtbl, @pITask, 4)
1564
+ memcpy(table, lpVtbl.unpack('L').first, 176)
1565
+ table = table.unpack('L*')
1566
+
1567
+ getMaxRunTime = Win32::API::Function.new(table[43], 'PP', 'L')
1568
+
1569
+ ptr = 0.chr * 4
1570
+ hr = getMaxRunTime.call(@pITask, ptr)
1571
+
1572
+ if hr != S_OK
1573
+ raise Error, get_last_error
1574
+ end
1575
+
1576
+ max_run_time = ptr.unpack('L').first
1577
+ end
1578
+
1579
+ # Sets the maximum length of time, in milliseconds, that the task can run
1580
+ # before terminating. Returns the value you specified if successful.
1581
+ #
1582
+ def max_run_time=(max_run_time)
1583
+ raise Error, 'null pointer' if @pITask.nil?
1584
+ raise Error, 'No currently active task' if @pITask.nil?
1585
+ raise TypeError unless max_run_time.is_a?(Numeric)
1586
+
1587
+ lpVtbl = 0.chr * 4
1588
+ table = 0.chr * 172
1589
+
1590
+ memcpy(lpVtbl, @pITask, 4)
1591
+ memcpy(table, lpVtbl.unpack('L').first, 172)
1592
+ table = table.unpack('L*')
1593
+
1594
+ setMaxRunTime = Win32::API::Function.new(table[42], 'PL', 'L')
1595
+ hr = setMaxRunTime.call(@pITask, max_run_time)
1596
+
1597
+ if hr != S_OK
1598
+ raise Error,get_last_error
1599
+ end
1600
+
1601
+ max_run_time
1602
+ end
1603
+
1604
+ # Returns whether or not the scheduled task exists.
1605
+ def exists?(job_name)
1606
+ bool = false
1607
+ Dir.foreach('C:/Windows/Tasks'){ |file|
1608
+ if File.basename(file, '.job') == job_name
1609
+ bool = true
1610
+ break
1611
+ end
1612
+ }
1613
+ bool
1614
+ end
1615
+
1616
+ private
1617
+
1618
+ # Used for the new_work_item method
1619
+ ValidTriggerKeys = [
1620
+ 'end_day',
1621
+ 'end_month',
1622
+ 'end_year',
1623
+ 'flags',
1624
+ 'minutes_duration',
1625
+ 'minutes_interval',
1626
+ 'random_minutes_interval',
1627
+ 'start_day',
1628
+ 'start_hour',
1629
+ 'start_minute',
1630
+ 'start_month',
1631
+ 'start_year',
1632
+ 'trigger_type',
1633
+ 'type'
1634
+ ]
1635
+
1636
+ ValidTypeKeys = [
1637
+ 'days_interval',
1638
+ 'weeks_interval',
1639
+ 'days_of_week',
1640
+ 'months',
1641
+ 'days',
1642
+ 'weeks'
1643
+ ]
1644
+
1645
+ # Private method that validates keys, and converts all keys to lowercase
1646
+ # strings.
1647
+ #
1648
+ def transform_and_validate(hash)
1649
+ new_hash = {}
1650
+
1651
+ hash.each{ |key, value|
1652
+ key = key.to_s.downcase
1653
+ if key == 'type'
1654
+ new_type_hash = {}
1655
+ raise ArgumentError unless value.is_a?(Hash)
1656
+ value.each{ |subkey, subvalue|
1657
+ subkey = subkey.to_s.downcase
1658
+ if ValidTypeKeys.include?(subkey)
1659
+ new_type_hash[subkey] = subvalue
1660
+ else
1661
+ raise ArgumentError, "Invalid type key '#{subkey}'"
1662
+ end
1663
+ }
1664
+ new_hash[key] = new_type_hash
1665
+ else
1666
+ if ValidTriggerKeys.include?(key)
1667
+ new_hash[key] = value
1668
+ else
1669
+ raise ArgumentError, "Invalid key '#{key}'"
1670
+ end
1671
+ end
1672
+ }
1673
+
1674
+ new_hash
1675
+ end
1676
+ end
1677
+ end