mirror of
https://github.com/MLBZ521/MacAdmin.git
synced 2026-02-03 14:03:26 +00:00
v1.0.0 = Initial Version
+ Initial Version (Re-organizing after merging repos.)
This commit is contained in:
271
Apple_RepairPrograms.py
Normal file
271
Apple_RepairPrograms.py
Normal file
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/python
|
||||
"""
|
||||
###################################################################################################
|
||||
# Script Name: Apple_RepairPrograms.py
|
||||
# By: Zack Thompson / Created: 8/24/2019
|
||||
# Version: 1.0.0 / Updated: 8/24/2019 / By: ZT
|
||||
#
|
||||
# Description: This script looks up provided devices and checks if they're eligible for a recall program.
|
||||
#
|
||||
# Exchange and Repair Extension Programs: https://support.apple.com/exchange_repair
|
||||
#
|
||||
# Inspired by the following thread on Jamf Nation: https://www.jamf.com/jamf-nation/discussions/32400/battery-recall-for-15-mid-2015-mbp
|
||||
# And the various forked scripts shared by:
|
||||
# Nick Tong (https://www.jamf.com/jamf-nation/discussions/32400/battery-recall-for-15-mid-2015-mbp#responseChild186454)
|
||||
# Neil Martin (https://soundmacguy.wordpress.com/2019/06/21/2015-macbook-pro-battery-recall-checker-script/)
|
||||
# William McGrath (https://gist.github.com/nzmacgeek/ef453c8e96a67ee12d973e4c0594e286)
|
||||
# jsherwood (https://www.jamf.com/jamf-nation/discussions/32400/battery-recall-for-15-mid-2015-mbp#responseChild186536)
|
||||
#
|
||||
###################################################################################################
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
|
||||
try:
|
||||
from urllib import request as urllib # For Python 3
|
||||
except ImportError:
|
||||
import urllib2 as urllib # For Python 2
|
||||
|
||||
|
||||
def runUtility(command):
|
||||
"""A helper function for subprocess.
|
||||
Args:
|
||||
command: String containing the commands and arguments that will be passed to a shell.
|
||||
Returns:
|
||||
stdout: output of the command
|
||||
"""
|
||||
|
||||
try:
|
||||
process = subprocess.check_output(command, shell=True)
|
||||
except subprocess.CalledProcessError as error:
|
||||
print ('Error code: {}'.format(error.returncode))
|
||||
print ('Error: {}'.format(error))
|
||||
process = "error"
|
||||
|
||||
return process
|
||||
|
||||
|
||||
# This function performs the lookup for each serial number against Apple's API
|
||||
def exchange_lookup(program, serial, guid):
|
||||
|
||||
data = {"serial": serial, "GUID": guid}
|
||||
url = "https://qualityprograms.apple.com/snlookup/{program}".format(program=program)
|
||||
|
||||
try:
|
||||
# print('Trying urllib...')
|
||||
headers = {'Accept': 'application/json'}
|
||||
request = urllib.Request(url, data=json.dumps(data), headers=headers)
|
||||
response = urllib.urlopen(request)
|
||||
statusCode = response.code
|
||||
json_response = json.loads(response.read())
|
||||
|
||||
except Exception:
|
||||
# If urllib fails, resort to using curl
|
||||
sys.exc_clear()
|
||||
# print('Trying curl...')
|
||||
# Build the command
|
||||
curl_cmd = '/usr/bin/curl --silent --show-error --no-buffer --fail --write-out "statusCode:%{{http_code}}" --location --header "Accept: application/json" --data "{data}" --url {url} --request GET'.format(data=data, url=url)
|
||||
response = runUtility(curl_cmd)
|
||||
json_content, statusCode = response.split('statusCode:')
|
||||
json_response = json.loads(json_content)
|
||||
|
||||
return 200, 'json_response'
|
||||
|
||||
|
||||
# This function checks if the model has an available exchange program
|
||||
def available_exchange_programs(model):
|
||||
program_number = []
|
||||
|
||||
mbp15Battery = re.compile("(?:MacBook Pro \(Retina, 15-inch, Mid 2015\))|(?:15-inch Retina MacBook Pro \(Mid 2015\))|(?:MacBookPro11,4)|(?:MacBookPro11,5)")
|
||||
if mbp15Battery.search(str(model)):
|
||||
# 15-inch MacBook Pro Battery Recall Program
|
||||
# https://support.apple.com/15-inch-macbook-pro-battery-recall
|
||||
program_number.append("062019")
|
||||
|
||||
mbp13SSD = re.compile("(?:MacBook Pro \(13-inch, 2017, Two Thunderbolt 3 ports\))|(?:13-inch Retina MacBook Pro \(Mid 2017\))|(?:MacBookPro11,4)|(?:MacBookPro14,1)")
|
||||
if mbp13SSD.search(str(model)):
|
||||
# 13-inch MacBook Pro (non Touch Bar) Solid-State Drive Service Program
|
||||
# https://support.apple.com/13-inch-macbook-pro-solid-state-drive-service
|
||||
program_number.append("112018")
|
||||
|
||||
mbp13Battery = re.compile("(?:13-inch MacBook Pro \(non Touch Bar\))|(?:13-inch Retina MacBook Pro \(Late 2016\))|(?:13-inch Retina MacBook Pro \(Mid 2017\))|(?:MacBookPro13,1)|(?:MacBookPro14,1)")
|
||||
if mbp13Battery.search(str(model)):
|
||||
# 13-inch MacBook Pro (non Touch Bar) Battery Replacement Program
|
||||
# https://support.apple.com/13inch-macbookpro-battery-replacement
|
||||
program_number.append("032018")
|
||||
|
||||
if model == "iPhone 8":
|
||||
# iPhone 8 Logic Board Replacement Program
|
||||
# https://support.apple.com/iphone-8-logic-board-replacement-program
|
||||
program_number.append("082018")
|
||||
|
||||
if model == "iPhone 6S":
|
||||
# iPhone 6s Program for Unexpected Shutdown Issues
|
||||
# https://support.apple.com/iphone6s-unexpectedshutdown
|
||||
program_number.append("112016")
|
||||
|
||||
if model == "iPhone 6 Plus":
|
||||
# iSight Camera Replacement Program for iPhone 6 Plus
|
||||
# https://support.apple.com/iphone6plus-isightcamera
|
||||
program_number.append("082015")
|
||||
|
||||
return program_number
|
||||
|
||||
|
||||
# This function takes a serial number and it's model, checks for available exchange programs and the loops through each program ID to check if it's eligible
|
||||
def loop(model, serial):
|
||||
results = []
|
||||
|
||||
# Check if model has available exchange program
|
||||
programs = available_exchange_programs(model)
|
||||
|
||||
# If available program(s) is found, loop through each
|
||||
if programs:
|
||||
for program in programs:
|
||||
|
||||
# Query Apple's API to see if device is eligible
|
||||
status_code, json_data = exchange_lookup(program, serial, str(uuid.uuid1()))
|
||||
|
||||
# Verify the status code was successful
|
||||
if int(status_code) == 200:
|
||||
# Get the attributes
|
||||
status = json_data["status"]
|
||||
|
||||
# Check if the device is eligible -- if not, move on without track this
|
||||
if status == "E00":
|
||||
print("{} is eligible for program: {}".format(serial, program))
|
||||
results.append(program)
|
||||
|
||||
# Sleep so we don't DDoS Apple!
|
||||
time.sleep(10)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
##################################################
|
||||
# Define Script Parameters
|
||||
|
||||
parser = argparse.ArgumentParser(description="Apple Exchange and Repair Extension Programs Lookup")
|
||||
single_run = parser.add_argument_group('Single Device')
|
||||
batch_run = parser.add_argument_group('Batch Process')
|
||||
|
||||
single_run.add_argument('--serialnumber', '-s', metavar='C02LA1K9G7DM', type=str, help='A single serial number', required=False)
|
||||
single_run.add_argument('--model', '-m', metavar='MacBookPro11,4', type=str, help='A model or model identifier. \
|
||||
Example: MacBook Pro (Retina, 15-inch, Mid 2015) or 15-inch Retina MacBook Pro (Mid 2015) or MacBookPro11,4', required=False)
|
||||
batch_run.add_argument('--input', '-i', metavar='/path/to/input_file.csv', type=str, help='Path to a CSV with a list of serial numbers, and models or model identifiers. \
|
||||
Example: MacBook Pro (Retina, 15-inch, Mid 2015) or 15-inch Retina MacBook Pro (Mid 2015) or MacBookPro11,4', required=False)
|
||||
batch_run.add_argument('--output', '-o', metavar='/path/to/output_file.csv', type=str, help='Path to a CSV where devices that are eligible for a repair will be written. \
|
||||
WARNING: If the files exists, it will be overwritten!', required=False)
|
||||
# parser.add_argument('--quiet', '-q', action='store_true', help='Do not print verbose messages.', required=False)
|
||||
|
||||
args = parser.parse_args()
|
||||
# args = parser.parse_known_args()
|
||||
# args = args[0]
|
||||
|
||||
# print('Argparse args: {}'.format(args))
|
||||
|
||||
##################################################
|
||||
# Bits Staged
|
||||
|
||||
if len(sys.argv) == 0:
|
||||
parser.print_help()
|
||||
sys.exit(0)
|
||||
else:
|
||||
if ( args.input or args.output ) and ( args.serialnumber or args.model ):
|
||||
parser.print_help()
|
||||
parser.exit(status=1, message='\nError: Unable to mix arguments between parameter groups.\n')
|
||||
|
||||
# if args.quiet:
|
||||
# verbose = False
|
||||
# else:
|
||||
# verbose = True
|
||||
|
||||
# If working with a csv...
|
||||
if args.input and args.output:
|
||||
input_file = args.input
|
||||
output_file = args.output
|
||||
eligible_devices = []
|
||||
serial_fieldname = None
|
||||
model_fieldname = None
|
||||
|
||||
if os.path.exists(input_file):
|
||||
# Open the provided CSV file
|
||||
with open(input_file, 'r') as csv_in:
|
||||
csv_reader = csv.DictReader(csv_in, delimiter=',')
|
||||
|
||||
# Get the field names so we can parse these for the fields we need as well as use for writing back out later
|
||||
field_names = csv_reader.fieldnames
|
||||
|
||||
# Common, expected patterns that a column header by look like for what is needed
|
||||
patternSerial = re.compile("[Ss]erial([\s_])?([Nn]umber)?")
|
||||
patternModel = re.compile("[Mm]odel([\s_])?([Ii]dentifier)?")
|
||||
|
||||
# For each field name, check if one matches
|
||||
for field_name in field_names:
|
||||
if patternSerial.search(field_name):
|
||||
serial_fieldname = field_name
|
||||
if patternModel.search(field_name):
|
||||
model_fieldname = field_name
|
||||
|
||||
# Verify the column values were found
|
||||
if serial_fieldname == None or model_fieldname == None:
|
||||
print("Aborting: Unable to correlate the header columns in the CSV file to expected values.")
|
||||
sys.exit(3)
|
||||
|
||||
# Loop through each row in the CSV file
|
||||
for row in csv_reader:
|
||||
|
||||
# Pass each row attributes to the loop function
|
||||
results = loop(model=row[model_fieldname], serial=row[serial_fieldname])
|
||||
|
||||
# Verify a result was found, if so, add it to a tracking list
|
||||
if len(results) != 0:
|
||||
row.update({'Eligible Programs': str(results).strip('[]\'')})
|
||||
eligible_devices.append(row)
|
||||
|
||||
# if len(eligible_devices) == 2:
|
||||
# break
|
||||
|
||||
# Check if any devices were eligible before doing anything else
|
||||
if len(eligible_devices) > 0:
|
||||
|
||||
# Write to the CSV specified, if it exists, it will be overwritten
|
||||
with open(output_file, mode='w') as csv_out:
|
||||
|
||||
# Build our new header...
|
||||
header_line = field_names
|
||||
header_line.append("Eligible Programs")
|
||||
writer = csv.DictWriter(csv_out, fieldnames=header_line)
|
||||
writer.writeheader()
|
||||
|
||||
for device in eligible_devices:
|
||||
writer.writerow(device)
|
||||
else:
|
||||
print('None of the devices provided were eligible for a recall program.')
|
||||
sys.exit(0)
|
||||
|
||||
elif args.serialnumber and args.model:
|
||||
# A single serial number and model were provided
|
||||
input_serialnumber = args.serialnumber
|
||||
input_model = args.model
|
||||
|
||||
# Pass attributes to the loop function
|
||||
loop(model=input_model, serial=input_serialnumber)
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
parser.exit(status=1, message='\nError: Not enough arguments provided.\n')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user