ruby-jss 1.2.9 → 1.5.1

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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +196 -1
  3. data/lib/jamf.rb +10 -3
  4. data/lib/jamf/api/abstract_classes/collection_resource.rb +329 -150
  5. data/lib/jamf/api/abstract_classes/generic_reference.rb +9 -1
  6. data/lib/jamf/api/abstract_classes/json_object.rb +107 -83
  7. data/lib/jamf/api/abstract_classes/prestage.rb +55 -30
  8. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +21 -0
  9. data/lib/jamf/api/abstract_classes/resource.rb +4 -4
  10. data/lib/jamf/api/abstract_classes/singleton_resource.rb +1 -1
  11. data/lib/jamf/api/connection.rb +20 -12
  12. data/lib/jamf/api/connection/api_error.rb +8 -8
  13. data/lib/jamf/api/connection/token.rb +36 -15
  14. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +14 -1
  15. data/lib/jamf/api/json_objects/device_enrollment_device.rb +14 -7
  16. data/lib/jamf/api/json_objects/device_enrollment_device_sync_state.rb +81 -0
  17. data/lib/jamf/api/json_objects/locale.rb +59 -0
  18. data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +50 -1
  19. data/lib/jamf/api/json_objects/prestage_location.rb +3 -3
  20. data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +7 -7
  21. data/lib/jamf/api/json_objects/prestage_scope.rb +1 -1
  22. data/lib/jamf/api/{resources/collection_resources → json_objects}/time_zone.rb +9 -23
  23. data/lib/jamf/api/mixins/bulk_deletable.rb +27 -6
  24. data/lib/jamf/api/mixins/change_log.rb +201 -51
  25. data/lib/jamf/api/mixins/filterable.rb +51 -0
  26. data/lib/jamf/api/mixins/pageable.rb +208 -0
  27. data/lib/jamf/api/mixins/sortable.rb +59 -0
  28. data/lib/jamf/api/resources/collection_resources/building.rb +19 -8
  29. data/lib/jamf/api/resources/collection_resources/category.rb +5 -3
  30. data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +11 -4
  31. data/lib/jamf/api/resources/collection_resources/department.rb +1 -1
  32. data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +13 -13
  33. data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +11 -3
  34. data/lib/jamf/api/resources/collection_resources/mobile_device_prestage.rb +24 -22
  35. data/lib/jamf/api/resources/collection_resources/script.rb +61 -25
  36. data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +15 -5
  37. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +14 -14
  38. data/lib/jamf/api/resources/singleton_resources/locales.rb +155 -0
  39. data/lib/jamf/api/resources/singleton_resources/time_zones.rb +213 -0
  40. data/lib/jamf/configuration.rb +7 -9
  41. data/lib/jamf/ruby_extensions.rb +1 -0
  42. data/lib/jamf/ruby_extensions/array.rb +1 -1
  43. data/lib/jamf/ruby_extensions/array/utils.rb +3 -3
  44. data/lib/jamf/ruby_extensions/dig.rb +52 -0
  45. data/lib/jamf/validate.rb +63 -24
  46. data/lib/jamf/version.rb +1 -1
  47. data/lib/jss.rb +4 -1
  48. data/lib/jss/api_connection.rb +110 -397
  49. data/lib/jss/api_object.rb +16 -13
  50. data/lib/jss/api_object/advanced_search.rb +27 -26
  51. data/lib/jss/api_object/app_store_country_codes.rb +298 -0
  52. data/lib/jss/api_object/categorizable.rb +1 -1
  53. data/lib/jss/api_object/computer.rb +5 -1
  54. data/lib/jss/api_object/configuration_profile.rb +34 -3
  55. data/lib/jss/api_object/directory_binding.rb +273 -0
  56. data/lib/jss/api_object/directory_binding_type.rb +96 -0
  57. data/lib/jss/api_object/directory_binding_type/active_directory.rb +539 -0
  58. data/lib/jss/api_object/directory_binding_type/admitmac.rb +594 -0
  59. data/lib/jss/api_object/directory_binding_type/centrify.rb +226 -0
  60. data/lib/jss/api_object/directory_binding_type/open_directory.rb +178 -0
  61. data/lib/jss/api_object/directory_binding_type/powerbroker_identity_services.rb +73 -0
  62. data/lib/jss/api_object/disk_encryption_configurations.rb +114 -0
  63. data/lib/jss/api_object/distribution_point.rb +97 -37
  64. data/lib/jss/api_object/dock_item.rb +143 -0
  65. data/lib/jss/api_object/ebook.rb +1 -2
  66. data/lib/jss/api_object/extendable.rb +68 -32
  67. data/lib/jss/api_object/extension_attribute.rb +4 -3
  68. data/lib/jss/api_object/group.rb +33 -2
  69. data/lib/jss/api_object/mac_application.rb +107 -8
  70. data/lib/jss/api_object/mobile_device.rb +3 -0
  71. data/lib/jss/api_object/mobile_device_application.rb +12 -0
  72. data/lib/jss/api_object/network_segment.rb +195 -70
  73. data/lib/jss/api_object/package.rb +105 -40
  74. data/lib/jss/api_object/patch_source.rb +10 -9
  75. data/lib/jss/api_object/policy.rb +491 -7
  76. data/lib/jss/api_object/printer.rb +446 -0
  77. data/lib/jss/api_object/scopable.rb +10 -15
  78. data/lib/jss/api_object/scopable/scope.rb +386 -71
  79. data/lib/jss/api_object/self_servable.rb +17 -9
  80. data/lib/jss/api_object/uploadable.rb +1 -1
  81. data/lib/jss/api_object/user.rb +42 -1
  82. data/lib/jss/api_object/vpp_account.rb +209 -0
  83. data/lib/jss/api_object/vppable.rb +169 -13
  84. data/lib/jss/composer.rb +1 -1
  85. data/lib/jss/exceptions.rb +3 -0
  86. data/lib/jss/server.rb +15 -0
  87. data/lib/jss/utility.rb +8 -22
  88. data/lib/jss/validate.rb +53 -10
  89. data/lib/jss/version.rb +1 -1
  90. metadata +50 -22
@@ -121,9 +121,31 @@ module JSS
121
121
  # @return [Array<String>] The current file names
122
122
  #
123
123
  def self.all_filenames(api: JSS.api)
