Class: Jamf::Client

Inherits:
Object show all
Defined in:
lib/jamf/client.rb

Overview

This class represents a Jamf/JSS Client computer, on which this code is running.

Since the class represents the current machine, there's no need to make an instance of it, all methods are class methods.

At the moment, only Macintosh computers are supported.

TODO: convert this to a module, since that's how it's used.

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/'
POLICY_SCRIPT_CMD_RE =

If some processs C has a parent process P whose command (via ps -o comm) matches this, then process C is being run by Jamf

%r{sh -c PATH=\$PATH:/usr/local/jamf/bin; '/Library/Application Support/JAMF/tmp/.* >& '/Library/Application Support/JAMF/tmp/\d+.tmp}.freeze
POLICY_CMD_RE =

If some processs C has a parent process P whose command (via ps -o comm) matching POLICY_SCRIPT_CMD_RE, and process P has a parent process G that matches this (C is grandchild of G), then process C is being run by a Jamf policy

%r{/bin/jamf policy }.freeze

Class Method Summary collapse

Class Method Details

.console_userObject

alias for primary_console_user



263
264
265
# File 'lib/jamf/client.rb', line 263

def self.console_user
  primary_console_user
end

.console_usersArray<String>

Who's currently got an active GUI session? - might be more than one if Fast User Switching is in use.

Returns:

  • (Array<String>)

    The current users with GUI sessions



243
244
245
246
247
248
# File 'lib/jamf/client.rb', line 243

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.

Parameters:

  • user (String, nil) (defaults to: nil)

    The user to query, the current user if nil.

Returns:

  • (Boolean, nil)

    Is 'Do Not Disturb' enabled for the user? nil if unknown/not-applicable



282
283
284
285
286
287
288
# File 'lib/jamf/client.rb', line 282

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?
  Jamf.parse_plist(nc_prefs_file)['doNotDisturb']
end

.hardware_dataHash

The parsed HardwareDataType output from system_profiler

Returns:

  • (Hash)

    the HardwareDataType data from the system_profiler command



233
234
235
236
# File 'lib/jamf/client.rb', line 233

def self.hardware_data
  raw = `/usr/sbin/system_profiler SPHardwareDataType -xml 2>/dev/null`
  Jamf.parse_plist(raw)[0]['_items'][0]
end

.homedir(user) ⇒ Pathname?

The home dir of the specified user, nil if no homedir in local dscl.

Parameters:

  • user (String)

    the user whose homedir to look up

Returns:

  • (Pathname, nil)

    The user's homedir or nil if no such user



297
298
299
300
# File 'lib/jamf/client.rb', line 297

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?

Returns:

  • (Boolean)

    is the jamf binary installed?



122
123
124
# File 'lib/jamf/client.rb', line 122

def self.installed?
  JAMF_BINARY.executable?
end

.jamf_plistHash

The contents of the JAMF plist

an empty hash if not

Returns:

  • (Hash)

    the parsed contents of the JAMF_PLIST if it exists,



180
181
182
183
# File 'lib/jamf/client.rb', line 180

def self.jamf_plist
  return {} unless JAMF_PLIST.file?
  Jamf.parse_plist JAMF_PLIST
end

.jamf_versionString?

What version of the jamf binary is installed?

Returns:

  • (String, nil)

    the version of the jamf binary installed on this client, nil if not installed



130
131
132
# File 'lib/jamf/client.rb', line 130

def self.jamf_version
  installed? ? run_jamf(:version).chomp.split('=')[1] : nil
end

.jss_available?Boolean

Is the JSS available right now?

Returns:

  • (Boolean)

    is the JSS available now?



198
199
200
201
# File 'lib/jamf/client.rb', line 198

def self.jss_available?
  run_jamf :checkJSSConnection, '-retry 1'
  $CHILD_STATUS.exitstatus.zero?
end

.jss_portInteger

The port number for JSS connections for this client

Returns:

  • (Integer)

    the port to the JSS for this client



170
171
172
173
# File 'lib/jamf/client.rb', line 170

def self.jss_port
  jss_url
  @port
end

.jss_protocolString

The protocol for JSS connections for this client

Returns:

  • (String)

    the protocol to the JSS for this client, “http” or “https”



161
162
163
164
# File 'lib/jamf/client.rb', line 161

def self.jss_protocol
  jss_url
  @protocol
end

.jss_recordJSS::Computer?

The JSS::Computer object for this computer

Returns:

  • (JSS::Computer, nil)

    The JSS record for this computer, nil if not in the JSS



207
208
209
210
211
# File 'lib/jamf/client.rb', line 207

def self.jss_record
  JSS::Computer.fetch udid: udid
