ruby-jss 0.10.2a5 → 0.10.2

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.

Potentially problematic release.


This version of ruby-jss might be problematic. Click here for more details.

@@ -63,24 +63,31 @@ module JSS
63
63
  #
64
64
  # === MDM Commands
65
65
  #
66
- # See the {JSS::MDM} mixin module for Class and Instance methods for
67
- # sending MDM commands to computers.
66
+ # The following methods can be used to send an APNS command to the
67
+ # computer represented by an instance of JSS::Computer, equivalent to
68
+ # clicking one of the buttons on the Management Commands section of the
69
+ # Management tab of the Computer details page in the JSS UI.
68
70
  #
69
- # To send MDM commands without fetching Computer instances, use the class
70
- # methods, which can take multiple computer identifiers at once.
71
+ # - {#blank_push} (aliases blank, noop, send_blank_push)
72
+ # - {#device_lock} (aliases lock, lock_device)
73
+ # - {#erase_device} (aliases wipe)
74
+ # - {#remove_mdm_profile}
71
75
  #
72
- # NOTE: the poorly named 'UnmanageDevice' mdm command is implemented
73
- # as {#remove_mdm_profile} (which is its name in the webUI) as well as
74
- # {#unmanage_device}.
75
- # Calling that method will NOT fully unmanage a computer from the JSS's point
76
+ # To send an MDM command without making a Computer instance, use the class
77
+ # {JSS::Computer.send_mdm_command} which can take multiple computer
78
+ # identifiers at once.
79
+ #
80
+ # NOTE: the poorly named 'UnmanageDevice' command via the API is implemented
81
+ # as the {#remove_mdm_profile} method (which is its name in the webUI).
82
+ # Calling that method will NOT unmanage the machine from the JSS's point
76
83
  # of view, it will just remove the mdm management profile from the machine
77
84
  # and all configuration profiles that were installed via the JSS. Those
78
85
  # profiles may be re-installed automatically later if the computer is still in
79
86
  # scope for them
80
87
  #
81
- # To properly unmanage a computer, use the {#make_unmanaged} Instance method
82
- # which removes the mdm profile, but also makes the machine unmanged by the
83
- # JSS, setting the management acct to nil, and requring re-enrollment.
88
+ # The {#make_unmanaged} method also removes the mdm profile, but actually
89
+ # does make the machine unmanged by the JSS, setting the management acct to
90
+ # nil, and requring re-enrollment.
84
91
  #
85
92
  # === Computer History
86
93
  #
@@ -167,8 +174,6 @@ module JSS
167
174
  include JSS::Purchasable
168
175
  include JSS::Uploadable
169
176
  include JSS::Extendable
170
- include JSS::Sitable
171
- include JSS::MDM
172
177
 
173
178
  extend JSS::Matchable
174
179
 
@@ -188,9 +193,6 @@ module JSS
188
193
  # It's also used in various error messages
189
194
  RSRC_OBJECT_KEY = :computer
190
195
 
191
- # Where is the Site data in the API JSON?
192
- SITE_SUBSET = :general
193
-
194
196
  # these keys, as well as :id and :name, are present in valid API JSON data for this class
195
197
  # DEPRECATED, with be removed in a future release.
196
198
  VALID_DATA_KEYS = %i[sus distribution_point alt_mac_address].freeze
@@ -216,8 +218,32 @@ module JSS
216
218
  # file uploads can send attachments to the JSS using :computers as the sub-resource.
217
219
  UPLOAD_TYPES = { attachment: :computers }.freeze
218
220
 
219
- # Tell the MDM module what kind of MDM commands we use.
220
- MDM_COMMAND_TARGET = :computers
221
+ # The base REST resource for sending computer MDM commands
222
+ COMPUTER_MDM_RSRC = 'computercommands/command'.freeze
223
+
224
+ # A mapping of Symbols available to the send_mdm_command class method, to
225
+ # the String commands actuallly sent via the API.
226
+ COMPUTER_MDM_COMMANDS = {
227
+ blank_push: 'BlankPush',
228
+ blankpush: 'BlankPush',
229
+ send_blank_push: 'BlankPush',
230
+ blank: 'BlankPush',
231
+ noop: 'BlankPush',
232
+ device_lock: 'DeviceLock',
233
+ devicelock: 'DeviceLock',
234
+ lock: 'DeviceLock',
235
+ lock_device: 'DeviceLock',
236
+ erase_device: 'EraseDevice',
237
+ erasedevice: 'EraseDevice',
238
+ erase: 'EraseDevice',
239
+ wipe: 'EraseDevice',
240
+ unmanage_device: 'UnmanageDevice',
241
+ unmanagedevice: 'UnmanageDevice',
242
+ unmanage: 'UnmanageDevice'
243
+ }.freeze
244
+
245
+ # these MDM commands require a passcode
246
+ COMPUTER_MDM_COMMANDS_NEEDING_PASSCODE = %w[DeviceLock EraseDevice].freeze
221
247
 
