win32-taskscheduler 0.2.2 → 0.3.0

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