ruby-jss 0.8.2 → 0.9.0.b1

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.

@@ -108,24 +108,18 @@ module JSS
108
108
  # *NOTE* Some API objects have data broken into subsections, in which case the
109
109
  # VALID_DATA_KEYS are expected in the section :general.
110
110
  #
111
+ #
112
+ # === Optional Constants
113
+ #
114
+ # ==== OTHER_LOOKUP_KEYS = [Hash{Symbol=>Hash}] Every object can be looked up by
115
+ # :id and :name, but some have other uniq identifiers that can also be used,
116
+ # e.g. :serial_number, :mac_address, and so on. This Hash, if defined,
117
+ # speficies those other keys for the subclass
118
+ # For more details about this hash, see {APIObject::DEFAULT_LOOKUP_KEYS},
119
+ # {APIObject.fetch}, and {APIObject#lookup_object_data}
120
+ #
111
121
  class APIObject
112
122
 
113
- # Mix-Ins
114
- #####################################
115
-
116
- # Class Variables
117
- #####################################
118
-
119
- # This Hash holds the most recent API query for a list of all items in any subclass,
120
- # keyed by the subclass's RSRC_LIST_KEY. See the self.all class method.
121
- #
122
- # When the .all method is called without an argument, and this hash has
123
- # a matching value, the value is returned, rather than requerying the
124
- # API. The first time a class calls .all, or whnever refresh is
125
- # not false, the API is queried and the value in this hash is updated.
126
- #
127
- @@all_items = {}
128
-
129
123
  # Class Methods
130
124
  #####################################
131
125
 
@@ -148,7 +142,7 @@ module JSS
148
142
  # class methods for accessing those other values as mapped Arrays,
149
143
  # e.g. JSS::Computer.all_udids
150
144
  #
151
- # The results of the first query for each subclass is stored in @@all_items
145
+ # The results of the first query for each subclass is stored in JSS.api.object_list_cache
152
146
  # and returned at every future call, so as to not requery the server every time.
153
147
  #
154
148
  # To force requerying to get updated data, provided a non-false argument.
@@ -161,9 +155,9 @@ module JSS
161
155
  #
162
156
  def self.all(refresh = false)
163
157
  raise JSS::UnsupportedError, '.all can only be called on subclasses of JSS::APIObject' if self == JSS::APIObject
164
- @@all_items[self::RSRC_LIST_KEY] = nil if refresh
165
- return @@all_items[self::RSRC_LIST_KEY] if @@all_items[self::RSRC_LIST_KEY]
166
- @@all_items[self::RSRC_LIST_KEY] = JSS::API.get_rsrc(self::RSRC_BASE)[self::RSRC_LIST_KEY]
158
+ JSS.api.object_list_cache[self::RSRC_LIST_KEY] = nil if refresh
159
+ return JSS.api.object_list_cache[self::RSRC_LIST_KEY] if JSS.api.object_list_cache[self::RSRC_LIST_KEY]
160
+ JSS.api.object_list_cache[self::RSRC_LIST_KEY] = JSS.api.get_rsrc(self::RSRC_BASE)[self::RSRC_LIST_KEY]
167
161
  end
168
162
 
169
163
  # Returns an Array of the JSS id numbers of all the members
@@ -174,7 +168,7 @@ module JSS
174
168
  #
175
169
  # @param refresh[Boolean] should the data be re-queried from the API?
176
170
  #
177
- # @return [Array<Integer>] the ids of all items of this subclass in the JSS
171
+ # @return [Array<Integer>] the ids of all it1ems of this subclass in the JSS
178
172
  #
179
173
  def self.all_ids(refresh = false)
180
174
  all(refresh).map { |i| i[:id] }
@@ -238,9 +232,9 @@ module JSS
238
232
  # @return [Hash{Integer => Object}] the objects requested
239
233
  def self.all_objects(refresh = false)
240
234
  objects_key = "#{self::RSRC_LIST_KEY}_objects".to_sym
241
- @@all_items[objects_key] = nil if refresh
242
- return @@all_items[objects_key] if @@all_items[objects_key]
243
- @@all_items[objects_key] = all(refresh).map { |o| new id: o[:id] }
235
+ JSS.api.object_list_cache[objects_key] = nil if refresh
236
+ return JSS.api.object_list_cache[objects_key] if JSS.api.object_list_cache[objects_key]
237
+ JSS.api.object_list_cache[objects_key] = all(refresh).map { |o| new id: o[:id] }
244
238
  end
245
239
 
246
240
  # Return true or false if an object of this subclass
@@ -368,6 +362,44 @@ module JSS
368
362
  end
369
363
  end
370
364
 
365
+ # What are all the lookup keys available for this class?
366
+ #
367
+ # @return [Array<Symbol>] the DEFAULT_LOOKUP_KEYS plus any OTHER_LOOKUP_KEYS
368
+ # defined for this class
369
+ #
370
+ def self.lookup_keys
371
+ return DEFAULT_LOOKUP_KEYS.keys unless defined? self::OTHER_LOOKUP_KEYS
372
+ DEFAULT_LOOKUP_KEYS.keys + self::OTHER_LOOKUP_KEYS.keys
373
+ end
374
+
375
+ # @return [Hash] the available lookup keys mapped to the appropriate
376
+ # resource key for building a REST url to retrieve an object.
377
+ #
378
+ def self.rsrc_keys
379
+ hash = {}
380
+ all_keys = if defined?(self::OTHER_LOOKUP_KEYS)
381
+ DEFAULT_LOOKUP_KEYS.merge self::OTHER_LOOKUP_KEYS
382
+ else
383
+ DEFAULT_LOOKUP_KEYS
384
+ end
385
+ all_keys.each { |key, deets| hash[key] = deets[:rsrc_key]}
386
+ hash
387
+ end
388
+
389
+ # @return [Hash] the available lookup keys mapped to the appropriate
390
+ # list class method (e.g. id: :all_ids )
391
+ #
392
+ def self.lookup_key_list_methods
393
+ hash = {}
394
+ all_keys = if defined?(self::OTHER_LOOKUP_KEYS)
395
+ DEFAULT_LOOKUP_KEYS.merge self::OTHER_LOOKUP_KEYS
396
+ else
397
+ DEFAULT_LOOKUP_KEYS
398
+ end
399
+ all_keys.each { |key, deets| hash[key] = deets[:list]}
400
+ hash
401
+ end
402
+
371
403
  # Retrieve an object from the API.
372
404
  #
373
405
  # This is the preferred way to retrieve existing objects from the JSS.
@@ -382,11 +414,26 @@ module JSS
382
414
  #
383
415
  # @return [APIObject] The ruby-instance of a JSS object
384
416
  #
385
- def self.fetch(**args)
417
+ def self.fetch(arg)
386
418
  raise JSS::UnsupportedError, 'JSS::APIObject cannot be instantiated' if self.class == JSS::APIObject
