win32-taskscheduler 1.0.10 → 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 708456348a54a2c6be8ec93ab9902d108170ad0289ac17c2104b4dbbe43bfd0b
4
- data.tar.gz: 3ba2b649ebf7f15bfe0e22b3f60d8462b925a26e0fb8277a4a5017647bf85a18
3
+ metadata.gz: 5b6d6ad467e4dd3518af4776d1937f9227afbc42bf743c3f3b09480141e08e15
4
+ data.tar.gz: 2a3a9d694c22b5734e30c50fe50142af6b23e6d467c8e38e33ee48bba3a15952
5
5
  SHA512:
6
- metadata.gz: b2cf2048d079aaf38b865b8ff391bb09e7926006f743503b4a4322d84cfd7d814c40daf105dac9846109d45e1e43f153702e4e38ace8674cdebb9d46e85fd74a
7
- data.tar.gz: 5e5e2c0fb0c1f45e90a19c7b50477958d18416a148a5ce16488121e06e947cdd2c7078a257847b4ff9a5e8b74351e0cc289e5ac7f6ae664261f2eaa044199095
6
+ metadata.gz: e5ca12f5424d5f790107a1af5ff8838e7931d846cdece3959627c0500a34ff2bcdae1fa749f253a89f4b523065ca27b15f1e629dc11ad586a990854cfbcb2a83
7
+ data.tar.gz: c423125d42002ae09d4bc1fab148004a325e399cbdeb5bb48fb6210f1f3f5af510a4ecd9f0345fd8ec1e4291905c214d2e8d58eb261cd5b7e791d2b3c59c9c49
@@ -4,21 +4,28 @@ Note: this log contains only changes from win32-taskscheduler release 0.4.0 and
4
4
  -- it does not contain the changes from prior releases. To view change history