222
248
  # The API resource for app usage
223
249
  APPLICATION_USAGE_RSRC = 'computerapplicationusage'.freeze
@@ -273,6 +299,8 @@ module JSS
273
299
  # converted to strings) and the second-level hash key of the
274
300
  # returned subset data.
275
301
  #
302
+ # The values are the key within each history item that contains the
303
+ # 'epoch' timestamp, for conver
276
304
  HISTORY_SUBSETS = %i[
277
305
  computer_usage_logs
278
306
  audits
@@ -285,29 +313,17 @@ module JSS
285
313
  mac_app_store_applications
286
314
  ].freeze
287
315
 
288
- # Most History Subsets contain Arrays of Hashes.
289
- #
290
- # However, these contain a Hash of Arrays of Hashes:
291
- #
292
- # :commands is a hash with these keys:
293
- # :completed - An array of hashes about completed MDM commands
294
- # :pending - An array of hashes about pending MDM commands
295
- # :failed - An array of hashes about failed MDM commands
296
- #
297
- # :mac_app_store_applications is a hash with these keys:
298
- # :installed - An array of hashes about installed apps
299
- # :pending - An array of hashes about apps pending installation
300
- # :failed - An array of hashes about apps that failed to install.
301
- #
302
- # The .history class and instance methods for JSS::Computer re-organize
303
- # those data structures to be consistent with the other subsets of history
304
- # data by turning them into an Array of Hashes, where each hash has a
305
- # :status key containing :completed/:installed, :pending, or :failed
306
- #
307
- # See {JSS::Computer.full_history}, {JSS::Computer.history_subset} and
308
- # {JSS::Computer.standardize_history_subset} class methods for details.
309
- #
310
- HISTORY_INCONSISTENT_SUBSETS = %i[commands mac_app_store_applications].freeze
316
+ # HISTORY_SUBSETS = %i(
317
+ # computer_usage_logs date_time_epoch
318
+ # audits
319
+ # policy_logs date_completed_epoch
320
+ # casper_remote_logs date_time_epoch
321
+ # screen_sharing_logs date_time_epoch
322
+ # casper_imaging_logs
323
+ # commands completed_epoch
324
+ # user_location
325
+ # mac_app_store_applications
326
+ # ).freeze
311
327
 
312
328
  POLICY_STATUS_COMPLETED = 'Completed'.freeze
313
329
 
@@ -315,23 +331,12 @@ module JSS
315
331
 
316
332
  POLICY_STATUS_PENDING = 'Pending'.freeze
317
333
 
318
- COMMAND_STATUS_COMPLETED = :completed
319
-
320
- COMMAND_STATUS_PENDING = :pending
321
-
322
- COMMAND_STATUS_FAILED = :failed
323
-
324
- APP_STORE_APP_STATUS_INSTALLED = :installed
325
-
326
- APP_STORE_APP_STATUS_PENDING = :pending
327
-
328
- APP_STORE_APP_STATUS_FAILED = :failed
329
-
330
334
  # the object type for this object in
331
335
  # the object history table.
332
336
  # See {APIObject#add_object_history_entry}
333
337
  OBJECT_HISTORY_OBJECT_TYPE = 1
334
338
 
339
+
335
340
  # Class Methods
336
341
  #####################################
337
342
 
@@ -458,191 +463,56 @@ module JSS
458
463
  all(refresh, api: api).select { |d| d[:model] =~ /^macpro/i }
459
464
  end
460
465
 
461
- # Retrieve Application Usage data for a computer by id, without
462
- # instantiation.
466
+ # Send an MDM command to one or more managed computers by id or name
463
467
  #
464
- # @param ident [Integer,String] An identifier (id, name, serialnumber,
465
- # macadress or udid) of the computer for which to retrieve Application Usage
466
468
  #
467
- # @param start_date [Time,Date,DateTime,String] The earliest date to retrieve
469
+ # @param targets[String,Integer,Array<String,Integer>]
470
+ # the name or id of the computer to receive the command, or
471
+ # an array of such names or ids, or a comma-separated string
472
+ # of them.
473
+ # @param command[Symbol] the command to send, one of the keys
474
+ # of COMPUTER_MDM_COMMANDS
468
475
  #
469
- # @param end_date [String,Date,DateTime,Time] Defaults to start_date
476
+ # @param passcode[String] some commands require a 6-character passcode
470
477
  #
471
478
  # @param api[JSS::APIConnection] an API connection to use for the query.