387
- raise ArgumentError, 'Use .create to create new JSS objects' if args[:id] == :new
388
- new args
389
- end
419
+
420
+ # if given a hash (or a colletion of named params)
421
+ # pass to .new
422
+ if arg.is_a? Hash
423
+ raise ArgumentError, 'Use .create to create new JSS objects' if arg[:id] == :new
424
+ return new arg
425
+ end
426
+
427
+ # loop thru the lookup_key list methods for this class
428
+ # and if it's result includes the desired value,
429
+ # the pass they key and arg to .new
430
+ lookup_key_list_methods.each do |key, method_name|
431
+ return new({key => arg}) if self.send(method_name).include? arg
432
+ end # each key
433
+
434
+ # if we're here, we couldn't find a matching object
435
+ raise NoSuchItemError, "No #{self::RSRC_OBJECT_KEY} found matching '#{arg}'"
436
+ end # fetch
390
437
 
391
438
  # Make a ruby instance of a not-yet-existing APIObject.
392
439
  #
@@ -404,14 +451,13 @@ module JSS
404
451
  #
405
452
  # @return [APIObject] The un-created ruby-instance of a JSS object
406
453
  #
407
- def self.make(**args)
454
+ def self.make(args = {})
408
455
  raise JSS::UnsupportedError, 'JSS::APIObject cannot be instantiated' if self.class == JSS::APIObject
409
456
  raise ArgumentError, "Use '#{self.class}.fetch id: xx' to retrieve existing JSS objects" if args[:id]
410
457
  args[:id] = :new
411
458
  new args
412
459
  end
413
460
 
414
-
415
461
  ### Class Constants
416
462
  #####################################
417
463
 
@@ -420,13 +466,37 @@ module JSS
420
466
  #
421
467
  REQUIRED_DATA_KEYS = [:id, :name].freeze
422
468
 
423
- # By default, these keys are available for object lookups
424
- # Others can be added by subclasses using an array of them
425
- # as the second argument to super(initialize)
426
- # The keys must be Symbols that match the keyname in the resource url.
427
- # e.g. :serialnumber for JSSResource/computers/serialnumber/xxxxx
469
+ # All API objects have an id and a name. As such By these keys are available
470
+ # for object lookups.
428
471
  #
429
- DEFAULT_LOOKUP_KEYS = [:id, :name].freeze
472
+ # Others can be defined by subclasses in their OTHER_LOOKUP_KEYS constant
473
+ # which has the same format, described here:
474
+ #
475
+ # The merged Hashes DEFAULT_LOOKUP_KEYS and OTHER_LOOKUP_KEYS
476
+ # define what unique identifiers can be passed as parameters to the
477
+ # fetch method for retrieving an object from the API.
478
+ # They also define the class methods that return a list (Array) of all such
479
+ # identifiers for the class (e.g. the :all_ids class method returns an array
480
+ # of all id's for an APIObject subclass)
481
+ #
482
+ # Since there's often a discrepency between the name of the identifier as
483
+ # an attribute (e.g. serial_number) and the REST resource key for
484
+ # retrieving that object (e.g. ../computers/serialnumber/xxxxx) this hash
485
+ # also explicitly provides the REST resource key for a given lookup key, so
486
+ # e.g. both serialnumber and serial_number can be used, and both will have
487
+ # the resource key 'serialnumber' and the list method ':all_serial_numbers'
488
+ #
489
+ # Here's how the Hash is structured, using serialnumber as an example:
490
+ #
491
+ # LOOKUP_KEYS = {
492
+ # serialnumber: {rsrc_key: :serialnumber, list: :all_serial_numbers},
493
+ # serial_number: {rsrc_key: :serialnumber, list: :all_serial_numbers}
494
+ # }
495
+ #
496
+ DEFAULT_LOOKUP_KEYS = {
497
+ id: {rsrc_key: :id, list: :all_ids},
498
+ name: {rsrc_key: :name, list: :all_names}
499
+ }.freeze
430
500
 
431
501
  # Attributes
432
502
  #####################################
@@ -461,20 +531,13 @@ module JSS
461
531
  # @option args :name[String] the name to look up
462
532
  #
463
533
  # @option args :data[Hash] the JSON output of a separate {JSS::APIConnection} query
534
+ # NOTE: This arg is deprecated and will be removed in a future release.
464
535
  #
465
- # @param other_lookup_keys[Array<Symbol>] Hash keys other than :id and :name, by which an API
466
- # lookup may be performed.
467
536
  #
468
- def initialize(args = {}, other_lookup_keys = [])
469
- args[:other_lookup_keys] ||= other_lookup_keys
537
+ def initialize(args = {})
470
538
 
471
539
  raise JSS::UnsupportedError, 'JSS::APIObject cannot be instantiated' if self.class == JSS::APIObject
472
540
 
473
- ### what lookup key are we using, if any?
474
- lookup_keys = DEFAULT_LOOKUP_KEYS
475
- lookup_keys += self.class::OTHER_LOOKUP_KEYS if defined? self.class::OTHER_LOOKUP_KEYS
476
- lookup_key = (lookup_keys & args.keys)[0]
477
-
478
541
  ####### Previously looked-up JSON data
479
542
  # DEPRECATED: pre-lookedup data is never used
480
543
  # and support for it will be going away.
@@ -486,9 +549,9 @@ module JSS
486
549
 
487
550
  ###### Make a new one in the JSS, but only if we've included the Creatable module
488
551
  elsif args[:id] == :new
489
-
490
552
  validate_init_for_creation(args)
491
553
  setup_object_for_creation(args)
554
+
492
555
  return
493
556
 
494
557
  ###### Look up the data via the API
@@ -496,6 +559,7 @@ module JSS
496
559
  @init_data = look_up_object_data(args)
497
560
  end ## end arg parsing
498
561
 
562
+
499
563
  parse_init_data
500
564
  @need_to_update = false
501
565
  end # init
@@ -596,7 +660,7 @@ module JSS
596
660
  #
597
661
  def delete
598
662
  return nil unless @in_jss
599
- JSS::API.delete_rsrc @rest_rsrc
663
+ JSS.api.delete_rsrc @rest_rsrc
600
664
  @rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name}"
601
665
  @id = nil
602
666
  @in_jss = false
@@ -668,16 +732,20 @@ module JSS
668
732
  #
669
733
  def look_up_object_data(args)
670
734
  # what lookup key are we using?
671
- combined_lookup_keys = self.class::DEFAULT_LOOKUP_KEYS + args[:other_lookup_keys]
672
- lookup_key = (combined_lookup_keys & args.keys)[0]
735
+ lookup_keys = self.class.lookup_keys
736
+ lookup_key = (self.class.lookup_keys & args.keys)[0]
737
+ raise JSS::MissingDataError, "Args must include a lookup key, one of: :#{lookup_keys.join(', :')}" unless lookup_key
738
+ rsrc_key = self.class.rsrc_keys[lookup_key]
673
739
 
674
- raise JSS::MissingDataError, "Args must include a lookup key, one of: :#{combined_lookup_keys.join(', :')}" unless lookup_key
740
+ rsrc = "#{self.class::RSRC_BASE}/#{rsrc_key}/#{args[lookup_key]}"
675
741
 