5
5
  prior to release 0.4.0, please visit the [source repository](https://github.com/chef/win32-taskscheduler/commits).
6
6
 
7
- <!-- latest_release 1.0.10 -->
8
- ## [win32-taskscheduler-1.0.10](https://github.com/chef/win32-taskscheduler/tree/win32-taskscheduler-1.0.10) (2018-07-24)
7
+ <!-- latest_release 1.0.12 -->
8
+ ## [win32-taskscheduler-1.0.12](https://github.com/chef/win32-taskscheduler/tree/win32-taskscheduler-1.0.12) (2018-10-11)
9
9
 
10
10
  #### Merged Pull Requests
11
- - Fix exists? method breaking task full path search [#62](https://github.com/chef/win32-taskscheduler/pull/62) ([Vasu1105](https://github.com/Vasu1105))
11
+ - Fixing user registration at Non English version of windows [#69](https://github.com/chef/win32-taskscheduler/pull/69) ([Nimesh-Msys](https://github.com/Nimesh-Msys))
12
12
  <!-- latest_release -->
13
13
 
14
- <!-- release_rollup since=1.0.9 -->
15
- ### Changes since 1.0.9 release
14
+ <!-- release_rollup since=1.0.10 -->
15
+ ### Changes since 1.0.10 release
16
16
 
17
17
  #### Merged Pull Requests
18
- - Fix exists? method breaking task full path search [#62](https://github.com/chef/win32-taskscheduler/pull/62) ([Vasu1105](https://github.com/Vasu1105)) <!-- 1.0.10 -->
18
+ - Fixing user registration at Non English version of windows [#69](https://github.com/chef/win32-taskscheduler/pull/69) ([Nimesh-Msys](https://github.com/Nimesh-Msys)) <!-- 1.0.12 -->
19
+ - Refactored configure_settings [#67](https://github.com/chef/win32-taskscheduler/pull/67) ([btm](https://github.com/btm)) <!-- 1.0.11 -->
19
20
  <!-- release_rollup -->
20
21
 
21
22
  <!-- latest_stable_release -->
23
+ ## [win32-taskscheduler-1.0.10](https://github.com/chef/win32-taskscheduler/tree/win32-taskscheduler-1.0.10) (2018-07-24)
24
+
25
+ #### Merged Pull Requests
26
+ - Fix exists? method breaking task full path search [#62](https://github.com/chef/win32-taskscheduler/pull/62) ([Vasu1105](https://github.com/Vasu1105))
27
+ <!-- latest_stable_release -->
28
+
22
29
  ## [win32-taskscheduler-1.0.9](https://github.com/chef/win32-taskscheduler/tree/win32-taskscheduler-1.0.9) (2018-07-23)
23
30
 
24
31
  #### Merged Pull Requests
@@ -29,7 +36,6 @@ prior to release 0.4.0, please visit the [source repository](https://github.com/
29
36
  - [MSYS-827] Add functional test cases [#58](https://github.com/chef/win32-taskscheduler/pull/58) ([Nimesh-Msys](https://github.com/Nimesh-Msys))
30
37
  - Add DisallowStartIfOnBatteries and StopIfGoingOnBatteries task configs [#61](https://github.com/chef/win32-taskscheduler/pull/61) ([dheerajd-msys](https://github.com/dheerajd-msys))
31
38
  - Fix priority should return unique value. [#60](https://github.com/chef/win32-taskscheduler/pull/60) ([Vasu1105](https://github.com/Vasu1105))
32
- <!-- latest_stable_release -->
33
39
 
34
40
  ## [win32-taskscheduler-1.0.2](https://github.com/chef/win32-taskscheduler/tree/win32-taskscheduler-1.0.2) (2018-06-13)
35
41
  - Fix for exists? method returning false for task without full path. [#43](https://github.com/chef/win32-taskscheduler/pull/43)([#Vasu1105](https://github.com/Vasu1105))
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.10
1
+ 1.0.12
@@ -6,30 +6,29 @@
6
6
  #
7
7
  # Modify as you see fit.
8
8
  #######################################################################
9
- require 'win32/taskscheduler'
10
- require 'fileutils'
11
- require 'pp'
9
+ require "win32/taskscheduler"
10
+ require "fileutils"
11
+ require "pp"
12
12
  include Win32
13
13
 
14
- puts 'VERSION: ' + TaskScheduler::VERSION
14
+ puts "VERSION: " + TaskScheduler::VERSION
15
15
 
16
16
  ts = TaskScheduler.new
17
17
 
18
18
  trigger = {
19
- "start_year" => 2009,
20
- "start_month" => 4,
21
- "start_day" => 11,
22
- "start_hour" => 7,
23
- "start_minute" => 14,
24
- "trigger_type" => TaskScheduler::DAILY,
25
- "type" => { "days_interval" => 1 }
19
+ start_year: 2009,
20
+ start_month: 4,
21
+ start_day: 11,
22
+ start_hour: 7,
23
+ start_minute: 14,
24
+ trigger_type: TaskScheduler::DAILY,
25
+ type: { "days_interval" => 1 }
26
26
  }
27
27
 
28
28
  unless ts.enum.grep(/foo/).length > 0
29
- ts.new_work_item("foo", trigger)
30
- ts.application_name = "notepad.exe"
31
- ts.save
32
- puts "Task Added"
29
+ ts.new_work_item("foo", trigger)
30
+ ts.application_name = "notepad.exe"
31
+ puts "Task Added"
33
32
  end
34
33
 
35
34
  ts.activate("foo")
@@ -39,7 +38,6 @@ ts.working_directory = "C:\\"
39
38
  puts "App name: " + ts.application_name
40
39
  puts "Creator: " + ts.creator
41
40
  puts "Exit code: " + ts.exit_code.to_s
42
- puts "Flags: " + ts.flags.to_s
43
41
  puts "Max run time: " + ts.max_run_time.to_s
44
42
  puts "Next run time: " + ts.next_run_time.to_s
45
43
  puts "Parameters: " + ts.parameters
@@ -329,36 +329,26 @@ module Win32
329
329
  # Sets the +user+ and +password+ for the given task. If the user and
330
330
  # password are set properly then true is returned.
331
331
  # throws TypeError if password is not provided for other than system users
332
- def set_account_information(user, password)
333
- raise TypeError unless user.is_a?(String)
334
- unless SYSTEM_USERS.include?(user.upcase)
335
- raise TypeError unless password.is_a?(String)
336
- end
332
+ def set_account_information(user_id, password)
333
+ check_credential_requirements(user_id, password)
337
334
  check_for_active_task
338
-
339
335
  @password = password
340
-
341
- begin
342
- @task = @root.RegisterTaskDefinition(
343
- @task.Path,
344
- @task.Definition,
345
- TASK_CREATE_OR_UPDATE,
346
- user,
347
- password,
348
- password ? TASK_LOGON_PASSWORD : TASK_LOGON_SERVICE_ACCOUNT
349
- )
350
- rescue WIN32OLERuntimeError => err
351
- raise Error, ole_error('RegisterTaskDefinition', err)
352
- end
353
-
336
+ register_task_definition(@task.Definition, @task.Path, user_id, password)
354
337
  true
355
338
  end
356
339
 
357
- # Returns the user associated with the task or nil if no user has yet
358
- # been associated with the task.
340
+ # Returns the user or group associated with the task or nil if no one has been associated with the task yet
341
+ #
342
+ # @return [String] user or group associated with the task
359
343
  #
360
344
  def account_information
361
- @task.nil? ? nil : @task.Definition.Principal.UserId
345
+ if @task.nil?
346
+ nil
347
+ elsif !@task.Definition.Principal.UserId.empty?
348
+ @task.Definition.Principal.UserId
349
+ else
350
+ @task.Definition.Principal.GroupId
351
+ end
362
352
  end
363
353
 
364
354
  # Returns the name of the application associated with the task. If
@@ -391,7 +381,7 @@ module Win32
391
381
  action.Path = app if action.Type == 0
392
382
  end
393
383
 
394
- update_task_definition(definition)
384
+ register_task_definition(definition)
395
385
 
396
386
  app
397
387
  end
@@ -426,7 +416,7 @@ module Win32
426
416
  action.Arguments = param if action.Type == 0
427
417
  end
428
418
 
429
- update_task_definition(definition)
419
+ register_task_definition(definition)
430
420
 
431
421
  param
432
422
  end
@@ -459,7 +449,7 @@ module Win32
459
449
  action.WorkingDirectory = dir if action.Type == 0
460
450
  end
461
451
 
462
- update_task_definition(definition)
452
+ register_task_definition(definition)
463
453
 
464
454
  dir
465
455
  end
@@ -511,7 +501,7 @@ module Win32
511
501
  definition = @task.Definition
512
502
  definition.Settings.Priority = priority
513
503
 
514
- update_task_definition(definition)
504
+ register_task_definition(definition)
515
505
 
516
506
  priority
517
507
  end
@@ -521,16 +511,12 @@ module Win32
521
511
  # job should run.
522
512
  #
523
513
  def new_work_item(task, trigger, userinfo = { user: nil, password: nil })
524
- raise TypeError unless userinfo.is_a?(Hash)
525
- raise TypeError unless task.is_a?(String)
526
- raise TypeError unless trigger.is_a?(Hash)
514
+ raise TypeError unless userinfo.is_a?(Hash) && task.is_a?(String) && trigger.is_a?(Hash)
527
515
 
528
- unless userinfo[:user].nil?
529
- raise TypeError unless userinfo[:user].is_a?(String)
530
- unless SYSTEM_USERS.include?(userinfo[:user])
531
- raise TypeError unless userinfo[:password].is_a?(String)
532
- end
533
- end
516
+ # If user ID is not given, consider it as a 'SYSTEM' user
517
+ userinfo[:user] = SERVICE_ACCOUNT_USERS.first if userinfo[:user].to_s.empty?
518
+
519
+ check_credential_requirements(userinfo[:user], userinfo[:password])
534
520
 
535
521
  taskDefinition = @service.NewTask(0)
536
522
  taskDefinition.RegistrationInfo.Description = ''
@@ -621,18 +607,8 @@ module Win32
621
607
  act.Path = 'cmd'
622
608
 
623
609
  @password = userinfo[:password]
624
- begin
625
- @task = @root.RegisterTaskDefinition(
626
- task,
627
- taskDefinition,
628
- TASK_CREATE_OR_UPDATE,
629
- userinfo[:user].nil? || userinfo[:user].empty? ? 'SYSTEM': userinfo[:user],
630
- userinfo[:password],
631
- userinfo[:password] ? TASK_LOGON_PASSWORD : TASK_LOGON_SERVICE_ACCOUNT
632
- )
633
- rescue WIN32OLERuntimeError => err
634
- raise Error, ole_error('RegisterTaskDefinition', err)
635
- end
610
+
611
+ register_task_definition(taskDefinition, task, userinfo[:user], userinfo[:password])
636
612
 
637
613
  @task = @root.GetTask(task)
638
614
  end
@@ -677,7 +653,7 @@ module Win32
677
653
 
678
654
  definition = @task.Definition
679
655
  definition.Triggers.Remove(index)
680
- update_task_definition(definition)
656
+ register_task_definition(definition)
681
657
 
682
658
  index
683
659
  end
@@ -860,7 +836,7 @@ module Win32
860
836
  end
861
837
  end
862
838
 
863
- update_task_definition(definition)
839
+ register_task_definition(definition)
864
840
 
865
841
  trigger
866
842
  end
@@ -947,7 +923,7 @@ module Win32
947
923
  trig.Delay = "PT#{trigger[:delay_duration]||0}M"
948
924
  end
949
925
 
950
- update_task_definition(definition)
926
+ register_task_definition(definition)
951
927
 
952
928
  true
953
929
  end
@@ -1004,7 +980,7 @@ module Win32
1004
980
 
1005
981
  definition = @task.Definition
1006
982
  definition.RegistrationInfo.Description = comment
1007
- update_task_definition(definition)
983
+ register_task_definition(definition)
1008
984
 
1009
985
  comment
1010
986
  end
@@ -1028,7 +1004,7 @@ module Win32
1028
1004
 
1029
1005
  definition = @task.Definition
1030
1006
  definition.RegistrationInfo.Author = creator
1031
- update_task_definition(definition)
1007
+ register_task_definition(definition)
1032
1008
 
1033
1009
  creator
1034
1010
  end
@@ -1059,18 +1035,6 @@ module Win32
1059
1035
  time
1060
1036
  end
1061
1037
 
1062
- # Returns idle settings for current active task
1063
- #
1064
- def idle_settings
1065
- check_for_active_task
1066
- idle_settings = {}
1067
- idle_settings[:idle_duration] = @task.Definition.Settings.IdleSettings.IdleDuration
1068
- idle_settings[:stop_on_idle_end] = @task.Definition.Settings.IdleSettings.StopOnIdleEnd
1069
- idle_settings[:wait_timeout] = @task.Definition.Settings.IdleSettings.WaitTimeout
1070
- idle_settings[:restart_on_idle] = @task.Definition.Settings.IdleSettings.RestartOnIdle
1071
- idle_settings
1072
- end
1073
-
1074
1038
  # Returns the execution time limit for current active task
1075
1039
  #
1076
1040
  def execution_time_limit
@@ -1120,89 +1084,93 @@ module Win32
1120
1084
 
1121
1085
  definition = @task.Definition
1122
1086
  definition.Settings.ExecutionTimeLimit = limit
1123
- update_task_definition(definition)
1087
+ register_task_definition(definition)
1124
1088
 
1125
1089
  max_run_time
1126
1090
  end
1127
1091
 
1128
- # Accepts a hash that lets you configure various task definition settings.
1129
- # The possible options are:
1130
- #
1131
- # * allow_demand_start
1132
- # * allow_hard_terminate
1133
- # * compatibility
1134
- # * delete_expired_task_after
1135
- # * disallowed_start_if_on_batteries
1136
- # * enabled
1137
- # * execution_time_limit (or max_run_time)
1138
- # * hidden
1139
- # * idle_settings
1140
- # * network_settings
1141
- # * priority
1142
- # * restart_count
1143
- # * restart_interval
1144
- # * run_only_if_idle
1145
- # * run_only_if_network_available
1146
- # * start_when_available
1147
- # * stop_if_going_on_batteries
1148
- # * wake_to_run
1149
- # * xml_text (or xml)
1092
+ # The Idle settings of a task
1150
1093
  #
1151
- def configure_settings(hash)
1152
- raise TypeError unless hash.is_a?(Hash)
1153
- check_for_active_task
1094
+ # @see https://docs.microsoft.com/en-us/windows/desktop/TaskSchd/idlesettings#properties
1095
+ #
1096
+ IdleSettings = %i[idle_duration restart_on_idle stop_on_idle_end wait_timeout]
1097
+
1098
+ # Configures tasks settings
1099
+ #
1100
+ # @param [Hash] settings_hash The settings to configure a task
1101
+ # @option settings_hash [Boolean] :allow_demand_start The subject
1102
+ # @option settings_hash [Boolean] :allow_hard_terminate
1103
+ # @option settings_hash [Boolean] :disallow_start_if_on_batteries
1104
+ # @option settings_hash [Boolean] :disallow_start_on_remote_app_session
1105
+ # @option settings_hash [Boolean] :enabled
1106
+ # @option settings_hash [Boolean] :hidden
1107
+ # @option settings_hash [Boolean] :run_only_if_idle
1108
+ # @option settings_hash [Boolean] :run_only_if_network_available
1109
+ # @option settings_hash [Boolean] :start_when_available
1110
+ # @option settings_hash [Boolean] :stop_if_going_on_batteries
1111
+ # @option settings_hash [Boolean] :use_unified_scheduling_engine
1112
+ # @option settings_hash [Boolean] :volatile
1113
+ # @option settings_hash [Boolean] :wake_to_run
1114
+ # @option settings_hash [Boolean] :restart_on_idle The Idle Setting
1115
+ # @option settings_hash [Boolean] :stop_on_idle_end The Idle Setting
1116
+ # @option settings_hash [Integer] :compatibility
1117
+ # @option settings_hash [Integer] :multiple_instances
1118
+ # @option settings_hash [Integer] :priority
1119
+ # @option settings_hash [Integer] :restart_count
1120
+ # @option settings_hash [String] :delete_expired_task_after
1121
+ # @option settings_hash [String] :execution_time_limit
1122
+ # @option settings_hash [String] :restart_interval
1123
+ # @option settings_hash [String] :idle_duration The Idle Setting
1124
+ # @option settings_hash [String] :wait_timeout The Idle Setting
1125
+ #
1126
+ # @return [Hash] User input
1127
+ #
1128
+ # @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa383480(v=vs.85).aspx#properties
1129
+ #
1130
+ def configure_settings(settings_hash)
1131
+ raise TypeError, "User input settings are required in hash" unless settings_hash.is_a?(Hash)
1154
1132
 
1133
+ check_for_active_task
1155
1134
  definition = @task.Definition
1156
1135
 
1157
- allow_demand_start = hash[:allow_demand_start]
1158
- allow_hard_terminate = hash[:allow_hard_terminate]
1159
- compatibility = hash[:compatibility]
1160
- delete_expired_task_after = hash[:delete_expired_task_after]
1161
- disallow_start_if_on_batteries = hash[:disallow_start_if_on_batteries]
1162
- enabled = hash[:enabled]
1163
- execution_time_limit = "PT#{hash[:execution_time_limit] || hash[:max_run_time] || 0}M"
1164
- hidden = hash[:hidden]
1165
- idle_duration = "PT#{hash[:idle_duration]||0}M"
1166
- stop_on_idle_end = hash[:stop_on_idle_end]
1167
- wait_timeout = "PT#{hash[:wait_timeout]||0}M"
1168
- restart_on_idle = hash[:restart_on_idle]
1169
- network_settings = hash[:network_settings]
1170
- priority = hash[:priority]
1171
- restart_count = hash[:restart_count]
1172
- restart_interval = hash[:restart_interval]
1173
- run_only_if_idle = hash[:run_only_if_idle]
1174
- run_only_if_network_available = hash[:run_only_if_network_available]
1175
- start_when_available = hash[:start_when_available]
1176
- stop_if_going_on_batteries = hash[:stop_if_going_on_batteries]
1177
- wake_to_run = hash[:wake_to_run]
1178
- xml_text = hash[:xml_text] || hash[:xml]
1136
+ # Check for invalid setting
1137
+ invalid_settings = settings_hash.keys - valid_settings_options
1138
+ raise TypeError, "Invalid setting passed: #{invalid_settings.join(', ')}" unless invalid_settings.empty?
1179
1139
 
1180
- definition.Settings.AllowDemandStart = allow_demand_start if allow_demand_start
1181
- definition.Settings.AllowHardTerminate = allow_hard_terminate if allow_hard_terminate
1182
- definition.Settings.Compatibility = compatibility if compatibility
1183
- definition.Settings.DeleteExpiredTaskAfter = delete_expired_task_after if delete_expired_task_after
1184
- definition.Settings.DisallowStartIfOnBatteries = disallow_start_if_on_batteries if !disallow_start_if_on_batteries.nil?
1185
- definition.Settings.Enabled = enabled if enabled
1186
- definition.Settings.ExecutionTimeLimit = execution_time_limit if execution_time_limit
1187
- definition.Settings.Hidden = hidden if hidden
1188
- definition.Settings.IdleSettings.IdleDuration = idle_duration if idle_duration
1189
- definition.Settings.IdleSettings.StopOnIdleEnd = stop_on_idle_end if stop_on_idle_end
1190
- definition.Settings.IdleSettings.WaitTimeout = wait_timeout if wait_timeout
1191
- definition.Settings.IdleSettings.RestartOnIdle = restart_on_idle if restart_on_idle
1192
- definition.Settings.NetworkSettings = network_settings if network_settings
1193
- definition.Settings.Priority = priority if priority
1194
- definition.Settings.RestartCount = restart_count if restart_count
1195
- definition.Settings.RestartInterval = restart_interval if restart_interval
1196
- definition.Settings.RunOnlyIfIdle = run_only_if_idle if run_only_if_idle
1197
- definition.Settings.RunOnlyIfNetworkAvailable = run_only_if_network_available if run_only_if_network_available
1198
- definition.Settings.StartWhenAvailable = start_when_available if start_when_available
1199
- definition.Settings.StopIfGoingOnBatteries = stop_if_going_on_batteries if !stop_if_going_on_batteries.nil?
1200
- definition.Settings.WakeToRun = wake_to_run if wake_to_run
1201
- definition.Settings.XmlText = xml_text if xml_text
1202
-
1203
- update_task_definition(definition)
1140
+ # Some modification is required in user input
1141
+ hash = settings_hash.dup
1204
1142
 
1205
- hash
1143
+ # Conversion of few settings
1144
+ hash[:execution_time_limit] = hash[:max_run_time] unless hash[:max_run_time].nil?
1145
+ %i[execution_time_limit idle_duration restart_interval wait_timeout].each do |setting|
1146
+ hash[setting] = "PT#{hash[setting]}M" unless hash[setting].nil?
1147
+ end
1148
+
1149
+ task_settings = definition.Settings
1150
+
1151
+ # Some Idle setting needs to be configured
1152
+ if IdleSettings.any? { |setting| hash.key?(setting) }
1153
+ idle_settings = task_settings.IdleSettings
1154
+ IdleSettings.each do |setting|
1155
+ unless hash[setting].nil?
1156
+ idle_settings.setproperty(camelize(setting.to_s), hash[setting])
1157
+ # This setting is not required to be configured now
1158
+ hash.delete(setting)
1159
+ end
1160
+ end
1161
+ end
1162
+
1163
+ # XML settings are not to be configured
1164
+ %i[xml_text xml].map { |x| hash.delete(x) }
1165
+
1166
+ hash.each do |setting, value|
1167
+ setting = camelize(setting.to_s)
1168
+ definition.Settings.setproperty(setting, value)
1169
+ end
1170
+
1171
+ register_task_definition(definition)
1172
+
1173
+ settings_hash
1206
1174
  end
1207
1175
 
1208
1176
  # Set registration information options. The possible options are:
@@ -1247,7 +1215,7 @@ module Win32
1247
1215
  definition.RegistrationInfo.Version = version if version
1248
1216
  definition.RegistrationInfo.XmlText = xml_text if xml_text
1249
1217
 
1250
- update_task_definition(definition)
1218
+ register_task_definition(definition)
1251
1219
 
1252
1220
  hash
1253
1221
  end
@@ -1266,7 +1234,7 @@ module Win32
1266
1234
  definition.Principal.LogonType = principals[:logon_type] if principals[:logon_type].to_s != ""
1267
1235
  definition.Principal.GroupId = principals[:group_id] if principals[:group_id].to_s != ""
1268
1236
  definition.Principal.RunLevel = principals[:run_level] if principals[:run_level].to_s != ""
1269
- update_task_definition(definition)
1237
+ register_task_definition(definition)
1270
1238
  principals
1271
1239
  end
1272
1240
 
@@ -1314,8 +1282,17 @@ module Win32
1314
1282
  symbolize_keys(settings_hash)
1315
1283
  end
1316
1284
 
1285
+ # Returns the user or group associated with the task. If no one is associated, it returns the default user i.e., 'SYSTEM'
1286
+ #
1287
+ # @param [WIN32OLE] definition
1288
+ #
1289
+ # @return [String] user_id
1290
+ #
1317
1291
  def task_user_id(definition)
1318
- definition.Principal.UserId
1292
+ user_id = definition.Principal.UserId.to_s
1293
+ user_id = definition.Principal.GroupId.to_s if user_id.empty?
1294
+ user_id = SERVICE_ACCOUNT_USERS.first if user_id.empty?
1295
+ user_id
1319
1296
  end
1320
1297
 
1321
1298
  private
@@ -1325,6 +1302,16 @@ module Win32
1325
1302
  string.gsub(/([a-z\d])([A-Z])/, '\1_\2'.freeze).downcase
1326
1303
  end
1327
1304
 
1305
+ # Converts a snake-case string to camel-case format
1306
+ #
1307
+ # @param [String] str
1308
+ #
1309
+ # @return [String] In camel case format
1310
+ #
1311
+ def camelize(str)
1312
+ str.split('_').map(&:capitalize).join
1313
+ end
1314
+
1328
1315
  # Converts all the keys of a hash to underscored-symbol format
1329
1316
  def symbolize_keys(hash)
1330
1317
  hash.each_with_object({}) do |(k, v), h|
@@ -1344,22 +1331,119 @@ module Win32
1344
1331
  }
1345
1332
  end
1346
1333
 
1334
+ # Configurable settings options
1335
+ #
1336
+ # @note Logically, this is summation of
1337
+ # * Settings
1338
+ # * IdleSettings - [:idle_settings]
1339
+ # * :max_run_time, :xml
1340
+ #
1341
+ # @return [Array]
1342
+ #
1343
+ def valid_settings_options
1344
+ %i[allow_demand_start allow_hard_terminate compatibility delete_expired_task_after
1345
+ disallow_start_if_on_batteries disallow_start_on_remote_app_session enabled
1346
+ execution_time_limit hidden idle_duration maintenance_settings max_run_time
1347
+ multiple_instances network_settings priority restart_count restart_interval
1348
+ restart_on_idle run_only_if_idle run_only_if_network_available
1349
+ start_when_available stop_if_going_on_batteries stop_on_idle_end
1350
+ use_unified_scheduling_engine volatile wait_timeout wake_to_run xml xml_text]
1351
+ end
1352
+
1347
1353
  def check_for_active_task
1348
1354
  raise Error, 'No currently active task' if @task.nil?
1349
1355
  end
1350
1356
 
1351
- def update_task_definition(definition)
1352
- user = task_user_id(definition) || 'SYSTEM'
1357
+ # Checks if the user belongs to service accounts category
1358
+ #
1359
+ # @return [Boolean] True or False
1360
+ #
1361
+ def service_account_user?(user)
1362
+ SERVICE_ACCOUNT_USERS.include?(user.to_s.upcase)
1363
+ end
1364
+
1365
+ # Checks if the user belongs to group accounts category
1366
+ #
1367
+ # @return [Boolean] True or False
1368
+ #
1369
+ def group_user?(user)
1370
+ BUILT_IN_GROUPS.include?(user.to_s.upcase)
1371
+ end
1372
+
1373
+ # Checks if the user belongs to system users category
1374
+ #
1375
+ # @return [Boolean] True or False
1376
+ #
1377
+ def system_user?(user)
1378
+ SYSTEM_USERS.include?(user.to_s.upcase)
1379
+ end
1380
+
1381
+ # Checks whether the given set of user_id and password suits our requirements.
1382
+ #
1383
+ # Raises the error in case of any failures
1384
+ #
1385
+ # Password should be nil for System Users. For other users, it is required.
1386
+ #
1387
+ # @param [String] user_id
1388
+ # @param [String] password
1389
+ #
1390
+ def check_credential_requirements(user_id, password)
1391
+ user_id = user_id.to_s
1392
+ password = password.to_s
1393
+ # Password will be required for non-system users
1394
+ if password.empty?
1395
+ unless system_user?(user_id)
1396
+ raise Error, 'Password is required for non-system users'
1397
+ end
1398
+ else
1399
+ if system_user?(user_id)
1400
+ raise Error, 'Password is not required for system users'
1401
+ end
1402
+ end
1403
+ end
1404
+
1405
+ # Returns the applicable flag as per the given users and groups which is used while
1406
+ # RegisterTaskDefinition
1407
+ #
1408
+ # @param [String] user_id
1409
+ # @param [String] password
1410
+ #
1411
+ # @return [Integer] Logon Types
1412
+ #
1413
+ def logon_type(user_id, password)
1414
+ if service_account_user?(user_id)
1415
+ TASK_LOGON_SERVICE_ACCOUNT
1416
+ elsif group_user?(user_id)
1417
+ TASK_LOGON_GROUP
1418
+ elsif !password.to_s.empty? # Password is present
1419
+ TASK_LOGON_PASSWORD
1420
+ else
1421
+ TASK_LOGON_INTERACTIVE_TOKEN
1422
+ end
1423
+ end
1424
+
1425
+ # Uses RegisterTaskDefinition and creates or updates the task
1426
+ #
1427
+ # @param [WIN32OLE] definition
1428
+ # @param [String] path
1429
+ # @param [String] user_id
1430
+ # @param [String] password
1431
+ #
1432
+ # @return [Integer] Logon Types
1433
+ #
1434
+ def register_task_definition(definition, path = nil, user_id = nil, password = nil)
1435
+ user_id ||= task_user_id(definition)
1436
+ path ||= @task.Path
1353
1437
  @task = @root.RegisterTaskDefinition(
1354
- @task.Path,
1355
- definition,
1356
- TASK_CREATE_OR_UPDATE,
1357
- user,
1358
- @password,
1359
- @password ? TASK_LOGON_PASSWORD : TASK_LOGON_SERVICE_ACCOUNT
1438
+ path, # Path (name) of the task
1439
+ definition, # definition of the task
1440
+ TASK_CREATE_OR_UPDATE, # Equivalent to TASK_CREATE | TASK_UPDATE
1441
+ user_id,
1442
+ password,
1443
+ logon_type(user_id, password)
1360
1444
  )
1361
1445
  rescue WIN32OLERuntimeError => err
1362
- method_name = caller_locations(1,1)[0].label
1446
+ method_name = caller_locations(1, 1)[0].label
1363
1447
  raise Error, ole_error(method_name, err)
1364
1448
  end
1365
1449
  end
@@ -1,6 +1,6 @@
1
1
  module Win32
2
2
  class TaskScheduler
3
3
  # The version of the win32-taskscheduler library
4
- VERSION = '1.0.10'.freeze
4
+ VERSION = '1.0.12'.freeze
5
5
  end
6
6
  end
@@ -1,7 +1,11 @@
1
+ require_relative 'sid'
1
2
  module Windows
2
3
  module TaskSchedulerConstants
4
+ SERVICE_ACCOUNT_USERS = SID::SERVICE_ACCOUNT_USERS
3
5
 
4
- SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', "SYSTEM", 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE', 'BUILTIN\USERS', "USERS"].freeze
6
+ BUILT_IN_GROUPS = SID::BUILT_IN_GROUPS
7
+
8
+ SYSTEM_USERS = SERVICE_ACCOUNT_USERS + BUILT_IN_GROUPS
5
9
 
6
10
  # Triggers
7
11
 
@@ -192,7 +196,6 @@ module Windows
192
196
  # Not in use; currently identical to TASK_LOGON_PASSWORD
193
197
  TASK_LOGON_INTERACTIVE_TOKEN_OR_PASSWORD = 6
194
198
 
195
-
196
199
  TASK_MAX_RUN_TIMES = 1440
197
200
  TASKS_TO_RETRIEVE = 5
198
201
 
@@ -215,12 +218,12 @@ module Windows
215
218
  BELOW_NORMAL_PRIORITY_CLASS = 7 # Or 8
216
219
  IDLE_PRIORITY_CLASS = 9 # Or 10
217
220
 
218
- CLSCTX_INPROC_SERVER = 0x1
219
- CLSID_CTask = [0x148BD520,0xA2AB,0x11CE,0xB1,0x1F,0x00,0xAA,0x00,0x53,0x05,0x03].pack('LSSC8')
220
- CLSID_CTaskScheduler = [0x148BD52A,0xA2AB,0x11CE,0xB1,0x1F,0x00,0xAA,0x00,0x53,0x05,0x03].pack('LSSC8')
221
- IID_ITaskScheduler = [0x148BD527,0xA2AB,0x11CE,0xB1,0x1F,0x00,0xAA,0x00,0x53,0x05,0x03].pack('LSSC8')
222
- IID_ITask = [0x148BD524,0xA2AB,0x11CE,0xB1,0x1F,0x00,0xAA,0x00,0x53,0x05,0x03].pack('LSSC8')
223
- IID_IPersistFile = [0x0000010b,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46].pack('LSSC8')
221
+ CLSCTX_INPROC_SERVER = 0x1
222
+ CLSID_CTask = [0x148BD520, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack('LSSC8')
223
+ CLSID_CTaskScheduler = [0x148BD52A, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack('LSSC8')
224
+ IID_ITaskScheduler = [0x148BD527, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack('LSSC8')
225
+ IID_ITask = [0x148BD524, 0xA2AB, 0x11CE, 0xB1, 0x1F, 0x00, 0xAA, 0x00, 0x53, 0x05, 0x03].pack('LSSC8')
226
+ IID_IPersistFile = [0x0000010b, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46].pack('LSSC8')
224
227
 
225
228
  # Days of month
226
229
 
@@ -8,11 +8,15 @@ module Windows
8
8
  FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
9
9
  FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF
10
10
 
11
- ffi_lib :kernel32
11
+ ffi_lib :kernel32, :advapi32
12
12
 
13
13
  attach_function :FormatMessage, :FormatMessageA,
14
14
  [:ulong, :pointer, :ulong, :ulong, :pointer, :ulong, :pointer], :ulong
15
15
 
16
+ attach_function :ConvertStringSidToSidW, [ :pointer, :pointer ], :bool
17
+ attach_function :LookupAccountSidW, [ :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer ], :bool
18
+ attach_function :LocalFree, [ :pointer ], :pointer
19
+
16
20
  def win_error(function, err=FFI.errno)
17
21
  err_msg = ''
18
22
  flags = FORMAT_MESSAGE_IGNORE_INSERTS |
@@ -0,0 +1,128 @@
1
+ require_relative 'helper'
2
+
3
+ module FFI
4
+ class Pointer
5
+ def read_wstring(num_wchars = nil)
6
+ if num_wchars.nil?
7
+ # Find the length of the string
8
+ length = 0
9
+ last_char = nil
10
+ while last_char != "\000\000"
11
+ length += 1
12
+ last_char = get_bytes(0, length * 2)[-2..-1]
13
+ end
14
+
15
+ num_wchars = length
16
+ end
17
+
18
+ wide_to_utf8(get_bytes(0, num_wchars * 2))
19
+ end
20
+
21
+ def wide_to_utf8(wstring)
22
+ # ensure it is actually UTF-16LE
23
+ # Ruby likes to mark binary data as ASCII-8BIT
24
+ wstring = wstring.force_encoding('UTF-16LE')
25
+
26
+ # encode it all as UTF-8 and remove trailing CRLF and NULL characters
27
+ wstring.encode('UTF-8').strip
28
+ end
29
+ end
30
+ end
31
+
32
+ class SID
33
+ extend Windows::TaskSchedulerHelper
34
+ ERROR_INSUFFICIENT_BUFFER = 122
35
+
36
+ def self.LocalSystem
37
+ from_string_sid('S-1-5-18')
38
+ end
39
+
40
+ def self.NtLocal
41
+ from_string_sid('S-1-5-19')
42
+ end
43
+
44
+ def self.NtNetwork
45
+ from_string_sid('S-1-5-20')
46
+ end
47
+
48
+ def self.BuiltinAdministrators
49
+ from_string_sid('S-1-5-32-544')
50
+ end
51
+
52
+ def self.BuiltinUsers
53
+ from_string_sid('S-1-5-32-545')
54
+ end
55
+
56
+ def self.Guests
57
+ from_string_sid('S-1-5-32-546')
58
+ end
59
+
60
+ # Converts a string-format security identifier (SID) into a valid, functional SID
61
+ # and returns a hash with account_name and account_simple_name
62
+ # @see https://docs.microsoft.com/en-us/windows/desktop/api/sddl/nf-sddl-convertstringsidtosidw
63
+ #
64
+ def self.from_string_sid(string_sid)
65
+ result = FFI::MemoryPointer.new :pointer
66
+ unless ConvertStringSidToSidW(utf8_to_wide(string_sid), result)
67
+ raise FFI::LastError.error
68
+ end
69
+ result_pointer = result.read_pointer
70
+ domain, name, use = account(result_pointer)
71
+ LocalFree(result_pointer)
72
+ account_names(domain, name, use)
73
+ end
74
+
75
+ def self.utf8_to_wide(ustring)
76
+ # ensure it is actually UTF-8
77
+ # Ruby likes to mark binary data as ASCII-8BIT
78
+ ustring = (ustring + '').force_encoding('UTF-8')
79
+
80
+ # ensure we have the double-null termination Windows Wide likes
81
+ ustring += "\000\000" if ustring.empty? || ustring[-1].chr != "\000"
82
+
83
+ # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
84
+ ustring.encode('UTF-16LE')
85
+ end
86
+
87
+ # Accepts a security identifier (SID) as input.
88
+ # It retrieves the name of the account for this SID and the name of the
89
+ # first domain on which this SID is found
90
+ # @see https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountsidw
91
+ #
92
+ def self.account(sid)
93
+ sid = sid.pointer if sid.respond_to?(:pointer)
94
+ name_size = FFI::Buffer.new(:long).write_long(0)
95
+ referenced_domain_name_size = FFI::Buffer.new(:long).write_long(0)
96
+
97
+ if LookupAccountSidW(nil, sid, nil, name_size, nil, referenced_domain_name_size, nil)
98
+ raise 'Expected ERROR_INSUFFICIENT_BUFFER from LookupAccountSid, and got no error!'
99
+ elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
100
+ raise FFI::LastError.error
101
+ end
102
+
103
+ name = FFI::MemoryPointer.new :char, (name_size.read_long * 2)
104
+ referenced_domain_name = FFI::MemoryPointer.new :char, (referenced_domain_name_size.read_long * 2)
105
+ use = FFI::Buffer.new(:long).write_long(0)
106
+ unless LookupAccountSidW(nil, sid, name, name_size, referenced_domain_name, referenced_domain_name_size, use)
107
+ raise FFI::LastError.error
108
+ end
109
+ [referenced_domain_name.read_wstring(referenced_domain_name_size.read_long), name.read_wstring(name_size.read_long), use.read_long]
110
+ end
111
+
112
+ # Formats domain, name and returns a hash with
113
+ # account_name and account_simple_name
114
+ #
115
+ def self.account_names(domain, name, _use)
116
+ account_name = !domain.to_s.empty? ? "#{domain}\\#{name}" : name
117
+ account_simple_name = name
118
+ { account_name: account_name, account_simple_name: account_simple_name }
119
+ end
120
+
121
+ SERVICE_ACCOUNT_USERS = [self.LocalSystem, self.NtLocal, self.NtNetwork].map do |user|
122
+ [user[:account_simple_name].upcase, user[:account_name].upcase]
123
+ end.flatten.freeze
124
+
125
+ BUILT_IN_GROUPS = [self.BuiltinAdministrators, self.BuiltinUsers, self.Guests].map do |user|
126
+ [user[:account_simple_name].upcase, user[:account_name].upcase]
127
+ end.flatten.freeze
128
+ end
@@ -53,7 +53,7 @@ RSpec.describe Win32::TaskScheduler, :windows_only do
53
53
  expect(no_of_tasks).to eq(0)
54
54
  end
55
55
 
56
- it 'Raises error when path separators(\\\) are absent' do
56
+ it "Raises error when path separators(\\\) are absent" do
57
57
  invalid_path = 'Foo'
58
58
  expect { ts.new(@task, @trigger, invalid_path) }.to raise_error(ArgumentError)
59
59
  expect(no_of_tasks).to eq(0)
@@ -215,9 +215,9 @@ RSpec.describe Win32::TaskScheduler, :windows_only do
215
215
 
216
216
  it 'Execute(Start) the Task' do
217
217
  @ts.run
218
- expect(@ts.status).to eq('running').
219
- or eq('queued').
220
- or eq('ready') # It takes time to load sometimes
218
+ expect(@ts.status).to eq('running')
219
+ .or eq('queued')
220
+ .or eq('ready') # It takes time to load sometimes
221
221
  end
222
222
 
223
223
  it 'Raises an error if task does not exists' do
@@ -518,20 +518,48 @@ RSpec.describe Win32::TaskScheduler, :windows_only do
518
518
 
519
519
  describe '#account_information' do
520
520
  before { create_task }
521
- it 'System users may not require any password' do
522
- user = 'SYSTEM'
523
- password = nil
524
- expect(@ts.set_account_information(user, password)). to be_truthy
525
- expect(@ts.account_information).to eq(user)
521
+ context 'Service Account Users' do
522
+ let(:service_user) { 'LOCAL SERVICE' }
523
+ let(:password) { nil }
524
+ it 'Does not require any password' do
525
+ expect(@ts.set_account_information(service_user, password)). to be_truthy
526
+ expect(@ts.account_information.upcase).to eq(service_user)
527
+ end
528
+ it 'passing account_name will display account_simple_name' do
529
+ user = 'NT AUTHORITY\LOCAL SERVICE'
530
+ expect(@ts.set_account_information(user, password)). to be_truthy
531
+ expect(@ts.account_information.upcase).to eq(service_user)
532
+ end
533
+ it 'Raises an error if password is given' do
534
+ password = 'XYZ'
535
+ expect { @ts.set_account_information(service_user, password) }. to raise_error(tasksch_err)
536
+ end
526
537
  end
527
-
528
- it 'User will require a password' do
529
- user = ENV['user']
530
- password = nil
531
- expect { @ts.set_account_information(user, password) }. to raise_error(TypeError)
532
- expect(@ts.account_information).to include(user)
538
+ context 'Built in Groups' do
539
+ let(:group_user) { 'USERS' }
540
+ let(:password) { nil }
541
+ it 'Does not require any password' do
542
+ expect(@ts.set_account_information(group_user, password)). to be_truthy
543
+ expect(@ts.account_information.upcase).to eq(group_user)
544
+ end
545
+ it 'passing account_name will display account_simple_name' do
546
+ user = 'BUILTIN\USERS'
547
+ expect(@ts.set_account_information(user, password)). to be_truthy
548
+ expect(@ts.account_information.upcase).to eq(group_user)
549
+ end
550
+ it 'Raises an error if password is given' do
551
+ password = 'XYZ'
552
+ expect { @ts.set_account_information(group_user, password) }. to raise_error(tasksch_err)
553
+ end
554
+ end
555
+ context 'Non-system users' do
556
+ it 'Require a password' do
557
+ user = ENV['user']
558
+ password = nil
559
+ expect { @ts.set_account_information(user, password) }. to raise_error(tasksch_err)
560
+ expect(@ts.account_information).to include(user)
561
+ end
533
562
  end
534
-
535
563
  it 'Raises an error if task does not exists' do
536
564
  @ts.instance_variable_set(:@task, nil)
537
565
  user = 'User'
@@ -640,31 +668,57 @@ RSpec.describe Win32::TaskScheduler, :windows_only do
640
668
  before { create_task }
641
669
  before { stub_user }
642
670
 
671
+ it 'Require a hash' do
672
+ expect { @ts.configure_settings('XYZ') }. to raise_error(TypeError)
673
+ expect(@ts.configure_settings({})).to eq({})
674
+ end
675
+
676
+ it 'Raises an error if invalid setting is passed' do
677
+ expect { @ts.configure_settings(invalid: true) }. to raise_error(TypeError)
678
+ end
679
+
680
+ it 'Sets settings of the task and returns a hash' do
681
+ settings = { allow_demand_start: false,
682
+ disallow_start_if_on_batteries: false,
683
+ enabled: false,
684
+ stop_if_going_on_batteries: false }
685
+
686
+ expect(@ts.configure_settings(settings)).to eq(settings)
687
+ expect(@ts.settings).to include(settings)
688
+ end
689
+
643
690
  it 'Sets idle settings of the task' do
644
- skip 'Functionality is not working'
645
- # Seems like it accepts time in minutes; but returns the result in ISO8601 format
646
- settings = { idle_duration: 'PT11M',
647
- wait_timeout: 'PT2H',
691
+ # It accepts time in minutes
692
+ # and returns the result in ISO8601 format
693
+ settings = { idle_duration: 11,
694
+ wait_timeout: 10,
648
695
  stop_on_idle_end: true,
649
696
  restart_on_idle: false }
650
- @ts.configure_settings(settings)
651
- expect(@ts.idle_settings).to eq(settings)
652
- end
653
697
 
654
- it 'Sets network settings of the task' do
655
- skip 'Functionality is not working'
656
- settings = { network_settings: { name: 'NET1' } }
698
+ result_settings = { idle_duration: 'PT11M',
699
+ wait_timeout: 'PT10M',
700
+ stop_on_idle_end: true,
701
+ restart_on_idle: false }
702
+
657
703
  @ts.configure_settings(settings)
658
- expect(@ts.network_settings).to eq(settings)
704
+ expect(@ts.idle_settings).to eq(result_settings)
659
705
  end
660
706
 
661
- it 'Configures the settings of the task' do
662
- skip 'Functionality is not working properly'
663
- settings = @ts.settings
664
- settings[:allow_demand_start] = false
665
- settings[:execution_time_limit] = 'PT70H'
666
- @ts.configure_settings(settings)
667
- expect(@ts.settings).to eq(settings)
707
+ it 'Set settings and idle settings of the task' do
708
+ settings = {
709
+ allow_demand_start: false,
710
+ disallow_start_if_on_batteries: false,
711
+ stop_if_going_on_batteries: false
712
+ }
713
+
714
+ idle_settings = {
715
+ stop_on_idle_end: true,
716
+ restart_on_idle: false
717
+ }
718
+
719
+ @ts.configure_settings(settings.merge(idle_settings))
720
+ expect(@ts.settings).to include(settings)
721
+ expect(@ts.idle_settings).to include(idle_settings)
668
722
  end
669
723
 
670
724
  it 'Raises an error if task does not exists' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: win32-taskscheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.10
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Park Heesob
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-07-24 00:00:00.000000000 Z
12
+ date: 2018-10-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
@@ -120,6 +120,7 @@ files:
120
120
  - lib/win32/taskscheduler/version.rb
121
121
  - lib/win32/windows/constants.rb
122
122
  - lib/win32/windows/helper.rb
123
+ - lib/win32/windows/sid.rb
123
124
  - lib/win32/windows/time_calc_helper.rb
124
125
  - spec/functional/win32/taskschedular_spec.rb
125
126
  - spec/functional/win32/windows/time_calc_helper_spec.rb