124
- pkgs_in_use = []
125
- all_ids.each { |pkg_id| pkgs_in_use << fetch(id: pkg_id, api: api).filename }
126
- pkgs_in_use.compact
124
+ all_filenames_by(:id, api: api).values
125
+ end
126
+
127
+ # A Hash of all dist-point filenames used by all JSS packages, keyed by
128
+ # package name or id
129
+ #
130
+ # Slow cuz we have to instantiate every pkg
131
+ #
132
+ # @param key[Symbol] either :id, or :name
133
+ #
134
+ # @param api[JSS::APIConnection] an API connection to use
135
+ # Defaults to the corrently active API. See {JSS::APIConnection}
136
+ #
137
+ # @return [Hash{Ingeter,String => String}] The current file names by key
138
+ #
139
+ def self.all_filenames_by(key, api: JSS.api)
140
+ raise ArgumentError, 'key must be :id or :name' unless %i[id name].include? key
141
+
142
+ files_in_use = {}
143
+ all_ids(:refresh).each do |pkg_id|
144
+ pkg = fetch id: pkg_id, api: api
145
+ files_in_use[pkg.send(key)] = pkg.filename
146
+ end
147
+
148
+ files_in_use
127
149
  end
128
150
 
129
151
  # An array of String filenames for all files DIST_POINT_PKGS_FOLDER
@@ -140,14 +162,17 @@ module JSS
140
162
  # @param api[JSS::APIConnection] an API connection to use
141
163
  # Defaults to the corrently active API. See {JSS::APIConnection}
142
164
  #
165
+ # @param dist_point [String,Integer] the name or id of the distribution
166
+ # point to use. Defaults to the Master Dist. Point
167
+ #
143
168
  # @return [Array<String>] The orphaned files
144
169
  #
145
- def self.orphaned_files(ro_pw, unmount = true, api: JSS.api)
146
- mdp = JSS::DistributionPoint.master_distribution_point api: api
147
- pkgs_dir = mdp.mount(ro_pw, :ro) + DIST_POINT_PKGS_FOLDER
148
- files_on_mdp = pkgs_dir.children.map { |f| f.basename.to_s }
149
- mdp.unmount if unmount
150
- files_on_mdp - all_filenames(api: api)
170
+ def self.orphaned_files(ro_pw, unmount = true, api: JSS.api, dist_point: nil)
171
+ dp = fetch_dist_point(dist_point, api: api)
172
+ pkgs_dir = dp.mount(ro_pw, :ro) + DIST_POINT_PKGS_FOLDER
173
+ files_on_dp = pkgs_dir.children.map { |f| f.basename.to_s }
174
+ dp.unmount if unmount
175
+ files_on_dp - all_filenames(api: api)
151
176
  end
152
177
 
153
178
  # An array of String filenames for all filenames in any
@@ -164,14 +189,18 @@ module JSS
164
189
  # @param api[JSS::APIConnection] an API connection to use
165
190
  # Defaults to the corrently active API. See {JSS::APIConnection}
166
191
  #
192
+ # @param dist_point [String,Integer] the name or id of the distribution
193
+ # point to use. Defaults to the Master Dist. Point
194
+ #
195
+ #
167
196
  # @return [Array<String>] The orphaned files
168
197
  #
169
- def self.missing_files(ro_pw, unmount = true, api: JSS.api)
170
- mdp = JSS::DistributionPoint.master_distribution_point api: api
171
- pkgs_dir = mdp.mount(ro_pw, :ro) + DIST_POINT_PKGS_FOLDER
172
- files_on_mdp = pkgs_dir.children.map { |f| f.basename.to_s }
173
- mdp.unmount if unmount
174
- all_filenames(api: api) - files_on_mdp
198
+ def self.missing_files(ro_pw, unmount = true, api: JSS.api, dist_point: nil)
199
+ dp = fetch_dist_point(dist_point, api: api)
200
+ pkgs_dir = dp.mount(ro_pw, :ro) + DIST_POINT_PKGS_FOLDER
201
+ files_on_dp = pkgs_dir.children.map { |f| f.basename.to_s }
202
+ dp.unmount if unmount
203
+ all_filenames(api: api) - files_on_dp
175
204
  end
176
205
 
177
206
  # Given a file path, and hash type, generate the checksum for an arbitrary
@@ -189,6 +218,18 @@ module JSS
189
218
  CHECKSUM_HASH_TYPES[type].file(filepath).hexdigest
190
219
  end
191
220
 
221
+ # @param dist_point [String,Integer] the name or id of the distribution
222
+ # point to use. Defaults to the Master Dist. Point
223
+ #
224
+ # @return [JSS::DistributionPoint]
225
+ def self.fetch_dist_point(dist_point, api: JSS.api)
226
+ if dist_point
227
+ JSS::DistributionPoint.fetch dist_point, api: api
228
+ else
229
+ JSS::DistributionPoint.master_distribution_point api: api
230
+ end
231
+ end
232
+
192
233
  # Attributes
193
234
  #####################################
194
235
 
@@ -324,7 +365,7 @@ module JSS
324
365
  new_val = nil if new_val == ''
325
366
  new_val ||= @name
326
367
  return nil if new_val == @filename
327
- warn 'WARNING: you must change the filename on the master Distribution Point. See JSS::Package.update_master_filename.' if @in_jss
368
+
328
369
  @filename = new_val
329
370
  @need_to_update = true
330
371
  end
@@ -590,13 +631,17 @@ module JSS
590
631
  # @param chksum [String] the constants CHECKSUM_HASH_TYPE_SHA512 or
591
632
  # CHECKSUM_HASH_TYPE_MD5. Anything else means don't calc.
592
633
  #
634
+ # @param dist_point [String,Integer] the name or id of the distribution
635
+ # point to use. Defaults to the Master Dist. Point
636
+ #
593
637
  # @return [void]
594
638
  #
595
- def upload_master_file(local_file_path, rw_pw, unmount = true, chksum: DEFAULT_CHECKSUM_HASH_TYPE )
639
+ def upload_master_file(local_file_path, rw_pw, unmount = true, chksum: DEFAULT_CHECKSUM_HASH_TYPE, dist_point: nil)
596
640
  raise JSS::NoSuchItemError, 'Please create this package in the JSS before uploading it.' unless @in_jss
597
641
 
