Files
MacAdmin/Software/Install-BomgarJumpClient.py
Zack T 9aa52432a4 v1.3.0 = Cleaned up output
+ Fixed a undeclared variable causing a trace back (likely a hasty copy and paste)
+ Modified the `Results:` output as well
2020-10-01 10:47:23 -07:00

314 lines
12 KiB
Python

#!/usr/bin/env python
"""
Script Name: Install-BomgarJumpClient.py
By: Zack Thompson / Created: 3/2/2020
Version: 1.3.0 / Updated: 6/24/2020 / By: ZT
Description: Installs a Bomgar Jump Client with the passed parameters
"""
import argparse
import objc
import os
import plistlib
import re
import shlex
import subprocess
import sys
import urllib
from Cocoa import NSBundle
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
from xml.etree import ElementTree
def runUtility(command):
"""A helper function for subprocess.
Args:
command: Must be a string.
Returns:
Results in a dictionary.
"""
# Validate that command is a string
if not isinstance(command, str):
raise TypeError('Command must be in a str')
command = shlex.split(command)
process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False )
(stdout, stderr) = process.communicate()
result_dict = {
"stdout": (stdout).strip(),
"stderr": (stderr).strip() if stderr != None else None,
"status": process.returncode,
"success": True if process.returncode == 0 else False
}
return result_dict
# Credit to (Mikey Mike/Froger/Pudquick/etc) for this logic: https://gist.github.com/pudquick/c7dd1262bd81a32663f0
def get_system(attribute):
"""A helper function to get specific system attributes.
Args:
attribute: The system attribute desired.
Returns:
stdout: The system attribute value.
"""
IOKit_bundle = NSBundle.bundleWithIdentifier_('com.apple.framework.IOKit')
functions = [("IOServiceGetMatchingService", b"II@"), ("IOServiceMatching", b"@*"), ("IORegistryEntryCreateCFProperty", b"@I@@I"),]
objc.loadBundleFunctions(IOKit_bundle, globals(), functions)
def io_key(keyname):
return IORegistryEntryCreateCFProperty(IOServiceGetMatchingService(0, IOServiceMatching("IOPlatformExpertDevice".encode("utf-8"))), keyname, None, 0)
def get_hardware_uuid():
return io_key("IOPlatformUUID".encode("utf-8"))
def get_hardware_serial():
return io_key("IOPlatformSerialNumber".encode("utf-8"))
# def get_board_id():
# return str(io_key("board-id".encode("utf-8"))).rstrip('\x00')
options = {'serial' : get_hardware_serial #,
# 'uuid' : get_hardware_uuid,
# 'boardID' : get_board_id
}
return options[attribute]()
def get_model(serial_number):
"""A helper function to get the friendly model.
Args:
serial_number: Devices' Serial Number.
Returns:
stdout: friendly model name or "".
"""
if len(serial_number) == 12:
lookup_code = serial_number[-4:]
elif len(serial_number) == 11:
lookup_code = serial_number[-3:]
else:
print("Unexpected serial number length: {}".format(serial_number))
return ""
lookup_url = "https://support-sp.apple.com/sp/product?cc={lookup_code}".format(lookup_code=lookup_code)
xml = urllib.urlopen(lookup_url).read()
try:
tree = ElementTree.fromstringlist(xml)
model_friendly = tree.find('.//configCode').text
return model_friendly
except ElementTree.ParseError as err:
print("Failed to retrieve model name: {}".format(err.strerror))
return ""
def mount(pathname):
"""A helper function to mount a volume and return the mount path.
Args:
pathname: Path to a dmg to mount.
Returns:
stdout: Returns the path to the mounted volume.
"""
mount_cmd = "/usr/bin/hdiutil attach -plist -mountrandom /private/tmp -nobrowse {}".format(pathname)
# print("mount_cmd: {}".format(mount_cmd))
results = runUtility(mount_cmd)
if not results['success']:
print("ERROR: failed to mount: {}".format(pathname))
print(results['stdout'])
print(results['stderr'])
sys.exit(2)
# Read output plist.
xml = plistlib.readPlistFromString(results['stdout'])
# xml = plistlib.readPlistFromString(pliststr.encode()) # Python3
# Find mount point.
for part in xml.get("system-entities", []):
if "mount-point" in part:
# print("mount_point: {}".format(part["mount-point"]))
return part["mount-point"]
print("mounting {} failed: unexpected output from hdiutil".format(pathname))
def main():
print('\n***** install_Bomgar process: START *****\n')
# Reset sys.argv to reformat parameters being passed from Jamf Pro.
orig_argv = sys.argv
# print('original_paramters: {}'.format(orig_argv))
sys.argv = []
# sys.argv.append(orig_argv[0])
for arg in orig_argv[1:]:
if len(arg) != 0:
sys.argv.extend(arg.split(" ", 1))
print('paramters: {}\n'.format(sys.argv))
##################################################
# Define Script Parameters
parser = argparse.ArgumentParser(description="This script installs a Bomgar Jump Client with the passed parameters.")
parser.add_argument('--key', '-k', help='Sets the Jump Client Key.', required=True)
parser.add_argument('--group', '-g', help='Sets the Jump Client Group. You must pass the Jump Group "code_name".', required=False)
parser.add_argument('--tag', '-t', help='Sets the Jump Client Tag.', required=False)
parser.add_argument('--name', '-a', help='Sets the Jump Client Name. default value: <Full Name>, <Username>', required=False)
parser.add_argument('--comments', '-c', help='Sets the Jump Client Comments. default value: <Friendly Model Name>, <Serial Number>', required=False)
parser.add_argument('--site', '-s', default="bomgar.company.org", help='Associates the Jump Client with the public portal which has the given hostname as a site address. default value: bomgar.company.org', required=False)
parser.add_argument('--policy-present', '-p', help='Policy that controls the permission policy during a support session if the customer is present at the console. You must pass the Policy\'s "code_name".', required=False)
# parser.add_argument('--policy-not-present', '-n', default="Policy-Unattended-Jump", help='Policy that controls the permission policy during a support session if the customer is not present at the console. You must pass the Policy's "code_name".', required=False)
parser.add_argument('--policy-not-present', '-n', help='Policy that controls the permission policy during a support session if the customer is not present at the console. You must pass the Policy\'s "code_name".', required=False)
args, unknown = parser.parse_known_args(sys.argv)
# print("parsed_parameters: {}".format(args))
# sys.exit(0)
# Set the Jump Client Key
jumpKey = args.key
# Set the Jump Client Group
if args.tag != None:
jumpGroup = "--jc-jump-group 'jumpgroup:{}'".format(args.group)
else:
jumpGroup = ""
# Set the Jump Client Tag
if args.tag != None:
jumpTag = "--jc-tag '{}'".format(args.tag)
else:
jumpTag = ""
# Set the Jump Client Name
if args.name != None:
jumpName = "--jc-name '{}'".format(args.name)
else:
# Get the Console User
username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]
console_user = [username,""][username in [u"loginwindow", None, u""]]
# Verify that a console user was present
if console_user:
# Get the Console Users' Full Name
full_name_cmd = "/usr/bin/dscl . -read \"/Users/{console_user}\" dsAttrTypeStandard:RealName".format(console_user=console_user)
full_name_results = runUtility(full_name_cmd)
if full_name_results['success']:
full_name = re.sub("RealName:\s+", "", full_name_results['stdout'])
jumpName = "--jc-name '{full_name} ({console_user})'".format(full_name=full_name, console_user=console_user)
else:
# In case something goes wrong with dscl, set to none
jumpName = ""
else:
# If a console user was not present, set to none
jumpName = ""
# Set the Jump Client Comments
if args.comments == None:
serial_number = get_system("serial")
model_friendly = get_model(serial_number)
jumpComments = "--jc-comments '{model_friendly}, {serial_number}'".format(model_friendly=model_friendly, serial_number=serial_number)
else:
jumpComments = "--jc-comments '{}'".format(args.comments)
# Set the Jump Client Site
if args.site != None:
jumpSite = "--jc-public-site-address '{}'".format(args.site)
else:
jumpSite = ""
# Set the Jump Client Console User Not Present Policy
if args.policy_not_present != None:
jumpPolicyNotPresent = "--jc-session-policy-not-present '{}'".format(args.policy_not_present)
else:
jumpPolicyNotPresent = ""
# Set the Jump Client Console User Present Policy
if args.policy_present != None:
jumpPolicyPresent = "--jc-session-policy-present '{}'".format(args.policy_present)
else:
jumpPolicyPresent = ""
##################################################
# Define Variables
parameters = [ jumpGroup, jumpSite, jumpPolicyNotPresent, jumpTag, jumpName, jumpComments, jumpPolicyPresent ]
install_parameters = " ".join( filter( None, parameters ) )
bomgar_dmg = ""
mount_point = ""
install_app = ""
##################################################
# Bits staged...
for a_file in os.listdir("/private/tmp"):
if re.search(r'(bomgar-scc-)[a-z0-9]+[.](dmg)', a_file):
bomgar_installer = os.path.join("/private/tmp", a_file)
os.rename(bomgar_installer, "/tmp/bomgar-scc-{}.dmg".format(jumpKey))
bomgar_dmg = "/tmp/bomgar-scc-{}.dmg".format(jumpKey)
break
if os.path.exists(bomgar_dmg):
mount_point = mount(bomgar_dmg)
else:
print("ERROR: Bomgar DMG was not found at the expected location!")
print('***** install_Bomgar process: FAILED *****')
sys.exit(1)
# print("mount_point: {}".format(mount_point))
try:
for a_file in os.listdir(mount_point):
if re.search(r'.+[.](app)', a_file):
install_app = os.path.join(mount_point, a_file)
break
# print("install_app: {}".format(install_app))
if os.path.exists(install_app):
# Build the command.
install_cmd = "'{install_app}/Contents/MacOS/sdcust' --silent {install_parameters}".format(install_app=install_app, install_parameters=install_parameters)
print("install_cmd: {}".format(install_cmd))
results = runUtility(install_cmd)
if not results['success']:
print("ERROR: failed to install Bomgar Jump Client")
print("Results: {}".format(results['stderr']))
sys.exit(2)
# print("Results: {}".format(results['stdout']))
else:
print("ERROR: Bomgar Jump Client installer was not found at the expected location!")
print('***** install_Bomgar process: FAILED *****')
sys.exit(2)
print('***** install_Bomgar process: SUCCESS *****')
finally:
unmount_cmd = "/usr/bin/hdiutil detach {mount_point}".format(mount_point=mount_point)
results = runUtility(unmount_cmd)
if not results['success']:
print("ERROR: failed to mount: {}".format(mount_point))
print(results['stdout'])
print(results['stderr'])
sys.exit(2)
if __name__ == "__main__":
main()