676
- rsrc = "#{self.class::RSRC_BASE}/#{lookup_key}/#{args[lookup_key]}"
742
+ # if needed, a non-standard object key can be passed by a subclass.
743
+ # e.g. User when loookup is by email.
744
+ rsrc_object_key = args[:rsrc_object_key] ? args[:rsrc_object_key] : self.class::RSRC_OBJECT_KEY
677
745
 
678
- return JSS::API.get_rsrc(rsrc)[self.class::RSRC_OBJECT_KEY]
746
+ return JSS.api.get_rsrc(rsrc)[rsrc_object_key]
679
747
  rescue RestClient::ResourceNotFound
680
- raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} found matching: #{lookup_key}/#{args[lookup_key]}"
748
+ raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} found matching: #{rsrc_key}/#{args[lookup_key]}"
681
749
  end
682
750
 
683
751
  # Start examining the @init_data recieved from the API
@@ -685,6 +753,7 @@ module JSS
685
753
  # @return [void]
686
754
  #
687
755
  def parse_init_data
756
+ @init_data ||= {}
688
757
  # set empty strings to nil
689
758
  @init_data.jss_nillify! '', :recurse
690
759
 
@@ -802,7 +871,7 @@ module JSS
802
871
  def setup_object_for_creation(args)
803
872
  # NOTE: subclasses may want to pre-populate more keys in @init_data when :id == :new
804
873
  # then parse them into attributes later.
805
- @init_data = { name: args[:name] }
874
+ @init_data = args
806
875
  @name = args[:name]
807
876
  @in_jss = false
808
877
  @rest_rsrc = "#{self.class::RSRC_BASE}/name/#{CGI.escape @name}"
@@ -61,7 +61,12 @@ module JSS
61
61
  RSRC_OBJECT_KEY = :account
62
62
 
63
63
  # these keys, as well as :id and :name, can be used to look up objects of this class in the JSS
64
- OTHER_LOOKUP_KEYS = [:userid, :username, :groupid, :groupname].freeze
64
+ OTHER_LOOKUP_KEYS = {
65
+ userid: {rsrc_key: :userid, list: :all_user_ids},
66
+ username: {rsrc_key: :username, list: :all_user_names},
67
+ groupid: {rsrc_key: :groupid, list: :all_group_ids},
68
+ groupname: {rsrc_key: :groupname, list: :all_group_names}
69
+ }.freeze
65
70
 
66
71
  # Class Methods
67
72
  #####################################
@@ -167,11 +167,11 @@ module JSS
167
167
  raise JSS::InvalidDataError, 'JSS::Criteriable::Criteria instance required' unless @criteria.is_a? JSS::Criteriable::Criteria
168
168
  raise JSS::InvalidDataError, 'display_fields must be an Array.' unless @display_fields.is_a? Array
169
169
 
170
- orig_timeout = JSS::API.cnx.options[:timeout]
171
- JSS::API.timeout = 1800
170
+ orig_timeout = JSS.api_connection.cnx.options[:timeout]
171
+ JSS.api_connection.timeout = 1800
172
172
  super()
173
173
  requery_search_results if get_results
174
- JSS::API.timeout = orig_timeout
174
+ JSS.api_connection.timeout = orig_timeout
175
175
 
176
176
  @id # remember to return the id
177
177
  end
@@ -185,11 +185,11 @@ module JSS
185
185
  # @return [Integer] the id of the updated search
186
186
  #
187
187
  def update(get_results = false)
188
- orig_timeout = JSS::API.cnx.options[:timeout]
189
- JSS::API.timeout = 1800
188
+ orig_timeout = JSS.api_connection.cnx.options[:timeout]
189
+ JSS.api_connection.timeout = 1800
190
190
  super()
191
191
  requery_search_results if get_results
192
- JSS::API.timeout = orig_timeout
192
+ JSS.api_connection.timeout = orig_timeout
193
193
 
194
194
  @id # remember to return the id
195
195
  end
@@ -201,17 +201,17 @@ module JSS
201
201
  # @return [Array<Hash>] the new search results
202
202
  #
203
203
  def requery_search_results
204
- orig_open_timeout = JSS::API.cnx.options[:open_timeout]
205
- orig_timeout = JSS::API.cnx.options[:timeout]
206
- JSS::API.timeout = 1800
207
- JSS::API.open_timeout = 1800
204
+ orig_open_timeout = JSS.api_connection.cnx.options[:open_timeout]
205
+ orig_timeout = JSS.api_connection.cnx.options[:timeout]
206
+ JSS.api_connection.timeout = 1800
207
+ JSS.api_connection.open_timeout = 1800
208
208
  begin
209
209
  requery = self.class.new(id: @id)
210
210
  @search_results = requery.search_results
211
211
  @result_display_keys = requery.result_display_keys
212
212
  ensure
213
- JSS::API.timeout = orig_timeout
214
- JSS::API.open_timeout = orig_open_timeout
213
+ JSS.api_connection.timeout = orig_timeout
214
+ JSS.api_connection.open_timeout = orig_open_timeout
215
215
  end
216
216
  end
217
217
 
@@ -26,24 +26,14 @@
26
26
  ###
27
27
  module JSS
28
28
 
29
- # Module Constants
30
- #####################################
31
-
32
- # Module Variables
33
- #####################################
34
-
35
- # Module Methods
36
- #####################################
37
-
38
29
  # This class represents a Computer in the JSS.
39
30
  #
40
- # ===Adding Computers to the JSS
31
+ # === Adding Computers to the JSS
41
32
  #
42
- # This class cannot be used to add new Computers to the JSS. Please use other
43
- # Casper methods (like the Recon App or QuickAdd package)
33
+ # At the moment, this class cannot be used to add new Computers to the JSS.
34
+ # Please use other methods (like the Recon App or QuickAdd package)
44
35
  #
45
- # ---
46
- # ===Editing values
36
+ # === Editing values
47
37
  #
48
38
  # Any data that arrives in the JSS via an "inventory update"
49
39
  # (a.k.a. 'recon') cannot be modified through this class, or the API.
@@ -66,27 +56,91 @@ module JSS
66
56
  # After making any changes, you must call #update to send those
67
57
  # changes to the server.
68
58
  #
69
- # ---
70
59
  # === MDM Commands
71
60
  #