472
479
  # Defaults to the corrently active API. See {JSS::APIConnection}
473
480
  #
474
- # @return [Hash{Date=>Array<Hash>}] A Hash with keys (Date instances) for
475
- # each day in the range.
481
+ # @return [String] The uuid of the MDM command sent, if applicable
482
+ # (blank pushes do not generate uuids)
476
483
  #
477
- # Each hash value contains an Array of apps used
478
- # on that day.
479
- #
480
- # Each item in the array is a hash of data about the app.
481
- # Those hash keys are:
482
- # :name => String, the name of the app
483
- # :version => String ,the version of the app
484
- # :foreground => Integer, the minutes it was in the foreground
485
- # :open => Integer, the minutes it was running.
486
- #
487
- def self.application_usage(ident, start_date, end_date = nil, api: JSS.api)
488
- id = valid_id ident, api: api
489
- raise "No computer matches identifier: #{ident}" unless id
490
- end_date ||= start_date
491
- start_date = Time.parse start_date if start_date.is_a? String
492
- end_date = Time.parse end_date if end_date.is_a? String
493
- unless ([start_date.class, end_date.class] - APPLICATION_USAGE_DATE_CLASSES).empty?
494
- raise JSS::InvalidDataError, 'Invalid Start or End Date'
495
- end
496
- start_date = start_date.strftime APPLICATION_USAGE_DATE_FMT
497
- end_date = end_date.strftime APPLICATION_USAGE_DATE_FMT
498
- data = api.get_rsrc(APPLICATION_USAGE_RSRC + "/id/#{id}/#{start_date}_#{end_date}")
499
- parsed_data = {}
500
- data[APPLICATION_USAGE_KEY].each do |day_hash|
501
- date = Date.parse day_hash[:date]
502
- parsed_data[date] = day_hash[:apps]
503
- end
504
- parsed_data
505
- end # app usage
484
+ def self.send_mdm_command(targets, command, passcode = nil, api: JSS.api)
485
+ raise JSS::NoSuchItemError, "Unknown command '#{command}'" unless COMPUTER_MDM_COMMANDS.keys.include? command
506
486
 
507
- # The 'computer management' data for a given computer by id,
508
- # looked up on the fly.
509
- #
510
- # Without specifying a subset:, the entire dataset is returned as a hash of
511
- # arrays, one per subset
512
- #
513
- # If a subset is given then only that array is returned, and it contains
514
- # hashes with data about each item (usually :name and :id)
515
- #
516
- # If the only: param is provided with a subset, it is used as a hash-key to
517
- # map the array to just those values, so subset: :smart_groups, only: :name
518
- # will return an array of names of smartgroups that contain the computer.
519
- #
520
- # @param ident [Integer,String] An identifier (id, name, serialnumber,
521
- # macadress or udid) of the computer for which to retrieve Application Usage
522
- #
523
- # @param subset[Symbol] Fetch only a subset of data, as an array.
524
- # must be one of the symbols in MGMT_DATA_SUBSETS
525
- #
526
- # @param only[Symbol] When fetching a subset, only return one value
527
- # per item in the array. meaningless without a subset.
528
- #
529
- # @param api[JSS::APIConnection] an API connection to use for the query.
530
- # Defaults to the corrently active API. See {JSS::APIConnection}
531
- #
532
- # @return [Hash] Without a subset:, a hash of all subsets, each of which is
533
- # an Array
534
- #
535
- # @return [Array] With a subset:, an array of items in that subset, possibly
536
- # limited to just certain values with only:
537
- #
538
- def self.management_data(ident, subset: nil, only: nil, api: JSS.api)
539
- id = valid_id ident, api: api
540
- raise "No computer matches identifier: #{ident}" unless id
541
- if subset
542
- management_data_subset id, subset: subset, only: only, api: api
543
- else
544
- full_management_data id, api: api
545
- end
546
- end
547
-
548
- # The full set of management data for a given computer.
549
- # This private method is called by self.management_data, q.v.
550
- #
551
- def self.full_management_data(id, api: JSS.api)
552
- mgmt_rsrc = MGMT_DATA_RSRC + "/id/#{id}"
553
- api.get_rsrc(mgmt_rsrc)[MGMT_DATA_KEY]
554
- end
555
- private_class_method :full_management_data
556
-
557
- # A subset of management data for a given computer.
558
- # This private method is called by self.management_data, q.v.
559
- #
560
- def self.management_data_subset(id, subset: nil, only: nil, api: JSS.api)
561
- raise "Subset must be one of :#{MGMT_DATA_SUBSETS.join ', :'}" unless MGMT_DATA_SUBSETS.include? subset
562
- subset_rsrc = MGMT_DATA_RSRC + "/id/#{id}/subset/#{subset}"
563
- subset_data = api.get_rsrc(subset_rsrc)[MGMT_DATA_KEY]
564
- return subset_data unless only
565
- subset_data.map { |d| d[only] }
566
- end
567
- private_class_method :management_data_subset
487
+ command = COMPUTER_MDM_COMMANDS[command]
488
+ cmd_rsrc = "#{COMPUTER_MDM_RSRC}/#{command}"
568
489
 
