ruby-jss 0.10.2a5 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.

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