win32-taskscheduler 0.2.2 → 0.3.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.
@@ -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