569
- # Return this computer's management history.
570
- # WARNING: Its huge, better to use a subset.
571
- #
572
- # NOTE: ruby-jss standardizes the inconsistent data-stucture of the subsets,
573
- # so they may not exactly match the raw API output.
574
- # See {JSS::Computer::HISTORY_INCONSISTENT_SUBSETS} for details
575
- #
576
- # @param ident [Integer,String] An identifier (id, name, serialnumber,
577
- # macadress or udid) of the computer for which to retrieve Application Usage
578
- #
579
- # @param subset[Symbol] the subset to return, rather than full history.
580
- #
581
- # @param api[JSS::APIConnection] an API connection to use for the query.
582
- # Defaults to the corrently active API. See {JSS::APIConnection}
583
- #
584
- # @return [Hash] The full history.
585
- #
586
- # @return [Array] The requested subset.
587
- #
588
- def self.history(ident, subset: nil, api: JSS.api)
589
- id = valid_id ident, api: api
590
- raise JSS::NoSuchItemError, "No Computer matches identifier: #{ident}" unless id
591
-
592
- if subset
593
- history_subset id, subset: subset, api: api
594
- else
595
- full_history id, api: api
490
+ if COMPUTER_MDM_COMMANDS_NEEDING_PASSCODE.include? command
491
+ unless passcode && passcode.is_a?(String) && passcode.length == 6
492
+ raise JSS::MissingDataError, "Command '#{command}' requires a 6-character passcode"
493
+ end
494
+ cmd_rsrc << "/passcode/#{passcode}"
596
495
  end
597
- end
598
496
 
599
- # The full management history for a given computer.
600
- # This private method is called by self.history, q.v.
601
- #
602
- def self.full_history(id, api: JSS.api)
603
- hist = api.get_rsrc(HISTORY_RSRC + "/id/#{id}")[HISTORY_KEY]
497
+ targets = JSS.to_s_and_a(targets.to_s)[:arrayform] unless targets.is_a? Array
604
498
 
605
- # rework the :commands and :mac_app_store_applications into a consistent data structure
606
- HISTORY_INCONSISTENT_SUBSETS.each do |subset|
607
- hist[subset] = standardize_history_subset hist[subset]
608
- end # :commands, :mac_app_store_applications].each do |subsect|
609
- hist
610
- end
611
- private_class_method :full_history
499
+ # make sure its an array of ids
500
+ targets.map! do |comp|
501
+ if all_ids(api: api).include? comp.to_i
502
+ comp.to_i
503
+ elsif all_names(api: api).include? comp
504
+ map_all_ids_to(:name, api: api).invert[comp]
505
+ else
506
+ raise JSS::NoSuchItemError, "No computer found matching '#{comp}'"
507
+ end # if
508
+ end # map!
612
509
 
613
- # A subset of the management history for a given computer.
614
- # This private method is called by self.history, q.v.
615
- #
616
- def self.history_subset(id, subset: nil, api: JSS.api)
617
- raise "Subset must be one of :#{HISTORY_SUBSETS.join ', :'}" unless HISTORY_SUBSETS.include? subset
618
- subset_rsrc = HISTORY_RSRC + "/id/#{id}/subset/#{subset}"
619
- subset_data = api.get_rsrc(subset_rsrc)[HISTORY_KEY][subset]
620
- return standardize_history_subset(subset_data) if HISTORY_INCONSISTENT_SUBSETS.include? subset
621
- subset_data
622
- end
623
- private_class_method :history_subset
510
+ cmd_rsrc << "/id/#{targets.join ','}"
624
511
 
625
- # rework the inconsistent data structure of :commands and
626
- # :mac_app_store_applications (Hash of Arrays of Hashes)
627
- # to the same structure as the other subsets (Array of Hashes)
628
- #
629
- # @param raw_data [Hash] The raw, inconsistent data structure from the API
630
- #
631
- # @return [Array] the same data restructured to match the rest of the
632
- # computer history subsets: An Array of Hashes, one per event, each with
633
- # a :status key.
634
- #
635
- def self.standardize_history_subset(raw_data)
636
- consistency_array = []
637
- raw_data.each do |status, events|
638
- events.each do |evt|
639
- evt[:status] = status
640
- consistency_array << evt
641
- end # cmd_events.each
642
- end # raw_hist[:commands].each
643
- consistency_array
644
- end
645
- private_class_method :standardize_history_subset
512
+ result = api.post_rsrc cmd_rsrc, nil
513
+ result =~ %r{<command_uuid>(.*)</command_uuid>}
514
+ Regexp.last_match(1)
515
+ end # send mdm command
646
516
 