598
- mdp = JSS::DistributionPoint.master_distribution_point api: @api
599
- destination = mdp.mount(rw_pw, :rw) + "#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
642
+ dp = self.class.fetch_dist_point(dist_point, api: @api)
643
+
644
+ destination = dp.mount(rw_pw, :rw) + "#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
600
645
 
601
646
  local_path = Pathname.new local_file_path
602
647
  raise JSS::NoSuchItemError, "Local file '#{@local_file}' doesn't exist" unless local_path.exist?
@@ -637,11 +682,11 @@ module JSS
637
682
 
638
683
  if CHECKSUM_HASH_TYPES.keys.include? chksum
639
684
  @checksum_type = chksum
640
- @checksum = calculate_checksum local_file: local_path, type: chksum, unmount: false
685
+ @checksum = calculate_checksum local_file: local_path, type: chksum, unmount: false, dist_point: dist_point
641
686
  @need_to_update = true
642
687
  end
643
688
  update if @need_to_update
644
- mdp.unmount if unmount
689
+ dp.unmount if unmount
645
690
  end # upload master file
646
691
 
647
692
  # Using either a local file, or the file on the master dist. point,
@@ -658,7 +703,7 @@ module JSS
658
703
  #
659
704
  # @return [void]
660
705
  #
661
- def reset_checksum(type: nil, local_file: nil, rw_pw: nil, ro_pw: nil, unmount: true)
706
+ def reset_checksum(type: nil, local_file: nil, rw_pw: nil, ro_pw: nil, unmount: true, dist_point: nil )
662
707
  type ||= DEFAULT_CHECKSUM_HASH_TYPE
663
708
 
664
709
  new_checksum = calculate_checksum(
@@ -666,7 +711,8 @@ module JSS
666
711
  local_file: local_file,
667
712
  rw_pw: rw_pw,
668
713
  ro_pw: ro_pw,
669
- unmount: unmount
714
+ unmount: unmount,
715
+ dist_point: dist_point
670
716
  )
671
717
  return if @checksum == new_checksum
672
718
 
@@ -694,11 +740,14 @@ module JSS
694
740
  # @param unmount [Boolean] Unmount the master dist point after using it.
695
741
  # Only used if the dist point is mounted. default: true
696
742
  #
743
+ # @param dist_point [String,Integer] the name or id of the distribution
744
+ # point to use. Defaults to the Master Dist. Point
745
+ #
697
746
  # @return [String] The calculated checksum
698
747
  #
699
- def calculate_checksum(type: nil, local_file: nil, rw_pw: nil, ro_pw: nil, unmount: true )
748
+ def calculate_checksum(type: nil, local_file: nil, rw_pw: nil, ro_pw: nil, unmount: true, dist_point: nil )
700
749
  type ||= DEFAULT_CHECKSUM_HASH_TYPE
701
- mdp = JSS::DistributionPoint.master_distribution_point api: @api
750
+ dp = self.class.fetch_dist_point(dist_point, api: @api)
702
751
 
703
752
  if local_file
704
753
  file_to_calc = local_file
@@ -712,10 +761,10 @@ module JSS
712
761
  else
713
762
  raise ArgumentError, 'Either rw_pw: or ro_pw: must be provided'
714
763
  end
715
- file_to_calc = mdp.mount(dppw, mnt) + "#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
764
+ file_to_calc = dp.mount(dppw, mnt) + "#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
716
765
  end
717
766
  new_checksum = self.class.calculate_checksum(file_to_calc, type)
718
- mdp.unmount if unmount && mdp.mounted?
767
+ dp.unmount if unmount && dp.mounted?
719
768
  new_checksum
720
769
  end
721
770
 
@@ -734,17 +783,21 @@ module JSS
734
783
  # @param unmount [Boolean] Unmount the master dist point after using it.
735
784
  # Only used if the dist point is mounted. default: true
736
785
  #
786
+ # @param dist_point [String,Integer] the name or id of the distribution
787
+ # point to use. Defaults to the Master Dist. Point
788
+ #
737
789
  # @return [Boolean] false if there is no checksum for this pkg, otherwise,
738
790
  # does the calculated checksum match the one stored for the pkg?
739
791
  #
740
- def checksum_valid?(local_file: nil, rw_pw: nil, ro_pw: nil, unmount: true)
792
+ def checksum_valid?(local_file: nil, rw_pw: nil, ro_pw: nil, unmount: true, dist_point: nil )
741
793
  return false unless @checksum
742
794
  new_checksum = calculate_checksum(
743
795
  type: @checksum_type,
744
796
  local_file: local_file,
745
797
  rw_pw: rw_pw,
746
798
  ro_pw: ro_pw,
747
- unmount: unmount
799
+ unmount: unmount,
800
+ dist_point: dist_point
748
801
  )
749
802
  new_checksum == @checksum
750
803
  end
@@ -760,12 +813,16 @@ module JSS
760
813
  # @param rw_pw[String,Symbol] the password for the read/write account on the master Distribution Point,
761
814
  # or :prompt, or :stdin# where # is the line of stdin containing the password See {JSS::DistributionPoint#mount}
762
815
  #
816
+ # @param dist_point [String,Integer] the name or id of the distribution
817
+ # point to use. Defaults to the Master Dist. Point
818
+ #
763
819
  # @return [nil]
764
820
  #
765
- def update_master_filename(old_file_name, new_file_name, rw_pw, unmount = true)
821
+ def update_master_filename(old_file_name, new_file_name, rw_pw, unmount = true, dist_point: nil)
766
822
  raise JSS::NoSuchItemError, "#{old_file_name} does not exist in the jss." unless @in_jss
767
- mdp = JSS::DistributionPoint.master_distribution_point api: @api
768
- pkgs_dir = mdp.mount(rw_pw, :rw) + DIST_POINT_PKGS_FOLDER.to_s
823
+ dp = self.class.fetch_dist_point(dist_point, api: @api)
824
+
825
+ pkgs_dir = dp.mount(rw_pw, :rw) + DIST_POINT_PKGS_FOLDER.to_s
769
826
  old_file = pkgs_dir + old_file_name
770
827
  raise JSS::NoSuchItemError, "File not found on the master distribution point at #{DIST_POINT_PKGS_FOLDER}/#{old_file_name}." unless \
771
828
  old_file.exist?
@@ -775,7 +832,7 @@ module JSS
775
832
  new_file = pkgs_dir + (new_file_name + old_file.extname) if new_file.extname.empty?
776
833
 
