jss-api 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/lib/jss-api.rb +1 -191
  2. metadata +16 -146
  3. data/.yardopts +0 -7
  4. data/CHANGES.md +0 -88
  5. data/LICENSE.txt +0 -174
  6. data/README.md +0 -396
  7. data/THANKS.md +0 -6
  8. data/bin/cgrouper +0 -485
  9. data/bin/subnet-update +0 -400
  10. data/lib/jss-api/api_connection.rb +0 -400
  11. data/lib/jss-api/api_object.rb +0 -616
  12. data/lib/jss-api/api_object/advanced_search.rb +0 -389
  13. data/lib/jss-api/api_object/advanced_search/advanced_computer_search.rb +0 -95
  14. data/lib/jss-api/api_object/advanced_search/advanced_mobile_device_search.rb +0 -96
  15. data/lib/jss-api/api_object/advanced_search/advanced_user_search.rb +0 -95
  16. data/lib/jss-api/api_object/building.rb +0 -92
  17. data/lib/jss-api/api_object/category.rb +0 -147
  18. data/lib/jss-api/api_object/computer.rb +0 -852
  19. data/lib/jss-api/api_object/creatable.rb +0 -98
  20. data/lib/jss-api/api_object/criteriable.rb +0 -189
  21. data/lib/jss-api/api_object/criteriable/criteria.rb +0 -231
  22. data/lib/jss-api/api_object/criteriable/criterion.rb +0 -228
  23. data/lib/jss-api/api_object/department.rb +0 -93
  24. data/lib/jss-api/api_object/distribution_point.rb +0 -560
  25. data/lib/jss-api/api_object/extendable.rb +0 -221
  26. data/lib/jss-api/api_object/extension_attribute.rb +0 -457
  27. data/lib/jss-api/api_object/extension_attribute/computer_extension_attribute.rb +0 -362
  28. data/lib/jss-api/api_object/extension_attribute/mobile_device_extension_attribute.rb +0 -189
  29. data/lib/jss-api/api_object/extension_attribute/user_extension_attribute.rb +0 -117
  30. data/lib/jss-api/api_object/group.rb +0 -380
  31. data/lib/jss-api/api_object/group/computer_group.rb +0 -124
  32. data/lib/jss-api/api_object/group/mobile_device_group.rb +0 -139
  33. data/lib/jss-api/api_object/group/user_group.rb +0 -139
  34. data/lib/jss-api/api_object/ldap_server.rb +0 -535
  35. data/lib/jss-api/api_object/locatable.rb +0 -286
  36. data/lib/jss-api/api_object/matchable.rb +0 -97
  37. data/lib/jss-api/api_object/mobile_device.rb +0 -556
  38. data/lib/jss-api/api_object/netboot_server.rb +0 -148
  39. data/lib/jss-api/api_object/network_segment.rb +0 -414
  40. data/lib/jss-api/api_object/osx_configuration_profile.rb +0 -261
  41. data/lib/jss-api/api_object/package.rb +0 -812
  42. data/lib/jss-api/api_object/peripheral.rb +0 -335
  43. data/lib/jss-api/api_object/peripheral_type.rb +0 -295
  44. data/lib/jss-api/api_object/policy.rb +0 -898
  45. data/lib/jss-api/api_object/purchasable.rb +0 -316
  46. data/lib/jss-api/api_object/removable_macaddr.rb +0 -98
  47. data/lib/jss-api/api_object/scopable.rb +0 -136
  48. data/lib/jss-api/api_object/scopable/scope.rb +0 -621
  49. data/lib/jss-api/api_object/script.rb +0 -631
  50. data/lib/jss-api/api_object/self_servable.rb +0 -355
  51. data/lib/jss-api/api_object/site.rb +0 -93
  52. data/lib/jss-api/api_object/software_update_server.rb +0 -109
  53. data/lib/jss-api/api_object/updatable.rb +0 -117
  54. data/lib/jss-api/api_object/uploadable.rb +0 -138
  55. data/lib/jss-api/api_object/user.rb +0 -272
  56. data/lib/jss-api/client.rb +0 -504
  57. data/lib/jss-api/compatibility.rb +0 -66
  58. data/lib/jss-api/composer.rb +0 -171
  59. data/lib/jss-api/configuration.rb +0 -306
  60. data/lib/jss-api/db_connection.rb +0 -298
  61. data/lib/jss-api/exceptions.rb +0 -95
  62. data/lib/jss-api/ruby_extensions.rb +0 -35
  63. data/lib/jss-api/ruby_extensions/filetest.rb +0 -43
  64. data/lib/jss-api/ruby_extensions/hash.rb +0 -79
  65. data/lib/jss-api/ruby_extensions/ipaddr.rb +0 -91
  66. data/lib/jss-api/ruby_extensions/pathname.rb +0 -77
  67. data/lib/jss-api/ruby_extensions/string.rb +0 -59
  68. data/lib/jss-api/ruby_extensions/time.rb +0 -63
  69. data/lib/jss-api/server.rb +0 -108
  70. data/lib/jss-api/utility.rb +0 -416
  71. data/lib/jss-api/version.rb +0 -31