647
517
 
648
518
  # Attributes
@@ -970,173 +840,239 @@ module JSS
970
840
 
971
841
  # Get application usage data for this computer
972
842
  # for a given date range.
973
- # See {JSS::Computer.application_usage} for details
843
+ #
844
+ # TODO: Make this a class method so we can retrieve it without
845
+ # instantiating the Computer.
846
+ #
847
+ # @param start_date [String,Date,DateTime,Time]
848
+ #
849
+ # @param end_date [String,Date,DateTime,Time] Defaults to start_date
850
+ #
851
+ # @return [Hash{Date=>Array<Hash>}] For each day in the range, an Array
852
+ # with one Hash per application used. The hash keys are:
853
+ # :name => String, the name of the app
854
+ # :version => String ,the version of the app
855
+ # :foreground => Integer, the minutes it was in the foreground
856
+ # :open => Integer, the minutes it was running.
974
857
  #
975
858
  def application_usage(start_date, end_date = nil)
976
- JSS::Computer.application_usage @id, start_date, end_date, api: @api
859
+ end_date ||= start_date
860
+ start_date = Time.parse start_date if start_date.is_a? String
861
+ end_date = Time.parse end_date if end_date.is_a? String
862
+ unless ([start_date.class, end_date.class] - APPLICATION_USAGE_DATE_CLASSES).empty?
863
+ raise JSS::InvalidDataError, 'Invalid Start or End Date'
864
+ end
865
+ start_date = start_date.strftime APPLICATION_USAGE_DATE_FMT
866
+ end_date = end_date.strftime APPLICATION_USAGE_DATE_FMT
867
+ data = @api.get_rsrc(APPLICATION_USAGE_RSRC + "/id/#{@id}/#{start_date}_#{end_date}")
868
+ parsed_data = {}
869
+ data[APPLICATION_USAGE_KEY].each do |day_hash|
870
+ date = Date.parse day_hash[:date]
871
+ parsed_data[date] = day_hash[:apps]
872
+ end
873
+ parsed_data
977
874
  end # app usage
978
875
 
979
- # The 'computer management' data for this computer
876
+ # The 'computer management' data for this computer, looked up on the fly.
877
+ #
878
+ # Without specifying a subset:, the entire dataset is returned as a hash of
879
+ # arrays, one per subset
880
+ # If a subset is given then only that array is returned, and it contains
881
+ # hashes with data about each item (usually :name and :id)
882
+ #
883
+ # If the only: param is provided with a subset, it is used as a hash-key to
884
+ # map the array to just those values, so subset: :smart_groups, only: :name
885
+ # will return an array of names of smartgroups that contain this computer.
886
+ #
887
+ # TODO: Make this a class method so we can retrieve it without
888
+ # instantiating the Computer.
889
+ #
890
+ # @param subset[Symbol] Fetch only a subset of data, as an array.
891
+ # must be one of the symbols in MGMT_DATA_SUBSETS
980
892
  #
981
- # NOTE: the data isn't cached locally, and the API is queried every time
893
+ # @param only[Symbol] When fetching a subset, only return one value
894
+ # per item in the array. meaningless without a subset.
982
895
  #
983
- # See {JSS::Computer.management_data} for details
896
+ # @param refresh[Boolean] should the data be re-cached from the API?
984
897
  #
985
- def management_data(subset: nil, only: nil)
986
- raise JSS::NoSuchItemError, 'Computer not yet saved in the JSS' unless @in_jss
987
- JSS::Computer.management_data @id, subset: subset, only: only, api: @api
898
+ # @return [Hash] Without a subset:, a hash of all subsets, each of which is
899
+ # an Array
900
+ #
901
+ # @return [Array] With a subset:, an array of items in that subset.
902
+ #
903
+ def management_data(subset: nil, only: nil, refresh: false)
904
+ @management_data ||= {}
905
+ if subset
906
+ management_data_subset(subset: subset, only: only, refresh: refresh)
907
+ else
908
+ full_management_data refresh
909
+ end
988
910
  end
989
911
 