72
- # ==== MDM Commands are Not Yet Supported!
73
- # *Hopefully they will be soon*
61
+ # The following methods can be used to send an APNS command to the
62
+ # computer represented by an instance of JSS::Computer, equivalent to
63
+ # clicking one of the buttons on the Management Commands section of the
64
+ # Management tab of the Computer details page in the JSS UI.
65
+ #
66
+ # - {#blank_push} (aliases blank, noop, send_blank_push)
67
+ # - {#device_lock} (aliases lock, lock_device)
68
+ # - {#erase_device} (aliases wipe)
69
+ # - {#remove_mdm_profile}
70
+ #
71
+ # To send an MDM command without making a Computer instance, use the class
72
+ # {JSS::Computer.send_mdm_command} which can take multiple computer
73
+ # identifiers at once.
74
+ #
75
+ # NOTE: the poorly named 'UnmanageDevice' command via the API is implemented
76
+ # as the {#remove_mdm_profile} method (which is its name in the webUI).
77
+ # Calling that method will NOT unmanage the machine from the JSS's point
78
+ # of view, it will just remove the mdm management profile from the machine
79
+ # and all configuration profiles that were installed via the JSS. Those
80
+ # profiles may be re-installed automatically later if the computer is still in
81
+ # scope for them
82
+ #
83
+ # The {#make_unmanaged} method also removes the mdm profile, but actually
84
+ # does make the machine unmanged by the JSS, setting the management acct to
85
+ # nil, and requring re-enrollment.
86
+ #
87
+ # === Computer History
88
+ #
89
+ # Computer instances can now retrieve their management history from the JSS.
90
+ #
91
+ # The full history data is available from the {#history} method, but beware that
92
+ # it is very large.
93
+ #
94
+ # Subsets of that history have their own methods, which are faster and only retrieve
95
+ # the subset requested. See {#usage_logs}, {#audits}, {#policy_los},
96
+ # {#completed_policies}, {#failed_polices}, {#casper_remote_logs},
97
+ # {#screen_sharing_logs}, {#casper_imaging_logs}, {#commands},
98
+ # {#user_location_history},and {#app_store_app_history}
99
+ #
100
+ # When any of the history methods is used the first time, the data is read
101
+ # from the API and cached internally, and that data is
102
+ # used for all future calls.. To re-read the data from the API and re-cache it,
103
+ # provide any non-false parameter to the subset methods , or `refresh: true`
104
+ # to the main {#history} method.
105
+ #
106
+ # === Appication Usage History
107
+ #
108
+ # Computer Instances now have access to their Application Usage history
109
+ # via the {#application_usage} method.
110
+ # Call the method with a start-date value (either a String or a Time object)
111
+ # and an optional end-date value. If you omite the end-date, the start-date
112
+ # is used and you'll see usage for just that day.
113
+ #
114
+ # See {#application_usage} for details about the data returned.
74
115
  #
75
- # The following methods will be used to send an APNS command to the computer represented by an
76
- # instance of JSS::Computer, equivalent to clicking one of the buttons on
77
- # the Management Commands section of the Management tab of the Computer details page in the JSS UI.
116
+ # NOTE: your JSS must be gathering Appication Usage data in order
117
+ # for any data to be returned, and the usage history will only go back as
118
+ # far as your setting for flushing of Application Usage Logs.
78
119
  #
79
- # The methods supported will be:
80
- # - #blank_push (aliases blank, noop, send_blank_push)
81
- # - #device_lock (aliases lock, lock_device)
82
- # - #erase_device (aliases wipe)
120
+ # === Management Data
83
121
  #
84
- # To send an MDM command without making an instance, use the class method {.send_mdm_command}
122
+ # The computers 'manamgement data', as presented on the 'Management' tab of
123
+ # the computer's detail page in the JSS web UI, is available from the
124
+ # {#management_data} method. That method may return a large dataset,
125
+ # unless a subset is requested.
85
126
  #
86
- # Each returns true if the command as sent.
127
+ # Subsets of management data have their own methods, which are faster and
128
+ # only retrieve the subset requested. See {#smart_groups}, {#static_groups},
129
+ # {#policies}, {#configuration_profiles}, {#ebooks}, {#app_store_apps},
130
+ # {#restricted_software}, and {#patch_titles}
87
131
  #
88
- # ---
89
- # ===Other Methods
132
+ # The subset methods can take an 'only:' parameter, which is a symbol specifying
133
+ # the value you care to see. For example {#smart_groups} returns an array
134
+ # of hashes, one for each smart_group the computer is in. Those hashes
135
+ # have two keys, :name, and :id. However if you only want an array of
136
+ # names, you can call `smart_groups only: :name`
137
+ #
138
+ # When any of the manamgement data methods are used the first time, the data
139
+ # is read from the API and cached internally, the cache is then
140
+ # used for all future calls. To re-read the data from the API and re-cache it,
141
+ # provide `refresh: true` to any of the manamgement data methods.
142
+ #
143
+ # === Other Methods
90
144
  #
91
145
  # - {#set_management_to} change the management acct and passwd for this computer, aliased to #make_managed
92
146
  # - requires calling #update to push changes to the server
@@ -118,14 +172,185 @@ module JSS
118
172
 
119
173
  extend JSS::Matchable
120
174
 
121
- # Class Variables
175
+ # Class Constants
122
176
  #####################################
123
177
 
