ruby-jss 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ruby-jss might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGES.md +21 -1
- data/lib/jss.rb +8 -6
- data/lib/jss/api_object.rb +1 -2
- data/lib/jss/api_object/computer.rb +45 -25
- data/lib/jss/api_object/computer/application_installs.rb +119 -0
- data/lib/jss/api_object/{osx_configuration_profile.rb → configuration_profile.rb} +14 -94
- data/lib/jss/api_object/configuration_profile/mobile_device_configuration_profile.rb +75 -0
- data/lib/jss/api_object/configuration_profile/osx_configuration_profile.rb +117 -0
- data/lib/jss/api_object/mdm.rb +2 -0
- data/lib/jss/api_object/mobile_device.rb +1 -1
- data/lib/jss/api_object/self_servable.rb +47 -9
- data/lib/jss/client.rb +62 -356
- data/lib/jss/client/jamf_binary.rb +132 -0
- data/lib/jss/client/jamf_helper.rb +298 -0
- data/lib/jss/client/management_action.rb +114 -0
- data/lib/jss/composer.rb +145 -168
- data/lib/jss/ruby_extensions/hash.rb +119 -64
- data/lib/jss/version.rb +1 -1
- metadata +29 -4
- data/lib/jss/api_object/mobile_device_configuration_profile.rb +0 -62
@@ -0,0 +1,75 @@
|
|
1
|
+
# This is just a stub for now.
|
2
|
+
|
3
|
+
#
|
4
|
+
module JSS
|
5
|
+
|
6
|
+
#
|
7
|
+
class MobileDeviceConfigurationProfile < JSS::ConfigurationProfile
|
8
|
+
|
9
|
+
|
10
|
+
### The base for REST resources of this class
|
11
|
+
RSRC_BASE = 'mobiledeviceconfigurationprofiles'.freeze
|
12
|
+
|
13
|
+
### the hash key used for the JSON list output of all objects in the JSS
|
14
|
+
RSRC_LIST_KEY = :configuration_profiles
|
15
|
+
|
16
|
+
### The hash key used for the JSON object output.
|
17
|
+
### It's also used in various error messages
|
18
|
+
RSRC_OBJECT_KEY = :configuration_profile
|
19
|
+
|
20
|
+
# the object type for this object in
|
21
|
+
# the object history table.
|
22
|
+
# See {APIObject#add_object_history_entry}
|
23
|
+
OBJECT_HISTORY_OBJECT_TYPE = 22
|
24
|
+
|
25
|
+
# Our scopes deal with mobile_devices
|
26
|
+
SCOPE_TARGET_KEY = :mobile_devices
|
27
|
+
|
28
|
+
# icons cant be uploaded yet
|
29
|
+
# UPLOAD_TYPES = { icon: :mobiledeviceconfigurationprofileicon }.freeze
|
30
|
+
|
31
|
+
# Attributes
|
32
|
+
###################################
|
33
|
+
|
34
|
+
# @return [Integer] how many days before a cert payload expires
|
35
|
+
# should this profile be automatically re-installed?
|
36
|
+
attr_reader :redeploy_days_before_certificate_expires
|
37
|
+
|
38
|
+
# Constructor
|
39
|
+
###################################
|
40
|
+
|
41
|
+
# See JSS::APIObject#initialize
|
42
|
+
#
|
43
|
+
def initialize(args = {})
|
44
|
+
super
|
45
|
+
@redeploy_days_before_certificate_expires = @main_subset[:redeploy_days_before_certificate_expires]
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param new_val[String] the new level for this profile (user/computer)
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
#
|
52
|
+
def redeploy_days_before_certificate_expires=(new_val)
|
53
|
+
return nil if redeploy_days_before_certificate_expires == new_val
|
54
|
+
raise JSS::InvalidDataError, 'New value must be an integer >= 0' unless new_val.is_a?(Integer) && new_val >= 0
|
55
|
+
@redeploy_days_before_certificate_expires = new_val
|
56
|
+
@need_to_update = true
|
57
|
+
end #
|
58
|
+
|
59
|
+
# Private Instance Methods
|
60
|
+
###################################
|
61
|
+
private
|
62
|
+
|
63
|
+
def rest_xml
|
64
|
+
doc = super
|
65
|
+
gen = doc.root.elements['general']
|
66
|
+
gen.add_element('redeploy_days_before_certificate_expires').text = redeploy_days_before_certificate_expires.to_s
|
67
|
+
doc.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
end # class MobileDeviceConfigurationProfile
|
71
|
+
|
72
|
+
end # module JSS
|
73
|
+
|
74
|
+
require 'jss/api_object/configuration_profile/mobile_device_configuration_profile'
|
75
|
+
require 'jss/api_object/configuration_profile/osx_configuration_profile'
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# Copyright 2018 Pixar
|
2
|
+
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "Apache License")
|
5
|
+
# with the following modification; you may not use this file except in
|
6
|
+
# compliance with the Apache License and the following modification to it:
|
7
|
+
# Section 6. Trademarks. is deleted and replaced with:
|
8
|
+
#
|
9
|
+
# 6. Trademarks. This License does not grant permission to use the trade
|
10
|
+
# names, trademarks, service marks, or product names of the Licensor
|
11
|
+
# and its affiliates, except as required to comply with Section 4(c) of
|
12
|
+
# the License and to reproduce the content of the NOTICE file.
|
13
|
+
#
|
14
|
+
# You may obtain a copy of the Apache License at
|
15
|
+
#
|
16
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
17
|
+
#
|
18
|
+
# Unless required by applicable law or agreed to in writing, software
|
19
|
+
# distributed under the Apache License with the above modification is
|
20
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
21
|
+
# KIND, either express or implied. See the Apache License for the specific
|
22
|
+
# language governing permissions and limitations under the Apache License.
|
23
|
+
#
|
24
|
+
#
|
25
|
+
|
26
|
+
#
|
27
|
+
module JSS
|
28
|
+
|
29
|
+
# Classes
|
30
|
+
###################################
|
31
|
+
|
32
|
+
# An OS X Configuration Profile in the JSS.
|
33
|
+
#
|
34
|
+
# Note that the profile payloads and the profile UUID cannot be edited or updated with this via this class.
|
35
|
+
# Use the web UI.
|
36
|
+
#
|
37
|
+
# @see JSS::APIObject
|
38
|
+
#
|
39
|
+
class OSXConfigurationProfile < JSS::ConfigurationProfile
|
40
|
+
|
41
|
+
# Class Constants
|
42
|
+
###################################
|
43
|
+
|
44
|
+
# The base for REST resources of this class
|
45
|
+
RSRC_BASE = 'osxconfigurationprofiles'.freeze
|
46
|
+
|
47
|
+
# the hash key used for the JSON list output of all objects in the JSS
|
48
|
+
RSRC_LIST_KEY = :os_x_configuration_profiles
|
49
|
+
|
50
|
+
# The hash key used for the JSON object output.
|
51
|
+
# It's also used in various error messages
|
52
|
+
RSRC_OBJECT_KEY = :os_x_configuration_profile
|
53
|
+
|
54
|
+
# these keys, as well as :id and :name, are present in valid API JSON data for this class
|
55
|
+
VALID_DATA_KEYS = %i[distribution_method scope redeploy_on_update].freeze
|
56
|
+
|
57
|
+
# Our scopes deal with computers
|
58
|
+
SCOPE_TARGET_KEY = :computers
|
59
|
+
|
60
|
+
# Our SelfService happens on OSX
|
61
|
+
SELF_SERVICE_TARGET = :osx
|
62
|
+
|
63
|
+
# The possible values for :level
|
64
|
+
LEVELS = %w[user computer].freeze
|
65
|
+
|
66
|
+
# can not yet upload icons
|
67
|
+
# UPLOAD_TYPES = { icon: :osxconfigurationprofileicon }.freeze
|
68
|
+
|
69
|
+
# the object type for this object in
|
70
|
+
# the object history table.
|
71
|
+
# See {APIObject#add_object_history_entry}
|
72
|
+
OBJECT_HISTORY_OBJECT_TYPE = 4
|
73
|
+
|
74
|
+
# Attributes
|
75
|
+
###################################
|
76
|
+
|
77
|
+
# @return [String] the level (user/computer) of this profile
|
78
|
+
attr_reader :level
|
79
|
+
|
80
|
+
# Constructor
|
81
|
+
###################################
|
82
|
+
|
83
|
+
# See JSS::APIObject#initialize
|
84
|
+
#
|
85
|
+
def initialize(args = {})
|
86
|
+
super
|
87
|
+
@level = @main_subset[:level]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Public Instance Methods
|
91
|
+
###################################
|
92
|
+
|
93
|
+
# @param new_val[String] the new level for this profile (user/computer)
|
94
|
+
#
|
95
|
+
# @return [void]
|
96
|
+
#
|
97
|
+
def level=(new_val)
|
98
|
+
return nil if @level == new_val
|
99
|
+
raise JSS::InvalidDataError, "New value must be one of '#{LEVELS.join("' '")}'" unless LEVELS.include? new_val
|
100
|
+
@level = new_val
|
101
|
+
@need_to_update = true
|
102
|
+
end #
|
103
|
+
|
104
|
+
# Private Instance Methods
|
105
|
+
###################################
|
106
|
+
private
|
107
|
+
|
108
|
+
def rest_xml
|
109
|
+
doc = super
|
110
|
+
gen = doc.root.elements['general']
|
111
|
+
gen.add_element('level').text = level
|
112
|
+
doc.to_s
|
113
|
+
end
|
114
|
+
|
115
|
+
end # class OSXConfigurationProfile
|
116
|
+
|
117
|
+
end # module
|
data/lib/jss/api_object/mdm.rb
CHANGED
@@ -760,6 +760,7 @@ module JSS
|
|
760
760
|
send_mdm_command targets, :device_name, opts: { device_name: name }, api: api
|
761
761
|
end
|
762
762
|
alias set_name device_name
|
763
|
+
alias set_device_name device_name
|
763
764
|
|
764
765
|
# Send a wallpaper command to one or more targets
|
765
766
|
#
|
@@ -1153,6 +1154,7 @@ module JSS
|
|
1153
1154
|
self.class.device_name @id, name, api: @api
|
1154
1155
|
end
|
1155
1156
|
alias set_name device_name
|
1157
|
+
alias set_device_name device_name
|
1156
1158
|
|
1157
1159
|
# Send a wallpaper command to this object
|
1158
1160
|
#
|
@@ -63,7 +63,7 @@ module JSS
|
|
63
63
|
# Classes including this module *MUST*:
|
64
64
|
# - call {#add_self_service_xml(xmldoc)} in their #rest_xml method
|
65
65
|
#
|
66
|
-
# IMPORTANT: Since SelfServable also includes #{JSS::
|
66
|
+
# IMPORTANT: Since SelfServable also includes #{JSS::Uploadable}, for uploading icons,
|
67
67
|
# see that module for its requirements.
|
68
68
|
#
|
69
69
|
#
|
@@ -90,6 +90,35 @@ module JSS
|
|
90
90
|
|
91
91
|
DEFAULT_INSTALL_BUTTON_TEXT = 'Install'.freeze
|
92
92
|
|
93
|
+
# This hash contains the details about the inconsistencies of how
|
94
|
+
# Self Service data is dealt with in the API data of the different
|
95
|
+
# self-servable classes.
|
96
|
+
#
|
97
|
+
# - in_self_service_data_path: Array, In the API data hash (the @init_data)
|
98
|
+
# where to find the value indicicating that a thing is in self service.
|
99
|
+
# e.g. [:self_service, :use_for_self_service] means
|
100
|
+
# @init_data[:self_service][:use_for_self_service]
|
101
|
+
#
|
102
|
+
# - in_self_service: Object, In the path defined above, what value means
|
103
|
+
# the thing IS in self service
|
104
|
+
#
|
105
|
+
# - not_in_self_service: Object, In the path defined above, what value means
|
106
|
+
# the thing IS NOT in self service
|
107
|
+
#
|
108
|
+
# - targets: Array<Symbol>, the array contains either :macos, :ios, or both.
|
109
|
+
#
|
110
|
+
# - payload: Symbol, The thing that is deployed by self service, one of:
|
111
|
+
# :policy, :app, :profile, :patchpolicy (ebooks are considered apps)
|
112
|
+
#
|
113
|
+
# - can_display_in_categories: Boolean, when adding 'self service categories'
|
114
|
+
# can the thing be 'displayed in' those categories?
|
115
|
+
#
|
116
|
+
# - can_feature_in_categories: Boolean, when adding 'self service categories'
|
117
|
+
# can the thing be 'featured in' those categories?
|
118
|
+
#
|
119
|
+
# It's unfortunate that this is needed in order to keep all the
|
120
|
+
# self service ruby code in this one module.
|
121
|
+
#
|
93
122
|
SELF_SERVICE_CLASSES = {
|
94
123
|
JSS::Policy => {
|
95
124
|
in_self_service_data_path: [:self_service, :use_for_self_service],
|
@@ -330,7 +359,11 @@ module JSS
|
|
330
359
|
# @return [void]
|
331
360
|
#
|
332
361
|
def self_service_user_removable=(new_val, pw = @self_service_removal_password)
|
333
|
-
|
362
|
+
new_val, pw = *new_val if new_val.is_a? Array
|
363
|
+
pw = nil unless new_val == :with_auth
|
364
|
+
|
365
|
+
return nil if new_val == self_service_user_removable && pw == self_service_removal_password
|
366
|
+
|
334
367
|
validate_user_removable new_val
|
335
368
|
|
336
369
|
@self_service_user_removable = new_val
|
@@ -354,12 +387,12 @@ module JSS
|
|
354
387
|
#
|
355
388
|
def icon=(new_icon)
|
356
389
|
if new_icon.is_a? Integer
|
357
|
-
return if new_icon == @icon.id
|
390
|
+
return if @icon && new_icon == @icon.id
|
358
391
|
validate_icon new_icon
|
359
392
|
@new_icon_id = new_icon
|
360
393
|
@need_to_update = true
|
361
394
|
else
|
362
|
-
unless uploadable? && self.class::UPLOAD_TYPES.keys.include?(:icon)
|
395
|
+
unless uploadable? && defined?(self.class::UPLOAD_TYPES) && self.class::UPLOAD_TYPES.keys.include?(:icon)
|
363
396
|
raise JSS::UnsupportedError, "Class #{self.class} does not support icon uploads."
|
364
397
|
end
|
365
398
|
new_icon = Pathname.new new_icon
|
@@ -473,7 +506,7 @@ module JSS
|
|
473
506
|
@init_data[subsection][key] == @self_service_data_config[:in_self_service]
|
474
507
|
end
|
475
508
|
|
476
|
-
def validate_user_removable
|
509
|
+
def validate_user_removable(new_val)
|
477
510
|
raise JSS::UnsupportedError, 'User removal settings not applicable to this class' unless self_service_payload == :profile
|
478
511
|
|
479
512
|
raise JSS::UnsupportedError, 'Removal :with_auth not applicable to this class' if new_val == :with_auth && !self_service_targets.include?(:ios)
|
@@ -483,7 +516,7 @@ module JSS
|
|
483
516
|
|
484
517
|
def validate_icon(id)
|
485
518
|
return nil unless JSS::DB_CNX.connected?
|
486
|
-
raise JSS::NoSuchItemError, "No icon with id #{
|
519
|
+
raise JSS::NoSuchItemError, "No icon with id #{id}" unless JSS::Icon.all_ids.include? id
|
487
520
|
end
|
488
521
|
|
489
522
|
# Re-read the icon data for this object from the API
|
@@ -533,9 +566,14 @@ module JSS
|
|
533
566
|
end
|
534
567
|
|
535
568
|
if self_service_payload == :profile
|
536
|
-
|
537
|
-
|
538
|
-
|
569
|
+
if self_service_targets.include? :ios
|
570
|
+
sec = ssvc.add_element('security')
|
571
|
+
sec.add_element('removal_disallowed').text = PROFILE_REMOVAL_BY_USER[@self_service_user_removable]
|
572
|
+
sec.add_element('password').text = @self_service_removal_password.to_s
|
573
|
+
else
|
574
|
+
gen = doc_root.elements['general']
|
575
|
+
gen.add_element('user_removable').text = (@self_service_user_removable == :always).to_s
|
576
|
+
end
|
539
577
|
end
|
540
578
|
|
541
579
|
return unless @self_service_data_config[:in_self_service_data_path]
|
data/lib/jss/client.rb
CHANGED
@@ -26,16 +26,7 @@
|
|
26
26
|
###
|
27
27
|
module JSS
|
28
28
|
|
29
|
-
#
|
30
|
-
#####################################
|
31
|
-
|
32
|
-
# Module Methods
|
33
|
-
#####################################
|
34
|
-
|
35
|
-
# Classes
|
36
|
-
#####################################
|
37
|
-
|
38
|
-
# This class represents a Casper/JSS Client computer, on which
|
29
|
+
# This class represents a Jamf/JSS Client computer, on which
|
39
30
|
# this code is running.
|
40
31
|
#
|
41
32
|
# Since the class represents the current machine, there's no need
|
@@ -43,39 +34,13 @@ module JSS
|
|
43
34
|
#
|
44
35
|
# At the moment, only Macintosh computers are supported.
|
45
36
|
#
|
37
|
+
# TODO: convert this to a module, since that's how it's used.
|
46
38
|
#
|
47
39
|
class Client
|
48
40
|
|
49
|
-
#
|
41
|
+
# Constants
|
50
42
|
#####################################
|
51
43
|
|
52
|
-
# The Pathname to the jamf binary executable
|
53
|
-
# As of El Capitan (OS X 10.11) the location has moved.
|
54
|
-
ORIG_JAMF_BINARY = Pathname.new '/usr/sbin/jamf'
|
55
|
-
ELCAP_JAMF_BINARY = Pathname.new '/usr/local/jamf/bin/jamf'
|
56
|
-
JAMF_BINARY = ELCAP_JAMF_BINARY.executable? ? ELCAP_JAMF_BINARY : ORIG_JAMF_BINARY
|
57
|
-
|
58
|
-
# The Pathname to the jamfHelper executable
|
59
|
-
JAMF_HELPER = Pathname.new '/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper'
|
60
|
-
|
61
|
-
# The window_type options for jamfHelper
|
62
|
-
JAMF_HELPER_WINDOW_TYPES = {
|
63
|
-
hud: 'hud',
|
64
|
-
utility: 'utility',
|
65
|
-
util: 'utility',
|
66
|
-
full_screen: 'fs',
|
67
|
-
fs: 'fs'
|
68
|
-
}.freeze
|
69
|
-
|
70
|
-
# The possible window positions for jamfHelper
|
71
|
-
JAMF_HELPER_WINDOW_POSITIONS = [nil, :ul, :ll, :ur, :lr].freeze
|
72
|
-
|
73
|
-
# The available buttons in jamfHelper
|
74
|
-
JAMF_HELPER_BUTTONS = [1, 2].freeze
|
75
|
-
|
76
|
-
# The possible alignment positions in jamfHelper
|
77
|
-
JAMF_HELPER_ALIGNMENTS = [:right, :left, :center, :justified, :natural].freeze
|
78
|
-
|
79
44
|
# The Pathname to the preferences plist used by the jamf binary
|
80
45
|
JAMF_PLIST = Pathname.new '/Library/Preferences/com.jamfsoftware.jamf.plist'
|
81
46
|
|
@@ -88,25 +53,33 @@ module JSS
|
|
88
53
|
# The JAMF downloads folder
|
89
54
|
DOWNLOADS_FOLDER = JAMF_SUPPORT_FOLDER + 'Downloads'
|
90
55
|
|
91
|
-
#
|
92
|
-
|
93
|
-
:about,
|
94
|
-
:checkJSSConnection,
|
95
|
-
:getARDFields,
|
96
|
-
:getComputerName,
|
97
|
-
:help,
|
98
|
-
:listUsers,
|
99
|
-
:version
|
100
|
-
].freeze
|
56
|
+
# The bin folder inside the Jamf support folder
|
57
|
+
SUPPORT_BIN_FOLDER = JAMF_SUPPORT_FOLDER + 'bin'
|
101
58
|
|
102
|
-
|
103
|
-
|
104
|
-
|
59
|
+
# The bin folder with the jamf binary and a few other things
|
60
|
+
USR_LOCAL_BIN_FOLDER = Pathname.new '/usr/local/jamf/bin'
|
61
|
+
|
62
|
+
# This command gives raw info about console users
|
63
|
+
CONSOLE_USERS_SCUTIL_CMD = 'echo "show State:/Users/ConsoleUser" | /usr/sbin/scutil'.freeze
|
64
|
+
|
65
|
+
# ignore console user = root (loginwindow)
|
66
|
+
ROOT_USER = 'root'.freeze
|
67
|
+
|
68
|
+
# ignore primary console user loginwindow
|
69
|
+
LOGINWINDOW_USER = 'loginwindow'.freeze
|
70
|
+
|
71
|
+
# The end of the path to the Self Service Executable.
|
72
|
+
# Used to figure out who's running Self Service.app
|
73
|
+
SELF_SERVICE_EXECUTABLE_END = '/Self Service.app/Contents/MacOS/Self Service'.freeze
|
74
|
+
|
75
|
+
# the ps command used to figure out who's running Self Service
|
76
|
+
PS_USER_COMM = 'ps -A -o user,comm'.freeze
|
105
77
|
|
106
|
-
#####################################
|
107
78
|
# Class Methods
|
108
79
|
#####################################
|
109
80
|
|
81
|
+
|
82
|
+
|
110
83
|
# Get the current IP address as a String.
|
111
84
|
#
|
112
85
|
# This handy code doesn't acutally make a UDP connection,
|
@@ -132,22 +105,6 @@ module JSS
|
|
132
105
|
Socket.do_not_reverse_lookup = orig
|
133
106
|
end
|
134
107
|
|
135
|
-
# Who's logged in to the console right now?
|
136
|
-
#
|
137
|
-
# @return [String, nil] the username of the current console user, or nil if none.
|
138
|
-
#
|
139
|
-
def self.console_user
|
140
|
-
cmd = '/usr/sbin/scutil'
|
141
|
-
qry = 'show State:/Users/ConsoleUser'
|
142
|
-
Open3.popen2e(cmd) do |cmdin, cmdouterr, _wait_thr|
|
143
|
-
cmdin.puts qry
|
144
|
-
cmdin.close
|
145
|
-
out = cmdouterr.read
|
146
|
-
user = out.lines.select { |l| l =~ /^\s+Name\s*:/ }.first.to_s.split(/\s*:\s*/).last
|
147
|
-
return user.nil? ? user : user.chomp
|
148
|
-
end # do
|
149
|
-
end
|
150
|
-
|
151
108
|
# Is the jamf binary installed?
|
152
109
|
#
|
153
110
|
# @return [Boolean] is the jamf binary installed?
|
@@ -207,11 +164,12 @@ module JSS
|
|
207
164
|
|
208
165
|
# The contents of the JAMF plist
|
209
166
|
#
|
210
|
-
# @return [Hash] the parsed contents of the JAMF_PLIST if it exists,
|
167
|
+
# @return [Hash] the parsed contents of the JAMF_PLIST if it exists,
|
168
|
+
# an empty hash if not
|
211
169
|
#
|
212
170
|
def self.jamf_plist
|
213
171
|
return {} unless JAMF_PLIST.file?
|
214
|
-
|
172
|
+
JSS.parse_plist JAMF_PLIST
|
215
173
|
end
|
216
174
|
|
217
175
|
# All the JAMF receipts on this client
|
@@ -264,303 +222,51 @@ module JSS
|
|
264
222
|
#
|
265
223
|
def self.hardware_data
|
266
224
|
raw = `/usr/sbin/system_profiler SPHardwareDataType -xml 2>/dev/null`
|
267
|
-
|
225
|
+
JSS.parse_plist(raw)[0]['_items'][0]
|
268
226
|
end
|
269
227
|
|
270
|
-
#
|
271
|
-
#
|
272
|
-
# @note Most jamf commands require superuser/root privileges.
|
273
|
-
#
|
274
|
-
# @param command[String,Symbol] the jamf binary command to run
|
275
|
-
# The command is the single jamf command that comes after the/usr/bin/jamf.
|
276
|
-
#
|
277
|
-
# @param args[String,Array] the arguments passed to the jamf command.
|
278
|
-
# This is to be passed to Kernel.` (backtick), after being combined with the
|
279
|
-
# jamf binary and the jamf command
|
280
|
-
#
|
281
|
-
# @param verbose[Boolean] Should the stdout & stderr of the jamf binary be sent to
|
282
|
-
# the current stdout in realtime, as well as returned as a string?
|
283
|
-
#
|
284
|
-
# @return [String] the stdout & stderr of the jamf binary.
|
228
|
+
# Who's currently got an active GUI session? - might be
|
229
|
+
# more than one if Fast User Switching is in use.
|
285
230
|
#
|
286
|
-
# @
|
287
|
-
# These two are equivalent:
|
288
|
-
#
|
289
|
-
# JSS::Client.run_jamf "recon", "-assetTag 12345 -department 'IT Support'"
|
290
|
-
#
|
291
|
-
# JSS::Client.run_jamf :recon, ['-assetTag', '12345', '-department', 'IT Support'"]
|
231
|
+
# @return [Array<String>] The current users with GUI sessions
|
292
232
|
#
|
233
|
+
def self.console_users
|
234
|
+
output = `#{CONSOLE_USERS_SCUTIL_CMD}`
|
235
|
+
userlines = output.lines.select { |l| l =~ /SessionUserNameKey\s*:/ }
|
236
|
+
userlines.map! { |ul| ul.split(':').last.strip }
|
237
|
+
userlines.reject { |un| un == ROOT_USER }
|
238
|
+
end
|
239
|
+
|
240
|
+
# Which console user is using the primary GUI console?
|
241
|
+
# Returns nil if the primary GUI console is at the login window.
|
293
242
|
#
|
294
|
-
#
|
295
|
-
#
|
243
|
+
# @return [String,nil] The login name of the user is using the primary
|
244
|
+
# GUI console, or nil if at the login window.
|
296
245
|
#
|
297
|
-
def self.
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
cmd = case args
|
303
|
-
when nil
|
304
|
-
"#{JAMF_BINARY} #{command}"
|
305
|
-
when String
|
306
|
-
"#{JAMF_BINARY} #{command} #{args}"
|
307
|
-
when Array
|
308
|
-
([JAMF_BINARY.to_s, command] + args).join(' ').to_s
|
309
|
-
else
|
310
|
-
raise JSS::InvalidDataError, 'args must be a String or Array of Strings'
|
311
|
-
end # case
|
312
|
-
|
313
|
-
cmd += ' -verbose' if verbose && (!cmd.include? ' -verbose')
|
314
|
-
puts "Running: #{cmd}" if verbose
|
246
|
+
def self.primary_console_user
|
247
|
+
`#{CONSOLE_USERS_SCUTIL_CMD}` =~ /^\s*Name : (\S+)$/
|
248
|
+
Regexp.last_match(1) == LOGINWINDOW_USER ? nil : user
|
249
|
+
end
|
315
250
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
puts line if verbose
|
321
|
-
end
|
322
|
-
end
|
323
|
-
install_out = output.join('')
|
324
|
-
install_out.force_encoding('UTF-8') if install_out.respond_to? :force_encoding
|
325
|
-
install_out
|
326
|
-
end # run_jamf
|
251
|
+
# alias for primary_console_user
|
252
|
+
def self.console_user
|
253
|
+
primary_console_user
|
254
|
+
end
|
327
255
|
|
328
|
-
#
|
329
|
-
#
|
330
|
-
# The first parameter must be a symbol defining what kind of window to display. The options are
|
331
|
-
# - :hud - creates an Apple "Heads Up Display" style window
|
332
|
-
# - :utility or :util - creates an Apple "Utility" style window
|
333
|
-
# - :fs or :full_screen or :fullscreen - creates a full screen window that restricts all user input
|
334
|
-
# WARNING: Remote access must be used to unlock machines in this mode
|
335
|
-
#
|
336
|
-
# The remaining options Hash can contain any of the options listed. See below for descriptions.
|
337
|
-
#
|
338
|
-
# The value returned is the Integer exitstatus/stdout (both are the same) of the jamfHelper command.
|
339
|
-
# The meanings of those integers are:
|
340
|
-
#
|
341
|
-
# - 0 - Button 1 was clicked
|
342
|
-
# - 1 - The Jamf Helper was unable to launch
|
343
|
-
# - 2 - Button 2 was clicked
|
344
|
-
# - 3 - Process was started as a launchd task
|
345
|
-
# - XX1 - Button 1 was clicked with a value of XX seconds selected in the drop-down
|
346
|
-
# - XX2 - Button 2 was clicked with a value of XX seconds selected in the drop-down
|
347
|
-
# - 239 - The exit button was clicked
|
348
|
-
# - 240 - The "ProductVersion" in sw_vers did not return 10.5.X, 10.6.X or 10.7.X
|
349
|
-
# - 243 - The window timed-out with no buttons on the screen
|
350
|
-
# - 250 - Bad "-windowType"
|
351
|
-
# - 254 - Cancel button was select with delay option present
|
352
|
-
# - 255 - No "-windowType"
|
353
|
-
#
|
354
|
-
# If the :abandon_process option is given, the integer returned is the Process ID
|
355
|
-
# of the abondoned process running jamfHelper.
|
356
|
-
#
|
357
|
-
# See also /Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -help
|
358
|
-
#
|
359
|
-
# @note the -startlaunchd and -kill options are not available in this implementation, since
|
360
|
-
# they don't work at the moment (casper 9.4).
|
361
|
-
# -startlaunchd seems to be required to NOT use launchd, and when it's ommited, an error is generated
|
362
|
-
# about the launchd plist permissions being incorrect.
|
363
|
-
#
|
364
|
-
# @param window_type[Symbol] The type of window to display
|
365
|
-
#
|
366
|
-
# @param opts[Hash] the options for the window
|
367
|
-
#
|
368
|
-
# @option opts :window_position [Symbol,nil] one of [ nil, :ul, :ll. :ur, :lr ]
|
369
|
-
# Positions window in the upper right, upper left, lower right or lower left of the user's screen
|
370
|
-
# If no input is given, the window defaults to the center of the screen
|
371
|
-
#
|
372
|
-
# @option opts :title [String]
|
373
|
-
# Sets the window's title to the specified string
|
374
|
-
#
|
375
|
-
# @option opts :heading [String]
|
376
|
-
# Sets the heading of the window to the specified string
|
377
|
-
#
|
378
|
-
# @option opts :align_heading [Symbol] one of [:right, :left, :center, :justified, :natural]
|
379
|
-
# Aligns the heading to the specified alignment
|
380
|
-
#
|
381
|
-
# @option opts :description [String]
|
382
|
-
# Sets the main contents of the window to the specified string
|
383
|
-
#
|
384
|
-
# @option opts :align_description [Symbol] one of [:right, :left, :center, :justified, :natural]
|
385
|
-
# Aligns the description to the specified alignment
|
386
|
-
#
|
387
|
-
# @option opts :icon [String,Pathname]
|
388
|
-
# Sets the windows image field to the image located at the specified path
|
389
|
-
#
|
390
|
-
# @option opts :icon_size [Integer]
|
391
|
-
# Changes the image frame to the specified pixel size
|
392
|
-
#
|
393
|
-
# @option opts :full_screen_icon [any value]
|
394
|
-
# Scales the "icon" to the full size of the window.
|
395
|
-
# Note: Only available in full screen mode
|
256
|
+
# Who's currently running Self Service.app? - might be
|
257
|
+
# more than one if Fast User Switching is in use.
|
396
258
|
#
|
397
|
-
# @
|
398
|
-
# Creates a button with the specified label
|
259
|
+
# @return [Array<String>] The current users running Self Service.app
|
399
260
|
#
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
# Sets the default button of the window to the specified button. The Default Button will respond to "return"
|
405
|
-
#
|
406
|
-
# @option opts :cancel_button [Integer] either 1 or 2
|
407
|
-
# Sets the cancel button of the window to the specified button. The Cancel Button will respond to "escape"
|
408
|
-
#
|
409
|
-
# @option opts :timeout [Integer]
|
410
|
-
# Causes the window to timeout after the specified amount of seconds
|
411
|
-
# Note: The timeout will cause the default button, button 1 or button 2 to be selected (in that order)
|
412
|
-
#
|
413
|
-
# @option opts :show_delay_options [String,Array<Integer>] A String of comma-separated Integers, or an Array of Integers.
|
414
|
-
# Enables the "Delay Options Mode". The window will display a dropdown with the values passed through the string
|
415
|
-
#
|
416
|
-
# @option opts :countdown [any value]
|
417
|
-
# Displays a string notifying the user when the window will time out
|
418
|
-
#
|
419
|
-
# @option opts :align_countdown [Symbol] one of [:right, :left, :center, :justified, :natural]
|
420
|
-
# Aligns the countdown to the specified alignment
|
421
|
-
#
|
422
|
-
# @option opts :lock_hud [Boolean]
|
423
|
-
# Removes the ability to exit the HUD by selecting the close button
|
424
|
-
#
|
425
|
-
# @option opts :abandon_process [Boolean] Abandon the jamfHelper process so that your code can exit.
|
426
|
-
# This is mostly used so that a policy can finish while a dialog is waiting
|
427
|
-
# (possibly forever) for user response. When true, the returned value is the
|
428
|
-
# process id of the abandoned jamfHelper process.
|
429
|
-
#
|
430
|
-
# @option opts :output_file [String, Pathname] Save the output of jamfHelper
|
431
|
-
# (the exit code) into this file. This is useful when using abandon_process.
|
432
|
-
# The output file can be examined later to see what happened. If this option
|
433
|
-
# is not provided, no output is saved.
|
434
|
-
#
|
435
|
-
# @option opts :arg_string [String] The jamfHelper commandline args as a single
|
436
|
-
# String, the way you'd specify them in a shell. This is appended to any
|
437
|
-
# Ruby options provided when calling the method. So calling:
|
438
|
-
# JSS::Client.jamf_helper :hud, title: 'This is a title', arg_string: '-heading "this is a heading"'
|
439
|
-
# will run
|
440
|
-
# jamfHelper -windowType hud -title 'this is a title' -heading "this is a heading"
|
441
|
-
# When using this, be careful not to specify the windowType, since it's generated
|
442
|
-
# by the first, required, parameter of this method.
|
443
|
-
#
|
444
|
-
# @return [Integer] the exit status of the jamfHelper command. See above.
|
445
|
-
#
|
446
|
-
def self.jamf_helper(window_type = :hud, opts = {})
|
447
|
-
raise JSS::UnmanagedError, 'The jamfHelper app is not installed properly on this computer.' unless JAMF_HELPER.executable?
|
448
|
-
|
449
|
-
unless JAMF_HELPER_WINDOW_TYPES.include? window_type
|
450
|
-
raise JSS::InvalidDataError, "The first parameter must be a window type, one of :#{JAMF_HELPER_WINDOW_TYPES.keys.join(', :')}."
|
451
|
-
end
|
452
|
-
|
453
|
-
# start building the arg array
|
454
|
-
|
455
|
-
args = ['-startlaunchd', '-windowType', JAMF_HELPER_WINDOW_TYPES[window_type]]
|
456
|
-
|
457
|
-
opts.keys.each do |opt|
|
458
|
-
case opt
|
459
|
-
when :window_position
|
460
|
-
raise JSS::InvalidDataError, ":window_position must be one of :#{JAMF_HELPER_WINDOW_POSITIONS.join(', :')}." unless \
|
461
|
-
JAMF_HELPER_WINDOW_POSITIONS.include? opts[opt].to_sym
|
462
|
-
args << '-windowPosition'
|
463
|
-
args << opts[opt].to_s
|
464
|
-
|
465
|
-
when :title
|
466
|
-
args << '-title'
|
467
|
-
args << opts[opt].to_s
|
468
|
-
|
469
|
-
when :heading
|
470
|
-
args << '-heading'
|
471
|
-
args << opts[opt].to_s
|
472
|
-
|
473
|
-
when :align_heading
|
474
|
-
raise JSS::InvalidDataError, ":align_heading must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \
|
475
|
-
JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
|
476
|
-
args << '-alignHeading'
|
477
|
-
args << opts[opt].to_s
|
478
|
-
|
479
|
-
when :description
|
480
|
-
args << '-description'
|
481
|
-
args << opts[opt].to_s
|
482
|
-
|
483
|
-
when :align_description
|
484
|
-
raise JSS::InvalidDataError, ":align_description must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \
|
485
|
-
JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
|
486
|
-
args << '-alignDescription'
|
487
|
-
args << opts[opt].to_s
|
488
|
-
|
489
|
-
when :icon
|
490
|
-
args << '-icon'
|
491
|
-
args << opts[opt].to_s
|
492
|
-
|
493
|
-
when :icon_size
|
494
|
-
args << '-iconSize'
|
495
|
-
args << opts[opt].to_s
|
496
|
-
|
497
|
-
when :full_screen_icon
|
498
|
-
args << '-fullScreenIcon'
|
499
|
-
|
500
|
-
when :button1
|
501
|
-
args << '-button1'
|
502
|
-
args << opts[opt].to_s
|
503
|
-
|
504
|
-
when :button2
|
505
|
-
args << '-button2'
|
506
|
-
args << opts[opt].to_s
|
507
|
-
|
508
|
-
when :default_button
|
509
|
-
raise JSS::InvalidDataError, ":default_button must be one of #{JAMF_HELPER_BUTTONS.join(', ')}." unless \
|
510
|
-
JAMF_HELPER_BUTTONS.include? opts[opt]
|
511
|
-
args << '-defaultButton'
|
512
|
-
args << opts[opt].to_s
|
513
|
-
|
514
|
-
when :cancel_button
|
515
|
-
raise JSS::InvalidDataError, ":cancel_button must be one of #{JAMF_HELPER_BUTTONS.join(', ')}." unless \
|
516
|
-
JAMF_HELPER_BUTTONS.include? opts[opt]
|
517
|
-
args << '-cancelButton'
|
518
|
-
args << opts[opt].to_s
|
519
|
-
|
520
|
-
when :timeout
|
521
|
-
args << '-timeout'
|
522
|
-
args << opts[opt].to_s
|
523
|
-
|
524
|
-
when :show_delay_options
|
525
|
-
args << '-showDelayOptions'
|
526
|
-
args << JSS.to_s_and_a(opts[opt])[:arrayform].join(', ')
|
527
|
-
|
528
|
-
when :countdown
|
529
|
-
args << '-countdown' if opts[opt]
|
530
|
-
|
531
|
-
when :align_countdown
|
532
|
-
raise JSS::InvalidDataError, ":align_countdown must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \
|
533
|
-
JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym
|
534
|
-
args << '-alignCountdown'
|
535
|
-
args << opts[opt].to_s
|
536
|
-
|
537
|
-
when :lock_hud
|
538
|
-
args << '-lockHUD' if opts[opt]
|
539
|
-
|
540
|
-
end # case opt
|
541
|
-
end # each do opt
|
542
|
-
|
543
|
-
cmd = Shellwords.escape JAMF_HELPER.to_s
|
544
|
-
args.each { |arg| cmd << " #{Shellwords.escape arg}" }
|
545
|
-
cmd << " #{opts[:arg_string]}" if opts[:arg_string]
|
546
|
-
cmd << " > #{Shellwords.escape opts[:output_file]}" if opts[:output_file]
|
547
|
-
|
548
|
-
if opts[:abandon_process]
|
549
|
-
pid = Process.fork
|
550
|
-
if pid.nil?
|
551
|
-
# In child
|
552
|
-
exec cmd
|
553
|
-
else
|
554
|
-
# In parent
|
555
|
-
Process.detach(pid)
|
556
|
-
pid
|
557
|
-
end
|
558
|
-
else
|
559
|
-
system cmd
|
560
|
-
$CHILD_STATUS.exitstatus
|
561
|
-
end
|
562
|
-
end # def self.jamf_helper
|
261
|
+
def self.self_service_users
|
262
|
+
ss_userlines = `#{PS_USER_COMM}`.lines.select { |l| l.include? SELF_SERVICE_EXECUTABLE_END }
|
263
|
+
ss_userlines.map { |ssl| ssl.split(' ').first }
|
264
|
+
end
|
563
265
|
|
564
266
|
end # class Client
|
565
267
|
|
566
268
|
end # module
|
269
|
+
|
270
|
+
require 'jss/client/jamf_binary'
|
271
|
+
require 'jss/client/jamf_helper'
|
272
|
+
require 'jss/client/management_action'
|