912
+ def full_management_data(refresh = false)
913
+ @management_data[:full] = nil if refresh
914
+ return @management_data[:full] if @management_data[:full]
915
+ mgmt_rsrc = MGMT_DATA_RSRC + "/id/#{@id}"
916
+ @management_data[:full] = @api.get_rsrc(mgmt_rsrc)[MGMT_DATA_KEY]
917
+ @management_data[:full]
918
+ end
919
+ private :full_management_data
920
+
921
+ def management_data_subset(subset: nil, only: nil, refresh: false)
922
+ raise "Subset must be one of :#{MGMT_DATA_SUBSETS.join ', :'}" unless MGMT_DATA_SUBSETS.include? subset
923
+ @management_data[subset] = nil if refresh
924
+ return @management_data[subset] if @management_data[subset]
925
+ subset_rsrc = MGMT_DATA_RSRC + "/id/#{@id}/subset/#{subset}"
926
+ @management_data[subset] = @api.get_rsrc(subset_rsrc)[MGMT_DATA_KEY]
927
+ return @management_data[subset] unless only
928
+ @management_data[subset].map { |d| d[only] }
929
+ end
930
+ private :management_data_subset
931
+
990
932
  # A shortcut for 'management_data subset: :smart_groups'
991
933
  #
992
- def smart_groups(only: nil)
993
- management_data subset: :smart_groups, only: only
934
+ def smart_groups(only: nil, refresh: false)
935
+ management_data subset: :smart_groups, only: only, refresh: refresh
994
936
  end
995
937
 
996
938
  # A shortcut for 'management_data subset: :static_groups'
997
939
  #
998
- def static_groups(only: nil)
999
- management_data subset: :static_groups, only: only
940
+ def static_groups(only: nil, refresh: false)
941
+ management_data subset: :static_groups, only: only, refresh: refresh
1000
942
  end
1001
943
 
1002
944
  # A shortcut for 'management_data subset: :policies'
1003
945
  #
1004
- def policies(only: nil)
1005
- management_data subset: :policies, only: only
946
+ def policies(only: nil, refresh: false)
947
+ management_data subset: :policies, only: only, refresh: refresh
1006
948
  end
1007
949
 
1008
950
  # A shortcut for 'management_data subset: :os_x_configuration_profiles'
1009
951
  #
1010
- def configuration_profiles(only: nil)
1011
- management_data subset: :os_x_configuration_profiles, only: only
952
+ def configuration_profiles(only: nil, refresh: false)
953
+ management_data subset: :os_x_configuration_profiles, only: only, refresh: refresh
1012
954
  end
1013
955
 
1014
956
  # A shortcut for 'management_data subset: :ebooks'
1015
957
  #
1016
- def ebooks(only: nil)
958
+ def ebooks(only: nil, refresh: false)
1017
959
  management_data subset: :ebooks, only: only, refresh: refresh
1018
960
  end
1019
961
 
1020
962
  # A shortcut for 'management_data subset: :mac_app_store_apps'
1021
963
  #
1022
- def app_store_apps(only: nil)
1023
- management_data subset: :mac_app_store_apps, only: only
964
+ def app_store_apps(only: nil, refresh: false)
965
+ management_data subset: :mac_app_store_apps, only: only, refresh: refresh
1024
966
  end
1025
967
 
1026
968
  # A shortcut for 'management_data subset: :restricted_software'
1027
969
  #
1028
- def restricted_software(only: nil)
1029
- management_data subset: :restricted_software, only: only
970
+ def restricted_software(only: nil, refresh: false)
971
+ management_data subset: :restricted_software, only: only, refresh: refresh
1030
972
  end
1031
973
 
1032
974
  # A shortcut for 'management_data subset: :patch_reporting_software_titles'
1033
975
  #
1034
- def patch_titles(only: nil)
1035
- management_data subset: :patch_reporting_software_titles, only: only
976
+ def patch_titles(only: nil, refresh: false)
977
+ management_data subset: :patch_reporting_software_titles, only: only, refresh: refresh
1036
978
  end
1037
979
 
1038
- # Return this computer's management history.
1039
- # WARNING: Its huge, better to use a subset or one of the shortcut methods.
980
+ # Return this computer's history.
981
+ # WARNING! Its huge, better to use a subset a
982
+ # nd one of the shortcut methods.
983
+ #
984
+ # TODO: Make this a class method so we can retrieve it without
985
+ # instantiating the Computer.
1040
986
  #
1041
- # NOTE: This is not the same as a computer's Object History
987
+ # @param subset[Symbol] the subset to return, rather than full history.
988
+ #
989
+ # @param refresh[Boolean] should we re-cache the data from the API?
1042
990
  #
1043
- # NOTE: The data isn't cached locally, the API is queried every time
991
+ # @return [Hash] The full history
1044
992
  #
1045
- # For details, see {JSS::Computer.history}
993
+ # @return [Array] The history subset requested
1046
994
  #
