win32-taskscheduler 1.0.10 → 1.0.12

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.
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