124
- @@all_computers = nil
178
+ # The base for REST resources of this class
179
+ RSRC_BASE = 'computers'.freeze
180
+
181
+ # The (temporary?) list-resource
182
+ LIST_RSRC = "#{RSRC_BASE}/subset/basic".freeze
183
+
184
+ # the hash key used for the JSON list output of all objects in the JSS
185
+ RSRC_LIST_KEY = :computers
186
+
187
+ # The hash key used for the JSON object output.
188
+ # It's also used in various error messages
189
+ RSRC_OBJECT_KEY = :computer
190
+
191
+ # these keys, as well as :id and :name, are present in valid API JSON data for this class
192
+ # DEPRECATED, with be removed in a future release.
193
+ VALID_DATA_KEYS = %i[sus distribution_point alt_mac_address].freeze
194
+
195
+ # these keys, as well as :id and :name, can be used to look up objects of this class in the JSS
196
+ OTHER_LOOKUP_KEYS = {
197
+ udid: { rsrc_key: :udid, list: :all_udids },
198
+ serialnumber: { rsrc_key: :serialnumber, list: :all_serial_numbers },
199
+ serial_number: { rsrc_key: :serialnumber, list: :all_serial_numbers },
200
+ macaddress: { rsrc_key: :macaddress, list: :all_mac_addresses },
201
+ mac_address: { rsrc_key: :macaddress, list: :all_mac_addresses }
202
+ }.freeze
203
+
204
+ # This class lets us seach for computers
205
+ SEARCH_CLASS = JSS::AdvancedComputerSearch
206
+
207
+ # This is the class for relevant Extension Attributes
208
+ EXT_ATTRIB_CLASS = JSS::ComputerExtensionAttribute
209
+
210
+ # Boot partitions are noted with the string "(Boot Partition)" at the end
211
+ BOOT_FLAG = ' (Boot Partition)'.freeze
212
+
213
+ # file uploads can send attachments to the JSS using :computers as the sub-resource.
214
+ UPLOAD_TYPES = { attachment: :computers }.freeze
215
+
216
+ # The base REST resource for sending computer MDM commands
217
+ COMPUTER_MDM_RSRC = 'computercommands/command'.freeze
218
+
219
+ # A mapping of Symbols available to the send_mdm_command class method, to
220
+ # the String commands actuallly sent via the API.
221
+ COMPUTER_MDM_COMMANDS = {
222
+ blank_push: 'BlankPush',
223
+ blankpush: 'BlankPush',
224
+ send_blank_push: 'BlankPush',
225
+ blank: 'BlankPush',
226
+ noop: 'BlankPush',
227
+ device_lock: 'DeviceLock',
228
+ devicelock: 'DeviceLock',
229
+ lock: 'DeviceLock',
230
+ lock_device: 'DeviceLock',
231
+ erase_device: 'EraseDevice',
232
+ erasedevice: 'EraseDevice',
233
+ erase: 'EraseDevice',
234
+ wipe: 'EraseDevice',
235
+ unmanage_device: 'UnmanageDevice',
236
+ unmanagedevice: 'UnmanageDevice',
237
+ unmanage: 'UnmanageDevice'
238
+ }.freeze
239
+
240
+ # these MDM commands require a passcode
241
+ COMPUTER_MDM_COMMANDS_NEEDING_PASSCODE = %w[DeviceLock EraseDevice].freeze
242
+
243
+ # The API resource for app usage
244
+ APPLICATION_USAGE_RSRC = 'computerapplicationusage'.freeze
245
+
246
+ # The date format for retrieving usage data
247
+ APPLICATION_USAGE_DATE_FMT = '%Y-%m-%d'.freeze
248
+
249
+ # The classes that can be used with the date format
250
+ APPLICATION_USAGE_DATE_CLASSES = [Time, DateTime, Date].freeze
251
+
252
+ # The top-level hash key of the raw app usage data
253
+ APPLICATION_USAGE_KEY = :computer_application_usage
254
+
255
+ # The API resource for computer_management data
256
+ MGMT_DATA_RSRC = 'computermanagement'.freeze
257
+
258
+ # The top-level hash key of the computer_management data
259
+ MGMT_DATA_KEY = :computer_management
260
+
261
+ # Thes are both the subset names in the resrouce URLS (when
262
+ # converted to strings) and the second-level hash key of the
263
+ # returned subset data.
264
+ MGMT_DATA_SUBSETS = %i[
265
+ smart_groups
266
+ static_groups
267
+ mac_app_store_apps
268
+ policies
269
+ ebooks
270
+ os_x_configuration_profiles
271
+ restricted_software
272
+ patch_reporting_software_titles
273
+ ].freeze
274
+
275
+ # The API Resource for the computer checkin settings
276
+ CHECKIN_RSRC = 'computercheckin'.freeze
277
+
278
+ # The top-level hash key for the checkin settings
279
+ CHECKIN_KEY = :computer_check_in
280
+
281
+ # The API Resource for the computer inventory collection settings
282
+ INV_COLLECTION_RSRC = 'computerinventorycollection'.freeze
283
+
284
+ # The top-level hash key for the inventory collection settings
285
+ INV_COLLECTION_KEY = :computer_inventory_collection
286
+
287
+ # The API Resource for the computer history data
288
+ HISTORY_RSRC = 'computerhistory'.freeze
289
+
290
+ # The top-level hash key for the history data
291
+ HISTORY_KEY = :computer_history
292
+
293
+ # The keys are both the subset names in the resrouce URLS (when
294
+ # converted to strings) and the second-level hash key of the
295
+ # returned subset data.
296
+ #
297
+ # The values are the key within each history item that contains the
298
+ # 'epoch' timestamp, for conver
299
+ HISTORY_SUBSETS = %i[
300
+ computer_usage_logs
301
+ audits
302
+ policy_logs
303
+ casper_remote_logs
304
+ screen_sharing_logs
305
+ casper_imaging_logs
306
+ commands
307
+ user_location
308
+ mac_app_store_applications
309
+ ].freeze
310
+
311
+ # HISTORY_SUBSETS = %i(
312
+ # computer_usage_logs date_time_epoch
313
+ # audits
314
+ # policy_logs date_completed_epoch
315
+ # casper_remote_logs date_time_epoch
316
+ # screen_sharing_logs date_time_epoch
317
+ # casper_imaging_logs
318
+ # commands completed_epoch
319
+ # user_location
320
+ # mac_app_store_applications
321
+ # ).freeze
322
+
323
+ POLICY_STATUS_COMPLETED = 'Completed'.freeze
324
+
325
+ POLICY_STATUS_FAILED = 'Failed'.freeze
326
+
327
+ POLICY_STATUS_PENDING = 'Pending'.freeze
125
328
 
126
329
  # Class Methods
127
330
  #####################################
128
331
 
332
+ # Display the current Computer CheckIn settings in the JSS.
333
+ # Currently this is read-only in ruby-jss, even tho the API
334
+ # allows updating.
335
+ #
336
+ # @return [Hash] the Computer Checkin Settings from the
337
+ # currently connected JSS.
338
+ #
339
+ def self.checkin_settings
340
+ JSS.api_connection.get_rsrc(CHECKIN_RSRC)[CHECKIN_KEY]
341
+ end
342
+
343
+ # Display the current Computer Inventory Collection settings in the JSS.
344
+ # Currently this is read-only in ruby-jss, even tho the API
345
+ # allows updating.
346
+ #
347
+ # @return [Hash] the Computer Inventpry Collection Settings from the
348
+ # currently connected JSS.
349
+ #
350
+ def self.inventory_collection_settings
351
+ JSS.api_connection.get_rsrc(INV_COLLECTION_RSRC)[INV_COLLECTION_KEY]
352
+ end
353
+
129
354
  # A larger set of info about the computers in the JSS.
130
355
  #
131
356
  # Casper 9.4 introduced the API Resource /computers/subset/basic
@@ -143,9 +368,9 @@ module JSS
143
368
  # @return [Array<Hash{:name=>String, :id=> Integer}>]
144
369
  #
145
370
  def self.all(refresh = false)
146
- @@all_computers = nil if refresh
147
- return @@all_computers if @@all_computers
148
- @@all_computers = JSS::API.get_rsrc(self::LIST_RSRC)[self::RSRC_LIST_KEY]
371
+ JSS.api.object_list_cache[RSRC_LIST_KEY] = nil if refresh
372
+ return JSS.api.object_list_cache[RSRC_LIST_KEY] if JSS.api.object_list_cache[RSRC_LIST_KEY]
373
+ JSS.api.object_list_cache[RSRC_LIST_KEY] = JSS.api_connection.get_rsrc(self::LIST_RSRC)[self::RSRC_LIST_KEY]
149
374
  end
150
375
 
151
376
  # @return [Array<String>] all computer serial numbers in the jss
@@ -170,7 +395,7 @@ module JSS
170
395
 
171
396
  # @return [Array<Hash>] all unmanaged computers in the jss
172
397
  def self.all_unmanaged(refresh = false)
173
- all(refresh).select { |d| !(d[:managed]) }
398
+ all(refresh).reject { |d| d[:managed] }
174
399
  end
175
400
 
176
401
  # @return [Array<Hash>] all laptop computers in the jss
@@ -200,7 +425,7 @@ module JSS
200
425
 
201
426
  # @return [Array<Hash>] all desktop macs in the jss