777
834
  old_file.rename new_file
778
- mdp.unmount if unmount
835
+ dp.unmount if unmount
779
836
  nil
780
837
  end # update_master_filename
781
838
 
@@ -791,18 +848,21 @@ module JSS
791
848
  #
792
849
  # @param unmount[Boolean] whether or not ot unount the distribution point when finished.
793
850
  #
851
+ # @param dist_point [String,Integer] the name or id of the distribution
852
+ # point to use. Defaults to the Master Dist. Point
853
+ #
794
854
  # @return [Boolean] was the file deleted?
795
855
  #
796
- def delete_master_file(rw_pw, unmount = true)
797
- mdp = JSS::DistributionPoint.master_distribution_point api: @api
798
- file = mdp.mount(rw_pw, :rw) + "#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
856
+ def delete_master_file(rw_pw, unmount = true, dist_point: nil)
857
+ dp = self.class.fetch_dist_point(dist_point, api: @api)
858
+ file = dp.mount(rw_pw, :rw) + "#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
799
859
  if file.exist?
800
860
  file.delete
801
861
  did_it = true
802
862
  else
803
863
  did_it = false
804
864
  end # if exists
805
- mdp.unmount if unmount
865
+ dp.unmount if unmount
806
866
  did_it
807
867
  end # delete master file
808
868
 
@@ -816,9 +876,13 @@ module JSS
816
876
  #
817
877
  # @param unmount[Boolean] whether or not ot unount the distribution point when finished.
818
878
  #
819
- def delete(delete_file: false, rw_pw: nil, unmount: true)
879
+ # @param dist_point [String,Integer] the name or id of the distribution
880
+ # point to use. Defaults to the Master Dist. Point
881
+ #
882
+ # @return [void]
883
+ def delete(delete_file: false, rw_pw: nil, unmount: true, dist_point: nil)
820
884
  super()
821
- delete_master_file(rw_pw, unmount) if delete_file
885
+ delete_master_file(rw_pw, unmount, dist_point: dist_point) if delete_file
822
886
  end
823
887
 
824
888
  # Install this package via the jamf binary 'install' command from the
@@ -885,7 +949,7 @@ module JSS
885
949
 
886
950
  # we'll re-add the filename below if needed.
887
951
  src_path = args[:alt_download_url].chomp "/#{@filename}"
888
-
952
+ using_http = true
889
953
  # use our appropriate dist. point for download
890
954
  else
891
955
  mdp = JSS::DistributionPoint.my_distribution_point api: @api
@@ -1021,6 +1085,7 @@ module JSS
1021
1085
 
1022
1086
  private
1023
1087
 
1088
+
1024
1089
  # Return the REST XML for this pkg, with the current values,
1025
1090
  # for saving or updating
1026
1091
  #
@@ -120,30 +120,31 @@ module JSS
120
120
 
121
121
  # Fetch either an internal or external patch source
122
122
  #
123
- # BUG: there's an API bug: fetching a non-existent ids
123
+ # BUG: there's an API bug: fetching a non-existent
124
124
  # which is why we rescue internal server errors.
125
125
  #
126
126
  # @see APIObject.fetch
127
127
  #
128
- def self.fetch(arg, api: JSS.api)
128
+ def self.fetch(searchterm = nil, **args)
129
129
  if self == JSS::PatchSource
130
130
  begin
131
- fetched = JSS::PatchInternalSource.fetch arg, api: api
132
- rescue RestClient::ResourceNotFound, RestClient::InternalServerError, JSS::NoSuchItemError
131
+ fetched = JSS::PatchInternalSource.fetch searchterm, **args
132
+ rescue
133
133
  fetched = nil
134
134
  end
135
135
  unless fetched
136
136
  begin
137
- fetched = JSS::PatchExternalSource.fetch arg, api: api
138
- rescue RestClient::ResourceNotFound, RestClient::InternalServerError, JSS::NoSuchItemError
137
+ fetched = JSS::PatchExternalSource.fetch searchterm, **args
138
+ rescue
139
139
  raise JSS::NoSuchItemError, 'No matching PatchSource found'
140
140
  end
141
141
  end
142
142
  return fetched
143
143
  end # if self == JSS::PatchSource
144
+
144
145
  begin
145
- super
146
- rescue RestClient::ResourceNotFound, RestClient::InternalServerError, JSS::NoSuchItemError
146
+ super searchterm, **args
147
+ rescue JSS::NoSuchItemError
147
148
  raise JSS::NoSuchItemError, "No matching #{self::RSRC_OBJECT_KEY} found"
148
149
  end
149
150
  end
@@ -211,7 +212,7 @@ module JSS
211
212
  begin
212
213
  # TODO: remove this and adjust parsing when jamf fixes the JSON
213
214
  raw = JSS::XMLWorkaround.data_via_xml(rsrc, AVAILABLE_TITLES_DATA_MAP, api)
214
- rescue RestClient::ResourceNotFound
215
+ rescue JSS::NoSuchItemError
215
216
  return []
216
217
  end
217
218
 
@@ -148,6 +148,12 @@ module JSS
148
148
  monthly: 'Once every month'
149
149
  }.freeze
150
150
 
151
+ RETRY_EVENTS = {
152
+ none: 'none',
153
+ checkin: 'check-in',
154
+ trigger: 'trigger'
155
+ }.freeze
156
+
151
157
  RESTART_WHEN = {
152
158
  if_pkg_requires: 'Restart if a package or update requires it',
153
159
  now: 'Restart immediately',
@@ -174,7 +180,9 @@ module JSS
174
180
  change_pw: 'specified',
175
181
  generate_pw: 'random',
176
182
  enable_fv2: 'fileVaultEnable',
177
- disable_fv2: 'fileVaultDisable'
183
+ disable_fv2: 'fileVaultDisable',
184
+ reset_random: 'resetRandom',
185
+ reset_pw: 'reset'
178
186
  }.freeze
179
187
 
180
188
  PACKAGE_ACTIONS = {
@@ -191,6 +199,12 @@ module JSS
191
199
  after: 'After'
192
200
  }.freeze
193
201
 
