Skip to content

Instantly share code, notes, and snippets.

@Jaharmi
Forked from bruienne/getosversionfromdmg.py
Last active September 26, 2017 03:05
Show Gist options
  • Save Jaharmi/ad08181118d5fab5afc713fd11e3b117 to your computer and use it in GitHub Desktop.
Save Jaharmi/ad08181118d5fab5afc713fd11e3b117 to your computer and use it in GitHub Desktop.
Get OS X version from DMG
#!/usr/bin/python
#
# getosversionfromdmg.py
#
# Copyright (c) 2014 The Regents of the University of Michigan
# Copyright (c) 2017 Jeremy Reichman
#
# Retrieves the OS version and build from the InstallESD.dmg contained in
# a typical "Install [Mac OS X|OS X|macOS] <Name>.app" bundle.
#
# To run:
# ./getosversionfromdmg.py \
# "/Applications/Install [Mac OS X|OS X|macOS] <Name>.app"
#
# One modes:
# - When run without sudo it reports the version and build to STDOUT
#
# Based on code written for the Munki project by Greg Neagle:
# https://code.google.com/p/munki/
# Based on code:
# https://gist.github.com/bruienne/f9fc1c7039f137a49203
import os
import sys
import tempfile
import subprocess
import plistlib
def cleanUp():
'''Cleanup our TMPDIR'''
if TMPDIR:
import shutil
shutil.rmtree(TMPDIR, ignore_errors=True)
def fail(errmsg=''):
'''Print any error message to stderr,
clean up install data, and exit'''
if errmsg:
print >> sys.stderr, errmsg
cleanUp()
# exit
exit(1)
def mountdmg(dmgpath, use_shadow=False):
"""
Attempts to mount the dmg at dmgpath
and returns a list of mountpoints
If use_shadow is true, mount image with shadow file
"""
mountpoints = []
dmgname = os.path.basename(dmgpath)
cmd = ['/usr/bin/hdiutil', 'attach', dmgpath,
'-mountRandom', TMPDIR, '-nobrowse', '-plist',
'-owners', 'on']
if use_shadow:
shadowname = dmgname + '.shadow'
shadowroot = os.path.dirname(dmgpath)
shadowpath = os.path.join(shadowroot, shadowname)
cmd.extend(['-shadow', shadowpath])
else:
shadowpath = None
proc = subprocess.Popen(cmd, bufsize=-1,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(pliststr, err) = proc.communicate()
if proc.returncode:
print >> sys.stderr, 'Error: "%s" while mounting %s.' % (err, dmgname)
if pliststr:
plist = plistlib.readPlistFromString(pliststr)
for entity in plist['system-entities']:
if 'mount-point' in entity:
mountpoints.append(entity['mount-point'])
return mountpoints, shadowpath
def unmountdmg(mountpoint):
"""
Unmounts the dmg at mountpoint
"""
proc = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint],
bufsize=-1, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(unused_output, err) = proc.communicate()
if proc.returncode:
print >> sys.stderr, 'Polite unmount failed: %s' % err
print >> sys.stderr, 'Attempting to force unmount %s' % mountpoint
# try forcing the unmount
retcode = subprocess.call(['/usr/bin/hdiutil', 'detach', mountpoint,
'-force'])
if retcode:
print >> sys.stderr, 'Failed to unmount %s' % mountpoint
def getosversioninfo(mountpoint):
""""getosversioninfo will attempt to retrieve the OS X version and build
from the given mount point by reading /S/L/CS/SystemVersion.plist
Most of the code comes from COSXIP without changes."""
system_version_plist = os.path.join(
mountpoint,
'System/Library/CoreServices/SystemVersion.plist')
# Check for availability of BaseSystem.dmg
basesystem_dmg_found = False
basesystem_dmg = os.path.join(mountpoint, 'BaseSystem.dmg')
if os.path.isfile(basesystem_dmg):
# Mount BaseSystem.dmg
basesystemmountpoints, unused_shadowpath = mountdmg(basesystem_dmg)
basesystemmountpoint = basesystemmountpoints[0]
# Read SystemVersion.plist from the mounted BaseSystem.dmg
system_version_plist = os.path.join(
basesystemmountpoint,
'System/Library/CoreServices/SystemVersion.plist')
basesystem_dmg_found = True
elif os.path.isfile(system_version_plist):
print('Found system version property list at path \'{0}.\''.format(system_version_plist))
else:
unmountdmg(mountpoint)
fail('Missing BaseSystem.dmg in %s'% mountpoint)
# Now parse the .plist file
try:
version_info = plistlib.readPlist(system_version_plist)
# Got errors?
except (ExpatError, IOError), err:
unmountdmg(basesystemmountpoint)
unmountdmg(mountpoint)
fail('Could not read %s: %s' % (system_version_plist, err))
# Done, unmount BaseSystem.dmg
else:
if basesystem_dmg_found:
unmountdmg(basesystemmountpoint)
# Return the version and build as found in the parsed plist
return version_info.get('ProductUserVisibleVersion'), \
version_info.get('ProductBuildVersion')
TMPDIR = None
def main():
"""Docstring"""
global TMPDIR
# Spin up a tmp dir for mounting
TMPDIR = tempfile.mkdtemp(dir=TMPDIR)
if len(sys.argv) < 2:
print '\nNo path to an InstallESD.dmg provided, stopping.\nInvoke with "getosversionfromdmg.py <path to InstallESD.dmg>" and try again.\n'
sys.exit(-1)
install_app_path_input = sys.argv[1]
install_app_path = os.path.abspath(os.path.expanduser(install_app_path_input))
install_app_path_name, install_app_path_extension = os.path.splitext(install_app_path)
install_app_path_basename = os.path.basename(install_app_path)
# dmgfilepath, ext = os.path.splitext(dmg)
if install_app_path_extension.endswith('.app') and install_app_path_basename.startswith("Install"):
print 'Path found for macOS installer application at \'{0}.\''.format(install_app_path)
else:
print 'Path for macOS installer application not found at \'{0}.\''.format(install_app_path)
sys.exit(1)
print 'Getting OS version and build for ' + install_app_path + "\n"
shared_support_path = 'Contents/SharedSupport'
shared_support_disk_images = [
'BaseSystem.dmg',
'InstallESD.dmg',
]
# Find first available disk image that can contain version information
disk_images_with_version_information = [os.path.join(
install_app_path,
shared_support_path,
shared_support_disk_image)
for shared_support_disk_image
in shared_support_disk_images]
found_disk_images = []
for disk_image_with_version_information in disk_images_with_version_information:
if os.path.isfile(disk_image_with_version_information):
print('Path \'{0}\' represents an existing file.').format(disk_image_with_version_information)
found_disk_images.append(disk_image_with_version_information)
else:
print('Path \'{0}\' does not represent an existing file.').format(disk_image_with_version_information)
if found_disk_images:
dmg = found_disk_images[0]
print('First disk image with version information at \'{0}\' selected.').format(dmg)
else:
print('No disk images with version information are available.').format()
mountpoints, shadow = mountdmg(dmg)
for mount in mountpoints:
print(mount)
if mount.find('dmg'):
os_version, os_build = getosversioninfo(mount)
print 'OS X Version: ' + os_version + '\n' + 'OS X Build: ' + os_build + '\n'
unmountdmg(mount)
# if dmgfilepath.endswith('InstallESD') and os.getuid() == 0:
# print "We're running as root so we'll rename the DMG to reflect its OS version and build.\n"
# os.rename(dmg, dmgfilepath + '_' + os_version + '_' + os_build + '.dmg')
# elif not dmgfilepath.endswith('InstallESD') and os.getuid() == 0:
# print "Looks like the DMG file was previously renamed so we're skipping that step."
# else:
# print "Not running as root so we're not renaming " + dmg + " to contain the version and build."
# print "Re-run this tool with sudo to rename it.\n"
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment