def main(): """Do the main thing here""" print("\n** Jamf category upload script") print("** Creates a category in Jamf Pro.") # parse the command line arguments args = get_args() # grab values from a prefs file if supplied jamf_url, _, _, enc_creds = api_connect.get_creds_from_args(args) # now get the session token token = api_connect.get_uapi_token(jamf_url, enc_creds, args.verbose) # now process the list of categories for category_name in args.category: # check for existing category print("\nChecking '{}' on {}".format(category_name, jamf_url)) obj_id = api_get.get_uapi_obj_id_from_name(jamf_url, "categories", category_name, token, args.verbose) if obj_id: print("Category '{}' already exists: ID {}".format( category_name, obj_id)) upload_category(jamf_url, category_name, args.priority, args.verbose, token, obj_id) else: # post the category upload_category(jamf_url, category_name, args.priority, args.verbose, token) print()
def main(): """Do the main thing here""" print("\n** Jamf Extension Attribute upload script") print("** Uploads Extension Attribute to Jamf Pro.") # parse the command line arguments args, cli_custom_keys = get_args() verbosity = args.verbose # grab values from a prefs file if supplied jamf_url, _, _, enc_creds = api_connect.get_creds_from_args(args) if not args.script: script = input("Enter the full path to the script to upload: ") args.script = script # now process the list of scripts for extatt_name in args.names: # check for existing Extension Attribute print("\nChecking '{}' on {}".format(extatt_name, jamf_url)) obj_id = api_get.check_api_obj_id_from_name(jamf_url, "extension_attribute", extatt_name, enc_creds, verbosity) if obj_id: print("Extension Attribute '{}' already exists: ID {}".format( extatt_name, obj_id)) if args.replace: upload_extatt( jamf_url, enc_creds, extatt_name, args.script, verbosity, cli_custom_keys, obj_id, ) else: print( "Not replacing existing Extension Attribute. Use --replace to enforce." ) else: print("Extension Attribute '{}' not found - will create".format( extatt_name)) upload_extatt( jamf_url, enc_creds, extatt_name, args.script, verbosity, cli_custom_keys, ) print()
def main(): """Do the main thing here""" print("\n** Jamf API Tool for Jamf Pro.\n") # parse the command line arguments args = get_args() verbosity = args.verbose # grab values from a prefs file if supplied jamf_url, _, _, slack_webhook, enc_creds = api_connect.get_creds_from_args(args) if args.slack: if not slack_webhook: print("slack_webhook value error. Please set it in your prefs file.") exit() # computers block #### if args.computer: if args.search and args.all: exit("syntax error: use either --search or --all, but not both") if not args.all: exit("syntax error: --computers requires --all as a minimum") recent_computers = [] # we'll need this later old_computers = [] warning = [] # stores full detailed computer info compliant = [] if args.all: """ fill up computers []""" obj = api_get.check_api_finds_all( jamf_url, "computer", enc_creds, verbosity ) try: computers = [] for x in obj: computers.append(x["id"]) except IndexError: computers = "404 computers not found" print(f"{len(computers)} computers found on {jamf_url}") for x in computers: """ load full computer info now """ print(f"...loading info for computer {x}") obj = api_get.get_api_obj_value_from_id( jamf_url, "computer", x, "", enc_creds, verbosity ) if obj: """ this is now computer object """ try: macos = obj["hardware"]["os_version"] name = obj["general"]["name"] dep = obj["general"]["management_status"]["enrolled_via_dep"] seen = datetime.strptime( obj["general"]["last_contact_time"], "%Y-%m-%d %H:%M:%S" ) now = datetime.utcnow() except IndexError: macos = "unknown" name = "unknown" dep = "unknown" seen = "unknown" now = "unknown" difference = (now - seen).days try: if (now - seen).days < 10 and not args.os: # if recent recent_computers.append( f"{x} {macos}\t" + f"name : {name}\n" + f"\t\tDEP : {dep}\n" + f"\t\tseen : {difference} days ago" ) if (now - seen).days < 10 and args.os and (macos >= args.os): compliant.append( f"{x} {macos}\t" + f"name : {name}\n" + f"\t\tDEP : {dep}\n" + f"\t\tseen : {difference} days ago" ) elif (now - seen).days < 10 and args.os and (macos < args.os): warning.append( f"{x} {macos}\t" + f"name : {name}\n" + f"\t\tDEP : {dep}\n" + f"\t\tseen : {difference} days ago" ) if (now - seen).days > 10: old_computers.append( f"{x} {macos}\t" + f"name : {name}\n" + f"\t\tDEP : {dep}\n" + f"\t\tseen : {difference} days ago" ) except IndexError: print("checkin calc. error") # recent_computers.remove(f"{macos} {name} dep:{dep} seen:{calc}") # query is done print(bcolors.OKCYAN + "Loading complete...\n\nSummary:" + bcolors.ENDC) if args.os: """ summarise os """ if compliant: print(f"{len(compliant)} compliant and recent:") for x in compliant: print(bcolors.OKGREEN + x + bcolors.ENDC) if warning: print(f"{len(warning)} non-compliant:") for x in warning: print(bcolors.WARNING + x + bcolors.ENDC) if old_computers: print(f"{len(old_computers)} stale - OS version not considered:") for x in old_computers: print(bcolors.FAIL + x + bcolors.ENDC) else: """ regular summary """ print(f"{len(recent_computers)} last check-in within the past 10 days") for x in recent_computers: print(bcolors.OKGREEN + x + bcolors.ENDC) print(f"{len(old_computers)} stale - last check-in more than 10 days") for x in old_computers: print(bcolors.FAIL + x + bcolors.ENDC) if args.slack: # end a slack api webhook with this number score = len(recent_computers) / (len(old_computers) + len(recent_computers)) score = "{:.2%}".format(score) slack_payload = str( f":hospital: update health: {score} - {len(old_computers)} " f"need to be fixed on {jamf_url}\n" ) print(slack_payload) data = {"text": slack_payload} for x in old_computers: print(bcolors.WARNING + x + bcolors.ENDC) if args.slack: # werk slack_payload += str(f"{x}\n") if args.slack: # send to slack data = {"text": slack_payload} url = slack_webhook request_type = "POST" curl.request(request_type, url, enc_creds, verbosity, data) exit() # policy block ##### if args.policies: if args.search and args.all: exit("syntax error: use --search or --all not both") if args.search: query = args.search obj = api_get.check_api_finds_all(jamf_url, "policy", enc_creds, verbosity) if obj: # targets is the new list targets = [] print( f"Searching {len(obj)} policy/ies on {jamf_url}:\n" "To delete policies, obtain a matching query, then run with the " "--delete argument" ) for x in query: for obj_item in obj: # do the actual search if x in obj_item["name"]: targets.append(obj_item.copy()) if len(targets) > 0: print("Policies found:") for target in targets: print( bcolors.WARNING + f"- policy {target['id']}" + f"\tname : {target['name']}" + bcolors.ENDC ) if args.delete: delete(target["id"], jamf_url, enc_creds, verbosity) print("{} total matches".format(len(targets))) else: for partial in query: print("No match found: {}".format(partial)) elif args.all: # assumes --policy flag due to prior exit on another style obj = api_get.check_api_finds_all( jamf_url, "category_all", enc_creds, verbosity ) if obj: for x in obj: # loop all the categories print( bcolors.OKCYAN + f"category {x['id']}\t{x['name']}" + bcolors.ENDC ) obj = api_get.get_policies_in_category( jamf_url, "category_all_items", x["id"], enc_creds, verbosity ) if obj: for x in obj: # loop all the policies # gather interesting info for each policy via API # use a single call # general/name # scope/computer_groups [0]['name'] generic_info = api_get.get_api_obj_value_from_id( jamf_url, "policy", x["id"], "", enc_creds, verbosity ) name = generic_info["general"]["name"] try: groups = generic_info["scope"]["computer_groups"][0][ "name" ] except IndexError: groups = "none" try: pkg = generic_info["package_configuration"]["packages"][ 0 ]["name"] except IndexError: pkg = "none" # now show all the policies as each category loops print( bcolors.WARNING + f" policy {x['id']}" + f"\tname : {x['name']}\n" + bcolors.ENDC + f"\t\tpkg : {pkg}\n" + f"\t\tscope : {groups}" ) else: print("something went wrong: no categories found.") print( "\n" + bcolors.OKGREEN + "All policies listed above.. program complete for {}".format(jamf_url) + bcolors.ENDC ) exit else: exit("syntax error: with --policies use --search or --all.") # set a list of names either from the CLI for Category erase all if args.category: categories = args.category print( "categories to check are:\n{}\nTotal: {}".format( categories, len(categories) ) ) # now process the list of categories for category_name in categories: category_name = category_name.replace(" ", "%20") # return all items found in each category print("\nChecking '{}' on {}".format(category_name, jamf_url)) obj = api_get.get_policies_in_category( jamf_url, "category_all_items", category_name, enc_creds, verbosity ) if obj: if not args.delete: print( f"Category '{category_name}' exists with {len(obj)} items: " "To delete them run this command again with the --delete flag." ) for obj_item in obj: print("~^~ {} -~- {}".format(obj_item["id"], obj_item["name"])) if args.delete: delete(obj_item["id"], jamf_url, enc_creds, verbosity) else: print("Category '{}' not found".format(category_name)) # process a name or list of names if args.names: names = args.names print("policy names to check are:\n{}\nTotal: {}".format(names, len(names))) for policy_name in names: print("\nChecking '{}' on {}".format(policy_name, jamf_url)) obj_id = api_get.get_api_obj_id_from_name( jamf_url, "policy", policy_name, enc_creds, verbosity ) if obj_id: # gather info from interesting parts of the policy API # use a single call # general/name # scope/computer_gropus [0]['name'] generic_info = api_get.get_api_obj_value_from_id( jamf_url, "policy", obj_id, "", enc_creds, verbosity ) name = generic_info["general"]["name"] try: groups = generic_info["scope"]["computer_groups"][0]["name"] except IndexError: groups = "" print("Match found: '{}' ID: {} Group: {}".format(name, obj_id, groups)) if args.delete: delete(obj_id, jamf_url, enc_creds, verbosity) else: print("Policy '{}' not found".format(policy_name)) print()
def main(): """Do the main thing here""" print("\n** Jamf policy upload script") print("** Creates a policy in Jamf Pro.") # parse the command line arguments args, cli_custom_keys = get_args() verbosity = args.verbose # grab values from a prefs file if supplied jamf_url, _, _, enc_creds = api_connect.get_creds_from_args(args) # import computer group from file and replace any keys in the XML with open(args.template, "r") as file: template_contents = file.read() # substitute user-assignable keys template_contents = actions.substitute_assignable_keys( template_contents, cli_custom_keys, verbosity) # set a list of names either from the CLI args or from the template if no arg provided if args.names: names = args.names else: names = [get_policy_name(template_contents, verbosity)] # now process the list of names for policy_name in names: # where a policy name was supplied via CLI arg, replace this in the template if args.names: template_contents = replace_policy_name(policy_name, template_contents, verbosity) # check for existing policy print("\nChecking '{}' on {}".format(policy_name, jamf_url)) obj_id = api_get.check_api_obj_id_from_name(jamf_url, "policy", policy_name, enc_creds, verbosity) if obj_id: print("Policy '{}' already exists: ID {}".format( policy_name, obj_id)) if args.replace: r = upload_policy( jamf_url, enc_creds, policy_name, template_contents, cli_custom_keys, verbosity, obj_id, ) else: print( "Not replacing existing policy. Use --replace to enforce.") else: print("Policy '{}' not found - will create".format(policy_name)) r = upload_policy( jamf_url, enc_creds, policy_name, template_contents, cli_custom_keys, verbosity, ) # now upload the icon to the policy if specified in the args if args.icon: # get the policy_id returned from the HTTP response try: policy_id = ElementTree.fromstring(r.text).findtext("id") upload_policy_icon( jamf_url, enc_creds, policy_name, args.icon, args.replace_icon, verbosity, policy_id, ) except UnboundLocalError: upload_policy_icon( jamf_url, enc_creds, policy_name, args.icon, args.replace_icon, verbosity, ) print()
def main(): """Do the main thing here""" print( "\n** Jamf Configuration Profile upload script", "\n** Uploads Configuration Profile to Jamf Pro.", "\n** WARNING: This is an experimental script! Using it may have unexpected results!", "\n", ) # parse the command line arguments args = get_args() verbosity = args.verbose # grab values from a prefs file if supplied jamf_url, jamf_user, jamf_pass, _, enc_creds = api_connect.get_creds_from_args(args) if verbosity > 1: print("URL: {}".format(jamf_url)) print("User: {}".format(jamf_user)) if verbosity > 2: print("Pass: {}".format(jamf_pass)) # if an unsigned mobileconfig file is supplied we can get the name, organization and # description from it if args.mobileconfig: print("mobileconfig file supplied: {}".format(args.mobileconfig)) # check if the file is signed mobileconfig_file = unsign_signed_mobileconfig(args.mobileconfig, verbosity) # quit if we get an unsigned profile back and we didn't select --unsign if mobileconfig_file and not args.unsign: print( "Signed profiles cannot be uploaded to Jamf Pro via the API. " "Use the GUI to upload the signed profile, or use --unsign to upload " "the profile with the signature removed." ) exit() # import mobileconfig with open(mobileconfig_file, "rb") as file: mobileconfig_contents = plistlib.load(file) with open(mobileconfig_file, "rb") as file: mobileconfig_plist = file.read() try: mobileconfig_name = mobileconfig_contents["PayloadDisplayName"] print("Configuration Profile name: {}".format(mobileconfig_name)) if verbosity > 2: print("\nMobileconfig contents:") print(mobileconfig_plist.decode("UTF-8")) except KeyError: exit("ERROR: Invalid mobileconfig file supplied - cannot import") try: description = mobileconfig_contents["PayloadDescription"] except KeyError: description = "" try: organization = mobileconfig_contents["PayloadOrganization"] except KeyError: organization = "" # otherwise we are dealing with a payload plist and we need a few other bits of info else: if not args.name: name = input("Enter the name of the configuration profile to upload: ") args.name = name if not args.payload: payload = input("Enter the full path to the payload plist to upload: ") args.payload = payload if not args.identifier: identifier = input("Enter the identifier of the custom payload to upload: ") args.identifier = identifier mobileconfig_name = args.name description = "" organization = "" # we provide a default template which has no category or scope if not args.template: # template = input("Enter the full path to the template XML to upload: ") args.template = "Jamf_Templates_and_Scripts/ProfileTemplate-default.xml" # automatically provide a description and organisation if not provided in the options if not args.description: if description: args.description = description else: description = input("Enter the description of the profile to upload: ") args.description = description if not args.organization: if organization: args.organization = organization else: organization = input("Enter the organization of the profile to upload: ") args.organization = organization # import profile template with open(args.template, "r") as file: template_contents = file.read() # check for existing Configuration Profile print("\nChecking '{}' on {}".format(mobileconfig_name, jamf_url)) obj_id = api_get.get_api_obj_id_from_name( jamf_url, "os_x_configuration_profile", mobileconfig_name, enc_creds, verbosity ) if obj_id: print( "Configuration Profile '{}' already exists: ID {}".format( mobileconfig_name, obj_id ) ) if args.replace: # grab existing UUID from profile as it MUST match on the destination existing_uuid = get_existing_uuid(jamf_url, obj_id, enc_creds, verbosity) if not args.mobileconfig: # generate the mobileconfig from the supplied payload mobileconfig_plist = make_mobileconfig_from_payload( args.payload, args.identifier, mobileconfig_name, args.organization, args.description, existing_uuid, verbosity, ) # now upload the mobileconfig by generating an XML template if mobileconfig_plist: upload_mobileconfig( jamf_url, enc_creds, mobileconfig_name, args.description, args.category, mobileconfig_plist, args.computergroup_name, template_contents, existing_uuid, verbosity, obj_id, ) else: print("A mobileconfig was not generated so cannot upload.") else: print( "Not replacing existing Configuration Profile. Use --replace to enforce." ) else: print( "Configuration Profile '{}' not found - will create".format( mobileconfig_name ) ) new_uuid = generate_uuid() if not args.mobileconfig: # generate the mobileconfig from the supplied payload mobileconfig_plist = make_mobileconfig_from_payload( args.payload, args.identifier, mobileconfig_name, args.organization, args.description, new_uuid, verbosity, ) # now upload the mobileconfig by generating an XML template if mobileconfig_plist: upload_mobileconfig( jamf_url, enc_creds, mobileconfig_name, args.description, args.category, mobileconfig_plist, args.computergroup_name, template_contents, new_uuid, verbosity, ) else: print("A mobileconfig was not generated so cannot upload.") print()
def main(): """Do the main thing here""" print("\n** Jamf computer group upload script") print("** Creates a computer group in Jamf Pro.") # parse the command line arguments args, cli_custom_keys = get_args() verbosity = args.verbose # grab values from a prefs file if supplied jamf_url, _, _, _, enc_creds = api_connect.get_creds_from_args(args) # import computer group from file and replace any keys in the XML with open(args.template, "r") as file: template_contents = file.read() # substitute user-assignable keys # pylint is incorrectly stating that 'verbosity' has no value. So... # pylint: disable=no-value-for-parameter template_contents = actions.substitute_assignable_keys(template_contents, cli_custom_keys, verbosity, xml_escape=True) # set a list of names either from the CLI args or from the template if no arg provided if args.names: names = args.names else: names = [get_computergroup_name(template_contents, verbosity)] # now process the list of names for computergroup_name in names: # where a group name was supplied via CLI arg, replace this in the template if args.names: template_contents = replace_computergroup_name( computergroup_name, template_contents, verbosity) # check for existing group print("\nChecking '{}' on {}".format(computergroup_name, jamf_url)) obj_id = api_get.get_api_obj_id_from_name(jamf_url, "computer_group", computergroup_name, enc_creds, verbosity) if obj_id: print("Computer Group '{}' already exists: ID {}".format( computergroup_name, obj_id)) if args.replace: upload_computergroup( jamf_url, enc_creds, computergroup_name, template_contents, cli_custom_keys, verbosity, obj_id, ) else: print( "Not replacing existing Computer Group. Use --replace to enforce." ) else: print("Computer Group '{}' not found - will create".format( computergroup_name)) upload_computergroup( jamf_url, enc_creds, computergroup_name, template_contents, cli_custom_keys, verbosity, ) print()
def main(): """Do the main thing here""" print("\n** Jamf script upload script") print("** Uploads script to Jamf Pro.") # parse the command line arguments args, cli_custom_keys = get_args() # grab values from a prefs file if supplied jamf_url, _, _, enc_creds = api_connect.get_creds_from_args(args) # now get the session token token = api_connect.get_uapi_token(jamf_url, enc_creds, args.verbose) if not args.script: script = input("Enter the full path to the script to upload: ") args.script = script # get the id for a category if supplied if args.category: print("Checking categories for {}".format(args.category)) category_id = api_get.get_uapi_obj_id_from_name( jamf_url, "categories", args.category, token, args.verbose ) if not category_id: print("WARNING: Category not found!") category_id = "-1" else: print("Category {} found: ID={}".format(args.category, category_id)) else: args.category = "" # now process the list of scripts for script_path in args.script: script_name = os.path.basename(script_path) # check for existing script print("\nChecking '{}' on {}".format(script_name, jamf_url)) if args.verbose: print("Full path: {}".format(script_path)) obj_id = api_get.get_uapi_obj_id_from_name( jamf_url, "scripts", script_name, token, args.verbose ) # post the script upload_script( jamf_url, script_name, script_path, category_id, args.category, args.info, args.notes, args.priority, args.parameter4, args.parameter5, args.parameter6, args.parameter7, args.parameter8, args.parameter9, args.parameter10, args.parameter11, args.osrequirements, args.verbose, token, cli_custom_keys, obj_id, ) print()
def main(): """Do the main thing here""" print("\n** Jamf package upload script") print("** Uploads packages to Jamf Cloud or SMB Distribution Points.") # parse the command line arguments args = get_args() # grab values from a prefs file if supplied jamf_url, jamf_user, jamf_password, enc_creds = api_connect.get_creds_from_args( args) if args.prefs: smb_url, smb_user, smb_password = api_connect.get_smb_credentials( args.prefs) else: smb_url = "" smb_user = "" smb_password = "" # repeat for optional SMB share (but must supply a share path to invoke this) if args.share: smb_url = args.share if smb_url: if args.shareuser: smb_user = args.shareuser elif not smb_user: smb_user = input( "Enter a user with read/write permissions to {} : ".format( smb_url)) if args.sharepass: smb_password = args.sharepass elif not smb_password: smb_password = getpass.getpass( "Enter the password for '{}' : ".format(smb_user)) # get HTTP request timeout r_timeout = float(args.timeout) if not args.pkg: pkg = input("Enter the full path to the package to upload: ") args.pkg = pkg # establish a web login session which is reusable for scraping tokens if args.direct: r, login_session = login(jamf_url, jamf_user, jamf_password, args.verbose) if r.status_code != 200: print("Failed to log in to the Jamf instance at: {}".format( jamf_url)) # get the id for a category if supplied if args.category: print("Checking ID for {}".format(args.category)) category_id = api_get.get_uapi_obj_id_from_name( jamf_url, enc_creds, args.category, args.verbose, "categories") if not category_id: print("WARNING: Category not found!") category_id = "-1" # now process the list of packages for pkg_path in args.pkg: pkg_name = os.path.basename(pkg_path) # See if the package is non-flat (requires zipping prior to upload). if os.path.isdir(pkg_path): pkg_path = zip_pkg_path(pkg_path) pkg_name += ".zip" # check for existing package print("\nChecking '{}' on {}".format(pkg_name, jamf_url)) if args.verbose: print("Full path: {}".format(pkg_path)) replace_pkg = True if args.replace else False obj_id = check_pkg(pkg_name, jamf_url, enc_creds) # post the package (won't run if the pkg exists and replace_pkg is False) # process for SMB shares if defined if smb_url: # mount the share mount_smb(smb_url, smb_user, smb_password, args.verbose) # check for existing package local_pkg = check_local_pkg(args.share, pkg_name, args.verbose) if not local_pkg or replace_pkg: # copy the file copy_pkg(smb_url, pkg_path, pkg_name) # unmount the share umount_smb(smb_url) # otherwise process for cloud DP else: if obj_id == "-1" or replace_pkg: # JCDS direct upload method option if args.direct: jcds_url, jcds_token, session_token = scrape_upload_token( login_session, jamf_url, args.verbose) if jcds_url and jcds_token and session_token: if args.verbose: print("JCDS URL: {}".format(jcds_url)) print("JCDS Upload token: {}".format(jcds_token)) print("Session token: {}".format(session_token)) # post the package as chunks post_pkg_chunks( pkg_name, pkg_path, jcds_url, jcds_token, obj_id, args.chunksize, args.verbose, ) # now create the package object and get the pkg ID pkg_id = update_pkg_by_form( login_session, session_token, jamf_url, pkg_name, pkg_path, obj_id, category_id, args.verbose, ) # curl -> dbfileupload upload method option elif args.curl: r = curl_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, args.verbose, ) try: pkg_id = ElementTree.fromstring(r).findtext("id") if pkg_id: print("\nPackage uploaded successfully, ID={}". format(pkg_id)) except ElementTree.ParseError: print("Could not parse XML. Raw output:") print(r.decode("ascii")) else: if args.verbose: if r: print("\nResponse:\n") print(r.decode("ascii")) else: print("No HTTP response") # requests -> dbfileupload upload method option else: r = post_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, args.verbose, ) # print result of the request if r.status_code == 200 or r.status_code == 201: pkg_id = ElementTree.fromstring(r.text).findtext("id") print("\nPackage uploaded successfully, ID={}".format( pkg_id)) if args.verbose: print("HTTP POST Response Code: {}".format( r.status_code)) else: print("\nHTTP POST Response Code: {}".format( r.status_code)) if args.verbose: api_get.get_headers(r) # now process the package metadata if a category is supplied, # or if we are dealing with an SMB share if (args.category or smb_url) and not args.direct: try: pkg_id update_pkg_metadata(jamf_url, enc_creds, pkg_name, args.category, args.verbose, pkg_id) except UnboundLocalError: update_pkg_metadata(jamf_url, enc_creds, pkg_name, args.category, args.verbose) print()
def main(): """Do the main thing here""" print("\n** Jamf package upload script") print("** Uploads packages to Jamf Cloud or SMB Distribution Points.") # parse the command line arguments args = get_args() verbosity = args.verbose # create a dictionary of package metadata from the args pkg_metadata = { "category": args.category, "info": args.info, "notes": args.notes, "reboot_required": args.reboot_required, "priority": args.priority, "os_requirement": args.os_requirement, "required_processor": args.required_processor, "send_notification": args.send_notification, } # grab values from a prefs file if supplied ( jamf_url, jamf_user, jamf_password, slack_webhook, enc_creds, ) = api_connect.get_creds_from_args(args) if args.prefs: smb_url, smb_user, smb_pass = api_connect.get_smb_credentials( args.prefs) else: smb_url = "" smb_user = "" smb_pass = "" # repeat for optional SMB share (but must supply a share path to invoke this) if args.smb_url: smb_url = args.smb_url if args.smb_user: smb_user = args.smb_user if not smb_user: smb_user = input( "Enter a user with read/write permissions to {} : ".format( smb_url)) if args.smb_pass: smb_pass = args.smb_pass if not smb_pass: if not smb_pass: smb_pass = getpass.getpass( "Enter the password for '{}' : ".format(smb_user)) # get HTTP request timeout r_timeout = float(args.timeout) if not args.pkg: pkg = input("Enter the full path to the package to upload: ") args.pkg = pkg # establish a web login session which is reusable for scraping tokens if args.direct: r, login_session = login(jamf_url, jamf_user, jamf_password, verbosity) if r.status_code != 200: print("Failed to log in to the Jamf instance at: {}".format( jamf_url)) # get the id for a category if supplied if args.category: print("Checking ID for category '{}'".format(args.category)) # now get the session token token = api_connect.get_uapi_token(jamf_url, enc_creds, verbosity) category_id = api_get.get_uapi_obj_id_from_name( jamf_url, "categories", args.category, token, verbosity) if not category_id: print("WARNING: Category not found!") category_id = "-1" # add to the pkg_metadata dictionary pkg_metadata["category_id"] = category_id # now process the list of packages for pkg_path in args.pkg: pkg_name = os.path.basename(pkg_path) # See if the package is non-flat (requires zipping prior to upload). if os.path.isdir(pkg_path): pkg_path = zip_pkg_path(pkg_path) pkg_name += ".zip" # calculate the SHA-512 hash of the package sha512string = sha512sum(pkg_path) # check for existing package print("\nChecking '{}' on {}".format(pkg_name, jamf_url)) if verbosity: print("Full path: {}".format(pkg_path)) replace_pkg = True if args.replace else False obj_id = check_pkg(pkg_name, jamf_url, enc_creds, verbosity) if obj_id != "-1": print("Package '{}' already exists: ID {}".format( pkg_name, obj_id)) pkg_id = obj_id # assign pkg_id for smb runs - JCDS runs get it from the pkg upload else: pkg_id = "" # post the package (won't run if the pkg exists and replace_pkg is False) # process for SMB shares if defined if args.smb_url: # mount the share smb_actions.mount_smb(args.smb_url, args.smb_user, args.smb_pass, verbosity) # check for existing package local_pkg = check_local_pkg(args.smb_url, pkg_name, verbosity) if not local_pkg or replace_pkg: # copy the file smb_actions.copy_pkg(args.smb_url, pkg_path, pkg_name) # unmount the share smb_actions.umount_smb(args.smb_url) # otherwise process for cloud DP else: if obj_id == "-1" or replace_pkg: # JCDS direct upload method option if args.direct: jcds_url, jcds_token, session_token = scrape_upload_token( login_session, jamf_url, verbosity) if jcds_url and jcds_token and session_token: if verbosity: print("JCDS URL: {}".format(jcds_url)) print("JCDS Upload token: {}".format(jcds_token)) print("Session token: {}".format(session_token)) # post the package as chunks post_pkg_chunks( pkg_name, pkg_path, jcds_url, jcds_token, obj_id, args.chunksize, verbosity, ) # now create the package object and get the pkg ID pkg_id = update_pkg_by_form( login_session, session_token, jamf_url, pkg_name, pkg_path, obj_id, pkg_metadata, verbosity, ) # curl -> dbfileupload upload method option elif args.nscurl: r = nscurl_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, verbosity, ) try: pkg_id = ElementTree.fromstring(r).findtext("id") if pkg_id: print("\nPackage uploaded successfully, ID={}". format(pkg_id)) except ElementTree.ParseError: print("Could not parse XML. Raw output:") print(r.decode("ascii")) else: if verbosity: if r: print("\nResponse:\n") print(r.decode("ascii")) else: print("No HTTP response") # requests -> dbfileupload upload method option elif args.requests: r = post_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, verbosity, ) # print result of the request if r.status_code == 200 or r.status_code == 201: pkg_id = ElementTree.fromstring(r.text).findtext("id") print("\nPackage uploaded successfully, ID={}".format( pkg_id)) if verbosity: print("HTTP POST Response Code: {}".format( r.status_code)) else: print("\nHTTP POST Response Code: {}".format( r.status_code)) if verbosity: api_get.get_headers(r) # curl -> dbfileupload upload method option else: r = curl_pkg( pkg_name, pkg_path, jamf_url, enc_creds, obj_id, r_timeout, verbosity, ) try: pkg_id = ElementTree.fromstring(r).findtext("id") if pkg_id: print("\nPackage uploaded successfully, ID={}". format(pkg_id)) except ElementTree.ParseError: print("Could not parse XML. Raw output:") print(r.decode("ascii")) else: if verbosity: if r: print("\nResponse:\n") print(r.decode("ascii")) else: print("No HTTP response") # now process the package metadata if a category is supplied, # or if we are dealing with an SMB share if not args.direct: if pkg_id: if verbosity: print("Updating package metadata for {}".format(pkg_id)) update_pkg_metadata( jamf_url, enc_creds, pkg_name, pkg_metadata, sha512string, verbosity, pkg_id, ) else: if verbosity: print("Creating package metadata") update_pkg_metadata(jamf_url, enc_creds, pkg_name, pkg_metadata, sha512string, verbosity) print()