mirror of
https://github.com/munki/munki.git
synced 2026-01-07 23:20:00 -06:00
First iteration of forced/unattended/silent installs.
Notes: - Needs documentation about how risky this is, and how it should only be used for simple packages that are known to be safely installed while app is running. - If notification is popped up, it will contain the unattended install; if notification is not due to be popped up the unattended install will be installed immediately. - Only works in --auto mode (which is what launchd execs); I will test more tomorrow with manual runs, as we may be able to do this before --installonly too. git-svn-id: http://munki.googlecode.com/svn/branches/unattended-installs@809 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
@@ -126,16 +126,21 @@ def initMunkiDirs():
|
||||
return True
|
||||
|
||||
|
||||
def doInstallTasks():
|
||||
def doInstallTasks(only_unattended=False):
|
||||
"""Perform our installation/removal tasks.
|
||||
|
||||
Args:
|
||||
only_unattended: Boolean. If True, only do unattended installs/removals.
|
||||
|
||||
Returns:
|
||||
Boolean. True if a restart is required, False otherwise.
|
||||
"""
|
||||
# first, clear the last notified date
|
||||
# so we can get notified of new changes after this round
|
||||
# of installs
|
||||
clearLastNotifiedDate()
|
||||
if not only_unattended:
|
||||
# first, clear the last notified date
|
||||
# so we can get notified of new changes after this round
|
||||
# of installs
|
||||
clearLastNotifiedDate()
|
||||
|
||||
need_to_restart = False
|
||||
# munki updates take priority over Apple Updates, because
|
||||
# a munki install or (especially) removal could make a
|
||||
@@ -147,7 +152,7 @@ def doInstallTasks():
|
||||
if munkiUpdatesAvailable():
|
||||
# install munki updates
|
||||
try:
|
||||
need_to_restart = installer.run()
|
||||
need_to_restart = installer.run(only_unattended=only_unattended)
|
||||
except:
|
||||
munkicommon.display_error('Unexpected error in '
|
||||
' munkilib.installer:')
|
||||
@@ -157,8 +162,10 @@ def doInstallTasks():
|
||||
|
||||
# clear any Apple update info since it may no longer
|
||||
# be relevant
|
||||
appleupdates.clearAppleUpdateInfo()
|
||||
elif munkicommon.pref('InstallAppleSoftwareUpdates'):
|
||||
if not only_unattended:
|
||||
appleupdates.clearAppleUpdateInfo()
|
||||
elif munkicommon.pref('InstallAppleSoftwareUpdates') and \
|
||||
not only_unattended:
|
||||
# are we supposed to handle Apple Software Updates?
|
||||
try:
|
||||
need_to_restart = appleupdates.installAppleUpdates()
|
||||
@@ -233,10 +240,15 @@ def recordUpdateCheckResult(result):
|
||||
|
||||
|
||||
def notifyUserOfUpdates():
|
||||
"""Notify the logged-in user of available updates."""
|
||||
"""Notify the logged-in user of available updates.
|
||||
|
||||
Returns:
|
||||
Boolean. True if the user was notified, False otherwise.
|
||||
"""
|
||||
# called when options.auto == True
|
||||
# someone is logged in, and we have updates.
|
||||
# if we haven't notified in a while, notify:
|
||||
user_was_notified = False
|
||||
lastNotifiedString = munkicommon.pref('LastNotifiedDate')
|
||||
daysBetweenNotifications = munkicommon.pref('DaysBetweenNotifications')
|
||||
now = NSDate.new()
|
||||
@@ -270,6 +282,8 @@ def notifyUserOfUpdates():
|
||||
time.sleep(0.1)
|
||||
if os.path.exists(launchfile):
|
||||
os.unlink(launchfile)
|
||||
user_was_notified = True
|
||||
return user_was_notified
|
||||
|
||||
|
||||
def runPreOrPostFlightScript(script, runtype='custom'):
|
||||
@@ -611,10 +625,18 @@ def main():
|
||||
pass
|
||||
elif not munkicommon.pref('SuppressUserNotification'):
|
||||
# notify the current console user
|
||||
notifyUserOfUpdates()
|
||||
user_was_notified = notifyUserOfUpdates()
|
||||
# if the user was not notified, do unattended installs.
|
||||
if not user_was_notified:
|
||||
munkicommon.munkistatusoutput = False
|
||||
doInstallTasks(only_unattended=True)
|
||||
else:
|
||||
munkicommon.log('Skipping user notification because '
|
||||
'SuppressUserNotification is true.')
|
||||
# Disable status output and install unattended installs.
|
||||
munkicommon.munkistatusoutput = False
|
||||
doInstallTasks(only_unattended=True)
|
||||
|
||||
elif not options.quiet:
|
||||
print ('\nRun %s --installonly to install the downloaded '
|
||||
'updates.' % myname)
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
# 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.
|
||||
@@ -55,7 +55,7 @@ def removeBundleRelocationInfo(pkgpath):
|
||||
"Removed Contents/Resources/TokenDefinitions.plist")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
plist = {}
|
||||
infoplist = os.path.join(pkgpath, "Contents/Info.plist")
|
||||
if os.path.exists(infoplist):
|
||||
@@ -63,7 +63,7 @@ def removeBundleRelocationInfo(pkgpath):
|
||||
plist = FoundationPlist.readPlist(infoplist)
|
||||
except FoundationPlist.NSPropertyListSerializationException:
|
||||
pass
|
||||
|
||||
|
||||
if 'IFPkgPathMappings' in plist:
|
||||
del plist['IFPkgPathMappings']
|
||||
try:
|
||||
@@ -72,26 +72,26 @@ def removeBundleRelocationInfo(pkgpath):
|
||||
"Removed IFPkgPathMappings")
|
||||
except FoundationPlist.NSPropertyListWriteException:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def install(pkgpath, choicesXMLpath=None, suppressBundleRelocation=False):
|
||||
"""
|
||||
Uses the apple installer to install the package or metapackage
|
||||
at pkgpath. Prints status messages to STDOUT.
|
||||
Returns a tuple:
|
||||
Returns a tuple:
|
||||
the installer return code and restart needed as a boolean.
|
||||
"""
|
||||
|
||||
|
||||
restartneeded = False
|
||||
installeroutput = []
|
||||
|
||||
|
||||
if os.path.islink(pkgpath):
|
||||
# resolve links before passing them to /usr/bin/installer
|
||||
pkgpath = os.path.realpath(pkgpath)
|
||||
|
||||
|
||||
if suppressBundleRelocation:
|
||||
removeBundleRelocationInfo(pkgpath)
|
||||
|
||||
|
||||
packagename = ''
|
||||
restartaction = 'None'
|
||||
pkginfo = munkicommon.getInstallerPkgInfo(pkgpath)
|
||||
@@ -100,20 +100,20 @@ def install(pkgpath, choicesXMLpath=None, suppressBundleRelocation=False):
|
||||
restartaction = pkginfo.get('RestartAction','None')
|
||||
if not packagename:
|
||||
packagename = os.path.basename(pkgpath)
|
||||
|
||||
|
||||
if munkicommon.munkistatusoutput:
|
||||
munkistatus.message("Installing %s..." % packagename)
|
||||
munkistatus.detail("")
|
||||
# clear indeterminate progress bar
|
||||
# clear indeterminate progress bar
|
||||
munkistatus.percent(0)
|
||||
|
||||
|
||||
munkicommon.log("Installing %s from %s" % (packagename,
|
||||
os.path.basename(pkgpath)))
|
||||
cmd = ['/usr/sbin/installer', '-query', 'RestartAction', '-pkg', pkgpath]
|
||||
if choicesXMLpath:
|
||||
cmd.extend(['-applyChoiceChangesXML', choicesXMLpath])
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, unused_err) = proc.communicate()
|
||||
restartaction = str(output).decode('UTF-8').rstrip("\n")
|
||||
@@ -122,19 +122,19 @@ def install(pkgpath, choicesXMLpath=None, suppressBundleRelocation=False):
|
||||
munkicommon.display_status("%s requires a restart after installation."
|
||||
% packagename)
|
||||
restartneeded = True
|
||||
|
||||
# get the OS version; we need it later when processing installer's output,
|
||||
# which varies depending on OS version.
|
||||
|
||||
# get the OS version; we need it later when processing installer's output,
|
||||
# which varies depending on OS version.
|
||||
osvers = int(os.uname()[2].split('.')[0])
|
||||
cmd = ['/usr/sbin/installer', '-verboseR', '-pkg', pkgpath,
|
||||
cmd = ['/usr/sbin/installer', '-verboseR', '-pkg', pkgpath,
|
||||
'-target', '/']
|
||||
if choicesXMLpath:
|
||||
cmd.extend(['-applyChoiceChangesXML', choicesXMLpath])
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
while True:
|
||||
while True:
|
||||
installinfo = proc.stdout.readline().decode('UTF-8')
|
||||
if not installinfo and (proc.poll() != None):
|
||||
break
|
||||
@@ -196,7 +196,7 @@ def install(pkgpath, choicesXMLpath=None, suppressBundleRelocation=False):
|
||||
munkicommon.log("Install of %s was successful." % packagename)
|
||||
if munkicommon.munkistatusoutput:
|
||||
munkistatus.percent(100)
|
||||
|
||||
|
||||
return (retcode, restartneeded)
|
||||
|
||||
|
||||
@@ -234,22 +234,22 @@ def installall(dirpath, choicesXMLpath=None, suppressBundleRelocation=False):
|
||||
if retcode:
|
||||
# ran into error; should unmount and stop.
|
||||
munkicommon.unmountdmg(mountpoints[0])
|
||||
return (retcode, restartflag)
|
||||
|
||||
return (retcode, restartflag)
|
||||
|
||||
munkicommon.unmountdmg(mountpoints[0])
|
||||
|
||||
|
||||
if (item.endswith(".pkg") or item.endswith(".mpkg")):
|
||||
(retcode, needsrestart) = install(itempath, choicesXMLpath,
|
||||
(retcode, needsrestart) = install(itempath, choicesXMLpath,
|
||||
suppressBundleRelocation)
|
||||
if needsrestart:
|
||||
restartflag = True
|
||||
if retcode:
|
||||
# ran into error; should stop.
|
||||
return (retcode, restartflag)
|
||||
|
||||
|
||||
return (retcode, restartflag)
|
||||
|
||||
|
||||
|
||||
|
||||
def copyAppFromDMG(dmgpath):
|
||||
'''copies application from DMG to /Applications'''
|
||||
munkicommon.display_status("Mounting disk image %s" %
|
||||
@@ -265,8 +265,8 @@ def copyAppFromDMG(dmgpath):
|
||||
if munkicommon.isApplication(itempath):
|
||||
appname = item
|
||||
break
|
||||
|
||||
if appname:
|
||||
|
||||
if appname:
|
||||
destpath = os.path.join("/Applications", appname)
|
||||
if os.path.exists(destpath):
|
||||
retcode = subprocess.call(["/bin/rm", "-r", destpath])
|
||||
@@ -276,48 +276,48 @@ def copyAppFromDMG(dmgpath):
|
||||
if retcode == 0:
|
||||
munkicommon.display_status(
|
||||
"Copying %s to Applications folder" % appname)
|
||||
retcode = subprocess.call(["/bin/cp", "-R",
|
||||
retcode = subprocess.call(["/bin/cp", "-R",
|
||||
itempath, destpath])
|
||||
if retcode:
|
||||
munkicommon.display_error("Error copying %s to %s" %
|
||||
munkicommon.display_error("Error copying %s to %s" %
|
||||
(itempath, destpath))
|
||||
if retcode == 0:
|
||||
# remove com.apple.quarantine attribute from copied app
|
||||
cmd = ["/usr/bin/xattr", destpath]
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(out, unused_err) = proc.communicate()
|
||||
if out:
|
||||
xattrs = str(out).splitlines()
|
||||
if "com.apple.quarantine" in xattrs:
|
||||
unused_result = subprocess.call(
|
||||
["/usr/bin/xattr", "-d",
|
||||
"com.apple.quarantine",
|
||||
["/usr/bin/xattr", "-d",
|
||||
"com.apple.quarantine",
|
||||
destpath])
|
||||
# let the user know we completed successfully
|
||||
munkicommon.display_status(
|
||||
"The software was successfully installed.")
|
||||
munkicommon.unmountdmg(mountpoint)
|
||||
if not appname:
|
||||
munkicommon.display_error("No application found on %s" %
|
||||
munkicommon.display_error("No application found on %s" %
|
||||
os.path.basename(dmgpath))
|
||||
retcode = -2
|
||||
|
||||
|
||||
return retcode
|
||||
else:
|
||||
munkicommon.display_error("No mountable filesystems on %s" %
|
||||
munkicommon.display_error("No mountable filesystems on %s" %
|
||||
os.path.basename(dmgpath))
|
||||
return -1
|
||||
|
||||
|
||||
|
||||
def copyFromDMG(dmgpath, itemlist):
|
||||
'''copies items from DMG to local disk'''
|
||||
if not itemlist:
|
||||
munkicommon.display_error("No items to copy!")
|
||||
return -1
|
||||
|
||||
|
||||
munkicommon.display_status("Mounting disk image %s" %
|
||||
os.path.basename(dmgpath))
|
||||
mountpoints = munkicommon.mountdmg(dmgpath)
|
||||
@@ -329,7 +329,7 @@ def copyFromDMG(dmgpath, itemlist):
|
||||
if not itemname:
|
||||
munkicommon.display_error("Missing name of item to copy!")
|
||||
retcode = -1
|
||||
|
||||
|
||||
if retcode == 0:
|
||||
itempath = os.path.join(mountpoint, itemname)
|
||||
if os.path.exists(itempath):
|
||||
@@ -351,69 +351,69 @@ def copyFromDMG(dmgpath, itemlist):
|
||||
munkicommon.display_error(
|
||||
"Source item %s does not exist!" % itemname)
|
||||
retcode = -1
|
||||
|
||||
|
||||
if retcode == 0:
|
||||
munkicommon.display_status(
|
||||
"Copying %s to %s" % (itemname, destpath))
|
||||
retcode = subprocess.call(["/bin/cp", "-R",
|
||||
retcode = subprocess.call(["/bin/cp", "-R",
|
||||
itempath, destpath])
|
||||
if retcode:
|
||||
munkicommon.display_error(
|
||||
"Error copying %s to %s" %
|
||||
"Error copying %s to %s" %
|
||||
(itempath, destpath))
|
||||
|
||||
|
||||
destitem = os.path.join(destpath, itemname)
|
||||
if (retcode == 0) and ('user' in item):
|
||||
munkicommon.display_detail(
|
||||
"Setting owner for '%s' to '%s'" %
|
||||
"Setting owner for '%s' to '%s'" %
|
||||
(destitem, item['user']))
|
||||
cmd = ['/usr/sbin/chown', '-R', item['user'], destitem]
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode:
|
||||
munkicommon.display_error("Error setting owner for %s" %
|
||||
munkicommon.display_error("Error setting owner for %s" %
|
||||
(destitem))
|
||||
|
||||
if (retcode == 0) and ('group' in item):
|
||||
munkicommon.display_detail(
|
||||
"Setting group for '%s' to '%s'" %
|
||||
"Setting group for '%s' to '%s'" %
|
||||
(destitem, item['group']))
|
||||
cmd = ['/usr/bin/chgrp', '-R', item['group'], destitem]
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode:
|
||||
munkicommon.display_error("Error setting group for %s" %
|
||||
munkicommon.display_error("Error setting group for %s" %
|
||||
(destitem))
|
||||
|
||||
if (retcode == 0) and ('mode' in item):
|
||||
munkicommon.display_detail(
|
||||
"Setting mode for '%s' to '%s'" %
|
||||
"Setting mode for '%s' to '%s'" %
|
||||
(destitem, item['mode']))
|
||||
cmd = ['/bin/chmod', '-R', item['mode'], destitem]
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode:
|
||||
munkicommon.display_error("Error setting mode for %s" %
|
||||
munkicommon.display_error("Error setting mode for %s" %
|
||||
(destitem))
|
||||
|
||||
if retcode == 0:
|
||||
# remove com.apple.quarantine attribute from copied item
|
||||
cmd = ["/usr/bin/xattr", destitem]
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(out, unused_err) = proc.communicate()
|
||||
if out:
|
||||
xattrs = str(out).splitlines()
|
||||
if "com.apple.quarantine" in xattrs:
|
||||
unused_result = subprocess.call(
|
||||
["/usr/bin/xattr", "-d",
|
||||
"com.apple.quarantine",
|
||||
["/usr/bin/xattr", "-d",
|
||||
"com.apple.quarantine",
|
||||
destitem])
|
||||
|
||||
|
||||
if retcode:
|
||||
# we encountered an error on this iteration;
|
||||
# we encountered an error on this iteration;
|
||||
# should not continue.
|
||||
break
|
||||
|
||||
|
||||
if retcode == 0:
|
||||
# let the user know we completed successfully
|
||||
munkicommon.display_status(
|
||||
@@ -421,7 +421,7 @@ def copyFromDMG(dmgpath, itemlist):
|
||||
munkicommon.unmountdmg(mountpoint)
|
||||
return retcode
|
||||
else:
|
||||
munkicommon.display_error("No mountable filesystems on %s" %
|
||||
munkicommon.display_error("No mountable filesystems on %s" %
|
||||
os.path.basename(dmgpath))
|
||||
return -1
|
||||
|
||||
@@ -433,7 +433,7 @@ def removeCopiedItems(itemlist):
|
||||
if not itemlist:
|
||||
munkicommon.display_error("Nothing to remove!")
|
||||
return -1
|
||||
|
||||
|
||||
for item in itemlist:
|
||||
itemname = item.get("source_item")
|
||||
if not itemname:
|
||||
@@ -458,7 +458,7 @@ def removeCopiedItems(itemlist):
|
||||
# note it, but not an error
|
||||
munkicommon.display_detail("Path %s doesn't exist." %
|
||||
path_to_remove)
|
||||
|
||||
|
||||
return retcode
|
||||
|
||||
def installWithInfo(dirpath, installlist):
|
||||
@@ -477,14 +477,14 @@ def installWithInfo(dirpath, installlist):
|
||||
item.get('manifestitem')
|
||||
version_to_install = item.get('version_to_install','')
|
||||
if munkicommon.munkistatusoutput:
|
||||
munkistatus.message("Installing %s (%s of %s)..." %
|
||||
(display_name, itemindex,
|
||||
munkistatus.message("Installing %s (%s of %s)..." %
|
||||
(display_name, itemindex,
|
||||
len(installlist)))
|
||||
munkistatus.detail("")
|
||||
munkistatus.percent(-1)
|
||||
else:
|
||||
munkicommon.display_status("Installing %s (%s of %s)" %
|
||||
(display_name, itemindex,
|
||||
munkicommon.display_status("Installing %s (%s of %s)" %
|
||||
(display_name, itemindex,
|
||||
len(installlist)))
|
||||
itempath = os.path.join(dirpath, item["installer_item"])
|
||||
if not os.path.exists(itempath):
|
||||
@@ -513,7 +513,7 @@ def installWithInfo(dirpath, installlist):
|
||||
elif installer_type == "appdmg":
|
||||
retcode = copyAppFromDMG(itempath)
|
||||
elif installer_type != "":
|
||||
# we've encountered an installer type
|
||||
# we've encountered an installer type
|
||||
# we don't know how to handle
|
||||
munkicommon.log("Unsupported install type: %s" %
|
||||
installer_type)
|
||||
@@ -525,7 +525,7 @@ def installWithInfo(dirpath, installlist):
|
||||
munkicommon.display_debug1("suppress_bundle_relocation: %s" %
|
||||
suppressBundleRelocation )
|
||||
if 'installer_choices_xml' in item:
|
||||
choicesXMLfile = os.path.join(munkicommon.tmpdir,
|
||||
choicesXMLfile = os.path.join(munkicommon.tmpdir,
|
||||
"choices.xml")
|
||||
FoundationPlist.writePlist(item['installer_choices_xml'],
|
||||
choicesXMLfile)
|
||||
@@ -538,7 +538,7 @@ def installWithInfo(dirpath, installlist):
|
||||
# we need to mount the diskimage as read/write to
|
||||
# be able to modify the package to suppress bundle
|
||||
# relocation
|
||||
mountpoints = munkicommon.mountdmg(itempath,
|
||||
mountpoints = munkicommon.mountdmg(itempath,
|
||||
use_shadow=mountWithShadow)
|
||||
if mountpoints == []:
|
||||
munkicommon.display_error("No filesystems mounted "
|
||||
@@ -551,9 +551,9 @@ def installWithInfo(dirpath, installlist):
|
||||
needtorestart = False
|
||||
if item.get('package_path','').endswith('.pkg') or \
|
||||
item.get('package_path','').endswith('.mpkg'):
|
||||
# admin has specified the relative path of the pkg
|
||||
# admin has specified the relative path of the pkg
|
||||
# on the DMG
|
||||
# this is useful if there is more than one pkg on
|
||||
# this is useful if there is more than one pkg on
|
||||
# the DMG, or the actual pkg is not at the root
|
||||
# of the DMG
|
||||
fullpkgpath = os.path.join(mountpoints[0],
|
||||
@@ -582,17 +582,17 @@ def installWithInfo(dirpath, installlist):
|
||||
if needtorestart:
|
||||
restartflag = True
|
||||
elif os.path.isdir(itempath):
|
||||
# directory of packages,
|
||||
# directory of packages,
|
||||
# like what we get from Software Update
|
||||
(retcode, needtorestart) = installall(itempath,
|
||||
choicesXMLfile,
|
||||
suppressBundleRelocation)
|
||||
if needtorestart:
|
||||
restartflag = True
|
||||
|
||||
|
||||
# record install success/failure
|
||||
if retcode == 0:
|
||||
success_msg = ("Install of %s-%s: SUCCESSFUL" %
|
||||
success_msg = ("Install of %s-%s: SUCCESSFUL" %
|
||||
(display_name, version_to_install))
|
||||
munkicommon.log(success_msg, "Install.log")
|
||||
munkicommon.report['InstallResults'].append(success_msg)
|
||||
@@ -602,10 +602,10 @@ def installWithInfo(dirpath, installlist):
|
||||
(display_name, version_to_install, retcode))
|
||||
munkicommon.log(failure_msg, "Install.log")
|
||||
munkicommon.report['InstallResults'].append(failure_msg)
|
||||
|
||||
# check to see if this installer item is needed by any additional
|
||||
|
||||
# check to see if this installer item is needed by any additional
|
||||
# items in installinfo
|
||||
# this might happen if there are multiple things being installed
|
||||
# this might happen if there are multiple things being installed
|
||||
# with choicesXML files applied to a metapackage or
|
||||
# multiple packages being installed from a single DMG
|
||||
foundagain = False
|
||||
@@ -622,9 +622,9 @@ def installWithInfo(dirpath, installlist):
|
||||
current_installer_item:
|
||||
foundagain = True
|
||||
break
|
||||
|
||||
|
||||
if not foundagain:
|
||||
# now remove the item from the install cache
|
||||
# now remove the item from the install cache
|
||||
# (if it's still there)
|
||||
itempath = os.path.join(dirpath, current_installer_item)
|
||||
if os.path.exists(itempath):
|
||||
@@ -636,7 +636,7 @@ def installWithInfo(dirpath, installlist):
|
||||
shadowfile = os.path.join(itempath,".shadow")
|
||||
if os.path.exists(shadowfile):
|
||||
retcode = subprocess.call(["/bin/rm", shadowfile])
|
||||
|
||||
|
||||
return restartflag
|
||||
|
||||
|
||||
@@ -650,19 +650,19 @@ def processRemovals(removallist):
|
||||
if not item.get('installed'):
|
||||
# not installed, so skip it
|
||||
continue
|
||||
|
||||
|
||||
index += 1
|
||||
name = item.get('display_name') or item.get('name') or \
|
||||
item.get('manifestitem')
|
||||
if munkicommon.munkistatusoutput:
|
||||
munkistatus.message("Removing %s (%s of %s)..." %
|
||||
munkistatus.message("Removing %s (%s of %s)..." %
|
||||
(name, index, len(removallist)))
|
||||
munkistatus.detail("")
|
||||
munkistatus.percent(-1)
|
||||
else:
|
||||
munkicommon.display_status("Removing %s (%s of %s)..." %
|
||||
(name, index, len(removallist)))
|
||||
|
||||
|
||||
if 'uninstall_method' in item:
|
||||
uninstallmethod = item['uninstall_method'].split(' ')
|
||||
if uninstallmethod[0] == "removepackages":
|
||||
@@ -681,13 +681,13 @@ def processRemovals(removallist):
|
||||
else:
|
||||
munkicommon.log("Uninstall of %s was "
|
||||
"successful." % name)
|
||||
|
||||
|
||||
elif uninstallmethod[0].startswith("Adobe"):
|
||||
retcode = adobeutils.doAdobeRemoval(item)
|
||||
|
||||
|
||||
elif uninstallmethod[0] == "remove_copied_items":
|
||||
retcode = removeCopiedItems(item.get('items_to_remove'))
|
||||
|
||||
|
||||
elif uninstallmethod[0] == "remove_app":
|
||||
remove_app_info = item.get('remove_app_info', None)
|
||||
if remove_app_info:
|
||||
@@ -702,9 +702,9 @@ def processRemovals(removallist):
|
||||
path_to_remove)
|
||||
else:
|
||||
munkicommon.display_error("Application removal "
|
||||
"info missing from %s" %
|
||||
"info missing from %s" %
|
||||
name)
|
||||
|
||||
|
||||
elif os.path.exists(uninstallmethod[0]) and \
|
||||
os.access(uninstallmethod[0], os.X_OK):
|
||||
# it's a script or program to uninstall
|
||||
@@ -712,20 +712,20 @@ def processRemovals(removallist):
|
||||
munkistatus.message("Running uninstall script "
|
||||
"for %s..." % name)
|
||||
munkistatus.detail("")
|
||||
# set indeterminate progress bar
|
||||
# set indeterminate progress bar
|
||||
munkistatus.percent(-1)
|
||||
|
||||
|
||||
if item.get('RestartAction') == "RequireRestart":
|
||||
restartFlag = True
|
||||
|
||||
|
||||
cmd = uninstallmethod
|
||||
uninstalleroutput = []
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
proc = subprocess.Popen(cmd, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
|
||||
while (proc.poll() == None):
|
||||
while (proc.poll() == None):
|
||||
msg = proc.stdout.readline().decode('UTF-8')
|
||||
# save all uninstaller output in case there is
|
||||
# an error so we can dump it to the log
|
||||
@@ -736,7 +736,7 @@ def processRemovals(removallist):
|
||||
pass
|
||||
else:
|
||||
print msg
|
||||
|
||||
|
||||
retcode = proc.poll()
|
||||
if retcode:
|
||||
message = "Uninstall of %s failed." % name
|
||||
@@ -756,17 +756,17 @@ def processRemovals(removallist):
|
||||
else:
|
||||
munkicommon.log("Uninstall of %s was "
|
||||
"successful." % name)
|
||||
|
||||
|
||||
if munkicommon.munkistatusoutput:
|
||||
# clear indeterminate progress bar
|
||||
# clear indeterminate progress bar
|
||||
munkistatus.percent(0)
|
||||
|
||||
|
||||
else:
|
||||
munkicommon.log("Uninstall of %s failed because "
|
||||
"there was no valid uninstall "
|
||||
"there was no valid uninstall "
|
||||
"method." % name)
|
||||
retcode = -99
|
||||
|
||||
|
||||
# record removal success/failure
|
||||
if retcode == 0:
|
||||
success_msg = "Removal of %s: SUCCESSFUL" % name
|
||||
@@ -780,7 +780,7 @@ def processRemovals(removallist):
|
||||
munkicommon.log(failure_msg, "Install.log")
|
||||
munkicommon.report[
|
||||
'RemovalResults'].append(failure_msg)
|
||||
|
||||
|
||||
return restartFlag
|
||||
|
||||
|
||||
@@ -804,17 +804,21 @@ def removeItemFromSelfServeUninstallList(itemname):
|
||||
try:
|
||||
FoundationPlist.writePlist(plist, selfservemanifest)
|
||||
except FoundationPlist.FoundationPlistException:
|
||||
pass
|
||||
|
||||
pass
|
||||
|
||||
def run():
|
||||
'''Runs the install/removal session'''
|
||||
|
||||
def run(only_unattended=False):
|
||||
"""Runs the install/removal session.
|
||||
|
||||
Args:
|
||||
only_unattended: Boolean. If True, only do unattended installs/removals.
|
||||
"""
|
||||
managedinstallbase = munkicommon.pref('ManagedInstallDir')
|
||||
installdir = os.path.join(managedinstallbase , 'Cache')
|
||||
|
||||
|
||||
removals_need_restart = installs_need_restart = False
|
||||
munkicommon.log("### Beginning managed installer session ###")
|
||||
|
||||
|
||||
installinfo = os.path.join(managedinstallbase, 'InstallInfo.plist')
|
||||
if os.path.exists(installinfo):
|
||||
try:
|
||||
@@ -822,40 +826,52 @@ def run():
|
||||
except FoundationPlist.NSPropertyListSerializationException:
|
||||
print >> sys.stderr, "Invalid %s" % installinfo
|
||||
return -1
|
||||
|
||||
# remove the install info file
|
||||
# it's no longer valid once we start running
|
||||
try:
|
||||
os.unlink(installinfo)
|
||||
except (OSError, IOError):
|
||||
munkicommon.display_warning("Could not remove %s" % installinfo)
|
||||
|
||||
if (munkicommon.munkistatusoutput and
|
||||
|
||||
if not only_unattended:
|
||||
# remove the install info file
|
||||
# it's no longer valid once we start running
|
||||
try:
|
||||
os.unlink(installinfo)
|
||||
except (OSError, IOError):
|
||||
munkicommon.display_warning(
|
||||
"Could not remove %s" % installinfo)
|
||||
|
||||
if (munkicommon.munkistatusoutput and
|
||||
munkicommon.pref('SuppressStopButtonOnInstall')):
|
||||
munkistatus.hideStopButton()
|
||||
|
||||
|
||||
if "removals" in plist:
|
||||
# filter list to items that need to be removed
|
||||
removallist = [item for item in plist['removals']
|
||||
if item.get('installed')]
|
||||
if only_unattended:
|
||||
removallist = [item for item in plist['removals']
|
||||
if item.get('installed') and \
|
||||
item.get('unattended', False) == True]
|
||||
else:
|
||||
removallist = [item for item in plist['removals']
|
||||
if item.get('installed')]
|
||||
munkicommon.report['ItemsToRemove'] = removallist
|
||||
if removallist:
|
||||
if munkicommon.munkistatusoutput:
|
||||
if len(removallist) == 1:
|
||||
munkistatus.message("Removing 1 item...")
|
||||
else:
|
||||
munkistatus.message("Removing %i items..." %
|
||||
munkistatus.message("Removing %i items..." %
|
||||
len(removallist))
|
||||
munkistatus.detail("")
|
||||
# set indeterminate progress bar
|
||||
# set indeterminate progress bar
|
||||
munkistatus.percent(-1)
|
||||
munkicommon.log("Processing removals")
|
||||
removals_need_restart = processRemovals(removallist)
|
||||
if "managed_installs" in plist:
|
||||
if not munkicommon.stopRequested():
|
||||
# filter list to items that need to be installed
|
||||
installlist = [item for item in plist['managed_installs']
|
||||
if item.get('installed') == False]
|
||||
if only_unattended:
|
||||
installlist = [item for item in plist['managed_installs']
|
||||
if item.get('installed') == False and \
|
||||
item.get('unattended', False) == True]
|
||||
else:
|
||||
installlist = [item for item in plist['managed_installs']
|
||||
if item.get('installed') == False]
|
||||
munkicommon.report['ItemsToInstall'] = installlist
|
||||
if installlist:
|
||||
if munkicommon.munkistatusoutput:
|
||||
@@ -865,17 +881,18 @@ def run():
|
||||
munkistatus.message("Installing %i items..." %
|
||||
len(installlist))
|
||||
munkistatus.detail("")
|
||||
# set indeterminate progress bar
|
||||
# set indeterminate progress bar
|
||||
munkistatus.percent(-1)
|
||||
munkicommon.log("Processing installs")
|
||||
installs_need_restart = installWithInfo(installdir,
|
||||
installlist)
|
||||
|
||||
|
||||
else:
|
||||
munkicommon.log("No %s found." % installinfo)
|
||||
|
||||
if not only_unattended:
|
||||
munkicommon.log("No %s found." % installinfo)
|
||||
|
||||
munkicommon.log("### End managed installer session ###")
|
||||
munkicommon.savereport()
|
||||
|
||||
|
||||
return (removals_need_restart or installs_need_restart)
|
||||
|
||||
|
||||
|
||||
@@ -1335,6 +1335,7 @@ def processInstall(manifestitem, cataloglist, installinfo):
|
||||
iteminfo['installer_item_size'] = item_pl.get('installer_item_size', 0)
|
||||
iteminfo['installed_size'] = item_pl.get('installer_item_size',
|
||||
iteminfo['installer_item_size'])
|
||||
iteminfo['unattended'] = item_pl.get('unattended', False)
|
||||
|
||||
if not isInstalled(item_pl):
|
||||
munkicommon.display_detail('Need to install %s' % manifestitemname)
|
||||
@@ -1367,8 +1368,8 @@ def processInstall(manifestitem, cataloglist, installinfo):
|
||||
'package_path',
|
||||
'items_to_copy', # used w/ copy_from_dmg
|
||||
'copy_local', # used w/ AdobeCS5 Updaters
|
||||
'silent_install'] # just install without
|
||||
# bothering the user
|
||||
'unattended'] # just install without
|
||||
# notifying the user
|
||||
for key in optional_keys:
|
||||
if key in item_pl:
|
||||
iteminfo[key] = item_pl[key]
|
||||
|
||||
Reference in New Issue
Block a user