1047
- def history(subset: nil)
1048
- JSS::Computer.history @id, subset: subset, api: @api
995
+ def history(subset: nil, refresh: false)
996
+ @history ||= {}
997
+ if subset
998
+ history_subset(subset: subset, refresh: refresh)
999
+ else
1000
+ full_history refresh
1001
+ end
1002
+ end
1003
+
1004
+ def full_history(refresh = false)
1005
+ @history[:full] = nil if refresh
1006
+ return @history[:full] if @history[:full]
1007
+ history_rsrc = HISTORY_RSRC + "/id/#{@id}"
1008
+ @history[:full] = @api.get_rsrc(history_rsrc)[HISTORY_KEY]
1009
+ @history[:full]
1010
+ end
1011
+ private :full_history
1012
+
1013
+ def history_subset(subset: nil, refresh: false)
1014
+ raise "Subset must be one of :#{HISTORY_SUBSETS.join ', :'}" unless HISTORY_SUBSETS.include? subset
1015
+ @history[subset] = nil if refresh
1016
+ return @history[subset] if @history[subset]
1017
+ subset_rsrc = HISTORY_RSRC + "/id/#{@id}/subset/#{subset}"
1018
+ @history[subset] = @api.get_rsrc(subset_rsrc)[HISTORY_KEY]
1019
+ @history[subset]
1049
1020
  end
1021
+ private :history_subset
1050
1022
 
1051
1023
  # Shortcut for history(:computer_usage_logs)
1052
- def usage_logs
1053
- history subset: :computer_usage_logs
1024
+ def usage_logs(refresh = false)
1025
+ history(subset: :computer_usage_logs, refresh: refresh)
1054
1026
  end
1055
1027
 
1056
1028
  # Shortcut for history(:audits)
1057
- def audits
1058
- history subset: :audits
1029
+ def audits(refresh = false)
1030
+ history(subset: :audits, refresh: refresh)
1059
1031
  end
1060
1032
 
1061
1033
  # Shortcut for history(:policy_logs)
1062
- def policy_logs
1063
- history subset: :policy_logs
1034
+ def policy_logs(refresh = false)
1035
+ history(subset: :policy_logs, refresh: refresh)
1064
1036
  end
1065
1037
 
1066
1038
  # Shortcut for history(:policy_logs), but just the completed policies
1067
- def completed_policies
1068
- policy_logs.select { |pl| pl[:status] == POLICY_STATUS_COMPLETED }
1039
+ def completed_policies(refresh = false)
1040
+ policy_logs(refresh).select { |pl| pl[:status] == POLICY_STATUS_COMPLETED }
1069
1041
  end
1070
1042
 
1071
1043
  # Shortcut for history(:policy_logs), but just the failes policies
1072
- def failed_policies
1073
- policy_logs.select { |pl| pl[:status] == POLICY_STATUS_FAILED }
1044
+ def failed_policies(refresh = false)
1045
+ policy_log(refresh).select { |pl| pl[:status] == POLICY_STATUS_FAILED }
1074
1046
  end
1075
1047
 
1076
1048
  # Shortcut for history(:casper_remote_logs)
1077
- def casper_remote_logs
1078
- history subset: :casper_remote_logs
1049
+ def casper_remote_logs(refresh = false)
1050
+ history(subset: :casper_remote_logs, refresh: refresh)
1079
1051
  end
1080
1052
 
1081
1053
  # Shortcut for history(:screen_sharing_logs)
1082
- def screen_sharing_logs
1083
- history subset: :screen_sharing_logs
1054
+ def screen_sharing_logs(refresh = false)
1055
+ history(subset: :screen_sharing_logs, refresh: refresh)
1084
1056
  end
1085
1057
 
1086
1058
  # Shortcut for history(:casper_imaging_logs)
1087
- def casper_imaging_logs
1088
- history subset: :casper_imaging_logs
1059
+ def casper_imaging_logs(refresh = false)
1060
+ history(subset: :casper_imaging_logs, refresh: refresh)
1089
1061
  end
1090
1062
 
1091
1063
  # Shortcut for history(:commands)
1092
- def commands
1093
- history subset: :commands
1094
- end
1095
-
1096
- # Shortcut for history(:commands) but just the completed commands
1097
- #
1098
- def completed_commands
1099
- commands.select { |cmd| cmd[:status] == COMMAND_STATUS_COMPLETED }
1100
- end
1101
-
1102
- # Shortcut for history(:commands) but just the pending commands
1103
- #
1104
- def pending_commands
1105
- commands.select { |cmd| cmd[:status] == COMMAND_STATUS_PENDING }
1106
- end
1107
-
1108
- # Shortcut for history(:commands) but just the failed commands
1109
- #
1110
- def failed_commands
1111
- commands.select { |cmd| cmd[:status] == COMMAND_STATUS_FAILED }
1064
+ def commands(refresh = false)
1065
+ history(subset: :commands, refresh: refresh)
1112
1066
  end
1113
1067
 
1114
1068
  # Shortcut for history(:user_location)
1115
- def user_location_history
1116
- history subset: :user_location
1069
+ def user_location_history(refresh = false)
1070
+ history(subset: :user_location, refresh: refresh)
1117
1071
  end
1118
1072
 
1119
1073
  # Shortcut for history(:mac_app_store_applications)
1120
- def app_store_app_history
1121
- history subset: :mac_app_store_applications
1122
- end
1123
-
1124
- # Shortcut for history(:mac_app_store_applications) but just the installed apps
1125
- #
1126
- def installed_app_store_apps
1127
- app_store_app_history.select { |app| app[:status] == APP_STORE_APP_STATUS_INSTALLED }
1128
- end
1129
-
1130
- # Shortcut for history(:mac_app_store_applications) but just the pending apps
1131
- #
1132
- def pending_app_store_apps
1133
- app_store_app_history.select { |app| app[:status] == APP_STORE_APP_STATUS_PENDING }
1134
- end
1135
-
1136
- # Shortcut for history(:mac_app_store_applications) but just the failed apps
1137
- #
1138
- def failed_app_store_apps
1139
- app_store_app_history.select { |app| app[:status] == APP_STORE_APP_STATUS_FAILED }
1074
+ def app_store_app_history(refresh = false)
1075
+ history(subset: :mac_app_store_applications, refresh: refresh)
1140
1076
  end
1141
1077
 
1142
1078
  # Set or unset management acct and password for this computer
@@ -1241,9 +1177,10 @@ module JSS
1241
1177
  # @return [void]
1242
1178
  #
1243
1179
  def update
1244
- remove_mdm_profile if mdm_capable && @unmange_at_update
1180
+ id = super
1181
+ remove_mdm_profile if mdm_capable && managed? && @unmange_at_update
1245
1182
  @unmange_at_update = false
1246
- super
1183
+ id
1247
1184
  end
1248
1185
 
1249
1186
  # Delete this computer from the JSS
@@ -1291,6 +1228,47 @@ module JSS
1291
1228
  @software = nil
1292
1229
  end # delete
1293
1230
 
1231
+ # Send a blank_push MDM command
1232
+ #
1233
+ # See JSS::Computer.send_mdm_command
1234
+ #
1235
+ def blank_push
1236
+ self.class.send_mdm_command @id, :blank_push, api: @api
1237
+ end
1238
+ alias noop blank_push
1239
+ alias send_blank_push blank_push
1240
+
1241
+ # Send a device_lock MDM command
1242
+ #
1243
+ # See JSS::Computer.send_mdm_command
1244
+ #
1245
+ def device_lock(passcode)
1246
+ self.class.send_mdm_command @id, :device_lock, passcode, api: @api
1247
+ end
1248
+ alias lock device_lock
1249
+ alias lock_device device_lock
1250
+
1251
+ # Send an erase_device MDM command
1252
+ #
1253
+ # See JSS::Computer.send_mdm_command
1254
+ #
1255
+ def erase_device(passcode)
1256
+ self.class.send_mdm_command @id, :erase_device, passcode, api: @api
1257
+ end
1258
+ alias erase erase_device
1259
+ alias wipe erase_device
1260
+
1261
+ # Remove MDM management profile without
1262
+ # un-enrolling from the JSS or
1263
+ # resetting the JSS management acct.
1264
+ #
1265
+ # To do those things as well, see {#make_unmanaged}
1266
+ #
1267
+ # See JSS::Computer.send_mdm_command
1268
+ #
1269
+ def remove_mdm_profile
1270
+ self.class.send_mdm_command @id, :unmanage_device, api: @api
1271
+ end
1294
1272
 
1295
1273
  # aliases
1296
1274
  alias alt_macaddress alt_mac_address
@@ -1336,14 +1314,12 @@ module JSS
1336
1314
  rmgmt.add_element('management_username').text = @management_username
1337
1315
  rmgmt.add_element('management_password').text = @management_password if @management_password
1338
1316
 
1339
- computer << ext_attr_xml
1317
+ computer << ext_attr_xml if @changed_eas && !@changed_eas.empty?
1340
1318
 
1341
1319
  computer << location_xml if has_location?
1342
1320
 
1343
1321
  computer << purchasing_xml if has_purchasing?
1344
1322
 
1345
- add_site_to_xml(doc)
1346
-
1347
1323
  doc.to_s
1348
1324
  end # rest_xml
1349
1325