202
427
  def self.all_desktops(refresh = false)
203
- all(refresh).select { |d| d[:model] !~ /serve|book/i }
428
+ all(refresh).reject { |d| d[:model] =~ /serve|book/i }
204
429
  end
205
430
 
206
431
  # @return [Array<Hash>] all imacs in the jss
@@ -218,88 +443,53 @@ module JSS
218
443
  all(refresh).select { |d| d[:model] =~ /^macpro/i }
219
444
  end
220
445
 
221
- # Send an MDM command to a managed computer by id or name
222
- #
223
- # @param computer[String,Integer] the name or id of the computer to recieve the command
224
- # @param command[Symbol] the command to send, one of the keys of COMPUTER_MDM_COMMANDS
225
- #
226
- # @return [true] if the command was sent
227
- #
228
-
229
- # Not functional until I get more docs from JAMF
446
+ # Send an MDM command to one or more managed computers by id or name
230
447
  #
231
- # def self.send_mdm_command(computer,command)
232
448
  #
233
- # raise JSS::NoSuchItemError, "Unknown command '#{command}'" unless COMPUTER_MDM_COMMANDS.keys.include? command
449
+ # @param targets[String,Integer,Array<String,Integer>]
450
+ # the name or id of the computer to receive the command, or
451
+ # an array of such names or ids, or a comma-separated string
452
+ # of them.
453
+ # @param command[Symbol] the command to send, one of the keys
454
+ # of COMPUTER_MDM_COMMANDS
234
455
  #
235
- # command_xml ="#{JSS::APIConnection::XML_HEADER}<computer><command>#{COMPUTER_MDM_COMMANDS[command]}</command></computer>"
236
- # the_id = nil
456
+ # @param passcode[String] some commands require a 6-character passcode
237
457
  #
238
- # if computer.to_s =~ /^\d+$/
239
- # the_id = computer
240
- # else
241
- # the_id = self.map_all_ids_to(:name).invert[computer]
242
- # end
458
+ # @return [String] The uuid of the MDM command sent, if applicable
459
+ # (blank pushes do not generate uuids)
243
460
  #
244
- # if the_id
245
- # response = JSS::API.put_rsrc("#{RSRC_BASE}/id/#{the_id}", command_xml)
246
- # response =~ %r{<notification_sent>(.+)</notification_sent>}
247
- # return ($1 and $1 == "true")
248
- # end
249
- # raise JSS::UnmanagedError, "Cannot send command to unknown/unmanaged computer '#{computer}'"
250
- # end
461
+ def self.send_mdm_command(targets, command, passcode = nil)
462
+ raise JSS::NoSuchItemError, "Unknown command '#{command}'" unless COMPUTER_MDM_COMMANDS.keys.include? command
251
463
 
252
- # Class Constants
253
- #####################################
464
+ command = COMPUTER_MDM_COMMANDS[command]
465
+ cmd_rsrc = "#{COMPUTER_MDM_RSRC}/#{command}"
254
466
 
255
- # The base for REST resources of this class
256
- RSRC_BASE = 'computers'.freeze
257
-
258
- # The (temporary?) list-resource
259
- LIST_RSRC = "#{RSRC_BASE}/subset/basic".freeze
260
-
261
- # the hash key used for the JSON list output of all objects in the JSS
262
- RSRC_LIST_KEY = :computers
263
-
264
- # The hash key used for the JSON object output.
265
- # It's also used in various error messages
266
- RSRC_OBJECT_KEY = :computer
467
+ if COMPUTER_MDM_COMMANDS_NEEDING_PASSCODE.include? command
468
+ unless passcode && passcode.is_a?(String) && passcode.length == 6
469
+ raise JSS::MissingDataError, "Command '#{command}' requires a 6-character passcode"
470
+ end
471
+ cmd_rsrc << "/passcode/#{passcode}"
472
+ end
267
473
 
268
- # these keys, as well as :id and :name, are present in valid API JSON data for this class
269
- # DEPRECATED, with be removed in a future release.
270
- VALID_DATA_KEYS = [:sus, :distribution_point, :alt_mac_address].freeze
474
+ targets = JSS.to_s_and_a(targets.to_s)[:arrayform] unless targets.is_a? Array
271
475
 
272
- # these keys, as well as :id and :name, can be used to look up objects of this class in the JSS
273
- OTHER_LOOKUP_KEYS = [:udid, :serialnumber, :mac_address].freeze
476
+ # make sure its an array of ids
477
+ targets.map! do |comp|
478
+ if all_ids.include? comp.to_i
479
+ comp.to_i
480
+ elsif all_names.include? comp
481
+ map_all_ids_to(:name).invert[comp]
482
+ else
483
+ raise JSS::NoSuchItemError, "No computer found matching '#{comp}'"
484
+ end # if
485
+ end # map!
274
486
 
275
- # This class lets us seach for computers
276
- SEARCH_CLASS = JSS::AdvancedComputerSearch
487
+ cmd_rsrc << "/id/#{targets.join ','}"
277
488
 
278
- # This is the class for relevant Extension Attributes
279
- EXT_ATTRIB_CLASS = JSS::ComputerExtensionAttribute
280
-
281
- # Boot partitions are noted with the string "(Boot Partition)" at the end
282
- BOOT_FLAG = ' (Boot Partition)'.freeze
283
-
284
- # file uploads can send attachments to the JSS using :computers as the sub-resource.
285
- UPLOAD_TYPES = { attachment: :computers }.freeze
286
-
287
- # A mapping of Symbols available to the send_mdm_command class method, to
288
- # the String commands actuallly sent via the API.
289
- COMPUTER_MDM_COMMANDS = {
290
- blank_push: 'BlankPush',
291
- send_blank_push: 'BlankPush',
292
- blank: 'BlankPush',
293
- noop: 'BlankPush',
294
- device_lock: 'DeviceLock',
295
- lock: 'DeviceLock',
296
- lock_device: 'DeviceLock',
297
- erase_device: 'EraseDevice',
298
- erase: 'EraseDevice',
299
- wipe: 'EraseDevice',
300
- unmanage_device: 'UnmanageDevice',
301
- unmanage: 'UnmanageDevice'
302
- }.freeze
489
+ result = JSS::API.post_rsrc cmd_rsrc, nil
490
+ result =~ %r{<command_uuid>(.*)</command_uuid>}
491
+ Regexp.last_match(1)
492
+ end # send mdm command
303
493
 
304
494
  # Attributes
305
495
  #####################################
@@ -526,7 +716,7 @@ module JSS
526
716
  # As well as :id and :name, computers can be queried using :udid, :serialnumber, and :mac_address
527
717
  #
528
718
  def initialize(args = {})
529
- super args, [:udid, :serialnumber, :mac_address]
719
+ super args
530
720
 
531
721
  # now we have raw @init_data with something in it, so fill out the instance vars
532
722
  @alt_mac_address = @init_data[:general][:alt_mac_address]
@@ -621,6 +811,234 @@ module JSS
621
811
  @software[:licensed_software]
