Class: JSS::Client
- Defined in:
- lib/jss.rb,
lib/jss/client.rb,
lib/jss/client/jamf_binary.rb,
lib/jss/client/jamf_helper.rb,
lib/jamf/client/jamf_binary.rb,
lib/jamf/client/jamf_helper.rb,
lib/jss/client/management_action.rb,
lib/jamf/client/management_action.rb
Overview
jamf client computer
Constant Summary collapse
- JAMF_PLIST =
The Pathname to the preferences plist used by the jamf binary
Pathname.new '/Library/Preferences/com.jamfsoftware.jamf.plist'
- JAMF_SUPPORT_FOLDER =
The Pathname to the JAMF support folder
Pathname.new '/Library/Application Support/JAMF'
- RECEIPTS_FOLDER =
The JAMF receipts folder, where package installs are tracked.
JAMF_SUPPORT_FOLDER + 'Receipts'
- DOWNLOADS_FOLDER =
The JAMF downloads folder
JAMF_SUPPORT_FOLDER + 'Downloads'
- SUPPORT_BIN_FOLDER =
The bin folder inside the Jamf support folder
JAMF_SUPPORT_FOLDER + 'bin'
- USR_LOCAL_BIN_FOLDER =
The bin folder with the jamf binary and a few other things
Pathname.new '/usr/local/jamf/bin'
- CONSOLE_USERS_SCUTIL_CMD =
This command gives raw info about console users
'echo "show State:/Users/ConsoleUser" | /usr/sbin/scutil'.freeze
- ROOT_USER =
ignore console user = root (loginwindow)
'root'.freeze
- LOGINWINDOW_USER =
ignore primary console user loginwindow
'loginwindow'.freeze
- SELF_SERVICE_EXECUTABLE_END =
The end of the path to the Self Service Executable. Used to figure out who's running Self Service.app
'/Self Service.app/Contents/MacOS/Self Service'.freeze
- PS_USER_COMM =
the ps command used to figure out who's running Self Service
'ps -A -o user,comm'.freeze
- USER_PREFS_BYHOST_FOLDER =
the path to a users byhost folder from home
'Library/Preferences/ByHost/'
- ORIG_JAMF_BINARY =
The Pathname to the jamf binary executable Before SIP (macOS 10.10 and below)
Pathname.new '/usr/sbin/jamf'
- SIP_JAMF_BINARY =
The Pathname to the jamf binary executable After SIP (OS X 10.11 and above)
USR_LOCAL_BIN_FOLDER + 'jamf'
- JAMF_BINARY =
The path to the jamf binary
SIP_JAMF_BINARY.executable? ? SIP_JAMF_BINARY : ORIG_JAMF_BINARY
- ROOTLESS_JAMF_COMMANDS =
These jamf commands don't need root privs (most do)
%i[ about checkJSSConnection getARDFields getComputerName help listUsers version ].freeze
- JAMF_VERBOSE_OPT =
the option that makes the jamf binary verbose
' -verbose'.freeze
- JAMF_HELPER =
The Pathname to the jamfHelper executable
SUPPORT_BIN_FOLDER + 'jamfHelper.app/Contents/MacOS/jamfHelper'
- JAMF_HELPER_WINDOW_TYPES =
The window_type options for jamfHelper
{ hud: 'hud', utility: 'utility', util: 'utility', full_screen: 'fs', fs: 'fs' }.freeze
- JAMF_HELPER_WINDOW_POSITIONS =
The possible window positions for jamfHelper
[nil, :ul, :ll, :ur, :lr].freeze
- JAMF_HELPER_BUTTONS =
The available buttons in jamfHelper
[1, 2].freeze
- JAMF_HELPER_ALIGNMENTS =
The possible alignment positions in jamfHelper
%i[right left center justified natural].freeze
- MGMT_ACTION =
The Pathname to the Management Action executable
SUPPORT_BIN_FOLDER + 'Management Action.app/Contents/MacOS/Management Action'
Class Method Summary collapse
- .build_jamf_command(command, args) ⇒ Object
-
.console_user ⇒ Object
alias for primary_console_user.
-
.console_users ⇒ Array<String>
Who's currently got an active GUI session? - might be more than one if Fast User Switching is in use.
-
.do_not_disturb?(user = nil) ⇒ Boolean?
Is 'Do Not Disturb' enabled for the user? nil if unknown/not-applicable.
- .execute_jamf(cmd, verbose) ⇒ Object
-
.force_alerts ⇒ Object
Skipping all the force-alerts stuff until we figure out cleaner ways to do it in 10.13+ The plan is to be able to make the NotificationCenter notification be an 'alert' (which stays visible til the user clicks) or a 'banner' (which vanishes in a few seconds), regardless of the user's setting in the NC prefs.
-
.hardware_data ⇒ Hash
The parsed HardwareDataType output from system_profiler.
-
.homedir(user) ⇒ Pathname?
The home dir of the specified user, nil if no homedir in local dscl.
-
.installed? ⇒ Boolean
Is the jamf binary installed?.
-
.jamf_helper(window_type = :hud, opts = {}) ⇒ Integer
A wrapper for the jamfHelper command, which can display a window on the client machine.
-
.jamf_plist ⇒ Hash
The contents of the JAMF plist.
-
.jamf_version ⇒ String?
What version of the jamf binary is installed?.
-
.jss_available? ⇒ Boolean
Is the JSS available right now?.
-
.jss_port ⇒ Integer
The port number for JSS connections for this client.
-
.jss_protocol ⇒ String
The protocol for JSS connections for this client.
-
.jss_record ⇒ JSS::Computer?
The JSS::Computer object for this computer.
-
.jss_server ⇒ String
The JSS server hostname for this client.
-
.jss_url ⇒ String
the URL to the jss for this client.
-
.management_action(msg, title: nil, subtitle: nil, delay: 0) ⇒ Object
class Methods.
-
.my_ip_address ⇒ String
Get the current IP address as a String.
-
.nc_notify(msg, title: nil, subtitle: nil, delay: 0) ⇒ Object
an alias of management_action.
-
.primary_console_user ⇒ String?
Which console user is using the primary GUI console? Returns nil if the primary GUI console is at the login window.
-
.receipts ⇒ Array<Pathname>
All the JAMF receipts on this client.
- .restore_alerts(orig_flags) ⇒ Object
-
.run_jamf(command, args = nil, verbose = false) ⇒ String
Run an arbitrary jamf binary command.
-
.self_service_users ⇒ Array<String>
Who's currently running Self Service.app? - might be more than one if Fast User Switching is in use.
-
.serial_number ⇒ String
The serial number for this computer via system_profiler.
-
.set_mgmt_action_ncprefs_flags(user, flags, hup: true) ⇒ Integer
set the NotificationCenter option flags for a user flags = an integer.
-
.udid ⇒ String
The UUID for this computer via system_profiler.
Class Method Details
.build_jamf_command(command, args) ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/jss/client/jamf_binary.rb', line 102 def self.build_jamf_command(command, args) case args when nil "#{JAMF_BINARY} #{command}" when String "#{JAMF_BINARY} #{command} #{args}" when Array ([JAMF_BINARY.to_s, command] + args).join(' ') else raise JSS::InvalidDataError, 'args must be a String or Array of Strings' end # case end |
.console_user ⇒ Object
alias for primary_console_user
256 257 258 |
# File 'lib/jss/client.rb', line 256 def self.console_user primary_console_user end |
.console_users ⇒ Array<String>
Who's currently got an active GUI session? - might be more than one if Fast User Switching is in use.
236 237 238 239 240 241 |
# File 'lib/jss/client.rb', line 236 def self.console_users output = `#{CONSOLE_USERS_SCUTIL_CMD}` userlines = output.lines.select { |l| l =~ /SessionUserNameKey\s*:/ } userlines.map! { |ul| ul.split(':').last.strip } userlines.reject { |un| un == ROOT_USER } end |
.do_not_disturb?(user = nil) ⇒ Boolean?
Returns Is 'Do Not Disturb' enabled for the user? nil if unknown/not-applicable.
275 276 277 278 279 280 281 |
# File 'lib/jss/client.rb', line 275 def self.do_not_disturb?(user = nil) home = user ? homedir(user) : Dir.home myudid = udid nc_prefs_file = Pathname.new "#{home}/#{USER_PREFS_BYHOST_FOLDER}/com.apple.notificationcenterui.#{myudid}.plist" return nil unless nc_prefs_file.readable? JSS.parse_plist(nc_prefs_file)['doNotDisturb'] end |
.execute_jamf(cmd, verbose) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/jss/client/jamf_binary.rb', line 115 def self.execute_jamf(cmd, verbose) puts "Running: #{cmd}" if verbose output = '' IO.popen("#{cmd} 2>&1") do |proc| loop do line = proc.gets break unless line output << line puts line if verbose end end output.force_encoding('UTF-8') output end |
.force_alerts ⇒ Object
Skipping all the force-alerts stuff until we figure out cleaner ways to do it in 10.13+ The plan is to be able to make the NotificationCenter notification be an 'alert' (which stays visible til the user clicks) or a 'banner' (which vanishes in a few seconds), regardless of the user's setting in the NC prefs.
71 72 73 74 75 76 77 78 79 |
# File 'lib/jss/client/management_action.rb', line 71 def self.force_alerts orig_flags = {} console_users.each do |user| orig_flags[user] = set_mgmt_action_ncprefs_flags user, NC_ALERT_STYLE_FLAGS, hup: false end system HUP_NOTIF_CTR_CMD unless orig_flags.empty? sleep 1 orig_flags end |
.hardware_data ⇒ Hash
The parsed HardwareDataType output from system_profiler
226 227 228 229 |
# File 'lib/jss/client.rb', line 226 def self.hardware_data raw = `/usr/sbin/system_profiler SPHardwareDataType -xml 2>/dev/null` JSS.parse_plist(raw)[0]['_items'][0] end |
.homedir(user) ⇒ Pathname?
The home dir of the specified user, nil if no homedir in local dscl.
290 291 292 293 |
# File 'lib/jss/client.rb', line 290 def self.homedir(user) dir = `/usr/bin/dscl . -read /Users/#{user} NFSHomeDirectory 2>/dev/null`.chomp.split(': ').last dir ? Pathname.new(dir) : nil end |
.installed? ⇒ Boolean
Is the jamf binary installed?
115 116 117 |
# File 'lib/jss/client.rb', line 115 def self.installed? JAMF_BINARY.executable? end |
.jamf_helper(window_type = :hud, opts = {}) ⇒ Integer
the -startlaunchd and -kill options are not available in this implementation, since they don't work at the moment (casper 9.4). -startlaunchd seems to be required to NOT use launchd, and when it's ommited, an error is generated about the launchd plist permissions being incorrect.
A wrapper for the jamfHelper command, which can display a window on the client machine.
The first parameter must be a symbol defining what kind of window to display. The options are
-
:hud - creates an Apple “Heads Up Display” style window
-
:utility or :util - creates an Apple “Utility” style window
-
:fs or :full_screen or :fullscreen - creates a full screen window that restricts all user input WARNING: Remote access must be used to unlock machines in this mode
The remaining options Hash can contain any of the options listed. See below for descriptions.
The value returned is the Integer exitstatus/stdout (both are the same) of the jamfHelper command. The meanings of those integers are:
-
0 - Button 1 was clicked
-
1 - The Jamf Helper was unable to launch
-
2 - Button 2 was clicked
-
3 - Process was started as a launchd task
-
XX1 - Button 1 was clicked with a value of XX seconds selected in the drop-down
-
XX2 - Button 2 was clicked with a value of XX seconds selected in the drop-down
-
239 - The exit button was clicked
-
240 - The “ProductVersion” in sw_vers did not return 10.5.X, 10.6.X or 10.7.X
-
243 - The window timed-out with no buttons on the screen
-
250 - Bad “-windowType”
-
254 - Cancel button was select with delay option present
-
255 - No “-windowType”
If the :abandon_process option is given, the integer returned is the Process ID of the abondoned process running jamfHelper.
See also /Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -help
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/jss/client/jamf_helper.rb', line 178 def self.jamf_helper(window_type = :hud, opts = {}) raise JSS::UnmanagedError, 'The jamfHelper app is not installed properly on this computer.' unless JAMF_HELPER.executable? unless JAMF_HELPER_WINDOW_TYPES.include? window_type raise JSS::InvalidDataError, "The first parameter must be a window type, one of :#{JAMF_HELPER_WINDOW_TYPES.keys.join(', :')}." end # start building the arg array args = ['-startlaunchd', '-windowType', JAMF_HELPER_WINDOW_TYPES[window_type]] opts.keys.each do |opt| case opt when :window_position raise JSS::InvalidDataError, ":window_position must be one of :#{JAMF_HELPER_WINDOW_POSITIONS.join(', :')}." unless \ JAMF_HELPER_WINDOW_POSITIONS.include? opts[opt].to_sym args << '-windowPosition' args << opts[opt].to_s when :title args << '-title' args << opts[opt].to_s when :heading args << '-heading' args << opts[opt].to_s when :align_heading raise JSS::InvalidDataError, ":align_heading must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \ JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym args << '-alignHeading' args << opts[opt].to_s when :description args << '-description' args << opts[opt].to_s when :align_description raise JSS::InvalidDataError, ":align_description must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \ JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym args << '-alignDescription' args << opts[opt].to_s when :icon args << '-icon' args << opts[opt].to_s when :icon_size args << '-iconSize' args << opts[opt].to_s when :full_screen_icon args << '-fullScreenIcon' when :button1 args << '-button1' args << opts[opt].to_s when :button2 args << '-button2' args << opts[opt].to_s when :default_button raise JSS::InvalidDataError, ":default_button must be one of #{JAMF_HELPER_BUTTONS.join(', ')}." unless \ JAMF_HELPER_BUTTONS.include? opts[opt] args << '-defaultButton' args << opts[opt].to_s when :cancel_button raise JSS::InvalidDataError, ":cancel_button must be one of #{JAMF_HELPER_BUTTONS.join(', ')}." unless \ JAMF_HELPER_BUTTONS.include? opts[opt] args << '-cancelButton' args << opts[opt].to_s when :timeout args << '-timeout' args << opts[opt].to_s when :show_delay_options args << '-showDelayOptions' args << JSS.to_s_and_a(opts[opt])[:arrayform].join(', ') when :countdown args << '-countdown' if opts[opt] when :align_countdown raise JSS::InvalidDataError, ":align_countdown must be one of :#{JAMF_HELPER_ALIGNMENTS.join(', :')}." unless \ JAMF_HELPER_ALIGNMENTS.include? opts[opt].to_sym args << '-alignCountdown' args << opts[opt].to_s when :lock_hud args << '-lockHUD' if opts[opt] end # case opt end # each do opt cmd = Shellwords.escape JAMF_HELPER.to_s args.each { |arg| cmd << " #{Shellwords.escape arg}" } cmd << " #{opts[:arg_string]}" if opts[:arg_string] cmd << " > #{Shellwords.escape opts[:output_file]}" if opts[:output_file] if opts[:abandon_process] pid = Process.fork if pid.nil? # In child exec cmd else # In parent Process.detach(pid) pid end else system cmd $CHILD_STATUS.exitstatus end end |
.jamf_plist ⇒ Hash
The contents of the JAMF plist
an empty hash if not
173 174 175 176 |
# File 'lib/jss/client.rb', line 173 def self.jamf_plist return {} unless JAMF_PLIST.file? JSS.parse_plist JAMF_PLIST end |
.jamf_version ⇒ String?
What version of the jamf binary is installed?
123 124 125 |
# File 'lib/jss/client.rb', line 123 def self.jamf_version installed? ? run_jamf(:version).chomp.split('=')[1] : nil end |
.jss_available? ⇒ Boolean
Is the JSS available right now?
191 192 193 194 |
# File 'lib/jss/client.rb', line 191 def self.jss_available? run_jamf :checkJSSConnection, '-retry 1' $CHILD_STATUS.exitstatus.zero? end |
.jss_port ⇒ Integer
The port number for JSS connections for this client
163 164 165 166 |
# File 'lib/jss/client.rb', line 163 def self.jss_port jss_url @port end |
.jss_protocol ⇒ String
The protocol for JSS connections for this client
154 155 156 157 |
# File 'lib/jss/client.rb', line 154 def self.jss_protocol jss_url @protocol end |
.jss_record ⇒ JSS::Computer?
The JSS::Computer object for this computer
200 201 202 203 204 |
# File 'lib/jss/client.rb', line 200 def self.jss_record JSS::Computer.fetch udid: udid rescue JSS::NoSuchItemError nil end |
.jss_server ⇒ String
The JSS server hostname for this client
145 146 147 148 |
# File 'lib/jss/client.rb', line 145 def self.jss_server jss_url @server end |
.jss_url ⇒ String
the URL to the jss for this client
131 132 133 134 135 136 137 138 139 |
# File 'lib/jss/client.rb', line 131 def self.jss_url @url = jamf_plist['jss_url'] return nil if @url.nil? @url =~ %r{(https?)://(.+):(\d+)/} @protocol = Regexp.last_match(1) @server = Regexp.last_match(2) @port = Regexp.last_match(3) @url end |
.management_action(msg, title: nil, subtitle: nil, delay: 0) ⇒ Object
class Methods
46 47 48 49 50 51 52 53 54 55 |
# File 'lib/jss/client/management_action.rb', line 46 def self.management_action(msg, title: nil, subtitle: nil, delay: 0) raise JSS::InvalidDataError, 'delay: must be a non-negative integer.' unless delay.is_a?(Integer) && delay > -1 cmd = Shellwords.escape MGMT_ACTION.to_s cmd << " -message #{Shellwords.escape msg.to_s}" cmd << " -title #{Shellwords.escape title.to_s}" if title cmd << " -subtitle #{Shellwords.escape subtitle.to_s}" if subtitle cmd << " -deliverydelay #{Shellwords.escape delay}" if delay > 0 `#{cmd} 2>&1` end |
.my_ip_address ⇒ String
Get the current IP address as a String.
This handy code doesn't acutally make a UDP connection, it just starts to set up the connection, then uses that to get the local IP.
Lifted gratefully from coderrr.wordpress.com/2008/05/28/get-your-local-ip-address/
97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/jss/client.rb', line 97 def self.my_ip_address # turn off reverse DNS resolution temporarily # @note the 'socket' library has already been required by 'rest-client' orig = Socket.do_not_reverse_lookup Socket.do_not_reverse_lookup = true UDPSocket.open do |s| s.connect '192.168.0.0', 1 s.addr.last end ensure Socket.do_not_reverse_lookup = orig end |
.nc_notify(msg, title: nil, subtitle: nil, delay: 0) ⇒ Object
an alias of management_action
58 59 60 |
# File 'lib/jss/client/management_action.rb', line 58 def self.nc_notify(msg, title: nil, subtitle: nil, delay: 0) management_action(msg, title: title, subtitle: subtitle, delay: delay) end |
.primary_console_user ⇒ String?
Which console user is using the primary GUI console? Returns nil if the primary GUI console is at the login window.
249 250 251 252 253 |
# File 'lib/jss/client.rb', line 249 def self.primary_console_user `#{CONSOLE_USERS_SCUTIL_CMD}` =~ /^\s*Name : (\S+)$/ user = Regexp.last_match(1) user == LOGINWINDOW_USER ? nil : user end |
.receipts ⇒ Array<Pathname>
All the JAMF receipts on this client
182 183 184 185 |
# File 'lib/jss/client.rb', line 182 def self.receipts raise JSS::NoSuchItemError, "The JAMF Receipts folder doesn't exist on this computer." unless RECEIPTS_FOLDER.exist? RECEIPTS_FOLDER.children.select(&:file?) end |
.restore_alerts(orig_flags) ⇒ Object
81 82 83 84 85 86 |
# File 'lib/jss/client/management_action.rb', line 81 def self.restore_alerts(orig_flags) orig_flags.each do |user, flags| set_mgmt_action_ncprefs_flags user, flags, hup: false end system HUP_NOTIF_CTR_CMD unless orig_flags.empty? end |
.run_jamf(command, args = nil, verbose = false) ⇒ String
Most jamf commands require superuser/root privileges.
Run an arbitrary jamf binary command.
The details of the Process::Status for the jamf binary process can be captured from $CHILD_STATUS immediately after calling. (See Process::Status)
90 91 92 93 94 95 96 97 98 |
# File 'lib/jss/client/jamf_binary.rb', line 90 def self.run_jamf(command, args = nil, verbose = false) raise JSS::UnmanagedError, 'The jamf binary is not installed on this computer.' unless installed? unless ROOTLESS_JAMF_COMMANDS.include?(command.to_sym) || JSS.superuser? raise JSS::UnsupportedError, 'You must have root privileges to run that jamf binary command' end cmd = build_jamf_command command, args cmd += " #{JAMF_VERBOSE_OPT}" if verbose && !cmd.include?(JAMF_VERBOSE_OPT) execute_jamf cmd, verbose end |
.self_service_users ⇒ Array<String>
Who's currently running Self Service.app? - might be more than one if Fast User Switching is in use.
265 266 267 268 |
# File 'lib/jss/client.rb', line 265 def self.self_service_users ss_userlines = `#{PS_USER_COMM}`.lines.select { |l| l.include? SELF_SERVICE_EXECUTABLE_END } ss_userlines.map { |ssl| ssl.split(' ').first } end |
.serial_number ⇒ String
The serial number for this computer via system_profiler
218 219 220 |
# File 'lib/jss/client.rb', line 218 def self.serial_number hardware_data['serial_number'] end |
.set_mgmt_action_ncprefs_flags(user, flags, hup: true) ⇒ Integer
set the NotificationCenter option flags for a user flags = an integer.
Doesn't seem to work in 10.13, so ignore this for now.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/jss/client/management_action.rb', line 95 def self.set_mgmt_action_ncprefs_flags(user, flags, hup: true) plist = Pathname.new "/Users/#{user}/Library/Preferences/#{NCPREFS_DOMAIN}.plist" prefs = JSS.parse_plist plist mgmt_action_setting = prefs['apps'].select { |a| a['bundle-id'] == MGMT_ACTION_BUNDLE_ID }.first if mgmt_action_setting orig_flags = mgmt_action_setting['flags'] mgmt_action_setting['flags'] = flags else orig_flags = flags prefs['apps'] << { 'bundle-id' => MGMT_ACTION_BUNDLE_ID, 'flags' => flags } end plist.open('w') { |f| f.write JSS.xml_plist_from(prefs) } system HUP_NOTIF_CTR_CMD if hup orig_flags end |
.udid ⇒ String
The UUID for this computer via system_profiler
210 211 212 |
# File 'lib/jss/client.rb', line 210 def self.udid hardware_data['platform_UUID'] end |