mirror of
https://github.com/munki/munki.git
synced 2026-02-22 23:20:04 -06:00
- ID has a 'Condition' key in its Application.json for the 'Base' (main) app HD package, but it's an empty string, so just include this explicitly in the conditional
788 lines
34 KiB
Python
788 lines
34 KiB
Python
# encoding: utf-8
|
|
# Copyright 2009-2017 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
|
|
|
|
"""
|
|
|
|
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 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(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 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 ot items of interest, returning
|
|
them in a dictionary'''
|
|
info = {}
|
|
dom = minidom.parse(option_xml_file)
|
|
installinfo = dom.getElementsByTagName('InstallInfo')
|
|
if installinfo:
|
|
if 'id' in installinfo[0].attributes.keys():
|
|
info['packager_id'] = installinfo[0].attributes['id'].value
|
|
if 'version' in 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 package.keys() or
|
|
package.get('Condition') == '' or
|
|
'[installLanguage]==en_US' in
|
|
package.get('Condition', '')):
|
|
installed_size += 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 += 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 (hopfully 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.'
|