622
812
  end
623
813
 
814
+ # Get application usage data for this computer
815
+ # for a given date range.
816
+ #
817
+ # @param start_date [String,Date,DateTime,Time]
818
+ #
819
+ # @param end_date [String,Date,DateTime,Time] Defaults to start_date
820
+ #
821
+ # @return [Hash{Date=>Array<Hash>}] For each day in the range, an Array
822
+ # with one Hash per application used. The hash keys are:
823
+ # :name => String, the name of the app
824
+ # :version => String ,the version of the app
825
+ # :foreground => Integer, the minutes it was in the foreground
826
+ # :open => Integer, the minutes it was running.
827
+ #
828
+ def application_usage(start_date, end_date = nil)
829
+ end_date ||= start_date
830
+ start_date = Time.parse start_date if start_date.is_a? String
831
+ end_date = Time.parse end_date if end_date.is_a? String
832
+ unless ([start_date.class, end_date.class] - APPLICATION_USAGE_DATE_CLASSES).empty?
833
+ raise JSS::InvalidDataError, 'Invalid Start or End Date'
834
+ end
835
+ start_date = start_date.strftime APPLICATION_USAGE_DATE_FMT
836
+ end_date = end_date.strftime APPLICATION_USAGE_DATE_FMT
837
+ data = JSS.api_connection.get_rsrc(APPLICATION_USAGE_RSRC + "/id/#{@id}/#{start_date}_#{end_date}")
838
+ parsed_data = {}
839
+ data[APPLICATION_USAGE_KEY].each do |day_hash|
840
+ date = Date.parse day_hash[:date]
841
+ parsed_data[date] = day_hash[:apps]
842
+ end
843
+ parsed_data
844
+ end # app usage
845
+
846
+ # The 'computer management' data for this computer, looked up on the fly.
847
+ #
848
+ # Without specifying a subset:, the entire dataset is returned as a hash of
849
+ # arrays, one per subset
850
+ # If a subset is given then only that array is returned, and it contains
851
+ # hashes with data about each item (usually :name and :id)
852
+ #
853
+ # If the only: param is provided with a subset, it is used as a hash-key to
854
+ # map the array to just those values, so subset: :smart_groups, only: :name
855
+ # will return an array of names of smartgroups that contain this computer.
856
+ #
857
+ # @param subset[Symbol] Fetch only a subset of data, as an array.
858
+ # must be one of the symbols in MGMT_DATA_SUBSETS
859
+ #
860
+ # @param only[Symbol] When fetching a subset, only return one value
861
+ # per item in the array. meaningless without a subset.
862
+ #
863
+ # @param refresh[Boolean] should the data be re-cached from the API?
864
+ #
865
+ # @return [Hash] Without a subset:, a hash of all subsets, each of which is
866
+ # an Array
867
+ #
868
+ # @return [Array] With a subset:, an array of items in that subset.
869
+ #
870
+ def management_data(subset: nil, only: nil, refresh: false)
871
+ @management_data ||= {}
872
+ if subset
873
+ management_data_subset(subset: subset, only: only, refresh: refresh)
874
+ else
875
+ full_management_data refresh
876
+ end
877
+ end
878
+
879
+ def full_management_data(refresh = false)
880
+ @management_data[:full] = nil if refresh
881
+ return @management_data[:full] if @management_data[:full]
882
+ mgmt_rsrc = MGMT_DATA_RSRC + "/id/#{@id}"
883
+ @management_data[:full] = JSS.api.get_rsrc(mgmt_rsrc)[MGMT_DATA_KEY]
884
+ @management_data[:full]
885
+ end
886
+ private :full_management_data
887
+
888
+ def management_data_subset(subset: nil, only: nil, refresh: false)
889
+ raise "Subset must be one of :#{MGMT_DATA_SUBSETS.join ', :'}" unless MGMT_DATA_SUBSETS.include? subset
890
+ @management_data[subset] = nil if refresh
891
+ return @management_data[subset] if @management_data[subset]
892
+ subset_rsrc = MGMT_DATA_RSRC + "/id/#{@id}/subset/#{subset}"
893
+ @management_data[subset] = JSS.api.get_rsrc(subset_rsrc)[MGMT_DATA_KEY]
894
+ return @management_data[subset] unless only
895
+ @management_data[subset].map { |d| d[only] }
896
+ end
897
+ private :management_data_subset
898
+
899
+ # A shortcut for 'management_data subset: :smart_groups'
900
+ #
901
+ def smart_groups(only: nil, refresh: false)
902
+ management_data subset: :smart_groups, only: only, refresh: refresh
903
+ end
904
+
905
+ # A shortcut for 'management_data subset: :static_groups'
906
+ #
907
+ def static_groups(only: nil, refresh: false)
908
+ management_data subset: :static_groups, only: only, refresh: refresh
909
+ end
910
+
911
+ # A shortcut for 'management_data subset: :policies'
912
+ #
913
+ def policies(only: nil, refresh: false)
914
+ management_data subset: :policies, only: only, refresh: refresh
915
+ end
916
+
917
+ # A shortcut for 'management_data subset: :os_x_configuration_profiles'
918
+ #
919
+ def configuration_profiles(only: nil, refresh: false)
920
+ management_data subset: :os_x_configuration_profiles, only: only, refresh: refresh
921
+ end
922
+
923
+ # A shortcut for 'management_data subset: :ebooks'
924
+ #
925
+ def ebooks(only: nil, refresh: false)
926
+ management_data subset: :ebooks, only: only, refresh: refresh
927
+ end
928
+
929
+ # A shortcut for 'management_data subset: :mac_app_store_apps'
930
+ #
931
+ def app_store_apps(only: nil, refresh: false)
932
+ management_data subset: :mac_app_store_apps, only: only, refresh: refresh
933
+ end
934
+
935
+ # A shortcut for 'management_data subset: :restricted_software'
936
+ #
937
+ def restricted_software(only: nil, refresh: false)
938
+ management_data subset: :restricted_software, only: only, refresh: refresh
939
+ end
940
+
941
+ # A shortcut for 'management_data subset: :patch_reporting_software_titles'
942
+ #
943
+ def patch_titles(only: nil, refresh: false)
944
+ management_data subset: :patch_reporting_software_titles, only: only, refresh: refresh
945
+ end
946
+
947
+ # Return this computer's history.
948
+ # WARNING! Its huge, better to use a subset a
949
+ # nd one of the shortcut methods.
950
+ #
951
+ # @param subset[Symbol] the subset to return, rather than full history.
952
+ #
953
+ # @param refresh[Boolean] should we re-cache the data from the API?
954
+ #
955
+ # @return [Hash] The full history
956
+ #
957
+ # @return [Array] The history subset requested
958
+ #
959
+ def history(subset: nil, refresh: false)
960
+ @history ||= {}
961
+ if subset
962
+ history_subset(subset: subset, refresh: refresh)
963
+ else
964
+ full_history refresh
965
+ end
966
+ end
967
+
968
+ def full_history(refresh = false)
969
+ @history[:full] = nil if refresh
970
+ return @history[:full] if @history[:full]
971
+ history_rsrc = HISTORY_RSRC + "/id/#{@id}"
972
+ @history[:full] = JSS.api.get_rsrc(history_rsrc)[HISTORY_KEY]
973
+ @history[:full]
974
+ end
975
+ private :full_history
976
+
977
+ def history_subset(subset: nil, refresh: false)
978
+ raise "Subset must be one of :#{HISTORY_SUBSETS.join ', :'}" unless HISTORY_SUBSETS.include? subset
979
+ @history[subset] = nil if refresh
980
+ return @history[subset] if @history[subset]
981
+ subset_rsrc = HISTORY_RSRC + "/id/#{@id}/subset/#{subset}"
982
+ @history[subset] = JSS.api.get_rsrc(subset_rsrc)[HISTORY_KEY]
983
+ @history[subset]
984
+ end
985
+ private :history_subset
986
+
987
+ # Shortcut for history(:computer_usage_logs)
988
+ def usage_logs(refresh = false)
989
+ history(subset: :computer_usage_logs, refresh: refresh)
990
+ end
991
+
992
+ # Shortcut for history(:audits)
993
+ def audits(refresh = false)
994
+ history(subset: :audits, refresh: refresh)
995
+ end
996
+
997
+ # Shortcut for history(:policy_logs)
998
+ def policy_logs(refresh = false)
999
+ history(subset: :policy_logs, refresh: refresh)
1000
+ end
1001
+
1002
+ # Shortcut for history(:policy_logs), but just the completed policies
1003
+ def completed_policies(refresh = false)
1004
+ policy_logs(refresh).select { |pl| pl[:status] == POLICY_STATUS_COMPLETED }
1005
+ end
1006
+
1007
+ # Shortcut for history(:policy_logs), but just the failes policies
1008
+ def failed_policies(refresh = false)
1009
+ policy_log(refresh).select { |pl| pl[:status] == POLICY_STATUS_FAILED }
1010
+ end
1011
+
1012
+ # Shortcut for history(:casper_remote_logs)
1013
+ def casper_remote_logs(refresh = false)
1014
+ history(subset: :casper_remote_logs, refresh: refresh)
1015
+ end
1016
+
1017
+ # Shortcut for history(:screen_sharing_logs)
1018
+ def screen_sharing_logs(refresh = false)
1019
+ history(subset: :screen_sharing_logs, refresh: refresh)
1020
+ end
1021
+
1022
+ # Shortcut for history(:casper_imaging_logs)
1023
+ def casper_imaging_logs(refresh = false)
1024
+ history(subset: :casper_imaging_logs, refresh: refresh)
1025
+ end
1026
+
1027
+ # Shortcut for history(:commands)
1028
+ def commands(refresh = false)
1029
+ history(subset: :commands, refresh: refresh)
1030
+ end
1031
+
1032
+ # Shortcut for history(:user_location)
1033
+ def user_location_history(refresh = false)
1034
+ history(subset: :user_location, refresh: refresh)
1035
+ end
1036
+
1037
+ # Shortcut for history(:mac_app_store_applications)
1038
+ def app_store_app_history(refresh = false)
1039
+ history(subset: :mac_app_store_applications, refresh: refresh)
1040
+ end
1041
+
624
1042
  # Set or unset management acct and password for this computer