202
+ DISK_ENCRYPTION_ACTIONS = {
203
+ apply: "apply",
204
+ remediate: "remediate",
205
+ none: "none"
206
+ }
207
+
194
208
  PRINTER_ACTIONS = {
195
209
  map: 'install',
196
210
  unmap: 'uninstall'
@@ -541,6 +555,7 @@ module JSS
541
555
 
542
556
  # @return [String] the message shown the user at policy end
543
557
  attr_reader :user_message_finish
558
+ alias user_message_end user_message_finish
544
559
 
545
560
  # @return [Hash]
546
561
  #
@@ -604,7 +619,6 @@ module JSS
604
619
 
605
620
  if @in_jss
606
621
  gen = @init_data[:general]
607
- @frequency = gen[:frequency]
608
622
  @target_drive = gen[:target_drive]
609
623
  @offline = gen[:offline]
610
624
  @enabled = gen[:enabled]
@@ -620,6 +634,10 @@ module JSS
620
634
  trigger_enrollment_complete: gen[:trigger_enrollment_complete],
621
635
  trigger_other: gen[:trigger_other]
622
636
  }
637
+ @frequency = gen[:frequency]
638
+ @retry_event = gen[:retry_event]
639
+ @retry_attempts = gen[:retry_attempts]
640
+ @notify_failed_retries = gen[:notify_on_each_failed_retry]
623
641
 
624
642
  dtl = gen[:date_time_limitations]
625
643
 
@@ -670,6 +688,7 @@ module JSS
670
688
  @disk_encryption = @init_data[:disk_encryption]
671
689
 
672
690
  @printers = @init_data[:printers]
691
+ @printers.shift
673
692
 
674
693
  # Not in jss yet
675
694
  end
@@ -735,8 +754,93 @@ module JSS
735
754
  # @return [void]
736
755
  #
737
756
  def frequency=(freq)
738
- raise JSS::InvalidDataError, "New frequency must be one of :#{FREQUENCIES.keys.join ', :'}" unless FREQUENCIES.key?(freq)
739
- @frequency = FREQUENCIES[freq]
757
+ raise JSS::InvalidDataError, "New frequency must be one of :#{FREQUENCIES.keys.join ', :'}" unless FREQUENCIES.key?(freq) || FREQUENCIES.value?(freq)
758
+
759
+ freq = freq.is_a?(Symbol) ? FREQUENCIES[freq] : freq
760
+ return if freq == @frequency
761
+
762
+ @frequency = freq
763
+ @need_to_update = true
764
+ end
765
+
766
+ # @return [String] The event that causes a policy retry
767
+ def retry_event
768
+ return RETRY_EVENTS[:none] unless FREQUENCIES[:once_per_computer] == @frequency
769
+
770
+ @retry_event
771
+ end
772
+
773
+ # Set the event that causes a retry if the policy fails.
774
+ # One of the ways to turn off policy retry is to set this to :none
775
+ # The other is to set the retry_attempts to 0
776
+ #
777
+ # @param [Symbol, String] A key or value from RETRY_EVENTS
778
+ # @return [void]
779
+ #
780
+ def retry_event=(evt)
781
+ validate_retry_opt
782
+ raise JSS::InvalidDataError, "Retry event must be one of :#{RETRY_EVENTS.keys.join ', :'}" unless RETRY_EVENTS.key?(evt) || RETRY_EVENTS.value?(evt)
783
+
784
+ evt = evt.is_a?(Symbol) ? RETRY_EVENTS[evt] : evt
785
+ return if evt == @retry_event
786
+
787
+ # if the event is not 'none' and attempts is <= 0,
788
+ # set events to 1, or the API won't accept it
789
+ unless evt == RETRY_EVENTS[:none]
790
+ @retry_attempts = 1 unless @retry_attempts.positive?
791
+ end
792
+
793
+ @retry_event = evt
794
+ @need_to_update = true
795
+ end
796
+
797
+ # @return [Integer] How many times wil the policy be retried if it fails.
798
+ # -1 means no retries, otherwise, an integer from 1 to 10
799
+ def retry_attempts
800
+ return 0 unless FREQUENCIES[:once_per_computer] == @frequency
801
+
802
+ @retry_attempts
803
+ end
804
+
805
+ # Set the number of times to retry if the policy fails.
806
+ # One of the ways to turn off policy retry is to set this to 0 or -1
807
+ # The other is to set retry_event to :none
808
+ #
809
+ # @param [Integer] From -1 to 10
810
+ # @return [void]
811
+ #
812
+ def retry_attempts=(int)
813
+ validate_retry_opt
814
+ raise JSS::InvalidDataError, 'Retry attempts must be an integer from 0-10' unless int.is_a?(Integer) && (-1..10).include?(int)
815
+
816
+ # if zero or -1, turn off retries
817
+ if int <= 0
818
+ @retry_event = RETRY_EVENTS[:none]
819
+ int = -1
820
+ end
821
+ return if @retry_attempts == int
822
+
823
+ @retry_attempts = int
824
+ @need_to_update = true
825
+ end
826
+
827
+ # @return [Boolean] Should admins be notified of failed retry attempts
828
+ def notify_failed_retries?
829
+ return false unless FREQUENCIES[:once_per_computer] == @frequency
830
+
831
+ @notify_failed_retries
832
+ end
833
+ alias notify_failed_retries notify_failed_retries?
834
+ alias notify_on_each_failed_retry notify_failed_retries?
835
+
836
+ # @param bool[Boolean] Should admins be notified of failed retry attempts
837
+ # @return [void]
838
+ def notify_failed_retries=(bool)
839
+ validate_retry_opt
840
+ bool = JSS::Validate.boolean bool
841
+ return if @notify_failed_retries == bool
842
+
843
+ @notify_failed_retries = bool
740
844
  @need_to_update = true
741
845
  end
742
846
 
@@ -914,6 +1018,30 @@ module JSS
914
1018
  end
915
1019
  alias message= reboot_message=
916
1020
 
1021
+ # Set User Start Message
1022
+ #
1023
+ # @param user_message[String] Text of User Message
1024
+ #
1025
+ # @return [void] description of returned object
1026
+ def user_message_start=(message)
1027
+ raise JSS::InvalidDataError, 'User message must be a String' unless message.is_a? String
1028
+ @user_message_start = message
1029
+ @need_to_update = true
1030
+ end
1031
+
1032
+ # Set User Finish Message
1033
+ #
1034
+ # @param user_message[String] Text of User Message
1035
+ #
1036
+ # @return [void] description of returned object
1037
+ def user_message_end=(message)
1038
+ raise JSS::InvalidDataError, 'User message must be a String' unless message.is_a? String
1039
+ @user_message_finish = message
1040
+ @need_to_update = true
1041
+ end
1042
+
1043
+ alias user_message_finish= user_message_end=
1044
+
917
1045
  # Set Startup Disk
