mirror of
https://github.com/munki/munki.git
synced 2026-02-14 03:00:00 -06:00
Notes provide an area for administrators to store information that is purely reserved for administrative use and not considered in any type of munki processing.
733 lines
31 KiB
Python
Executable File
733 lines
31 KiB
Python
Executable File
#!/usr/bin/python
|
|
# encoding: utf-8
|
|
#
|
|
# Copyright 2009-2011 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
|
|
#
|
|
# http://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.
|
|
"""
|
|
makepkginfo
|
|
|
|
Created by Greg Neagle on 2008-11-25.
|
|
Creates a managed install pkg info plist given an Installer item:
|
|
a .pkg, a .mpkg, or a .dmg containing a .pkg or .mpkg
|
|
at the root of the mounted disk image.
|
|
|
|
You may also pass additional items that are installed by the package. These
|
|
are added to the 'installs' key of the catalog item plist and are used when
|
|
processing the catalog to check if the package needs to be installed or
|
|
reinstalled.
|
|
|
|
The generated plist is printed to STDOUT.
|
|
|
|
Usage: makepkginfo /path/to/package_or_dmg [-f /path/to/item/it/installs ...]
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
import optparse
|
|
from optparse import OptionValueError
|
|
import subprocess
|
|
|
|
from munkilib import munkicommon
|
|
from munkilib import FoundationPlist
|
|
from munkilib import adobeutils
|
|
|
|
|
|
def getCatalogInfoFromDmg(dmgpath, options):
|
|
"""
|
|
* Mounts a disk image
|
|
* Gets catalog info for the first installer item found at the root level.
|
|
* Unmounts the disk image
|
|
|
|
To-do: handle multiple installer items on a disk image(?)
|
|
"""
|
|
cataloginfo = None
|
|
mountpoints = munkicommon.mountdmg(dmgpath)
|
|
if not mountpoints:
|
|
print >> sys.stderr, "Could not mount %s!" % dmgpath
|
|
exit(-1)
|
|
|
|
if options.pkgname:
|
|
pkgpath = os.path.join(mountpoints[0], options.pkgname)
|
|
if os.path.exists(pkgpath):
|
|
cataloginfo = munkicommon.getPackageMetaData(pkgpath)
|
|
if options.installer_choices_xml:
|
|
installer_choices_xml = munkicommon.getChoiceChangesXML(pkgpath)
|
|
if installer_choices_xml:
|
|
cataloginfo['installer_choices_xml'] = installer_choices_xml
|
|
if cataloginfo:
|
|
cataloginfo['package_path'] = options.pkgname
|
|
elif not options.item:
|
|
# search for first package at root
|
|
for fsitem in munkicommon.listdir(mountpoints[0]):
|
|
itempath = os.path.join(mountpoints[0], fsitem)
|
|
if itempath.endswith('.pkg') or itempath.endswith('.mpkg'):
|
|
cataloginfo = munkicommon.getPackageMetaData(itempath)
|
|
if options.installer_choices_xml:
|
|
installer_choices_xml = munkicommon.getChoiceChangesXML(itempath)
|
|
if installer_choices_xml:
|
|
cataloginfo['installer_choices_xml'] = installer_choices_xml
|
|
# get out of fsitem loop
|
|
break
|
|
|
|
if cataloginfo:
|
|
# we found a package, but let's see if it's an Adobe CS5 install
|
|
# (AAMEE) package
|
|
if 'receipts' in cataloginfo:
|
|
try:
|
|
pkgid = cataloginfo['receipts'][0].get('packageid')
|
|
except IndexError:
|
|
pkgid = ""
|
|
if pkgid.startswith("com.adobe.Enterprise.install"):
|
|
# we have an Adobe CS5 install package, process
|
|
# as Adobe install
|
|
adobepkgname = cataloginfo['receipts'][0].get('filename')
|
|
cataloginfo = adobeutils.getAdobeCatalogInfo(
|
|
mountpoints[0], adobepkgname)
|
|
|
|
else:
|
|
# maybe an Adobe installer/updater/patcher?
|
|
cataloginfo = adobeutils.getAdobeCatalogInfo(mountpoints[0],
|
|
options.pkgname or '')
|
|
|
|
if not cataloginfo:
|
|
# maybe this is a drag-n-drop dmg
|
|
# look for given item or an app at the top level of the dmg
|
|
iteminfo = {}
|
|
if options.item:
|
|
item = options.item
|
|
itempath = os.path.join(mountpoints[0], item)
|
|
if os.path.exists(itempath):
|
|
iteminfo = getiteminfo(itempath)
|
|
else:
|
|
print >> sys.stderr, \
|
|
"%s not found on disk image." % item
|
|
else:
|
|
# no item specified; look for an application at root of
|
|
# mounted dmg
|
|
item = ''
|
|
for itemname in munkicommon.listdir(mountpoints[0]):
|
|
itempath = os.path.join(mountpoints[0], itemname)
|
|
if munkicommon.isApplication(itempath):
|
|
item = itemname
|
|
iteminfo = getiteminfo(itempath)
|
|
if iteminfo:
|
|
break
|
|
|
|
if iteminfo:
|
|
if options.destinationpath:
|
|
iteminfo['path'] = os.path.join(options.destinationpath,
|
|
item)
|
|
else:
|
|
iteminfo['path'] = os.path.join("/Applications", item)
|
|
cataloginfo = {}
|
|
cataloginfo['name'] = iteminfo.get('CFBundleName',
|
|
os.path.splitext(item)[0])
|
|
cataloginfo['version'] = \
|
|
iteminfo.get('CFBundleShortVersionString', "0")
|
|
cataloginfo['installs'] = [iteminfo]
|
|
if options.appdmg:
|
|
cataloginfo['installer_type'] = "appdmg"
|
|
cataloginfo['uninstallable'] = True
|
|
cataloginfo['uninstall_method'] = "remove_app"
|
|
else:
|
|
cataloginfo['installer_type'] = "copy_from_dmg"
|
|
item_to_copy = {}
|
|
item_to_copy['source_item'] = item
|
|
item_to_copy['destination_path'] = \
|
|
options.destinationpath or "/Applications"
|
|
if options.user:
|
|
item_to_copy['user'] = options.user
|
|
if options.group:
|
|
item_to_copy['group'] = options.group
|
|
if options.mode:
|
|
item_to_copy['mode'] = options.mode
|
|
cataloginfo['items_to_copy'] = [item_to_copy]
|
|
cataloginfo['uninstallable'] = True
|
|
cataloginfo['uninstall_method'] = "remove_copied_items"
|
|
|
|
#eject the dmg
|
|
munkicommon.unmountdmg(mountpoints[0])
|
|
return cataloginfo
|
|
|
|
|
|
def getBundleInfo(path):
|
|
"""
|
|
Returns Info.plist data if available
|
|
for bundle at path
|
|
"""
|
|
infopath = os.path.join(path, "Contents", "Info.plist")
|
|
if not os.path.exists(infopath):
|
|
infopath = os.path.join(path, "Resources", "Info.plist")
|
|
|
|
if os.path.exists(infopath):
|
|
try:
|
|
plist = FoundationPlist.readPlist(infopath)
|
|
return plist
|
|
except FoundationPlist.NSPropertyListSerializationException:
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
def readfile(path):
|
|
'''Reads file at path. Returns a string.'''
|
|
try:
|
|
fileobject = open(os.path.expanduser(path), mode='r', buffering=1)
|
|
data = fileobject.read()
|
|
fileobject.close()
|
|
return data
|
|
except (OSError, IOError):
|
|
print >> sys.stderr, "Couldn't read %s" % path
|
|
return ""
|
|
|
|
|
|
def getiteminfo(itempath):
|
|
"""
|
|
Gets info for filesystem items passed to makecatalog item, to be used for
|
|
the "installs" key.
|
|
Determines if the item is an application, bundle, Info.plist, or a file or
|
|
directory and gets additional metadata for later comparison.
|
|
"""
|
|
infodict = {}
|
|
if munkicommon.isApplication(itempath):
|
|
infodict['type'] = 'application'
|
|
infodict['path'] = itempath
|
|
plist = getBundleInfo(itempath)
|
|
if 'CFBundleName' in plist:
|
|
infodict['CFBundleName'] = plist['CFBundleName']
|
|
if 'CFBundleIdentifier' in plist:
|
|
infodict['CFBundleIdentifier'] = plist['CFBundleIdentifier']
|
|
infodict['CFBundleShortVersionString'] = \
|
|
munkicommon.getVersionString(plist)
|
|
if 'LSMinimumSystemVersion' in plist:
|
|
infodict['minosversion'] = plist['LSMinimumSystemVersion']
|
|
elif 'SystemVersionCheck:MinimumSystemVersion' in plist:
|
|
infodict['minosversion'] = \
|
|
plist['SystemVersionCheck:MinimumSystemVersion']
|
|
|
|
elif os.path.exists(os.path.join(itempath, 'Contents', 'Info.plist')) or \
|
|
os.path.exists(os.path.join(itempath, 'Resources', 'Info.plist')):
|
|
infodict['type'] = 'bundle'
|
|
infodict['path'] = itempath
|
|
plist = getBundleInfo(itempath)
|
|
infodict['CFBundleShortVersionString'] = \
|
|
munkicommon.getVersionString(plist)
|
|
|
|
elif itempath.endswith("Info.plist") or \
|
|
itempath.endswith("version.plist"):
|
|
infodict['type'] = 'plist'
|
|
infodict['path'] = itempath
|
|
try:
|
|
plist = FoundationPlist.readPlist(itempath)
|
|
infodict['CFBundleShortVersionString'] = \
|
|
munkicommon.getVersionString(plist)
|
|
except FoundationPlist.NSPropertyListSerializationException:
|
|
pass
|
|
|
|
if not 'CFBundleShortVersionString' in infodict and \
|
|
not 'CFBundleVersion' in infodict:
|
|
infodict['type'] = 'file'
|
|
infodict['path'] = itempath
|
|
if os.path.isfile(itempath):
|
|
infodict['md5checksum'] = munkicommon.getmd5hash(itempath)
|
|
return infodict
|
|
|
|
|
|
def check_mode(option, opt, value, parser):
|
|
'''Callback to check --mode options'''
|
|
modes = value.lower().replace(',', ' ').split()
|
|
value = None
|
|
rex = re.compile("[augo]+[=+-][rstwxXugo]+")
|
|
for mode in modes:
|
|
if rex.match(mode):
|
|
value = mode if not value else (value + "," + mode)
|
|
else:
|
|
raise OptionValueError("option %s: invalid mode: %s" %
|
|
(opt, mode))
|
|
setattr(parser.values, option.dest, value)
|
|
|
|
|
|
def main():
|
|
'''Main routine'''
|
|
usage = """usage: %prog [options] [/path/to/installeritem]
|
|
%prog --help for more information."""
|
|
p = optparse.OptionParser(usage=usage)
|
|
p.add_option('--file', '-f', action="append",
|
|
help='''Path to a filesystem item installed by this
|
|
package, typically an application. This generates an
|
|
"installs" item for the pkginfo, an item munki can
|
|
use to determine if this software has been installed.
|
|
Can be specified multiple times.''')
|
|
p.add_option('--pkgname', '-p',
|
|
help='''Optional flag.
|
|
|
|
-If the installer item is a disk image containing
|
|
multiple packages, or the package to be installed
|
|
is not at the root of the mounted disk image, PKGNAME
|
|
is a relative path from the root of the mounted
|
|
disk image to the specific package to be installed.
|
|
|
|
-If the installer item is a disk image containing
|
|
an Adobe CS4 Deployment Toolkit installation, PKGNAME
|
|
is the name of an Adobe CS4 Deployment Toolkit
|
|
installer package folder at the top level of the
|
|
mounted dmg.
|
|
|
|
If this flag is missing, the AdobeUber* files should
|
|
be at the top level of the mounted dmg.''')
|
|
p.add_option('--appdmg', action="store_true",
|
|
help='''Optional flag.
|
|
|
|
Causes makepkginfo to create a pkginfo item describing
|
|
an appdmg install instead of the newer copy_from_dmg
|
|
installer type. Meant for use with older munki
|
|
clients, as copy_from_dmg replaces appdmg in munki
|
|
0.6.0 and later.''')
|
|
p.add_option('--itemname', '-i', '--appname', '-a',
|
|
metavar='ITEM',
|
|
dest='item',
|
|
help='''Optional flag.
|
|
|
|
If the installer item is a disk image with a
|
|
drag-and-drop item, ITEMNAME is the name or
|
|
relative path of the item to be installed.
|
|
Useful if there is more than one item at the
|
|
root of the dmg.''')
|
|
p.add_option('--displayname',
|
|
help='''Optional flag.
|
|
|
|
String display name of the package.
|
|
Note: overrides any display_name in the package itself''')
|
|
p.add_option('--description',
|
|
help='''Optional flag.
|
|
|
|
String description of the package.
|
|
Note: overrides any description in the package itself''')
|
|
p.add_option('--autoremove', action='store_true',
|
|
help='''Optional flag.
|
|
|
|
Implies 'true' for 'autoremove' for the installer item.''')
|
|
p.add_option('--unattended_install', action='store_true',
|
|
help='''Optional flag.
|
|
|
|
Implies 'true' for 'unattended_install' for the installer item.''')
|
|
p.add_option('--unattended_uninstall', action='store_true',
|
|
help='''Optional flag.
|
|
|
|
Implies 'true' for 'unattended_uninstall' for the installer item.''')
|
|
p.add_option('--minimum_os_version',
|
|
metavar='VERSION',
|
|
help='''Optional flag.
|
|
|
|
Minimum OS version for the installer item.''')
|
|
p.add_option('--maximum_os_version',
|
|
metavar='VERSION',
|
|
help='''Optional flag.
|
|
|
|
Maximum OS version for the installer item.''')
|
|
p.add_option('--installer_choices_xml', action='store_true',
|
|
help='''Optional flag
|
|
|
|
Specify that installer choices should be generated.
|
|
Note: Requires Mac OS X 10.6.6 or later.''')
|
|
p.add_option('--force_install_after_date',
|
|
metavar='yyyy-dd-hhThh:mm:ssZ',
|
|
help='''Optional flag.
|
|
|
|
Specify a date after which the package will be forcefully installed.
|
|
|
|
Example: '2011-08-03T13:00:00Z' equates to August 3rd 2011 at 1PM.''')
|
|
p.add_option('--RestartAction',
|
|
metavar='ACTION',
|
|
help='''Optional flag.
|
|
|
|
Specify a 'RestartAction' for the package.
|
|
|
|
RequireRestart
|
|
RequireLogout
|
|
RecommendRestart''')
|
|
p.add_option('--update_for', '-U', action="append",
|
|
metavar='PACKAGE',
|
|
help='''Optional flag.
|
|
|
|
Specifies which package this is an update for.
|
|
Can be specified multiple times
|
|
to build an array of multiple packages.''')
|
|
p.add_option('--requires', '-r', action="append",
|
|
metavar='PACKAGE',
|
|
help='''Optional flag.
|
|
|
|
Specifies which package this package is requires.
|
|
Can be specified multiple times
|
|
to build an array of required packages.''')
|
|
p.add_option('--blocking_applications', '-b', action="append",
|
|
metavar='APPLICATION',
|
|
help='''Optional flag.
|
|
|
|
Specifies a blocking application that will prevent installation.
|
|
Can be specified multiple times
|
|
to build an array of blocking applications.''')
|
|
p.add_option('--notes',
|
|
metavar='[STRING|PATH]',
|
|
help='''Optional flag.
|
|
|
|
Specifies administrator provided notes
|
|
to be inserted into the pkginfo.
|
|
Can be a PATH to a file.''')
|
|
p.add_option('--destinationpath', '-d',
|
|
help='''Optional flag.
|
|
|
|
If the installer item is a disk image with a
|
|
drag-and-drop item, this is the path to which
|
|
the item should be copied. Defaults to
|
|
"/Applications".''')
|
|
p.add_option('--uninstallerdmg', '-u',
|
|
help='''Optional flag.
|
|
|
|
If the installer item is a disk image containing an
|
|
Adobe CS4 Deployment Toolkit installation package or
|
|
Adobe CS3 deployment package, UNINSTALLERDMG is a path
|
|
to a disk image containing an AdobeUberUninstaller for
|
|
this item.''')
|
|
p.add_option('--postinstall_script',
|
|
metavar='SCRIPT_PATH',
|
|
help='''Optional flag.
|
|
|
|
Path to an optional postinstall script to be run after
|
|
installation of the item. The script will be read and
|
|
embedded into the pkginfo.''')
|
|
p.add_option('--preinstall_script',
|
|
metavar='SCRIPT_PATH',
|
|
help='''Optional flag.
|
|
|
|
Path to an optional preinstall script to be run before
|
|
installation of the item. The script will be read and
|
|
embedded into the pkginfo.''')
|
|
p.add_option('--postuninstall_script',
|
|
metavar='SCRIPT_PATH',
|
|
help='''Optional flag.
|
|
|
|
Path to an optional postuninstall script to be run after
|
|
removal of the item. The script will be read and
|
|
embedded into the pkginfo.''')
|
|
p.add_option('--preuninstall_script',
|
|
metavar='SCRIPT_PATH',
|
|
help='''Optional flag.
|
|
|
|
Path to an optional preuninstall script to be run before
|
|
removal of the item. The script will be read and
|
|
embedded into the pkginfo.''')
|
|
p.add_option('--uninstall_script',
|
|
metavar='SCRIPT_PATH',
|
|
help='''Optional flag.
|
|
|
|
Path to an uninstall script to be run in order to
|
|
uninstall this item. The script will be read and
|
|
embedded into the pkginfo.''')
|
|
p.add_option('--catalog', '-c', action="append",
|
|
help='''Optional flag.
|
|
|
|
Specifies in which catalog the item should appear. The
|
|
default is 'testing'. Can be specified multiple times
|
|
to add the item to multiple catalogs.''')
|
|
p.add_option('-o', '--owner',
|
|
metavar='USER',
|
|
dest='user',
|
|
help='''Optional flag.
|
|
|
|
If the installer item is a disk image used with
|
|
the copy_from_dmg installer type, this sets the
|
|
owner of the item specified by the --item flag.
|
|
The owner may be either a UID or a symbolic name.
|
|
The owner will be set recursively on the item.''')
|
|
p.add_option('-g', '--group',
|
|
metavar='GROUP',
|
|
dest='group',
|
|
help='''Optional flag.
|
|
|
|
If the installer item is a disk image used with
|
|
the copy_from_dmg installer type, this sets the
|
|
group of the item specified by the --item flag.
|
|
The group may be either a GID or a symbolic name.
|
|
The group will be set recursively on the item.''')
|
|
p.add_option('-m', '--mode',
|
|
metavar='MODE',
|
|
dest='mode',
|
|
action='callback',
|
|
type='string',
|
|
callback=check_mode,
|
|
help='''Optional flag.
|
|
|
|
If the installer item is a disk used with
|
|
the copy_from_dmg installer type, this sets the
|
|
mode of the item specified by the --item flag.
|
|
The specified mode must be in symbolic form.
|
|
See the manpage for chmod(1) for more information.
|
|
The mode is applied recursively.''')
|
|
p.add_option('--version', '-V', action='store_true',
|
|
help='Print the version of the munki tools and exit.')
|
|
|
|
options, arguments = p.parse_args()
|
|
|
|
if options.version:
|
|
print munkicommon.get_version()
|
|
exit(0)
|
|
|
|
if (len(arguments) == 0
|
|
and not options.file
|
|
and not options.preinstall_script
|
|
and not options.postinstall_script
|
|
and not options.preuninstall_script
|
|
and not options.postuninstall_script
|
|
and not options.uninstall_script):
|
|
p.print_usage()
|
|
exit(-1)
|
|
|
|
if len(arguments) > 1:
|
|
print >> sys.stderr, 'Can process only one installer item at a time.'
|
|
print >> sys.stderr, 'Ignoring additional installer items:'
|
|
print >> sys.stderr, '\t', '\n\t'.join(arguments[1:])
|
|
|
|
if options.installer_choices_xml:
|
|
os_version = munkicommon.getOsVersion(only_major_minor=False,as_tuple=True)
|
|
if os_version < (10, 6, 6):
|
|
options.installer_choices_xml = False
|
|
|
|
catinfo = {}
|
|
installs = []
|
|
if arguments:
|
|
item = arguments[0].rstrip("/")
|
|
if item and os.path.exists(item):
|
|
# get size of installer item
|
|
itemsize = 0
|
|
itemhash = "N/A"
|
|
if os.path.isfile(item):
|
|
itemsize = int(os.path.getsize(item))
|
|
itemhash = munkicommon.getsha256hash(item)
|
|
|
|
if item.endswith('.dmg'):
|
|
catinfo = getCatalogInfoFromDmg(item, options)
|
|
if (catinfo and
|
|
catinfo.get('installer_type') == "AdobeCS5Installer"):
|
|
print >> sys.stderr, (
|
|
"This disk image appears to contain an Adobe CS5 "
|
|
"product install.\n"
|
|
"Please use Adobe Application Manager, Enterprise "
|
|
"Edition (AAMEE) to create an installation package "
|
|
"for this product.")
|
|
exit(-1)
|
|
if not catinfo:
|
|
print >> sys.stderr, \
|
|
"Could not find a supported installer item in %s!" % \
|
|
item
|
|
exit(-1)
|
|
|
|
elif item.endswith('.pkg') or item.endswith('.mpkg'):
|
|
catinfo = munkicommon.getPackageMetaData(item)
|
|
if options.installer_choices_xml:
|
|
installer_choices_xml = munkicommon.getChoiceChangesXML(item)
|
|
if installer_choices_xml:
|
|
catinfo['installer_choices_xml'] = installer_choices_xml
|
|
if not catinfo:
|
|
print >> sys.stderr, \
|
|
"%s doesn't appear to be a valid installer item!" % \
|
|
item
|
|
exit(-1)
|
|
if os.path.isdir(item):
|
|
print >> sys.stderr, (
|
|
"WARNING: %s is a bundle-style package!\n"
|
|
"To use it with munki, you should encapsulate it "
|
|
"in a disk image.\n") % item
|
|
# need to walk the dir and add it all up
|
|
for (path, unused_dirs, files) in os.walk(item):
|
|
for name in files:
|
|
filename = os.path.join(path, name)
|
|
# use os.lstat so we don't follow symlinks
|
|
itemsize += int(os.lstat(filename).st_size)
|
|
# convert to kbytes
|
|
itemsize = int(itemsize/1024)
|
|
|
|
else:
|
|
print >> sys.stderr, "%s is not an installer package!" % item
|
|
exit(-1)
|
|
|
|
if options.description:
|
|
catinfo['description'] = options.description
|
|
if options.displayname:
|
|
catinfo['display_name'] = options.displayname
|
|
|
|
catinfo['installer_item_size'] = int(itemsize/1024)
|
|
catinfo['installer_item_hash'] = itemhash
|
|
|
|
# try to generate the correct item location
|
|
temppath = item
|
|
location = ""
|
|
while len(temppath) > 4:
|
|
if temppath.endswith('/pkgs'):
|
|
location = item[len(temppath)+1:]
|
|
break
|
|
else:
|
|
temppath = os.path.dirname(temppath)
|
|
|
|
if not location:
|
|
#just the filename
|
|
location = os.path.split(item)[1]
|
|
catinfo['installer_item_location'] = location
|
|
|
|
# ADOBE STUFF - though maybe generalizable in the future?
|
|
if options.uninstallerdmg:
|
|
uninstallerpath = options.uninstallerdmg
|
|
if os.path.exists(uninstallerpath):
|
|
# try to generate the correct item location
|
|
temppath = uninstallerpath
|
|
location = ""
|
|
while len(temppath) > 4:
|
|
if temppath.endswith('/pkgs'):
|
|
location = uninstallerpath[len(temppath)+1:]
|
|
break
|
|
else:
|
|
temppath = os.path.dirname(temppath)
|
|
|
|
if not location:
|
|
#just the filename
|
|
location = os.path.split(uninstallerpath)[1]
|
|
catinfo['uninstaller_item_location'] = location
|
|
itemsize = int(os.path.getsize(uninstallerpath))
|
|
itemhash = munkicommon.getsha256hash(uninstallerpath)
|
|
catinfo['uninstaller_item_size'] = int(itemsize/1024)
|
|
catinfo['uninstaller_item_hash'] = itemhash
|
|
else:
|
|
print >> sys.stderr, "No uninstaller at %s" % \
|
|
uninstallerpath
|
|
|
|
# some metainfo
|
|
if options.catalog:
|
|
catinfo['catalogs'] = options.catalog
|
|
else:
|
|
catinfo['catalogs'] = ['testing']
|
|
if catinfo.get('receipts', None):
|
|
catinfo['uninstallable'] = True
|
|
catinfo['uninstall_method'] = "removepackages"
|
|
|
|
minosversion = ""
|
|
maxfileversion = "0.0.0.0.0"
|
|
if catinfo:
|
|
catinfo['autoremove'] = False
|
|
if minosversion:
|
|
catinfo['minimum_os_version'] = minosversion
|
|
else:
|
|
catinfo['minimum_os_version'] = "10.4.0"
|
|
if not 'version' in catinfo:
|
|
if maxfileversion != "0.0.0.0.0":
|
|
catinfo['version'] = maxfileversion
|
|
else:
|
|
catinfo['version'] = "1.0.0.0.0 (Please edit me!)"
|
|
|
|
if options.file:
|
|
for fitem in options.file:
|
|
# no trailing slashes, please.
|
|
fitem = fitem.rstrip('/')
|
|
if fitem.startswith('/Library/Receipts'):
|
|
# no receipts, please!
|
|
print >> sys.stderr, \
|
|
"Item %s appears to be a receipt. Skipping." % fitem
|
|
continue
|
|
if os.path.exists(fitem):
|
|
iteminfodict = getiteminfo(fitem)
|
|
if 'minosversion' in iteminfodict:
|
|
thisminosversion = iteminfodict.pop('minosversion')
|
|
if not minosversion:
|
|
minosversion = thisminosversion
|
|
elif (munkicommon.MunkiLooseVersion(thisminosversion) <
|
|
munkicommon.MunkiLooseVersion(minosversion)):
|
|
minosversion = thisminosversion
|
|
if 'CFBundleShortVersionString' in iteminfodict:
|
|
thisitemversion = \
|
|
iteminfodict['CFBundleShortVersionString']
|
|
if (munkicommon.MunkiLooseVersion(thisitemversion) >
|
|
munkicommon.MunkiLooseVersion(maxfileversion)):
|
|
maxfileversion = thisitemversion
|
|
installs.append(iteminfodict)
|
|
else:
|
|
print >> sys.stderr, (
|
|
"Item %s doesn't exist. Skipping." % fitem)
|
|
|
|
if installs:
|
|
catinfo['installs'] = installs
|
|
|
|
if options.postinstall_script:
|
|
scriptstring = readfile(options.postinstall_script)
|
|
if scriptstring:
|
|
catinfo['postinstall_script'] = scriptstring
|
|
if options.preinstall_script:
|
|
scriptstring = readfile(options.preinstall_script)
|
|
if scriptstring:
|
|
catinfo['preinstall_script'] = scriptstring
|
|
if options.postuninstall_script:
|
|
scriptstring = readfile(options.postuninstall_script)
|
|
if scriptstring:
|
|
catinfo['postuninstall_script'] = scriptstring
|
|
if options.preuninstall_script:
|
|
scriptstring = readfile(options.preuninstall_script)
|
|
if scriptstring:
|
|
catinfo['preuninstall_script'] = scriptstring
|
|
if options.uninstall_script:
|
|
scriptstring = readfile(options.uninstall_script)
|
|
if scriptstring:
|
|
catinfo['uninstall_script'] = scriptstring
|
|
catinfo['uninstall_method'] = 'uninstall_script'
|
|
if options.notes:
|
|
if os.path.exists(os.path.expanduser(options.notes)):
|
|
notesString = readfile(options.notes)
|
|
else:
|
|
notesString = options.notes
|
|
catinfo['notes'] = notesString
|
|
if options.autoremove:
|
|
catinfo['autoremove'] = True
|
|
if options.unattended_install:
|
|
catinfo['unattended_install'] = True
|
|
if options.unattended_uninstall:
|
|
catinfo['unattended_uninstall'] = True
|
|
if options.minimum_os_version:
|
|
catinfo['minimum_os_version'] = options.minimum_os_version
|
|
if options.maximum_os_version:
|
|
catinfo['maximum_os_version'] = options.maximum_os_version
|
|
if options.force_install_after_date:
|
|
force_install_after_date = munkicommon.validateForceInstallAfterDateFormat(options.force_install_after_date)
|
|
if force_install_after_date:
|
|
catinfo['force_install_after_date'] = force_install_after_date
|
|
if options.RestartAction:
|
|
validActions=['RequireRestart','RequireLogout','RecommendRestart']
|
|
if options.RestartAction in validActions:
|
|
catinfo['RestartAction'] = options.RestartAction
|
|
elif 'restart' in options.RestartAction.lower():
|
|
catinfo['RestartAction'] = 'RequireRestart'
|
|
elif 'logout' in options.RestartAction.lower():
|
|
catinfo['RestartAction'] = 'RequireLogout'
|
|
if options.update_for:
|
|
catinfo['update_for'] = options.update_for
|
|
if options.requires:
|
|
catinfo['requires'] = options.requires
|
|
if options.blocking_applications:
|
|
catinfo['blocking_applications'] = options.blocking_applications
|
|
|
|
# and now, what we've all been waiting for...
|
|
print FoundationPlist.writePlistToString(catinfo)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|