625
1043
  #
626
1044
  # @param name[String] the name of the management acct.
@@ -629,7 +1047,7 @@ module JSS
629
1047
  #
630
1048
  # @return [void]
631
1049
  #
632
- # The changes will need to be pushed to the server with #update
1050
+ # The changes will need to be pushed to the server with {#update}
633
1051
  # before they take effect.
634
1052
  #
635
1053
  # CAUTION: this does nothing to confirm the name and password
@@ -656,10 +1074,8 @@ module JSS
656
1074
  def make_unmanaged
657
1075
  return nil unless managed?
658
1076
  set_management_to(nil, nil)
659
- begin
660
- self.class.send_mdm_command(@id, :unmanage_device)
661
- rescue
662
- end
1077
+ return unless mdm_capable
1078
+ self.class.send_mdm_command(@id, :unmanage_device)
663
1079
  end
664
1080
 
665
1081
  #
@@ -751,34 +1167,47 @@ module JSS
751
1167
  @software = nil
752
1168
  end # delete
753
1169
 
754
- # Not Functional until I get more docs from JAMF
755
- #
756
- # #
757
- # # Send a blank_push MDM command
758
- # #
759
- # def blank_push
760
- # self.class.send_mdm_command @id, :blank_push
761
- # end
762
- # alias noop blank_push
763
- # alias send_blank_push blank_push
764
- #
765
- # #
766
- # # Send a device_lock MDM command
767
- # #
768
- # def device_lock
769
- # self.class.send_mdm_command @id, :device_lock
770
- # end
771
- # alias lock device_lock
772
- # alias lock_device device_lock
773
- #
774
- # #
775
- # # Send an erase_device MDM command
776
- # #
777
- # def erase_device
778
- # self.class.send_mdm_command @id, :erase_device
779
- # end
780
- # alias erase erase_device
781
- # alias wipe erase_device
1170
+ # Send a blank_push MDM command
1171
+ #
1172
+ # See JSS::Computer.send_mdm_command
1173
+ #
1174
+ def blank_push
1175
+ self.class.send_mdm_command @id, :blank_push
1176
+ end
1177
+ alias noop blank_push
1178
+ alias send_blank_push blank_push
1179
+
1180
+ # Send a device_lock MDM command
1181
+ #
1182
+ # See JSS::Computer.send_mdm_command
1183
+ #
1184
+ def device_lock(passcode)
1185
+ self.class.send_mdm_command @id, :device_lock, passcode
1186
+ end
1187
+ alias lock device_lock
1188
+ alias lock_device device_lock
1189
+
1190
+ # Send an erase_device MDM command
1191
+ #
1192
+ # See JSS::Computer.send_mdm_command
1193
+ #
1194
+ def erase_device(passcode)
1195
+ self.class.send_mdm_command @id, :erase_device, passcode
1196
+ end
1197
+ alias erase erase_device
1198
+ alias wipe erase_device
1199
+
1200
+ # Remove MDM management profile without
1201
+ # un-enrolling from the JSS or
1202
+ # resetting the JSS management acct.
1203
+ #
1204
+ # To do those things as well, see {#make_unmanaged}
1205
+ #
1206
+ # See JSS::Computer.send_mdm_command
1207
+ #
1208
+ def remove_mdm_profile
1209
+ self.class.send_mdm_command(@id, :unmanage_device)
1210
+ end
782
1211
 
783
1212
  # aliases
784
1213
  alias alt_macaddress alt_mac_address
@@ -830,7 +1259,7 @@ module JSS
830
1259
  computer << purchasing_xml if has_purchasing?
831
1260
 
832
1261
  doc.to_s
833
- end
1262
+ end # rest_xml
834
1263
 
835
1264
  end # class Computer
836
1265