mirror of
https://github.com/munki/munki.git
synced 2026-01-10 08:40:22 -06:00
789 lines
34 KiB
Python
789 lines
34 KiB
Python
# encoding: utf-8
|
|
# Copyright 2009-2020 Greg Neagle.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# https://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""
|
|
adobeutils.adobeinfo
|
|
|
|
Created by Greg Neagle on 2017-01-06.
|
|
|
|
Utilities to get info about Adobe installers/uninstallers
|
|
|
|
"""
|
|
from __future__ import absolute_import, print_function
|
|
|
|
import os
|
|
import json
|
|
import sqlite3
|
|
|
|
from glob import glob
|
|
from xml.dom import minidom
|
|
|
|
from .. import osutils
|
|
from .. import pkgutils
|
|
|
|
|
|
def find_install_app(dirpath):
|
|
'''Searches dirpath and enclosed directories for Install.app.
|
|
Returns the path to the actual executable.'''
|
|
for (path, dummy_dirs, dummy_files) in os.walk(dirpath):
|
|
if path.endswith("Install.app"):
|
|
setup_path = os.path.join(path, "Contents", "MacOS", "Install")
|
|
if os.path.exists(setup_path):
|
|
return setup_path
|
|
return ''
|
|
|
|
|
|
def find_setup_app(dirpath):
|
|
'''Search dirpath and enclosed directories for Setup.app.
|
|
Returns the path to the actual executable.'''
|
|
for (path, dummy_dirs, dummy_files) in os.walk(dirpath):
|
|
if path.endswith("Setup.app"):
|
|
setup_path = os.path.join(path, "Contents", "MacOS", "Setup")
|
|
if os.path.exists(setup_path):
|
|
return setup_path
|
|
return ''
|
|
|
|
|
|
def find_adobepatchinstaller_app(dirpath):
|
|
'''Searches dirpath and enclosed directories for AdobePatchInstaller.app.
|
|
Returns the path to the actual executable.'''
|
|
for (path, dummy_dirs, dummy_files) in os.walk(dirpath):
|
|
if path.endswith("AdobePatchInstaller.app"):
|
|
setup_path = os.path.join(
|
|
path, "Contents", "MacOS", "AdobePatchInstaller")
|
|
if os.path.exists(setup_path):
|
|
return setup_path
|
|
return ''
|
|
|
|
|
|
def find_adobe_deployment_manager(dirpath):
|
|
'''Searches dirpath and enclosed directories for AdobeDeploymentManager.
|
|
Returns path to the executable.'''
|
|
for (path, dummy_dirs, dummy_files) in os.walk(dirpath):
|
|
if path.endswith("pkg/Contents/Resources"):
|
|
dm_path = os.path.join(path, "AdobeDeploymentManager")
|
|
if os.path.exists(dm_path):
|
|
return dm_path
|
|
return ''
|
|
|
|
|
|
def find_acrobat_patch_app(dirpath):
|
|
'''Attempts to find an AcrobatPro patching application
|
|
in dirpath. If found, returns the path to the bundled
|
|
patching script.'''
|
|
|
|
for (path, dummy_dirs, dummy_files) in os.walk(dirpath):
|
|
if path.endswith(".app"):
|
|
# look for Adobe's patching script
|
|
patch_script_path = os.path.join(
|
|
path, 'Contents', 'Resources', 'ApplyOperation.py')
|
|
if os.path.exists(patch_script_path):
|
|
return path
|
|
return ''
|
|
|
|
|
|
def get_payload_info(dirpath):
|
|
'''Parses Adobe payloads, pulling out info useful to munki.
|
|
.proxy.xml files are used if available, or for CC-era updates
|
|
which do not contain one, the Media_db.db file, which contains
|
|
identical XML, is instead used.
|
|
|
|
CS3/CS4: contain only .proxy.xml
|
|
CS5/CS5.5/CS6: contain both
|
|
CC: contain only Media_db.db'''
|
|
|
|
payloadinfo = {}
|
|
# look for .proxy.xml file dir
|
|
if os.path.isdir(dirpath):
|
|
proxy_paths = glob(os.path.join(dirpath, '*.proxy.xml'))
|
|
if proxy_paths:
|
|
xmlpath = proxy_paths[0]
|
|
dom = minidom.parse(xmlpath)
|
|
# if there's no .proxy.xml we should hope there's a Media_db.db
|
|
else:
|
|
db_path = os.path.join(dirpath, 'Media_db.db')
|
|
if os.path.exists(db_path):
|
|
conn = sqlite3.connect(db_path)
|
|
cur = conn.cursor()
|
|
cur.execute("SELECT value FROM PayloadData WHERE "
|
|
"PayloadData.key = 'PayloadInfo'")
|
|
result = cur.fetchone()
|
|
cur.close()
|
|
if result:
|
|
info_xml = result[0].encode('UTF-8')
|
|
dom = minidom.parseString(info_xml)
|
|
else:
|
|
# no xml, no db, no payload info!
|
|
return payloadinfo
|
|
|
|
payload_info = dom.getElementsByTagName('PayloadInfo')
|
|
if payload_info:
|
|
installer_properties = payload_info[0].getElementsByTagName(
|
|
'InstallerProperties')
|
|
if installer_properties:
|
|
properties = installer_properties[0].getElementsByTagName(
|
|
'Property')
|
|
for prop in properties:
|
|
if 'name' in list(prop.attributes.keys()):
|
|
propname = prop.attributes['name'].value.encode('UTF-8')
|
|
propvalue = ''
|
|
for node in prop.childNodes:
|
|
propvalue += node.nodeValue
|
|
if propname == 'AdobeCode':
|
|
payloadinfo['AdobeCode'] = propvalue
|
|
if propname == 'ProductName':
|
|
payloadinfo['display_name'] = propvalue
|
|
if propname == 'ProductVersion':
|
|
payloadinfo['version'] = propvalue
|
|
|
|
installmetadata = payload_info[0].getElementsByTagName(
|
|
'InstallDestinationMetadata')
|
|
if installmetadata:
|
|
totalsizes = installmetadata[0].getElementsByTagName(
|
|
'TotalSize')
|
|
if totalsizes:
|
|
installsize = ''
|
|
for node in totalsizes[0].childNodes:
|
|
installsize += node.nodeValue
|
|
payloadinfo['installed_size'] = int(int(installsize)/1024)
|
|
|
|
return payloadinfo
|
|
|
|
|
|
def get_adobe_setup_info(installroot):
|
|
'''Given the root of mounted Adobe DMG,
|
|
look for info about the installer or updater'''
|
|
|
|
info = {}
|
|
payloads = []
|
|
|
|
# look for all the payloads folders
|
|
for (path, dummy_dirs, dummy_files) in os.walk(installroot):
|
|
if path.endswith('/payloads'):
|
|
driverfolder = ''
|
|
media_signature = ''
|
|
setupxml = os.path.join(path, 'setup.xml')
|
|
if os.path.exists(setupxml):
|
|
dom = minidom.parse(setupxml)
|
|
drivers = dom.getElementsByTagName('Driver')
|
|
if drivers:
|
|
driver = drivers[0]
|
|
if 'folder' in list(driver.attributes.keys()):
|
|
driverfolder = driver.attributes[
|
|
'folder'].value.encode('UTF-8')
|
|
if driverfolder == '':
|
|
# look for mediaSignature (CS5 AAMEE install)
|
|
setup_elements = dom.getElementsByTagName('Setup')
|
|
if setup_elements:
|
|
media_signature_elements = setup_elements[
|
|
0].getElementsByTagName('mediaSignature')
|
|
if media_signature_elements:
|
|
element = media_signature_elements[0]
|
|
for node in element.childNodes:
|
|
media_signature += node.nodeValue
|
|
|
|
for item in osutils.listdir(path):
|
|
payloadpath = os.path.join(path, item)
|
|
payloadinfo = get_payload_info(payloadpath)
|
|
if payloadinfo:
|
|
payloads.append(payloadinfo)
|
|
if ((driverfolder and item == driverfolder) or
|
|
(media_signature and
|
|
payloadinfo['AdobeCode'] == media_signature)):
|
|
info['display_name'] = payloadinfo['display_name']
|
|
info['version'] = payloadinfo['version']
|
|
info['AdobeSetupType'] = 'ProductInstall'
|
|
|
|
if not payloads:
|
|
# look for an extensions folder; almost certainly this is an Updater
|
|
for (path, dummy_dirs, dummy_files) in os.walk(installroot):
|
|
if path.endswith("/extensions"):
|
|
for item in osutils.listdir(path):
|
|
#skip LanguagePacks
|
|
if item.find("LanguagePack") == -1:
|
|
itempath = os.path.join(path, item)
|
|
payloadinfo = get_payload_info(itempath)
|
|
if payloadinfo:
|
|
payloads.append(payloadinfo)
|
|
|
|
# we found an extensions dir,
|
|
# so no need to keep walking the install root
|
|
break
|
|
|
|
if payloads:
|
|
if len(payloads) == 1:
|
|
info['display_name'] = payloads[0]['display_name']
|
|
info['version'] = payloads[0]['version']
|
|
else:
|
|
if 'display_name' not in info:
|
|
info['display_name'] = "ADMIN: choose from payloads"
|
|
if 'version' not in info:
|
|
info['version'] = "ADMIN please set me"
|
|
info['payloads'] = payloads
|
|
installed_size = 0
|
|
for payload in payloads:
|
|
installed_size = installed_size + payload.get('installed_size', 0)
|
|
info['installed_size'] = installed_size
|
|
return info
|
|
|
|
|
|
def get_adobe_package_info(installroot):
|
|
'''Gets the package name from the AdobeUberInstaller.xml file;
|
|
other info from the payloads folder'''
|
|
|
|
info = get_adobe_setup_info(installroot)
|
|
info['description'] = ""
|
|
installerxml = os.path.join(installroot, "AdobeUberInstaller.xml")
|
|
if os.path.exists(installerxml):
|
|
description = ''
|
|
dom = minidom.parse(installerxml)
|
|
installinfo = dom.getElementsByTagName("InstallInfo")
|
|
if installinfo:
|
|
packagedescriptions = \
|
|
installinfo[0].getElementsByTagName("PackageDescription")
|
|
if packagedescriptions:
|
|
prop = packagedescriptions[0]
|
|
for node in prop.childNodes:
|
|
description += node.nodeValue
|
|
|
|
if description:
|
|
description_parts = description.split(' : ', 1)
|
|
info['display_name'] = description_parts[0]
|
|
if len(description_parts) > 1:
|
|
info['description'] = description_parts[1]
|
|
else:
|
|
info['description'] = ""
|
|
return info
|
|
else:
|
|
installerxml = os.path.join(installroot, "optionXML.xml")
|
|
if os.path.exists(installerxml):
|
|
dom = minidom.parse(installerxml)
|
|
installinfo = dom.getElementsByTagName("InstallInfo")
|
|
if installinfo:
|
|
pkgname_elems = installinfo[0].getElementsByTagName(
|
|
"PackageName")
|
|
if pkgname_elems:
|
|
prop = pkgname_elems[0]
|
|
pkgname = ""
|
|
for node in prop.childNodes:
|
|
pkgname += node.nodeValue
|
|
info['display_name'] = pkgname
|
|
|
|
if not info.get('display_name'):
|
|
info['display_name'] = os.path.basename(installroot)
|
|
return info
|
|
|
|
|
|
def get_xml_text_element(dom_node, name):
|
|
'''Returns the text value of the first item found with the given
|
|
tagname'''
|
|
value = None
|
|
subelements = dom_node.getElementsByTagName(name)
|
|
if subelements:
|
|
value = ''
|
|
for node in subelements[0].childNodes:
|
|
value += node.nodeValue
|
|
return value
|
|
|
|
|
|
def parse_option_xml(option_xml_file):
|
|
'''Parses an optionXML.xml file and pulls the items of interest, returning
|
|
them in a dictionary'''
|
|
info = {}
|
|
dom = minidom.parse(option_xml_file)
|
|
installinfo = dom.getElementsByTagName('InstallInfo')
|
|
if installinfo:
|
|
if 'id' in list(installinfo[0].attributes.keys()):
|
|
info['packager_id'] = installinfo[0].attributes['id'].value
|
|
if 'version' in list(installinfo[0].attributes.keys()):
|
|
info['packager_version'] = installinfo[
|
|
0].attributes['version'].value
|
|
info['package_name'] = get_xml_text_element(
|
|
installinfo[0], 'PackageName')
|
|
info['package_id'] = get_xml_text_element(installinfo[0], 'PackageID')
|
|
info['products'] = []
|
|
|
|
# CS5 to CC 2015.0-2015.2 releases use RIBS, and we retrieve a
|
|
# display name, version and 'mediaSignature' for building installs
|
|
# items. SAPCode is also stored so that we can later search by this
|
|
# key across both RIBS and HyperDrive installer metadata.
|
|
medias_elements = installinfo[0].getElementsByTagName('Medias')
|
|
if medias_elements:
|
|
media_elements = medias_elements[0].getElementsByTagName('Media')
|
|
if media_elements:
|
|
for media in media_elements:
|
|
product = {}
|
|
product['prodName'] = get_xml_text_element(
|
|
media, 'prodName')
|
|
product['prodVersion'] = get_xml_text_element(
|
|
media, 'prodVersion')
|
|
product['SAPCode'] = get_xml_text_element(media, 'SAPCode')
|
|
setup_elements = media.getElementsByTagName('Setup')
|
|
if setup_elements:
|
|
media_signature_elements = setup_elements[
|
|
0].getElementsByTagName('mediaSignature')
|
|
if media_signature_elements:
|
|
product['mediaSignature'] = ''
|
|
element = media_signature_elements[0]
|
|
for node in element.childNodes:
|
|
product['mediaSignature'] += node.nodeValue
|
|
info['products'].append(product)
|
|
|
|
# HD (HyperDrive) media for new mid-June 2016 products. We need the
|
|
# SAP codes, versions, and which ones are MediaType 'Product'. Support
|
|
# payloads seem to all be 'STI', and are listed as STIDependencies under
|
|
# the main product.
|
|
hd_medias_elements = installinfo[0].getElementsByTagName('HDMedias')
|
|
if hd_medias_elements:
|
|
hd_media_elements = hd_medias_elements[0].getElementsByTagName(
|
|
'HDMedia')
|
|
if hd_media_elements:
|
|
for hd_media in hd_media_elements:
|
|
product = {}
|
|
product['hd_installer'] = True
|
|
# productVersion is the 'full' version number
|
|
# prodVersion seems to be the "customer-facing" version for
|
|
# this update
|
|
# baseVersion is the first/base version for this standalone
|
|
# product/channel/LEID,
|
|
# not really needed here so we don't copy it
|
|
for elem in [
|
|
'mediaLEID',
|
|
'prodVersion',
|
|
'productVersion',
|
|
'SAPCode',
|
|
'MediaType',
|
|
'TargetFolderName']:
|
|
product[elem] = get_xml_text_element(hd_media, elem)
|
|
info['products'].append(product)
|
|
|
|
return info
|
|
|
|
|
|
def get_hd_installer_info(hd_payload_root, sap_code):
|
|
'''Attempts to extract some information from a HyperDrive payload
|
|
application.json file and return a reduced set in a dict'''
|
|
hd_app_info = {}
|
|
app_json_path = os.path.join(hd_payload_root, sap_code, 'Application.json')
|
|
json_info = json.loads(open(app_json_path, 'r').read())
|
|
|
|
# Copy some useful top-level keys, useful later for:
|
|
# - Name: display_name pkginfo key
|
|
# - ProductVersion: version pkginfo key and uninstall XML location
|
|
# - SAPCode: an uninstallXml for an installs item if it's a 'core' Type
|
|
# - BaseVersion and version: not currently used but may be useful once
|
|
# there are more HD installers in the future
|
|
for key in ['BaseVersion', 'Name', 'ProductVersion', 'SAPCode', 'version']:
|
|
hd_app_info[key] = json_info[key]
|
|
hd_app_info['SAPCode'] = json_info['SAPCode']
|
|
|
|
# Adobe puts an array of dicts in a dict with one key called 'Package'
|
|
pkgs = [pkg for pkg in json_info['Packages']['Package']]
|
|
hd_app_info['Packages'] = pkgs
|
|
return hd_app_info
|
|
|
|
|
|
def get_cs5_media_signature(dirpath):
|
|
'''Returns the CS5 mediaSignature for an AAMEE CS5 install.
|
|
dirpath is typically the root of a mounted dmg'''
|
|
|
|
payloads_dir = ""
|
|
# look for a payloads folder
|
|
for (path, dummy_dirs, dummy_files) in os.walk(dirpath):
|
|
if path.endswith('/payloads'):
|
|
payloads_dir = path
|
|
|
|
# return empty-handed if we didn't find a payloads folder
|
|
if not payloads_dir:
|
|
return ''
|
|
|
|
# now look for setup.xml
|
|
setupxml = os.path.join(payloads_dir, 'Setup.xml')
|
|
if os.path.exists(setupxml) and os.path.isfile(setupxml):
|
|
# parse the XML
|
|
dom = minidom.parse(setupxml)
|
|
setup_elements = dom.getElementsByTagName('Setup')
|
|
if setup_elements:
|
|
media_signature_elements = (
|
|
setup_elements[0].getElementsByTagName('mediaSignature'))
|
|
if media_signature_elements:
|
|
element = media_signature_elements[0]
|
|
elementvalue = ''
|
|
for node in element.childNodes:
|
|
elementvalue += node.nodeValue
|
|
return elementvalue
|
|
|
|
return ""
|
|
|
|
|
|
def get_cs5_uninstall_xml(option_xml_file):
|
|
'''Gets the uninstall deployment data from a CS5 installer'''
|
|
xml = ''
|
|
dom = minidom.parse(option_xml_file)
|
|
deployment_info = dom.getElementsByTagName('DeploymentInfo')
|
|
if deployment_info:
|
|
for info_item in deployment_info:
|
|
deployment_uninstall = info_item.getElementsByTagName(
|
|
'DeploymentUninstall')
|
|
if deployment_uninstall:
|
|
deployment_data = deployment_uninstall[0].getElementsByTagName(
|
|
'Deployment')
|
|
if deployment_data:
|
|
deployment = deployment_data[0]
|
|
xml += deployment.toxml('UTF-8')
|
|
return xml
|
|
|
|
|
|
def count_payloads(dirpath):
|
|
'''Attempts to count the payloads in the Adobe installation item.
|
|
Used for rough percent-done progress feedback.'''
|
|
count = 0
|
|
for (path, dummy_dirs, files) in os.walk(dirpath):
|
|
if path.endswith("/payloads"):
|
|
# RIBS-style installers
|
|
for subitem in osutils.listdir(path):
|
|
subitempath = os.path.join(path, subitem)
|
|
if os.path.isdir(subitempath):
|
|
count = count + 1
|
|
elif "/HD/" in path and "Application.json" in files:
|
|
# we're inside an HD installer directory. The payloads/packages
|
|
# are .zip files
|
|
zip_file_count = len(
|
|
[item for item in files if item.endswith(".zip")])
|
|
count = count + zip_file_count
|
|
return count
|
|
|
|
|
|
def get_adobe_install_info(installdir):
|
|
'''Encapsulates info used by the Adobe Setup/Install app.'''
|
|
adobe_install_info = {}
|
|
if installdir:
|
|
adobe_install_info['media_signature'] = get_cs5_media_signature(
|
|
installdir)
|
|
adobe_install_info['payload_count'] = count_payloads(installdir)
|
|
option_xml_file = os.path.join(installdir, "optionXML.xml")
|
|
if os.path.exists(option_xml_file):
|
|
adobe_install_info['uninstallxml'] = get_cs5_uninstall_xml(
|
|
option_xml_file)
|
|
|
|
return adobe_install_info
|
|
|
|
|
|
# Disable PyLint complaining about 'invalid' camelCase names
|
|
# pylint: disable=invalid-name
|
|
def getAdobeCatalogInfo(mountpoint, pkgname=""):
|
|
'''Used by makepkginfo to build pkginfo data for Adobe
|
|
installers/updaters'''
|
|
|
|
# look for AdobeDeploymentManager (AAMEE installer)
|
|
deploymentmanager = find_adobe_deployment_manager(mountpoint)
|
|
if deploymentmanager:
|
|
dirpath = os.path.dirname(deploymentmanager)
|
|
option_xml_file = os.path.join(dirpath, 'optionXML.xml')
|
|
option_xml_info = {}
|
|
if os.path.exists(option_xml_file):
|
|
option_xml_info = parse_option_xml(option_xml_file)
|
|
cataloginfo = get_adobe_package_info(dirpath)
|
|
if cataloginfo:
|
|
# add some more data
|
|
if option_xml_info.get('packager_id') == u'CloudPackager':
|
|
# CCP package
|
|
cataloginfo['display_name'] = option_xml_info.get(
|
|
'package_name', 'unknown')
|
|
cataloginfo['name'] = cataloginfo['display_name'].replace(
|
|
' ', '')
|
|
cataloginfo['uninstallable'] = True
|
|
cataloginfo['uninstall_method'] = "AdobeCCPUninstaller"
|
|
cataloginfo['installer_type'] = "AdobeCCPInstaller"
|
|
cataloginfo['minimum_os_version'] = "10.6.8"
|
|
mediasignatures = [
|
|
item['mediaSignature']
|
|
for item in option_xml_info.get('products', [])
|
|
if 'mediaSignature' in item]
|
|
else:
|
|
# AAMEE package
|
|
cataloginfo['name'] = cataloginfo['display_name'].replace(
|
|
' ', '')
|
|
cataloginfo['uninstallable'] = True
|
|
cataloginfo['uninstall_method'] = "AdobeCS5AAMEEPackage"
|
|
cataloginfo['installer_type'] = "AdobeCS5AAMEEPackage"
|
|
cataloginfo['minimum_os_version'] = "10.5.0"
|
|
cataloginfo['adobe_install_info'] = get_adobe_install_info(
|
|
installdir=dirpath)
|
|
mediasignature = cataloginfo['adobe_install_info'].get(
|
|
"media_signature")
|
|
mediasignatures = [mediasignature]
|
|
|
|
# Determine whether we have HD media as well in this installer
|
|
hd_metadata_dirs = [
|
|
product['TargetFolderName']
|
|
for product in option_xml_info['products']
|
|
if product.get('hd_installer')]
|
|
|
|
hd_app_infos = []
|
|
for sap_code in hd_metadata_dirs:
|
|
hd_app_info = get_hd_installer_info(
|
|
os.path.join(dirpath, 'HD'), sap_code)
|
|
hd_app_infos.append(hd_app_info)
|
|
|
|
# 'installs' array will be populated if we have either RIBS
|
|
# or HD installers, which may be mixed together in one
|
|
# CCP package.
|
|
# Acrobat Pro DC doesn't currently generate any useful installs
|
|
# info if it's part of a CCP package.
|
|
installs = []
|
|
|
|
# media signatures are used for RIBS (CS5 to CC mid-2015)
|
|
if mediasignatures:
|
|
# make a default <key>installs</key> array
|
|
uninstalldir = "/Library/Application Support/Adobe/Uninstall"
|
|
for mediasignature in mediasignatures:
|
|
signaturefile = mediasignature + ".db"
|
|
filepath = os.path.join(uninstalldir, signaturefile)
|
|
installitem = {}
|
|
installitem['path'] = filepath
|
|
installitem['type'] = 'file'
|
|
installs.append(installitem)
|
|
|
|
# Custom installs items for HD installers seem to need only HDMedias
|
|
# from optionXML.xml with a MediaType of 'Product' and their
|
|
# 'core' packages (e.g. language packs are 'non-core')
|
|
if hd_app_infos:
|
|
if 'payloads' not in cataloginfo:
|
|
cataloginfo['payloads'] = []
|
|
cataloginfo['payloads'].extend(hd_app_infos)
|
|
|
|
# Calculate installed_size by counting packages in payloads
|
|
# in these indexed HD medias. installed_size may exist already
|
|
# if this package contained RIBS payloads, so try reading it
|
|
# and default to 0. This will typically include several very
|
|
# small packages (language or regional recommended settings)
|
|
# which would not actually get installed. These seem to be
|
|
# no larger than a few MB, so in practice it increases the
|
|
# 'installed_size' value by only ~1%.
|
|
installed_size = cataloginfo.get('installed_size', 0)
|
|
for hd_payload in hd_app_infos:
|
|
for package in hd_payload['Packages']:
|
|
# Generally, all app installs will include 1-3 'core'
|
|
# packages and then additional language/settings/color
|
|
# packages which are regional or language-specific.
|
|
# If we filter this by including both unconditional
|
|
# installs and those which are language/region specific,
|
|
# we get a rough approximation of the total size of
|
|
# supplemental packages, as their equivalents for other
|
|
# languages are very close to the same size. We also
|
|
# get one included language package which would be the
|
|
# case for any install.
|
|
#
|
|
# Because InDesign CC 2017 is not like any other package
|
|
# and contains a 'Condition' key but as an empty
|
|
# string, we explicitly test this case as well.
|
|
if ('Condition' not in list(package.keys()) or
|
|
package.get('Condition') == '' or
|
|
'[installLanguage]==en_US' in
|
|
package.get('Condition', '')):
|
|
installed_size += int(package.get(
|
|
'ExtractSize', 0) / 1024)
|
|
# We get much closer to Adobe's "HDSetup" calculated
|
|
# install space requirement if we include both the
|
|
# DownloadSize and ExtractSize data
|
|
# (DownloadSize is just the zip file size)
|
|
installed_size += int(package.get(
|
|
'DownloadSize', 0) / 1024)
|
|
# Add another 300MB for the CC app and plumbing in case they've
|
|
# never been installed on the system
|
|
installed_size += 307200
|
|
cataloginfo['installed_size'] = installed_size
|
|
|
|
uninstalldir = (
|
|
'/Library/Application Support/Adobe/Installers/uninstallXml'
|
|
)
|
|
product_saps = [
|
|
prod['SAPCode'] for
|
|
prod in option_xml_info['products']
|
|
if prod.get('MediaType') == 'Product'
|
|
]
|
|
|
|
product_app_infos = [app for app in hd_app_infos
|
|
if app['SAPCode'] in product_saps]
|
|
# if we had only a single HD and no legacy apps, set a sane
|
|
# version and display_name derived from the app's metadata
|
|
if (len(product_app_infos) == 1) and not mediasignatures:
|
|
cataloginfo.update({
|
|
'display_name': product_app_infos[0]['Name'],
|
|
'version': product_app_infos[0]['ProductVersion'],
|
|
})
|
|
|
|
for app_info in product_app_infos:
|
|
for pkg in app_info['Packages']:
|
|
# Don't assume 'Type' key always exists. At least the
|
|
#'AdobeIllustrator20-Settings'
|
|
# package doesn't have this key set.
|
|
if pkg.get('Type') == 'core':
|
|
# We can't use 'ProductVersion' from
|
|
# Application.json for the part following the
|
|
# SAPCode, because it's usually too specific and
|
|
# won't match the "short" product version.
|
|
# We can take 'prodVersion' from the optionXML.xml
|
|
# instead.
|
|
# We filter out any non-HD installers to avoid
|
|
# matching up the wrong versions for packages that
|
|
# may contain multiple different major versions of
|
|
# a given SAPCode
|
|
pkg_prod_vers = [
|
|
prod['prodVersion']
|
|
for prod in option_xml_info['products']
|
|
if prod.get('hd_installer') and
|
|
prod['SAPCode'] == app_info['SAPCode']][0]
|
|
uninstall_file_name = '_'.join([
|
|
app_info['SAPCode'],
|
|
pkg_prod_vers.replace('.', '_'),
|
|
pkg['PackageName'],
|
|
pkg['PackageVersion']]) + '.pimx'
|
|
filepath = os.path.join(
|
|
uninstalldir, uninstall_file_name)
|
|
installitem = {}
|
|
installitem['path'] = filepath
|
|
installitem['type'] = 'file'
|
|
installs.append(installitem)
|
|
|
|
if installs:
|
|
cataloginfo['installs'] = installs
|
|
|
|
return cataloginfo
|
|
|
|
# Look for Install.app (Bare metal CS5 install)
|
|
# we don't handle this type, but we'll report it
|
|
# back so makepkginfo can provide an error message
|
|
installapp = find_install_app(mountpoint)
|
|
if installapp:
|
|
cataloginfo = {}
|
|
cataloginfo['installer_type'] = "AdobeCS5Installer"
|
|
return cataloginfo
|
|
|
|
# Look for AdobePatchInstaller.app (CS5 updater)
|
|
installapp = find_adobepatchinstaller_app(mountpoint)
|
|
if os.path.exists(installapp):
|
|
# this is a CS5 updater disk image
|
|
cataloginfo = get_adobe_package_info(mountpoint)
|
|
if cataloginfo:
|
|
# add some more data
|
|
cataloginfo['name'] = cataloginfo['display_name'].replace(' ', '')
|
|
cataloginfo['uninstallable'] = False
|
|
cataloginfo['installer_type'] = "AdobeCS5PatchInstaller"
|
|
if pkgname:
|
|
cataloginfo['package_path'] = pkgname
|
|
|
|
# make some (hopefully functional) installs items from the payloads
|
|
installs = []
|
|
uninstalldir = "/Library/Application Support/Adobe/Uninstall"
|
|
# first look for a payload with a display_name matching the
|
|
# overall display_name
|
|
for payload in cataloginfo.get('payloads', []):
|
|
if (payload.get('display_name', '') ==
|
|
cataloginfo['display_name']):
|
|
if 'AdobeCode' in payload:
|
|
dbfile = payload['AdobeCode'] + ".db"
|
|
filepath = os.path.join(uninstalldir, dbfile)
|
|
installitem = {}
|
|
installitem['path'] = filepath
|
|
installitem['type'] = 'file'
|
|
installs.append(installitem)
|
|
break
|
|
|
|
if installs == []:
|
|
# didn't find a payload with matching name
|
|
# just add all of the non-LangPack payloads
|
|
# to the installs list.
|
|
for payload in cataloginfo.get('payloads', []):
|
|
if 'AdobeCode' in payload:
|
|
if ("LangPack" in payload.get("display_name") or
|
|
"Language Files" in payload.get(
|
|
"display_name")):
|
|
# skip Language Packs
|
|
continue
|
|
dbfile = payload['AdobeCode'] + ".db"
|
|
filepath = os.path.join(uninstalldir, dbfile)
|
|
installitem = {}
|
|
installitem['path'] = filepath
|
|
installitem['type'] = 'file'
|
|
installs.append(installitem)
|
|
|
|
cataloginfo['installs'] = installs
|
|
return cataloginfo
|
|
|
|
# Look for AdobeUberInstaller items (CS4 install)
|
|
pkgroot = os.path.join(mountpoint, pkgname)
|
|
adobeinstallxml = os.path.join(pkgroot, "AdobeUberInstaller.xml")
|
|
if os.path.exists(adobeinstallxml):
|
|
# this is a CS4 Enterprise Deployment package
|
|
cataloginfo = get_adobe_package_info(pkgroot)
|
|
if cataloginfo:
|
|
# add some more data
|
|
cataloginfo['name'] = cataloginfo['display_name'].replace(' ', '')
|
|
cataloginfo['uninstallable'] = True
|
|
cataloginfo['uninstall_method'] = "AdobeUberUninstaller"
|
|
cataloginfo['installer_type'] = "AdobeUberInstaller"
|
|
if pkgname:
|
|
cataloginfo['package_path'] = pkgname
|
|
return cataloginfo
|
|
|
|
# maybe this is an Adobe update DMG or CS3 installer
|
|
# look for Adobe Setup.app
|
|
setuppath = find_setup_app(mountpoint)
|
|
if setuppath:
|
|
cataloginfo = get_adobe_setup_info(mountpoint)
|
|
if cataloginfo:
|
|
# add some more data
|
|
cataloginfo['name'] = cataloginfo['display_name'].replace(' ', '')
|
|
cataloginfo['installer_type'] = "AdobeSetup"
|
|
if cataloginfo.get('AdobeSetupType') == "ProductInstall":
|
|
cataloginfo['uninstallable'] = True
|
|
cataloginfo['uninstall_method'] = "AdobeSetup"
|
|
else:
|
|
cataloginfo['description'] = "Adobe updater"
|
|
cataloginfo['uninstallable'] = False
|
|
cataloginfo['update_for'] = ["PleaseEditMe-1.0.0.0.0"]
|
|
return cataloginfo
|
|
|
|
# maybe this is an Adobe Acrobat 9 Pro patcher?
|
|
acrobatpatcherapp = find_acrobat_patch_app(mountpoint)
|
|
if acrobatpatcherapp:
|
|
cataloginfo = {}
|
|
cataloginfo['installer_type'] = "AdobeAcrobatUpdater"
|
|
cataloginfo['uninstallable'] = False
|
|
plist = pkgutils.getBundleInfo(acrobatpatcherapp)
|
|
cataloginfo['version'] = pkgutils.getVersionString(plist)
|
|
cataloginfo['name'] = "AcrobatPro9Update"
|
|
cataloginfo['display_name'] = "Adobe Acrobat Pro Update"
|
|
cataloginfo['update_for'] = ["AcrobatPro9"]
|
|
cataloginfo['RestartAction'] = 'RequireLogout'
|
|
cataloginfo['requires'] = []
|
|
cataloginfo['installs'] = [
|
|
{'CFBundleIdentifier': 'com.adobe.Acrobat.Pro',
|
|
'CFBundleName': 'Acrobat',
|
|
'CFBundleShortVersionString': cataloginfo['version'],
|
|
'path': '/Applications/Adobe Acrobat 9 Pro/Adobe Acrobat Pro.app',
|
|
'type': 'application'}
|
|
]
|
|
return cataloginfo
|
|
|
|
# didn't find any Adobe installers/updaters we understand
|
|
return None
|
|
# pylint: enable=invalid-name
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print('This is a library of support tools for the Munki Suite.')
|