rescue JSS::NoSuchItemError
  nil
end

.jss_serverString

The JSS server hostname for this client

Returns:

  • (String)

    the JSS server for this client



152
153
154
155
# File 'lib/jamf/client.rb', line 152

def self.jss_server
  jss_url
  @server
end

.jss_urlString

the URL to the jss for this client

Returns:

  • (String)

    the url to the JSS for this client



138
139
140
141
142
143
144
145
146
# File 'lib/jamf/client.rb', line 138

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

.my_ip_addressString

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/

Returns:

  • (String)

    the current IP address.



104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/jamf/client.rb', line 104

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

.parent_pid_user_and_cmd(pid, ps_lines = nil) ⇒ Array<Integer, String, String>

given a pid and optionally the output of `ps -o pid -o ppid -o user -o command` return an array with the pid, user, and command of the pid's parent process

Parameters:

  • pid (Integer, String)

    the process id for which we want parent info

  • ps_lines (Array<String>) (defaults to: nil)

    the lines of output from `ps -o pid -o ppid -o user -o command` possibly with other options.

    If omitted, `ps -a -x -o pid -o ppid -o user -o command` will be used
    

Returns:

  • (Array<Integer, String, String>)

    the pid, user, and commandline of the parent process of the given pid. All will be nil if not found



340
341
342
343
344
345
346
347
348
# File 'lib/jamf/client.rb', line 340

def self.parent_pid_user_and_cmd(pid, ps_lines = nil)
  ps_lines ||= `ps -a -x -o pid -o ppid -o user -o command`.lines

  parent_ps_line = ps_lines.select { |l| l =~ /^\s*#{pid}\s/ }.first
  return [nil, nil, nil] unless parent_ps_line

  parent_ps_line =~ /^\s*\d+\s+(\d+)\s+(\S+)\s+(.*)$/
  [Regexp.last_match(1).to_i, Regexp.last_match(2), Regexp.last_match(3)]
end

.primary_console_userString?

Which console user is using the primary GUI console? Returns nil if the primary GUI console is at the login window.

Returns:

  • (String, nil)

    The login name of the user is using the primary GUI console, or nil if at the login window.



256
257
258
259
260
# File 'lib/jamf/client.rb', line 256

def self.primary_console_user
  `#{CONSOLE_USERS_SCUTIL_CMD}` =~ /^\s*Name : (\S+)$/
  user = Regexp.last_match(1)
  user == LOGINWINDOW_USER ? nil : user
end

.receiptsArray<Pathname>

All the JAMF receipts on this client

Returns:

  • (Array<Pathname>)

    an array of Pathnames for all regular files in the jamf receipts folder

Raises:



189
190
191
192
# File 'lib/jamf/client.rb', line 189

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

.script_running_via_policy?Boolean

Search up the process lineage to see if any ancestor processes indicate that the current process is being run by a Jamf Policy.

Returns:

  • (Boolean)

    Is the current process being run as a script by a Jamf Policy?



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/jamf/client.rb', line 307

def self.script_running_via_policy?
  root_ps_lines = `ps -u root -x -o pid -o ppid -o user -o command`.lines

  parent_pid, _parent_user, parent_command = parent_pid_user_and_cmd Process.pid, root_ps_lines
  return false unless parent_pid

  until parent_command =~ POLICY_SCRIPT_CMD_RE || parent_pid.nil?
    parent_pid, _parent_user, parent_command = parent_pid_user_and_cmd parent_pid, root_ps_lines
    return false if parent_pid.zero?
  end
  return false if parent_pid.nil?

  # if we're here, our parent is a jamf process, lets confirm that its
  # a jamf policy
  until parent_command =~ POLICY_CMD_RE || parent_pid.nil?
    parent_pid, _parent_user, parent_command = parent_pid_user_and_cmd parent_pid, root_ps_lines
    return false if parent_pid.zero?
  end
  !parent_pid.nil?
end

.self_service_usersArray<String>

Who's currently running Self Service.app? - might be more than one if Fast User Switching is in use.

Returns:

  • (Array<String>)

    The current users running Self Service.app



272
273
274
275
# File 'lib/jamf/client.rb', line 272

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_numberString

The serial number for this computer via system_profiler

Returns:

  • (String)

    the serial number for this computer



225
226
227
# File 'lib/jamf/client.rb', line 225

def self.serial_number
  hardware_data['serial_number']
end

.udidString

The UUID for this computer via system_profiler

Returns:

  • (String)

    the UUID/UDID for this computer



217
218
219
# File 'lib/jamf/client.rb', line 217

def self.udid
  hardware_data['platform_UUID']
end