918
1046
  # Only Supports 'Specify Local Startup Disk' at the moment
919
1047
  #
@@ -1180,7 +1308,7 @@ module JSS
1180
1308
 
1181
1309
  # Remove a package from this policy by name or id
1182
1310
  #
1183
- # @param identfier [String,Integer] the name or id of the package to remove
1311
+ # @param identifier [String,Integer] the name or id of the package to remove
1184
1312
  #
1185
1313
  # @return [Array, nil] the new packages array or nil if no change
1186
1314
  #
@@ -1270,7 +1398,7 @@ module JSS
1270
1398
 
1271
1399
  # Remove a script from this policy by name or id
1272
1400
  #
1273
- # @param identfier [String,Integer] the name or id of the script to remove
1401
+ # @param identifier [String,Integer] the name or id of the script to remove
1274
1402
  #
1275
1403
  # @return [Array, nil] the new scripts array or nil if no change
1276
1404
  #
@@ -1292,6 +1420,49 @@ module JSS
1292
1420
  @directory_bindings.map { |p| p[:name] }
1293
1421
  end
1294
1422
 
1423
+ # Add a Directory Bidning to the list of directory_bindings handled by this policy.
1424
+ # If the directory binding already exists in the policy, nil is returned and
1425
+ # no changes are made.
1426
+ #
1427
+ # @param [String,Integer] identifier the name or id of the directory binding to add to this policy
1428
+ #
1429
+ # @param position [Symbol, Integer] where to add this directory binding among the list of
1430
+ # directory_bindings. Zero-based, :start and 0 are the same, as are :end and -1.
1431
+ # Defaults to :end
1432
+ #
1433
+ # @return [Array, nil] the new @directory_bindings array, nil if directory_binding was already in the policy
1434
+ #
1435
+ def add_directory_binding(identifier, **opts)
1436
+ id = validate_directory_binding_opts identifier, opts
1437
+
1438
+ return nil if @directory_bindings.map { |s| s[:id] }.include? id
1439
+
1440
+ name = JSS::DirectoryBinding.map_all_ids_to(:name, api: @api)[id]
1441
+
1442
+ directory_binding_data = {
1443
+ id: id,
1444
+ name: name
1445
+ }
1446
+
1447
+ @directory_bindings.insert opts[:position], directory_binding_data
1448
+
1449
+ @need_to_update = true
1450
+ @directory_bindings
1451
+ end
1452
+
1453
+
1454
+ # Remove a directory binding from this policy by name or id
1455
+ #
1456
+ # @param identifier [String,Integer] the name or id of the directory binding to remove
1457
+ #
1458
+ # @return [Array, nil] the new directory bindings array or nil if no change
1459
+ #
1460
+ def remove_directory_binding(identifier)
1461
+ removed = @directory_bindings.delete_if { |s| s[:id] == identifier || s[:name] == identifier }
1462
+ @need_to_update = true if removed
1463
+ removed
1464
+ end
1465
+
1295
1466
  ###### Dock items
1296
1467
 
1297
1468
  # @return [Array] the id's of the dock_items handled by the policy
@@ -1304,6 +1475,86 @@ module JSS
1304
1475
  @dock_items.map { |p| p[:name] }
1305
1476
  end
1306
1477
 
1478
+
1479
+ ###### Printers
1480
+
1481
+ # Add a specific printer object to the policy.
1482
+ #
1483
+ # @author Tyler Morgan
1484
+ #
1485
+ # @param newvalue [String,Integer] The name or the id of the printer to be added to this policy.
1486
+ #
1487
+ # @param position [Symbol, Integer] where to add this printer object among the list of printer
1488
+ # objects. Zero-based, :start and 0 are the same, as are :end and -1.
1489
+ # Defaults to :end
1490
+ #
1491
+ # @param action [Symbol] One of the PRINTER_ACTIONS symbols. What you want done with the printer object upon policy execution.
1492
+ #
1493
+ # @param make_default [TrueClass,FalseClass] Should this printer object be set to default.
1494
+ # Defaults to false
1495
+ #
1496
+ # @return [String] The new printers array or nil if the printer was already in the policy
1497
+ def add_printer(identifier, **opts)
1498
+ id = validate_printer_opts identifier, opts
1499
+
1500
+ return nil if @printers.map { |p| p[:id] }.include? id
1501
+
1502
+ name = JSS::Printer.map_all_ids_to(:name, api: @api)[id]
1503
+
1504
+ printer_data = {
1505
+ id: id,
1506
+ name: name,
1507
+ action: PRINTER_ACTIONS[opts[:action]],
1508
+ make_default: opts[:make_default]
1509
+ }
1510
+
1511
+ @printers.insert opts[:position], printer_data
1512
+
1513
+ @need_to_update = true
1514
+ @printers
1515
+ end
1516
+
1517
+
1518
+ # Remove a specific printer object from the policy.
1519
+ #
1520
+ # @author Tyler Morgan
1521
+ #
1522
+ # @param identifier [String,Integer] The name or id of the printer to be removed.
1523
+ #
1524
+ # @return [Array, nil] The new printers array or nil if no change.
1525
+ def remove_printer(identifier)
1526
+ removed = @printers.delete_if { |p| p[:id] == identifier || p[:name] == identifier }
1527
+
1528
+ @need_to_update = true
1529
+ removed
1530
+ end
1531
+
1532
+ # Add a dock item to the policy
1533
+ def add_dock_item(identifier, action)
1534
+ id = JSS::DockItem.valid_id identifier, api: @api
1535
+
1536
+ raise JSS::NoSuchItemError, "No Dock Item matches '#{identifier}'" unless id
1537
+
1538
+ raise JSS::InvalidDataError, "Action must be one of: :#{DOCK_ITEM_ACTIONS.keys.join ', :'}" unless DOCK_ITEM_ACTIONS.include? action
1539
+
1540
+ return nil if @dock_items.map { |d| d[:id] }.include? id
1541
+
1542
+ name = JSS::DockItem.map_all_ids_to(:name, api: @api)[id]
1543
+
1544
+ @dock_items << {id: id, name: name, action: DOCK_ITEM_ACTIONS[action]}
1545
+
1546
+ @need_to_update = true
1547
+ @dock_items
1548
+ end
1549
+
1550
+ # Remove a dock item from the policy
1551
+ def remove_dock_item(identifier)
1552
+ # TODO: Add validation against JSS::DockItem
1553
+ removed = @dock_items.delete_if { |d| d[:id] == identifier || d[:name] == identifier }
1554
+ @need_to_update = true if removed
1555
+ removed
1556
+ end
1557
+
1307
1558
  # @return [Array] the id's of the printers handled by the policy
