From 2e5fda160c73760f62bdddde7a5e37568daf8f1b Mon Sep 17 00:00:00 2001 From: Zack T Date: Thu, 6 Jan 2022 15:20:40 -0700 Subject: [PATCH] v1.5.0 = Support for Macs with 10 character serial numbers + Updated to support 2021 Macs with 10 character serial numbers --- .../Install-BomgarJumpClient.py | 196 ++++++++++-------- 1 file changed, 109 insertions(+), 87 deletions(-) diff --git a/Software/Beyond Trust Bomgar/Install-BomgarJumpClient.py b/Software/Beyond Trust Bomgar/Install-BomgarJumpClient.py index c3fa7a5..70ea7dc 100644 --- a/Software/Beyond Trust Bomgar/Install-BomgarJumpClient.py +++ b/Software/Beyond Trust Bomgar/Install-BomgarJumpClient.py @@ -3,26 +3,27 @@ """ Script Name: Install-BomgarJumpClient.py By: Zack Thompson / Created: 3/2/2020 -Version: 1.4.2 / Updated: 1/4/2022 / By: ZT +Version: 1.5.0 / Updated: 1/5/2022 / 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 -from Cocoa import NSBundle -from SystemConfiguration import SCDynamicStoreCopyConsoleUser from xml.etree import ElementTree +from Cocoa import NSBundle, NSString +import objc import requests +from SystemConfiguration import SCDynamicStoreCopyConsoleUser -def execute_process(command): + +def execute_process(command, input=None): """ A helper function for subprocess. @@ -36,15 +37,20 @@ def execute_process(command): # Validate that command is not a string if not isinstance(command, str): - raise TypeError('Command must be a str type') + raise TypeError("Command must be a str type") # Format the command command = shlex.split(command) # Run the command - process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + process = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, universal_newlines=True ) - (stdout, stderr) = process.communicate() + + if input: + (stdout, stderr) = process.communicate(input=input) + + else: + (stdout, stderr) = process.communicate() return { "stdout": (stdout).strip(), @@ -54,11 +60,13 @@ def execute_process(command): } -def get_system(attribute): - """A helper function to get specific system attributes. +def get_system_info(): + """ + A helper function to get specific system attributes. Credit: Mikey Mike/Froger/Pudquick/etc Source: https://gist.github.com/pudquick/c7dd1262bd81a32663f0 + Notes: Modified from source Args: attribute: The system attribute desired. @@ -67,68 +75,79 @@ def get_system(attribute): stdout: The system attribute value. """ - IOKit_bundle = NSBundle.bundleWithIdentifier_('com.apple.framework.IOKit') + IOKit_bundle = NSBundle.bundleWithIdentifier_("com.apple.framework.IOKit") functions = [ + ("IORegistryEntryCreateCFProperty", b"@I@@I"), ("IOServiceGetMatchingService", b"II@"), - ("IOServiceMatching", b"@*"), - ("IORegistryEntryCreateCFProperty", b"@I@@I") + ("IOServiceMatching", b"@*") ] objc.loadBundleFunctions(IOKit_bundle, globals(), functions) - def io_key(keyname): - return IORegistryEntryCreateCFProperty( - IOServiceGetMatchingService( - 0, IOServiceMatching( - "IOPlatformExpertDevice".encode("utf-8"))), keyname, None, 0) + def io_key(key_name, service_name="IOPlatformExpertDevice"): + service = IOServiceMatching(service_name.encode("utf-8")) + key = NSString.stringWithString_(key_name) + return IORegistryEntryCreateCFProperty(IOServiceGetMatchingService(0, service), key, None, 0) - # def get_hardware_uuid(): - # return io_key("IOPlatformUUID".encode("utf-8")) + def get_hardware_uuid(): + return io_key("IOPlatformUUID") def get_hardware_serial(): return io_key("IOPlatformSerialNumber") - # def get_board_id(): - # return str(io_key("board-id".encode("utf-8"))).rstrip('\x00') + def get_board_id(): + try: + return bytes(io_key("board-id")).decode().rstrip("\x00") + except TypeError: + return "" - options = {'serial' : get_hardware_serial #, - # 'uuid' : get_hardware_uuid, - # 'boardID' : get_board_id + def get_model_id(): + return bytes(io_key("model")).decode().rstrip("\x00") + + def lookup_model(lookup_code): + xml = requests.get("https://support-sp.apple.com/sp/product?cc={}".format(lookup_code)).text + + try: + tree = ElementTree.fromstringlist(xml) + return tree.find(".//configCode").text + + except ElementTree.ParseError as err: + print("Failed to retrieve model name: {}".format(err.strerror)) + return "" + + + serial_number = get_hardware_serial() + sn_length = len(serial_number) + model = "" + + if sn_length == 10: + results = execute_process("/usr/sbin/ioreg -arc IOPlatformDevice -k product-name") + + if results["success"]: + plist_contents = plistlib.loads(results["stdout"].encode()) + model = plist_contents[0].get("product-name").decode().rstrip("\x00") + + elif sn_length == 12: + model = lookup_model(serial_number[-4:]) + + elif sn_length == 11: + model = lookup_model(serial_number[-3:]) + + return { + "serial_number": serial_number, + "uuid": get_hardware_uuid(), + "board_id": get_board_id(), + "model_id": get_model_id(), + "model_friendly": model } - 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={}".format(lookup_code) - - xml = requests.get(lookup_url).text - - try: - tree = ElementTree.fromstringlist(xml) - return tree.find('.//configCode').text - - 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. + """ + 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. """ @@ -138,14 +157,14 @@ def mount(pathname): results = execute_process(mount_cmd) - if not results['success']: + if not results["success"]: print("ERROR: failed to mount: {}".format(pathname)) - print(results['stdout']) - print(results['stderr']) + print(results["stdout"]) + print(results["stderr"]) sys.exit(2) # Read output plist. - xml = plistlib.loads(results['stdout'].encode()) + xml = plistlib.loads(results["stdout"].encode()) # Find mount point. for part in xml.get("system-entities", []): @@ -155,12 +174,13 @@ def mount(pathname): print("mounting {} failed: unexpected output from hdiutil".format(pathname)) + def main(): - print('\n***** install_Bomgar process: START *****\n') + 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)) + # print("original_paramters: {}".format(orig_argv)) sys.argv = [] # sys.argv.append(orig_argv[0]) @@ -168,22 +188,22 @@ def main(): if len(arg) != 0: sys.argv.extend(arg.split(" ", 1)) - print('paramters: {}\n'.format(sys.argv)) + 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: , ', required=False) - parser.add_argument('--comments', '-c', help='Sets the Jump Client Comments. default value: , ', 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) + 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: , ", required=False) + parser.add_argument("--comments", "-c", help="Sets the Jump Client Comments. default value: , ", 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)) @@ -212,13 +232,13 @@ def main(): # Verify that a console user was present if console_user: - # Get the Console Users' Full Name + # Get the Console Users" Full Name full_name_cmd = "/usr/bin/dscl . -read \"/Users/{}\" dsAttrTypeStandard:RealName".format( console_user) full_name_results = execute_process(full_name_cmd) - if full_name_results['success']: - full_name = re.sub("RealName:\s+", "", full_name_results['stdout']) + if full_name_results["success"]: + full_name = re.sub("RealName:\s+", "", full_name_results["stdout"]) if "Setup User (_mbsetupuser)" != "{} ({})".format(full_name, console_user): jumpName = "--jc-name '{} ({})'".format(full_name, console_user) @@ -238,8 +258,10 @@ def main(): # Set the Jump Client Comments if args.comments is None: - serial_number = get_system("serial") - model_friendly = get_model(serial_number) + system_info = get_system_info() + serial_number = system_info.get("serial_number") + model_friendly = system_info.get("model_friendly") if ( system_info.get("model_friendly") + ) else system_info.get("model_id") jumpComments = "--jc-comments '{}, {}'".format(model_friendly, serial_number) else: jumpComments = "--jc-comments '{}'".format(args.comments) @@ -279,7 +301,7 @@ def main(): # Bits staged... for a_file in os.listdir("/private/tmp"): - if re.search(r'(bomgar-scc-)[a-z0-9]+[.](dmg)', a_file): + 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) @@ -289,14 +311,14 @@ def main(): mount_point = mount(bomgar_dmg) else: print("ERROR: Bomgar DMG was not found at the expected location!") - print('***** install_Bomgar process: FAILED *****') + 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): + if re.search(r".+[.](app)", a_file): install_app = os.path.join(mount_point, a_file) break @@ -310,28 +332,28 @@ def main(): results = execute_process(install_cmd) - if not results['success']: + if not results["success"]: print("ERROR: failed to install Bomgar Jump Client") - print("Results: {}".format(results['stderr'])) + print("Results: {}".format(results["stderr"])) sys.exit(2) - # print("Results: {}".format(results['stdout'])) + # print("Results: {}".format(results["stdout"])) else: print("ERROR: Bomgar Jump Client installer was not found at the expected location!") - print('***** install_Bomgar process: FAILED *****') + print("***** install_Bomgar process: FAILED *****") sys.exit(2) - print('***** install_Bomgar process: SUCCESS *****') + print("***** install_Bomgar process: SUCCESS *****") finally: unmount_cmd = "/usr/bin/hdiutil detach {}".format(mount_point) results = execute_process(unmount_cmd) - if not results['success']: + if not results["success"]: print("ERROR: failed to mount: {}".format(mount_point)) - print(results['stdout']) - print(results['stderr']) + print(results["stdout"]) + print(results["stderr"]) sys.exit(2) if __name__ == "__main__":