@@ -1,504 +0,0 @@
1
- ### Copyright 2016 Pixar
2
- ###
3
- ### Licensed under the Apache License, Version 2.0 (the "Apache License")
4
- ### with the following modification; you may not use this file except in
5
- ### compliance with the Apache License and the following modification to it:
6
- ### Section 6. Trademarks. is deleted and replaced with:
7
- ###
8
- ### 6. Trademarks. This License does not grant permission to use the trade
9
- ### names, trademarks, service marks, or product names of the Licensor
10
- ### and its affiliates, except as required to comply with Section 4(c) of
11
- ### the License and to reproduce the content of the NOTICE file.
12
- ###
13
- ### You may obtain a copy of the Apache License at
14
- ###
15
- ### http://www.apache.org/licenses/LICENSE-2.0
16
- ###
17
- ### Unless required by applicable law or agreed to in writing, software
18
- ### distributed under the Apache License with the above modification is
19
- ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
- ### KIND, either express or implied. See the Apache License for the specific
21
- ### language governing permissions and limitations under the Apache License.
22
- ###
23
- ###
24
-
25
- ###
26
- module JSS
27
-
28
- #####################################
29
- ### Module Variables
30
- #####################################
31
-
32
- #####################################
33
- ### Module Methods
34
- #####################################
35
-
36
- #####################################
37
- ### Classes
38
- #####################################
39
-
40
- ###
41
- ### This class represents a Casper/JSS Client computer, on which
42
- ### this code is running.
43
- ###
44
- ### Since the class represents the current machine, there's no need
45
- ### to make an instance of it, all methods are class methods.
46
- ###
47
- ### At the moment, only Macintosh computers are supported.
48
- ###
49
- ###
50
- class Client
51
-
52
- #####################################
53
- ### Class Constants
54
- #####################################
55
-
56
- ### The Pathname to the jamf binary executable
57
- ### As of El Capitan (OS X 10.11) the location has moved.
58
- ORIG_JAMF_BINARY = Pathname.new "/usr/sbin/jamf"
59
- ELCAP_JAMF_BINARY = Pathname.new "/usr/local/jamf/bin/jamf"
60
- JAMF_BINARY = ELCAP_JAMF_BINARY.executable? ? ELCAP_JAMF_BINARY : ORIG_JAMF_BINARY
61
-
62
- ### The Pathname to the jamfHelper executable
63
- JAMF_HELPER = Pathname.new "/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
64
-
65
- ### The window_type options for jamfHelper
66
- JAMF_HELPER_WINDOW_TYPES = {
67
- :hud => 'hud',
68
- :utility => 'utility',
69
- :util => 'utility',
70
- :full_screen => 'fs',
71
- :fs => 'fs'
72
- }
73
-
74
- ### The possible window positions for jamfHelper
75
- JAMF_HELPER_WINDOW_POSITIONS = [nil, :ul, :ll, :ur, :lr]
76
-
77
- ### The available buttons in jamfHelper
78
- JAMF_HELPER_BUTTONS = [1,2]
79
-
80
- ### The possible alignment positions in jamfHelper
81
- JAMF_HELPER_ALIGNMENTS = [:right, :left, :center, :justified, :natural]
82
-
83
- ### The Pathname to the preferences plist used by the jamf binary
84
- JAMF_PLIST = Pathname.new "/Library/Preferences/com.jamfsoftware.jamf.plist"
85
-
86
- ### The Pathname to the JAMF support folder
87
- JAMF_SUPPORT_FOLDER = Pathname.new "/Library/Application Support/JAMF"
88
-
89
- ### The JAMF receipts folder, where package installs are tracked.
90
- RECEIPTS_FOLDER = JAMF_SUPPORT_FOLDER + "Receipts"
91
-
92
- ### The JAMF downloads folder
93
- DOWNLOADS_FOLDER = JAMF_SUPPORT_FOLDER + "Downloads"
94
-
95
- ### These jamf commands don't need root privs (most do)
96
- ROOTLESS_JAMF_COMMANDS = [
97
- :about,
98
- :checkJSSConnection,
99
- :getARDFields,
100
- :getComputerName,
101
- :help,
102
- :listUsers,
103
- :version ]
104
-
105
- #####################################
106
- ### Class Variables
107
- #####################################
108
-
109
- #####################################
110
- ### Class Methods
111
- #####################################
112
-
113
- ###
114
- ### Get the current IP address as a String.
115
- ###
116
- ### This handy code doesn't acutally make a UDP connection,
117
- ### it just starts to set up the connection, then uses that to get
118
- ### the local IP.
119
- ###
120
- ### Lifted gratefully from
121
- ### http://coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
122
- ###
123
- ### @return [String] the current IP address.
124
- ###
125
- def self.my_ip_address
126
- ### turn off reverse DNS resolution temporarily
127
- ### @note the 'socket' library has already been required by 'rest-client'
128
- orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
129
-
130
- UDPSocket.open do |s|
131
- s.connect '192.168.0.0', 1
132
- s.addr.last
133
- end
134
- ensure
135
- Socket.do_not_reverse_lookup = orig
136
- end
137
-
138
-
139
-
140
- ###
141
- ### @return [Boolean] is the jamf binary installed?
142
- ###
143
- def self.installed?
144
- JAMF_BINARY.executable?
145
- end
146
-
147
- ###
148
- ### @return [String,nil] the version of the jamf binary installed on this client, nil if not installed
149
- ###
150
- def self.jamf_version
151
- self.installed? ? self.run_jamf(:version).chomp.split('=')[1] : nil
152
- end
153
-
154
- ###
155
- ### @return [String] the url to the JSS for this client
156
- ###
157
- def self.jss_url
158
- @url = self.jamf_plist['jss_url']
159
- return nil if @url.nil?
160
- @url =~ %r{(https?)://(.+):(\d+)/}
161
- @protocol = $1
162
- @server = $2
163
- @port = $3
164
- return @url
165
- end
166
-
167
- ###
168
- ### @return [String] the JSS server for this client
169
- ###
170
- def self.jss_server
171
- self.jss_url
172
- return @server
173
- end
174
-
175
- ###
176
- ### @return [String] the protocol to the JSS for this client, "http" or "https"
177
- ###
178
- def self.jss_protocol
179
- self.jss_url
180
- return @protocol
181
- end
182
-
183
- ###
184
- ### @return [Integer] the port to the JSS for this client
185
- ###
186
- def self.jss_port
187
- self.jss_url
188
- @port ? @port.to_i : 80
189
- end
190
-
191
- ###
192
- ### @return [Hash] the parsed contents of the JAMF_PLIST if it exists, an empty hash if not
193
- ###
194
- def self.jamf_plist
195
- return {} unless JAMF_PLIST.file?
196
- Plist.parse_xml `/usr/libexec/PlistBuddy -x -c print #{Shellwords.escape JSS::Client::JAMF_PLIST.to_s}`
197
- end
198
-
199
-
200
- ###
201
- ### @return [Array<Pathname>] an array of Pathnames for all regular files in the jamf receipts folder
202
- ###
203
- def self.receipts
204
- raise JSS::NoSuchItemError, "The JAMF Receipts folder doesn't exist on this computer." unless RECEIPTS_FOLDER.exist?
205
- RECEIPTS_FOLDER.children.select{|c| c.file?}
206
- end
207
-
208
- ###
209
- ### @return [Boolean] is the JSS available now?
210
- ###
211
- def self.jss_available?
212
- output = run_jamf :checkJSSConnection, "-retry 1"
213
- $?.exitstatus == 0
214
- end
215
-
216
-
217
- ###
218
- ### @return [JSS::Computer] The JSS record for this computer
219
- ###
220
- def self.jss_record
221
- begin
222
- JSS::Computer.new :udid => self.udid
223
- rescue JSS::NoSuchItemError
224
- JSS::Computer.new :serial_number => self.serial_number
225
- end
226
- end
227
-
228
- ###
229
- ### @return [String] the UUID/UDID for this computer
230
- ###
231
- def self.udid
232
- self.hardware_data["platform_UUID"]
233
- end
234
-
235
- ###
236
- ### @return [String] the serial number for this computer
237
- ###
238
- def self.serial_number
239
- self.hardware_data["serial_number"]
240
- end
241
-
242
- ###
243
- ### @return [Hash] the HardwareDataType data from the system_profiler command
244
- ###
245
- def self.hardware_data
246
- raw = `/usr/sbin/system_profiler SPHardwareDataType -xml 2>/dev/null`
247
- Plist.parse_xml(raw)[0]["_items"][0]
248
- end
249
-
250
-
251
- ### Run an arbitrary jamf binary command.
252
- ###
253
- ### @note Most jamf commands require superuser/root privileges.
254
- ###
255
- ### @param command[String,Symbol] the jamf binary command to run
256
- ### The command is the single jamf command that comes after the/usr/bin/jamf.
257
- ###
258
- ### @param args[String,Array] the arguments passed to the jamf command.
259
- ### This is to be passed to Kernel.` (backtick), after being combined with the
260
- ### jamf binary and the jamf command
261
- ###
262
- ### @param verbose[Boolean] Should the stdout & stderr of the jamf binary be sent to
263
- ### the current stdout in realtime, as well as returned as a string?
264
- ###
265
- ### @return [String] the stdout & stderr of the jamf binary.
266
- ###
267
- ### @example
268
- ### These two are equivalent:
269
- ###
270
- ### JSS::Client.run_jamf "recon", "-assetTag 12345 -department 'IT Support'"
271
- ###
272
- ### JSS::Client.run_jamf :recon, ['-assetTag', '12345', '-department', 'IT Support'"]
273
- ###
274
- ###
275
- ### The details of the Process::Status for the jamf binary process can be captured from $?
276
- ### immediately after calling. (See Process::Status)
277
- ###
278
- def self.run_jamf(command, args = nil, verbose = false)
279
- raise JSS::UnmanagedError, "The jamf binary is not installed on this computer." unless self.installed?
280
- raise JSS::UnsupportedError, "You must have root privileges to run that jamf binary command" unless ROOTLESS_JAMF_COMMANDS.include? command.to_sym or JSS.superuser?
281
-
282
- cmd = case args
283
- when nil
284
- "#{JAMF_BINARY} #{command}"
285
- when String
286
- "#{JAMF_BINARY} #{command} #{args}"
287
- when Array
288
- "#{([JAMF_BINARY.to_s, command] + args).join(' ')}"
289
- else
290
- raise JSS::InvalidDataError, "args must be a String or Array of Strings"
291
- end # case
292
-
293
- cmd += " -verbose" if verbose and (not cmd.include? " -verbose")
294
- puts "Running: #{cmd}" if verbose
295
-
296
- output = []
297
- IO.popen("#{cmd} 2>&1") do |proc|
298
- while line = proc.gets
299
- output << line
300
- puts line if verbose
301
- end
302
- end
303
- install_out = output.join('')
304
- install_out.force_encoding("UTF-8") if install_out.respond_to? :force_encoding
305
- return install_out
306
- end # run_jamf
307
-
308
-
309
- ### A wrapper for the jamfHelper command, which can display a window on the client machine.
310
- ###
311
- ### The first parameter must be a symbol defining what kind of window to display. The options are
312
- ### - :hud - creates an Apple "Heads Up Display" style window
313
- ### - :utility or :util - creates an Apple "Utility" style window
314
- ### - :fs or :full_screen or :fullscreen - creates a full screen window that restricts all user input
315
- ### WARNING: Remote access must be used to unlock machines in this mode
316
- ###
317
- ### The remaining options Hash can contain any of the options listed. See below for descriptions.
318
- ###
319
- ### The value returned is the Integer exitstatus/stdout (both are the same) of the jamfHelper command.
320
- ### The meanings of those integers are:
321
- ###
322
- ### - 0 - Button 1 was clicked
323
- ### - 1 - The Jamf Helper was unable to launch
324
- ### - 2 - Button 2 was clicked
325
- ### - 3 - Process was started as a launchd task
326
- ### - XX1 - Button 1 was clicked with a value of XX seconds selected in the drop-down
327
- ### - XX2 - Button 2 was clicked with a value of XX seconds selected in the drop-down
328
- ### - 239 - The exit button was clicked
329
- ### - 240 - The "ProductVersion" in sw_vers did not return 10.5.X, 10.6.X or 10.7.X
330
- ### - 243 - The window timed-out with no buttons on the screen
331
- ### - 250 - Bad "-windowType"
332
- ### - 254 - Cancel button was select with delay option present
333
- ### - 255 - No "-windowType"
334
- ###
335
- ### See also /Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -help
336
- ###
337
- ### @note the -startlaunchd and -kill options are not available in this implementation, since
338
- ### they don't work at the moment (casper 9.4).
339
- ### -startlaunchd seems to be required to NOT use launchd, and when it's ommited, an error is generated
340
- ### about the launchd plist permissions being incorrect.
341
- ###
342
- ### @param window_type[Symbol] The type of window to display
343
- ###
344
- ### @param opts[Hash] the options for the window
345
- ###
346
- ### @option opts :window_position [Symbol,nil] one of [ nil, :ul, :ll. :ur, :lr ]
347
- ### Positions window in the upper right, upper left, lower right or lower left of the user's screen
348
- ### If no input is given, the window defaults to the center of the screen
349
- ###
350
- ### @option opts :title [String]
351
- ### Sets the window's title to the specified string
352
- ###
353
- ### @option opts :heading [String]
354
- ### Sets the heading of the window to the specified string
355
- ###
356
- ### @option opts :align_heading [Symbol] one of [:right, :left, :center, :justified, :natural]
357
- ### Aligns the heading to the specified alignment
358
- ###
359
- ### @option opts :description [String]
360
- ### Sets the main contents of the window to the specified string
361
- ###
362
- ### @option opts :align_description [Symbol] one of [:right, :left, :center, :justified, :natural]
363
- ### Aligns the description to the specified alignment
364
- ###
365
- ### @option opts :icon [String,Pathname]
366
- ### Sets the windows image field to the image located at the specified path
367
- ###
368
- ### @option opts :icon_size [Integer]
369
- ### Changes the image frame to the specified pixel size
370
- ###
371
- ### @option opts :full_screen_icon [any value]
372
- ### Scales the "icon" to the full size of the window.
373
- ### Note: Only available in full screen mode
374
- ###
375
- ### @option opts :button1 [String]
376
- ### Creates a button with the specified label
377
- ###
378
- ### @option opts :button2 [String]
379
- ### Creates a second button with the specified label
380
- ###
381
- ### @option opts :default_button [Integer] either 1 or 2
382
- ### Sets the default button of the window to the specified button. The Default Button will respond to "return"
383
- ###
384
- ### @option opts :cancel_button [Integer] either 1 or 2
385
- ### Sets the cancel button of the window to the specified button. The Cancel Button will respond to "escape"
386
- ###
387
- ### @option opts :timeout [Integer]
388
- ### Causes the window to timeout after the specified amount of seconds
389
- ### Note: The timeout will cause the default button, button 1 or button 2 to be selected (in that order)
390
- ###
391
- ### @option opts :show_delay_options [String,Array<Integer>] A String of comma-separated Integers, or an Array of Integers.
392
- ### Enables the "Delay Options Mode". The window will display a dropdown with the values passed through the string
393
- ###
394
- ### @option opts :countdown [any value]
395
- ### Displays a string notifying the user when the window will time out
396
- ###
397
- ### @option opts :align_countdown [Symbol] one of [:right, :left, :center, :justified, :natural]
398
- ### Aligns the countdown to the specified alignment
399
- ###
400
- ### @option opts :lock_hud [any value]
401
- ### Removes the ability to exit the HUD by selecting the close button
402
- ###
403
- ### @return [Integer] the exit status of the jamfHelper command. See above.
404
- ###
405
- def self.jamf_helper(window_type = :hud, opts = {})
406
-
407
- raise JSS::UnmanagedError, "The jamfHelper app is not installed properly on this computer." unless JAMF_HELPER.executable?
408
-
409
- unless JAMF_HELPER_WINDOW_TYPES.include? window_type
410
- raise JSS::InvalidDataError, "The first parameter must be a window type, one of :#{JAMF_HELPER_WINDOW_TYPES.keys.join(', :')}."
411
- end
412
-
413
- # start building the arg array
414
-
415
- args = ["-startlaunchd", "-windowType", JAMF_HELPER_WINDOW_TYPES[window_type]]
416
-
417
- opts.keys.each do |opt|
418
- case opt
419
- when :window_position
420
- raise JSS::InvalidDataError, ":window_position must be one of :#{JAMF_HELPER_WINDOW_POSITIONS.join(', :')}." unless JAMF_HELPER_WINDOW_POSITIONS.include? opts[opt].to_sym
421
- args << "-windowPosition"
422
- args << opts[opt].to_s
423
-
424
- when :title
425
- args << "-title"
426
- args << opts[opt].to_s
427
-
428
- when :heading
429
- args << "-heading"
430
- args << opts[opt].to_s
431
-
432
- when :align_heading
433
- raise JSS::InvalidDataError, ":align_heading must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
434
- args << "-alignHeading"
435
- args << opts[opt].to_s
436
-
437
- when :description
438
- args << "-description"
439
- args << opts[opt].to_s
440
-
441
- when :align_description
442
- raise JSS::InvalidDataError, ":align_description must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
443
- args << "-alignDescription"
444
- args << opts[opt].to_s
445
-
446
- when :icon
447
- args << "-icon"
448
- args << opts[opt].to_s
449
-
450
- when :icon_size
451
- args << "-iconSize"
452
- args << opts[opt].to_s
453
-
454
- when :full_screen_icon
455
- args << "-fullScreenIcon"
456
-
457
- when :button1
458
- args << "-button1"
459
- args << opts[opt].to_s
460
-
461
- when :button2
462
- args << "-button2"
463
- args << opts[opt].to_s
464
-
465
- when :default_button
466
- raise JSS::InvalidDataError, ":default_button must be one of #{JAMF_HELPER_BUTTONS.join(', ')}." unless JAMF_HELPER_BUTTONS.include? opts[opt]
467
- args << "-defaultButton"
468
- args << opts[opt].to_s
469
-
470
- when :cancel_button
471
- raise JSS::InvalidDataError, ":cancel_button must be one of #{JAMF_HELPER_BUTTONS.join(', ')}." unless JAMF_HELPER_BUTTONS.include? opts[opt]
472
- args << "-cancelButton"
473
- args << opts[opt].to_s
474
-
475
- when :timeout
476
- args << "-timeout"
477
- args << opts[opt].to_s
478
-
479
- when :show_delay_options
480
- args << "-showDelayOptions"
481
- args << JSS.to_s_and_a(opts[opt])[:arrayform].join(', ')
482
-
483
- when :countdown
484
- args << "-countdown"
485
-
486
- when :align_countdown
487
- raise JSS::InvalidDataError, ":align_countdown must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
488
- args << "-alignCountdown"
489
- args << opts[opt].to_s
490
-
491
- when :lock_hud
492
- args << " -lockHUD "
493
- end # case opt
494
- end # each do opt
495
-
496
- system JAMF_HELPER.to_s, *args
497
- return $?.exitstatus
498
-
499
- end # def self.jamf_helper
500
-
501
-
502
- end # class Client
503
-
504
- end # module