1308
1559
  def printer_ids
1309
1560
  begin
@@ -1312,7 +1563,7 @@ module JSS
1312
1563
  return []
1313
1564
  end
1314
1565
  end
1315
-
1566
+
1316
1567
  # @return [Array] the names of the printers handled by the policy
1317
1568
  def printer_names
1318
1569
  begin
@@ -1322,6 +1573,130 @@ module JSS
1322
1573
  end
1323
1574
  end
1324
1575
 
1576
+
1577
+
1578
+ ###### Disk Encryption
1579
+
1580
+ # Sets the Disk Encryption application to "Remediate" and sets the remediation key type to individual.
1581
+ #
1582
+ # @author Tyler Morgan
1583
+ #
1584
+ # @return [Void]
1585
+ #
1586
+ def reissue_key()
1587
+ if @disk_encryption[:action] != DISK_ENCRYPTION_ACTIONS[:remediate]
1588
+ # Setting New Action
1589
+ hash = {
1590
+ action: DISK_ENCRYPTION_ACTIONS[:remediate],
1591
+ remediate_key_type: "Individual"
1592
+ }
1593
+
1594
+ @disk_encryption = hash
1595
+ @need_to_update = true
1596
+
1597
+ else
1598
+ # Update
1599
+ return
1600
+ end
1601
+
1602
+ end
1603
+
1604
+
1605
+ # Sets the Disk Encryption application to "Apply" and sets the correct disk encryption configuration ID using either the name or id.
1606
+ #
1607
+ # @author Tyler Morgan
1608
+ #
1609
+ # @return [Void]
1610
+ #
1611
+ def apply_encryption_configuration(identifier)
1612
+
1613
+ id = JSS::DiskEncryptionConfiguration.valid_id identifier
1614
+
1615
+ return if id.nil?
1616
+
1617
+ hash = {
1618
+ action: DISK_ENCRYPTION_ACTIONS[:apply],
1619
+ disk_encryption_configuration_id: id,
1620
+ auth_restart: false
1621
+ }
1622
+
1623
+ @disk_encryption = hash
1624
+ @need_to_update = true
1625
+ end
1626
+
1627
+
1628
+ # Removes the Disk Encryption settings associated with this specific policy.
1629
+ #
1630
+ # @author Tyler Morgan
1631
+ #
1632
+ # @return [Void]
1633
+ #
1634
+ def remove_encryption_configuration()
1635
+ hash = {
1636
+ action: DISK_ENCRYPTION_ACTIONS[:none]
1637
+ }
1638
+
1639
+ @disk_encryption = hash
1640
+ @need_to_update = true
1641
+ end
1642
+
1643
+ # Interact with management account settings
1644
+ #
1645
+ # @param action [Key] one of the MGMT_ACCOUNT_ACTIONS keys
1646
+ #
1647
+ # @return The current specified management settings.
1648
+ #
1649
+ # Reference: https://developer.jamf.com/documentation#resources-with-passwords
1650
+ #
1651
+ def set_management_account(action, **opts)
1652
+ # TODO: Add proper error handling
1653
+ raise JSS::InvalidDataError, "Action must be one of: :#{MGMT_ACCOUNT_ACTIONS.keys.join ', :'}" unless MGMT_ACCOUNT_ACTIONS.include? action
1654
+
1655
+ management_data = {}
1656
+
1657
+ if action == :change_pw || action == :reset_pw
1658
+ raise JSS::MissingDataError, ":password must be provided when changing management account password" if opts[:password].nil?
1659
+
1660
+ management_data = {
1661
+ action: MGMT_ACCOUNT_ACTIONS[action],
1662
+ managed_password: opts[:password]
1663
+ }
1664
+ elsif action == :reset_random || action == :generate_pw
1665
+ raise JSS::MissingDataError, ":password_length must be provided when setting a random password" if opts[:password_length].nil?
1666
+ raise JSS::InvalidDataError, ":password_length must be an Integer" unless opts[:password_length].is_a? Integer
1667
+
1668
+ management_data = {
1669
+ action: MGMT_ACCOUNT_ACTIONS[action],
1670
+ managed_password_length: opts[:password_length]
1671
+ }
1672
+ else
1673
+ management_data = {
1674
+ action: MGMT_ACCOUNT_ACTIONS[action]
1675
+ }
1676
+ end
1677
+
1678
+ @management_account = management_data
1679
+
1680
+ @need_to_update = true
1681
+
1682
+ @management_account
1683
+
1684
+ end
1685
+
1686
+ # Check if management password matches provided password
1687
+ #
1688
+ # @param password[String] the password that is SHA256'ed to compare to the one from the API.
1689
+ #
1690
+ # @return [Boolean] The result of the comparison of the management password and provided text.
1691
+ #
1692
+ def verify_management_password(password)
1693
+ raise JSS::InvalidDataError, "Management password must be a string." unless password.is_a? String
1694
+
1695
+ raise JSS::UnsupportedError, "'#{@management_account[:action].to_s}' does not support management passwords." unless @management_account[:action] == MGMT_ACCOUNT_ACTIONS[:change_pw] || @management_account[:action] == MGMT_ACCOUNT_ACTIONS[:reset_pw]
1696
+
1697
+ return Digest::SHA256.hexdigest(password).to_s == @management_account[:managed_password_sha256].to_s
1698
+ end
1699
+
1325
1700
  ###### Actions
1326
1701
 
1327
1702
  # Try to execute this policy on this machine.
@@ -1375,6 +1750,17 @@ module JSS
1375
1750
 
1376
1751
  private
1377
1752
 
1753
+ # raise an error if a trying to set retry options when
1754
+ # frequency is not 'once per comptuer'
1755
+ #
1756
+ # @return [void]
1757
+ #
1758
+ def validate_retry_opt
1759
+ return if FREQUENCIES[:once_per_computer] == @frequency
1760
+
1761
+ raise JSS::UnsupportedError, 'Policy retry is only available when frequency is set to :once_per_computer'
1762
+ end
1763
+
1378
1764
  # raise an error if a package being added isn't valid
1379
1765
  #
1380
1766
  # @see #add_package
@@ -1439,6 +1825,64 @@ module JSS
1439
1825
  id
1440
1826
  end
1441
1827
 
1828
+ # raise an error if the directory binding being added isn't valid
1829
+ #
1830
+ # @see #add_directory_binding
1831
+ #
1832
+ # @return [Integer, nil] the valid id for the package
1833
+ #
1834
+ def validate_directory_binding_opts(identifier, opts)
1835
+ opts[:position] ||= -1
1836
+
1837
+ opts[:position] =
1838
+ case opts[:position]
1839
+ when :start then 0
1840
+ when :end then -1
1841
+ else JSS::Validate.integer(opts[:position])
1842
+ end
1843
+
1844
+ # if the given position is past the end, set it to -1 (the end)
1845
+ opts[:position] = -1 if opts[:position] > @directory_bindings.size
1846
+
1847
+ id = JSS::DirectoryBinding.valid_id identifier, api: @api
1848
+ raise JSS::NoSuchItemError, "No directory binding matches '#{identifier}'" unless id
1849
+ id
1850
+ end
1851
+
1852
+ # Raises an error if the printer being added isn't valid, additionally checks the options and sets defaults where possible.
1853
+ #
1854
+ # @see #add_printer
1855
+ #
1856
+ # @return [Integer, nil] the valid id for the package
1857
+ #
1858
+ def validate_printer_opts(identifier, opts)
1859
+ opts[:position] ||= -1
1860
+
1861
+ opts[:position] =
1862
+ case opts[:position]
1863
+ when :start then 0
1864
+ when :end then -1
1865
+ else JSS::Validate.integer(opts[:position])
1866
+ end
1867
+
1868
+ # If the given position is past the end, set it to -1 (the end)
1869
+ opts[:position] = -1 if opts[:position] > @printers.size
1870
+
1871
+ # Checks if action to be done with the printer object is provided and valid.
1872
+ raise JSS::MissingDataError, "action must be provided, must be one of :#{PRINTER_ACTIONS.keys.join(':,')}." if opts[:action].nil?
1873
+ raise JSS::InvalidDataError, "action must be one of :#{PRINTER_ACTIONS.keys.join(',:')}." unless PRINTER_ACTIONS.keys.include? opts[:action]
1874
+
1875
+
1876
+ # Checks if the make_default option is valid, and sets the default if needed.
1877
+ raise JSS::InvalidDataError, "make_default must be either true or false." unless opts[:make_default].is_a?(TrueClass) || opts[:make_default].is_a?(FalseClass) || opts[:make_default].nil?
1878
+
1879
+ opts[:make_default] = false if opts[:make_default].nil?
1880
+
1881
+ id = JSS::Printer.valid_id identifier, api: @api
1882
+ raise JSS::NoSuchItemError, "No printer matches '#{identifier}'" unless id
1883
+ id
1884
+ end
1885
+
1442
1886
  def rest_xml
1443
1887
  doc = REXML::Document.new APIConnection::XML_HEADER
1444
1888
  obj = doc.add_element RSRC_OBJECT_KEY.to_s
@@ -1447,6 +1891,10 @@ module JSS
1447
1891
  general.add_element('name').text = @name
1448
1892
  general.add_element('enabled').text = @enabled
1449
1893
  general.add_element('frequency').text = @frequency
1894
+ general.add_element('retry_event').text = @retry_event
1895
+ general.add_element('retry_attempts').text = @retry_attempts.to_s
1896
+ general.add_element('notify_on_each_failed_retry').text = @notify_failed_retries.to_s
1897
+
1450
1898
  general.add_element('target_drive').text = @target_drive
1451
1899
  general.add_element('offline').text = @offline
1452
1900
 
@@ -1475,6 +1923,22 @@ module JSS
1475
1923
  maint.add_element('user_cache').text = @user_cache.to_s
1476
1924
  maint.add_element('verify').text = @verify_startup_disk.to_s
1477
1925
 
1926
+ acct_maint = obj.add_element 'account_maintenance'
1927
+
1928
+ mgmt_acct = acct_maint.add_element 'management_account'
1929
+ JSS.hash_to_rexml_array(@management_account).each { |x| mgmt_acct << x }
1930
+
1931
+ directory_bindings = acct_maint.add_element 'directory_bindings'
1932
+ @directory_bindings.each do |b|
1933
+ directory_binding = directory_bindings.add_element 'binding'
1934
+ dbdeets = JSS.hash_to_rexml_array b
1935
+ dbdeets.each { |d| directory_binding << d }
1936
+ end
1937
+
1938
+ user_interaction = obj.add_element 'user_interaction'
1939
+ user_interaction.add_element('message_start').text = @user_message_start.to_s
1940
+ user_interaction.add_element('message_finish').text = @user_message_finish.to_s
1941
+
1478
1942
  files_processes = obj.add_element 'files_processes'
1479
1943
  JSS.hash_to_rexml_array(@files_processes).each { |f| files_processes << f }
1480
1944
 
@@ -1493,6 +1957,26 @@ module JSS
1493
1957
  sdeets.each { |d| script << d }
1494
1958
  end
1495
1959
 
1960
+ disk_encryption = obj.add_element 'disk_encryption'
1961
+
1962
+ @disk_encryption.each do |k,v|
1963
+ disk_encryption.add_element(k.to_s).text = v.to_s
1964
+ end
1965
+
1966
+ printers = obj.add_element 'printers'
1967
+ @printers.each do |pr|
1968
+ printer = printers.add_element 'printer'
1969
+ pdeets = JSS.hash_to_rexml_array pr
1970
+ pdeets.each { |d| printer << d }
1971
+ end
1972
+
1973
+ dock_items = obj.add_element 'dock_items'
1974
+ @dock_items.each do |d|
1975
+ dock_item = dock_items.add_element 'dock_item'
1976
+ ddeets = JSS.hash_to_rexml_array d
1977
+ ddeets.each { |de| dock_item << de }
1978
+ end
1979
+
1496
1980
  add_self_service_xml doc
1497
1981
  add_site_to_xml doc
1498
1982