def appleSoftwareUpdatesAvailable(forcecheck=False, suppresscheck=False): '''Checks for available Apple Software Updates, trying not to hit the SUS more than needed''' if suppresscheck: # typically because we're doing a logout install; if # there are no waiting Apple Updates we shouldn't # trigger a check for them. pass elif forcecheck: # typically because user initiated the check from # Managed Software Update.app unused_retcode = checkForSoftwareUpdates(forcecheck=True) else: # have we checked recently? Don't want to check with # Apple Software Update server too frequently now = NSDate.new() nextSUcheck = now lastSUcheckString = munkicommon.pref('LastAppleSoftwareUpdateCheck') if lastSUcheckString: try: lastSUcheck = NSDate.dateWithString_(lastSUcheckString) interval = 24 * 60 * 60 nextSUcheck = lastSUcheck.dateByAddingTimeInterval_(interval) except (ValueError, TypeError): pass if now.timeIntervalSinceDate_(nextSUcheck) >= 0: unused_retcode = checkForSoftwareUpdates(forcecheck=True) else: unused_retcode = checkForSoftwareUpdates(forcecheck=False) if writeAppleUpdatesFile(): displayAppleUpdateInfo() return True else: return False
def addPayloadFromPlistContents(self, plist_dict, domain, manage, is_byhost=False): """Add one plist dict contents to the profile's payloads. domain is the preferences domain (ie. com.apple.finder), manage is one of 'Once', 'Often' or 'Always', and is_byhost is a boolean representing whether the preference is to be used as a ByHost. """ payload_dict = {} # Frequency to apply settings, or 'state' if manage == 'Always': state = 'Forced' else: state = 'Set-Once' if is_byhost: domain += '.ByHost' # Yet another nested dict for the actual contents payload_dict[domain] = {} payload_dict[domain][state] = [] payload_dict[domain][state].append({}) payload_dict[domain][state][0]['mcx_preference_settings'] = plist_dict # Add a datestamp if we're managing 'Once' if manage == 'Once': now = NSDate.new() payload_dict[domain][state][0]['mcx_data_timestamp'] = now self._addPayload(payload_dict)
def format_time(timestamp=None): """Return timestamp as an ISO 8601 formatted string, in the current timezone. If timestamp isn't given the current time is used.""" if timestamp is None: return str(NSDate.new()) return str(NSDate.dateWithTimeIntervalSince1970_(timestamp))
def software_updates_available( self, force_check=False, suppress_check=False): """Checks for available Apple Software Updates, trying not to hit the SUS more than needed. Args: force_check: Boolean. If True, forces a softwareupdate run. suppress_check: Boolean. If True, skips a softwareupdate run. Returns: Integer. Count of available Apple updates. """ success = True if suppress_check: # don't check at all -- # typically because we are doing a logout install # just return any AppleUpdates info we already have if not os.path.exists(self.apple_updates_plist): return 0 try: plist = FoundationPlist.readPlist(self.apple_updates_plist) except FoundationPlist.FoundationPlistException: plist = {} return len(plist.get('AppleUpdates', [])) if force_check: # typically because user initiated the check from # Managed Software Update.app success = self.check_for_software_updates(force_check=True) else: # have we checked recently? Don't want to check with # Apple Software Update server too frequently now = NSDate.new() next_su_check = now last_su_check_string = prefs.pref( 'LastAppleSoftwareUpdateCheck') if last_su_check_string: try: last_su_check = NSDate.dateWithString_( last_su_check_string) # dateWithString_ returns None if invalid date string. if not last_su_check: raise ValueError interval = 24 * 60 * 60 # only force check every 24 hours. next_su_check = last_su_check.dateByAddingTimeInterval_( interval) except (ValueError, TypeError): pass if now.timeIntervalSinceDate_(next_su_check) >= 0: success = self.check_for_software_updates(force_check=True) else: success = self.check_for_software_updates(force_check=False) display.display_debug1( 'CheckForSoftwareUpdates result: %s' % success) if success: count = self.write_appleupdates_file() else: self.clear_apple_update_info() return 0 return count
def format_time(timestamp=None): """Return timestamp as an ISO 8601 formatted string, in the current timezone. If timestamp isn't given the current time is used.""" if timestamp is None: return str(NSDate.new()) else: return str(NSDate.dateWithTimeIntervalSince1970_(timestamp))
def make_pkginfo_metadata(): '''Records information about the environment in which the pkginfo was created so we have a bit of an audit trail. Returns a dictionary.''' metadata = {} metadata['created_by'] = NSUserName() metadata['creation_date'] = NSDate.new() metadata['munki_version'] = info.get_version() metadata['os_version'] = osutils.getOsVersion(only_major_minor=False) return metadata
def software_updates_available(self, force_check=False, suppress_check=False): """Checks for available Apple Software Updates, trying not to hit the SUS more than needed. Args: force_check: Boolean. If True, forces a softwareupdate run. suppress_check: Boolean. If True, skips a softwareupdate run. Returns: Integer. Count of available Apple updates. """ if suppress_check: # don't check at all -- # typically because we are doing a logout install # just return any AppleUpdates info we already have return self.cached_update_count() if force_check: # typically because user initiated the check from # Managed Software Update.app updatecount = self.check_for_software_updates(force_check=True) else: # have we checked recently? Don't want to check with # Apple Software Update server too frequently now = NSDate.new() next_su_check = now last_su_check_string = prefs.pref('LastAppleSoftwareUpdateCheck') if last_su_check_string: try: last_su_check = NSDate.dateWithString_( last_su_check_string) # dateWithString_ returns None if invalid date string. if not last_su_check: raise ValueError interval = 24 * 60 * 60 # only force check every 24 hours. next_su_check = last_su_check.dateByAddingTimeInterval_( interval) except (ValueError, TypeError): pass if now.timeIntervalSinceDate_(next_su_check) >= 0: updatecount = self.check_for_software_updates(force_check=True) else: updatecount = self.check_for_software_updates( force_check=False) display.display_debug1('CheckForSoftwareUpdates result: %s' % updatecount) if updatecount == -1: # some (transient?) communications error with the su server; return # cached AppleInfo return self.cached_update_count() elif updatecount == 0: self.clear_apple_update_info() else: _ = self.write_appleupdates_file() return updatecount
def _no_nudge_all_time(self): if (self.time_between_notif > 0 and self.first_seen and self.last_seen): difference = _last_seen_vs_now(self.last_seen) if difference.seconds < self.time_between_notif: info = 'Last seen date is within notification threshold' nudgelog(f'{info}: {str(self.time_between_notif)} seconds') sys.exit(0) if not self.first_seen: set_app_pref('first_seen', NSDate.new()) self.first_seen = app_pref('first_seen')
def run(self): '''Function that will run nudge experience''' set_app_pref('last_seen', NSDate.new()) self.nudge.hidden = True self.nudge.run()
def install_apple_updates(self, only_unattended=False): """Uses softwareupdate to install previously downloaded updates. Returns: Boolean. True if a restart is needed after install, False otherwise. """ # disable Stop button if we are presenting GUI status if display.munkistatusoutput: munkistatus.hideStopButton() # Get list of unattended_installs if only_unattended: msg = 'Installing unattended Apple Software Updates...' unattended_install_items, unattended_install_product_ids = \ self.get_unattended_installs() # ensure that we don't restart for unattended installations restartneeded = False if not unattended_install_items: return False # didn't find any unattended installs else: msg = 'Installing available Apple Software Updates...' restartneeded = self.restart_needed() self._display_status_major(msg) installlist = self.software_update_info() remaining_apple_updates = [] installresults = {'installed': [], 'download': []} su_options = ['-i'] if only_unattended: # Append list of unattended_install items su_options.extend(unattended_install_items) # Filter installist to only include items # which we're attempting to install filtered_installlist = [item for item in installlist if item.get('productKey') in unattended_install_product_ids] # record items we aren't planning to attempt to install remaining_apple_updates = [item for item in installlist if item not in filtered_installlist] installlist = filtered_installlist else: # We're installing all available updates; add all their names for item in installlist: su_options.append( item['name'] + '-' + item['version_to_install']) # new in 10.11: '--no-scan' flag to tell softwareupdate to just install # and not rescan for available updates. os_version_tuple = osutils.getOsVersion(as_tuple=True) if os_version_tuple >= (10, 11): su_options.append('--no-scan') # 10.11 seems not to like file:// URLs, and we don't really need # to switch to a local file URL anyway since we now have the # --no-scan option catalog_url = None else: # use our local catalog if not os.path.exists(self.applesync.local_catalog_path): display.display_error( 'Missing local Software Update catalog at %s', self.applesync.local_catalog_path) return False # didn't do anything, so no restart needed catalog_url = 'file://localhost' + urllib2.quote( self.applesync.local_catalog_path) retcode = self._run_softwareupdate( su_options, mode='install', catalog_url=catalog_url, results=installresults) if not 'InstallResults' in reports.report: reports.report['InstallResults'] = [] display.display_debug1( 'Raw Apple Update install results: %s', installresults) for item in installlist: rep = {} rep['name'] = item.get('apple_product_name') rep['version'] = item.get('version_to_install', '') rep['applesus'] = True rep['time'] = NSDate.new() rep['productKey'] = item.get('productKey', '') message = 'Apple Software Update install of %s-%s: %s' if rep['name'] in installresults['installed']: rep['status'] = 0 install_status = 'SUCCESSFUL' elif ('display_name' in item and item['display_name'] in installresults['installed']): rep['status'] = 0 install_status = 'SUCCESSFUL' elif rep['name'] in installresults['download']: rep['status'] = -1 install_status = 'FAILED due to missing package.' display.display_warning( 'Apple update %s, %s failed. A sub-package was missing ' 'on disk at time of install.' % (rep['name'], rep['productKey'])) else: rep['status'] = -2 install_status = 'FAILED for unknown reason' display.display_warning( 'Apple update %s, %s may have failed to install. No record ' 'of success or failure.', rep['name'], rep['productKey']) if installresults['installed']: display.display_warning( 'softwareupdate recorded these installations: %s', installresults['installed']) reports.report['InstallResults'].append(rep) log_msg = message % (rep['name'], rep['version'], install_status) munkilog.log(log_msg, 'Install.log') if retcode: # there was an error display.display_error('softwareupdate error: %s' % retcode) if not remaining_apple_updates: # clean up our now stale local cache self.applesync.clean_up_cache() # remove the now invalid AppleUpdates.plist self.clear_apple_update_info() else: # we installed some of the updates, but some are still uninstalled. # re-write the apple_update_info to match plist = {'AppleUpdates': remaining_apple_updates} FoundationPlist.writePlist(plist, self.apple_updates_plist) #TODO: clean up cached items we no longer need # Also clear our pref value for last check date. We may have # just installed an update which is a pre-req for some other update. # Let's check again soon. prefs.set_pref('LastAppleSoftwareUpdateCheck', None) # show stop button again if display.munkistatusoutput: munkistatus.showStopButton() return restartneeded
def process_removals(removallist, only_unattended=False): '''processes removals from the removal list''' restart_flag = False index = 0 skipped_removals = [] for item in removallist: if only_unattended: if not item.get('unattended_uninstall'): skipped_removals.append(item) display.display_detail( ('Skipping removal of %s because it\'s not unattended.' % item['name'])) continue elif processes.blocking_applications_running(item): skipped_removals.append(item) display.display_detail( 'Skipping unattended removal of %s because ' 'blocking application(s) running.' % item['name']) continue dependent_skipped_items = skipped_items_that_require_this( item, skipped_removals) if dependent_skipped_items: # need to skip this too skipped_removals.append(item) display.display_detail( 'Skipping removal of %s because these ' 'skipped items required it: %s' % (item['name'], ", ".join(dependent_skipped_items))) continue if processes.stop_requested(): return restart_flag, skipped_removals if not item.get('installed'): # not installed, so skip it (this shouldn't happen...) continue index += 1 display_name = item.get('display_name') or item.get('name') display.display_status_major( "Removing %s (%s of %s)...", display_name, index, len(removallist)) retcode = 0 # run preuninstall_script if it exists if 'preuninstall_script' in item: retcode = scriptutils.run_embedded_script( 'preuninstall_script', item) if retcode == 0 and 'uninstall_method' in item: uninstallmethod = item['uninstall_method'] if uninstallmethod == "removepackages": if 'packages' in item: restart_flag = requires_restart(item) retcode = rmpkgs.removepackages(item['packages'], forcedeletebundles=True) if retcode: if retcode == -128: message = ( "Uninstall of %s was cancelled." % display_name) else: message = "Uninstall of %s failed." % display_name display.display_error(message) else: munkilog.log( "Uninstall of %s was successful." % display_name) elif uninstallmethod.startswith("Adobe"): retcode = adobeutils.do_adobe_removal(item) elif uninstallmethod == "remove_copied_items": retcode = remove_copied_items(item.get('items_to_remove')) elif uninstallmethod == "remove_app": # deprecated with appdmg! remove_app_info = item.get('remove_app_info', None) if remove_app_info: path_to_remove = remove_app_info['path'] display.display_status_minor( 'Removing %s' % path_to_remove) retcode = subprocess.call( ["/bin/rm", "-rf", path_to_remove]) if retcode: display.display_error( "Removal error for %s", path_to_remove) else: display.display_error( "Application removal info missing from %s", display_name) elif uninstallmethod == 'remove_profile': identifier = item.get('PayloadIdentifier') if identifier: retcode = 0 if not profiles.remove_profile(identifier): retcode = -1 display.display_error( "Profile removal error for %s", identifier) else: display.display_error( "Profile removal info missing from %s", display_name) elif uninstallmethod == 'uninstall_script': retcode = scriptutils.run_embedded_script( 'uninstall_script', item) if retcode == 0 and requires_restart(item): restart_flag = True elif (os.path.exists(uninstallmethod) and os.access(uninstallmethod, os.X_OK)): # it's a script or program to uninstall retcode = scriptutils.run_script( display_name, uninstallmethod, 'uninstall script') if retcode == 0 and requires_restart(item): restart_flag = True else: munkilog.log("Uninstall of %s failed because there was no " "valid uninstall method." % display_name) retcode = -99 if retcode == 0 and item.get('postuninstall_script'): retcode = scriptutils.run_embedded_script( 'postuninstall_script', item) if retcode: # we won't consider postuninstall script failures as fatal # since the item has been uninstalled # but admin should be notified display.display_warning( 'Postuninstall script for %s returned %s' % (item['name'], retcode)) # reset retcode to 0 so we will mark this uninstall # as successful retcode = 0 # record removal success/failure if not 'RemovalResults' in reports.report: reports.report['RemovalResults'] = [] if retcode == 0: success_msg = "Removal of %s: SUCCESSFUL" % display_name munkilog.log(success_msg, "Install.log") remove_from_selfserve_uninstalls(item['name']) else: failure_msg = "Removal of %s: " % display_name + \ " FAILED with return code: %s" % retcode munkilog.log(failure_msg, "Install.log") # append failed removal to skipped_removals so dependencies # aren't removed yet. skipped_removals.append(item) removal_result = { 'display_name': display_name, 'name': item['name'], 'status': retcode, 'time': NSDate.new(), 'unattended': only_unattended, } reports.report['RemovalResults'].append(removal_result) return (restart_flag, skipped_removals)
def install_with_info( dirpath, installlist, only_unattended=False, applesus=False): """ Uses the installlist to install items in the correct order. """ restartflag = False itemindex = 0 skipped_installs = [] for item in installlist: # Keep track of when this particular install started. utc_now = datetime.datetime.utcnow() itemindex = itemindex + 1 if item.get('installer_type') == 'startosinstall': skipped_installs.append(item) display.display_debug1( 'Skipping install of %s because it\'s a startosinstall item. ' 'Will install later.' % item['name']) continue if only_unattended: if not item.get('unattended_install'): skipped_installs.append(item) display.display_detail( 'Skipping install of %s because it\'s not unattended.' % item['name']) continue elif processes.blocking_applications_running(item): skipped_installs.append(item) display.display_detail( 'Skipping unattended install of %s because blocking ' 'application(s) running.' % item['name']) continue skipped_prereqs = item_prereqs_in_skipped_items(item, skipped_installs) if skipped_prereqs: # one or more prerequisite for this item was skipped or failed; # need to skip this item too skipped_installs.append(item) if only_unattended: format_str = ('Skipping unattended install of %s because these ' 'prerequisites were skipped: %s') else: format_str = ('Skipping install of %s because these ' 'prerequisites were not installed: %s') display.display_detail( format_str % (item['name'], ", ".join(skipped_prereqs))) continue if processes.stop_requested(): return restartflag, skipped_installs display_name = item.get('display_name') or item.get('name') version_to_install = item.get('version_to_install', '') retcode = 0 if 'preinstall_script' in item: retcode = scriptutils.run_embedded_script('preinstall_script', item) if retcode == 0 and 'installer_item' in item: display.display_status_major( "Installing %s (%s of %s)" % (display_name, itemindex, len(installlist))) installer_type = item.get("installer_type", "") itempath = os.path.join(dirpath, item["installer_item"]) if installer_type != "nopkg" and not os.path.exists(itempath): # can't install, so we should stop. Since later items might # depend on this one, we shouldn't continue display.display_error( "Installer item %s was not found.", item["installer_item"]) return restartflag, skipped_installs # Adobe installs if installer_type.startswith("Adobe"): retcode = adobeutils.do_adobe_install(item) if retcode == 0 and requires_restart(item): restartflag = True if retcode == 8: # Adobe Setup says restart needed. restartflag = True retcode = 0 # copy_from_dmg install elif installer_type == "copy_from_dmg": retcode = dmg.copy_from_dmg(itempath, item.get('items_to_copy')) if retcode == 0 and requires_restart(item): restartflag = True # appdmg install (depercated) elif installer_type == "appdmg": display.display_warning( "install_type 'appdmg' is deprecated. Use 'copy_from_dmg'.") retcode = dmg.copy_app_from_dmg(itempath) # configuration profile install elif installer_type == 'profile': # profiles.install_profile returns True/False retcode = 0 identifier = item.get('PayloadIdentifier') if not profiles.install_profile(itempath, identifier): retcode = -1 # nopkg (Packageless) install elif installer_type == "nopkg": restartflag = requires_restart(item) # unknown installer_type elif installer_type != "": # we've encountered an installer type # we don't know how to handle display.display_error( "Unsupported install type: %s" % installer_type) retcode = -99 # better be Apple installer package else: (retcode, need_to_restart) = handle_apple_package_install( item, itempath) if need_to_restart: restartflag = True if processes.stop_requested(): return restartflag, skipped_installs # install succeeded. Do we have a postinstall_script? if retcode == 0 and 'postinstall_script' in item: # only run embedded postinstall script if the install did not # return a failure code retcode = scriptutils.run_embedded_script( 'postinstall_script', item) if retcode: # we won't consider postinstall script failures as fatal # since the item has been installed via package/disk image # but admin should be notified display.display_warning( 'Postinstall script for %s returned %s' % (item['name'], retcode)) # reset retcode to 0 so we will mark this install # as successful retcode = 0 # if install was successful and this is a SelfService OnDemand install # remove the item from the SelfServeManifest's managed_installs if retcode == 0 and item.get('OnDemand'): remove_from_selfserve_installs(item['name']) # record install success/failure if not 'InstallResults' in reports.report: reports.report['InstallResults'] = [] if applesus: message = "Apple SUS install of %s-%s: %s" else: message = "Install of %s-%s: %s" if retcode == 0: status = "SUCCESSFUL" else: status = "FAILED with return code: %s" % retcode # add this failed install to the skipped_installs list # so that any item later in the list that requires this # item is skipped as well. skipped_installs.append(item) log_msg = message % (display_name, version_to_install, status) munkilog.log(log_msg, "Install.log") # Calculate install duration; note, if a machine is put to sleep # during the install this time may be inaccurate. utc_now_complete = datetime.datetime.utcnow() duration_seconds = (utc_now_complete - utc_now).seconds download_speed = item.get('download_kbytes_per_sec', 0) install_result = { 'display_name': display_name, 'name': item['name'], 'version': version_to_install, 'applesus': applesus, 'status': retcode, 'time': NSDate.new(), 'duration_seconds': duration_seconds, 'download_kbytes_per_sec': download_speed, 'unattended': only_unattended, } reports.report['InstallResults'].append(install_result) # 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 # with choicesXML files applied to a metapackage or # multiple packages being installed from a single DMG foundagain = False current_installer_item = item['installer_item'] # are we at the end of the installlist? # (we already incremented itemindex for display # so with zero-based arrays itemindex now points to the item # after the current item) if itemindex < len(installlist): # nope, let's check the remaining items for lateritem in installlist[itemindex:]: if (lateritem.get('installer_item') == current_installer_item): foundagain = True break # need to check skipped_installs as well if not foundagain: for skipped_item in skipped_installs: if (skipped_item.get('installer_item') == current_installer_item): foundagain = True break # ensure package is not deleted from cache if installation # fails by checking retcode if not foundagain and retcode == 0: # 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): if os.path.isdir(itempath): retcode = subprocess.call(["/bin/rm", "-rf", itempath]) else: # flat pkg or dmg retcode = subprocess.call(["/bin/rm", itempath]) if pkgutils.hasValidDiskImageExt(itempath): shadowfile = os.path.join(itempath, ".shadow") if os.path.exists(shadowfile): retcode = subprocess.call(["/bin/rm", shadowfile]) return (restartflag, skipped_installs)
def process_removals(removallist, only_unattended=False): '''processes removals from the removal list''' restart_flag = False index = 0 skipped_removals = [] for item in removallist: if only_unattended: if not item.get('unattended_uninstall'): skipped_removals.append(item) display.display_detail( ('Skipping removal of %s because it\'s not unattended.' % item['name'])) continue elif processes.blocking_applications_running(item): skipped_removals.append(item) display.display_detail( 'Skipping unattended removal of %s because ' 'blocking application(s) running.' % item['name']) continue dependent_skipped_items = skipped_items_that_require_this( item, skipped_removals) if dependent_skipped_items: # need to skip this too skipped_removals.append(item) display.display_detail( 'Skipping removal of %s because these ' 'skipped items required it: %s' % (item['name'], ", ".join(dependent_skipped_items))) continue if processes.stop_requested(): return restart_flag, skipped_removals if not item.get('installed'): # not installed, so skip it (this shouldn't happen...) continue index += 1 display_name = item.get('display_name') or item.get('name') display.display_status_major("Removing %s (%s of %s)...", display_name, index, len(removallist)) retcode = 0 # run preuninstall_script if it exists if 'preuninstall_script' in item: retcode = scriptutils.run_embedded_script('preuninstall_script', item) if retcode == 0 and 'uninstall_method' in item: uninstallmethod = item['uninstall_method'] if uninstallmethod == "removepackages": if 'packages' in item: restart_flag = requires_restart(item) retcode = rmpkgs.removepackages(item['packages'], forcedeletebundles=True) if retcode: if retcode == -128: message = ("Uninstall of %s was cancelled." % display_name) else: message = "Uninstall of %s failed." % display_name display.display_error(message) else: munkilog.log("Uninstall of %s was successful." % display_name) elif uninstallmethod.startswith("Adobe"): retcode = adobeutils.do_adobe_removal(item) elif uninstallmethod == "remove_copied_items": retcode = remove_copied_items(item.get('items_to_remove')) elif uninstallmethod == "remove_app": # deprecated with appdmg! remove_app_info = item.get('remove_app_info', None) if remove_app_info: path_to_remove = remove_app_info['path'] display.display_status_minor('Removing %s' % path_to_remove) retcode = subprocess.call( ["/bin/rm", "-rf", path_to_remove]) if retcode: display.display_error("Removal error for %s", path_to_remove) else: display.display_error( "Application removal info missing from %s", display_name) elif uninstallmethod == 'remove_profile': identifier = item.get('PayloadIdentifier') if identifier: retcode = 0 if not profiles.remove_profile(identifier): retcode = -1 display.display_error("Profile removal error for %s", identifier) else: display.display_error( "Profile removal info missing from %s", display_name) elif uninstallmethod == 'uninstall_script': retcode = scriptutils.run_embedded_script( 'uninstall_script', item) if retcode == 0 and requires_restart(item): restart_flag = True elif (os.path.exists(uninstallmethod) and os.access(uninstallmethod, os.X_OK)): # it's a script or program to uninstall retcode = scriptutils.run_script(display_name, uninstallmethod, 'uninstall script') if retcode == 0 and requires_restart(item): restart_flag = True else: munkilog.log("Uninstall of %s failed because there was no " "valid uninstall method." % display_name) retcode = -99 if retcode == 0 and item.get('postuninstall_script'): retcode = scriptutils.run_embedded_script( 'postuninstall_script', item) if retcode: # we won't consider postuninstall script failures as fatal # since the item has been uninstalled # but admin should be notified display.display_warning( 'Postuninstall script for %s returned %s' % (item['name'], retcode)) # reset retcode to 0 so we will mark this uninstall # as successful retcode = 0 # record removal success/failure if not 'RemovalResults' in reports.report: reports.report['RemovalResults'] = [] if retcode == 0: success_msg = "Removal of %s: SUCCESSFUL" % display_name munkilog.log(success_msg, "Install.log") manifestutils.remove_from_selfserve_uninstalls(item['name']) else: failure_msg = "Removal of %s: " % display_name + \ " FAILED with return code: %s" % retcode munkilog.log(failure_msg, "Install.log") # append failed removal to skipped_removals so dependencies # aren't removed yet. skipped_removals.append(item) removal_result = { 'display_name': display_name, 'name': item['name'], 'status': retcode, 'time': NSDate.new(), 'unattended': only_unattended, } reports.report['RemovalResults'].append(removal_result) return (restart_flag, skipped_removals)
def install_with_info(dirpath, installlist, only_unattended=False, applesus=False): """ Uses the installlist to install items in the correct order. """ restartflag = False itemindex = 0 skipped_installs = [] for item in installlist: # Keep track of when this particular install started. utc_now = datetime.datetime.utcnow() itemindex = itemindex + 1 if item.get('installer_type') == 'startosinstall': skipped_installs.append(item) display.display_debug1( 'Skipping install of %s because it\'s a startosinstall item. ' 'Will install later.' % item['name']) continue if only_unattended: if not item.get('unattended_install'): skipped_installs.append(item) display.display_detail( 'Skipping install of %s because it\'s not unattended.' % item['name']) continue elif processes.blocking_applications_running(item): skipped_installs.append(item) display.display_detail( 'Skipping unattended install of %s because blocking ' 'application(s) running.' % item['name']) continue skipped_prereqs = item_prereqs_in_skipped_items(item, skipped_installs) if skipped_prereqs: # one or more prerequisite for this item was skipped or failed; # need to skip this item too skipped_installs.append(item) if only_unattended: format_str = ( 'Skipping unattended install of %s because these ' 'prerequisites were skipped: %s') else: format_str = ('Skipping install of %s because these ' 'prerequisites were not installed: %s') display.display_detail(format_str % (item['name'], ", ".join(skipped_prereqs))) continue if processes.stop_requested(): return restartflag, skipped_installs display_name = item.get('display_name') or item.get('name') version_to_install = item.get('version_to_install', '') retcode = 0 if 'preinstall_script' in item: retcode = scriptutils.run_embedded_script('preinstall_script', item) if retcode == 0 and 'installer_item' in item: display.display_status_major( "Installing %s (%s of %s)" % (display_name, itemindex, len(installlist))) installer_type = item.get("installer_type", "") itempath = os.path.join(dirpath, item["installer_item"]) if installer_type != "nopkg" and not os.path.exists(itempath): # can't install, so we should stop. Since later items might # depend on this one, we shouldn't continue display.display_error("Installer item %s was not found.", item["installer_item"]) return restartflag, skipped_installs # Adobe installs if installer_type.startswith("Adobe"): retcode = adobeutils.do_adobe_install(item) if retcode == 0 and requires_restart(item): restartflag = True if retcode == 8: # Adobe Setup says restart needed. restartflag = True retcode = 0 # copy_from_dmg install elif installer_type == "copy_from_dmg": retcode = dmg.copy_from_dmg(itempath, item.get('items_to_copy')) if retcode == 0 and requires_restart(item): restartflag = True # appdmg install (depercated) elif installer_type == "appdmg": display.display_warning( "install_type 'appdmg' is deprecated. Use 'copy_from_dmg'." ) retcode = dmg.copy_app_from_dmg(itempath) # configuration profile install elif installer_type == 'profile': # profiles.install_profile returns True/False retcode = 0 identifier = item.get('PayloadIdentifier') if not profiles.install_profile(itempath, identifier): retcode = -1 if retcode == 0 and requires_restart(item): restartflag = True # nopkg (Packageless) install elif installer_type == "nopkg": restartflag = restartflag or requires_restart(item) # unknown installer_type elif installer_type != "": # we've encountered an installer type # we don't know how to handle display.display_error("Unsupported install type: %s" % installer_type) retcode = -99 # better be Apple installer package else: (retcode, need_to_restart) = handle_apple_package_install( item, itempath) if need_to_restart: restartflag = True if processes.stop_requested(): return restartflag, skipped_installs # install succeeded. Do we have a postinstall_script? if retcode == 0 and 'postinstall_script' in item: # only run embedded postinstall script if the install did not # return a failure code retcode = scriptutils.run_embedded_script('postinstall_script', item) if retcode: # we won't consider postinstall script failures as fatal # since the item has been installed via package/disk image # but admin should be notified display.display_warning( 'Postinstall script for %s returned %s' % (item['name'], retcode)) # reset retcode to 0 so we will mark this install # as successful retcode = 0 # if install was successful and this is a SelfService OnDemand install # remove the item from the SelfServeManifest's managed_installs if retcode == 0 and item.get('OnDemand'): manifestutils.remove_from_selfserve_installs(item['name']) # record install success/failure if not 'InstallResults' in reports.report: reports.report['InstallResults'] = [] if applesus: message = "Apple SUS install of %s-%s: %s" else: message = "Install of %s-%s: %s" if retcode == 0: status = "SUCCESSFUL" else: status = "FAILED with return code: %s" % retcode # add this failed install to the skipped_installs list # so that any item later in the list that requires this # item is skipped as well. skipped_installs.append(item) log_msg = message % (display_name, version_to_install, status) munkilog.log(log_msg, "Install.log") # Calculate install duration; note, if a machine is put to sleep # during the install this time may be inaccurate. utc_now_complete = datetime.datetime.utcnow() duration_seconds = (utc_now_complete - utc_now).seconds download_speed = item.get('download_kbytes_per_sec', 0) install_result = { 'display_name': display_name, 'name': item['name'], 'version': version_to_install, 'applesus': applesus, 'status': retcode, 'time': NSDate.new(), 'duration_seconds': duration_seconds, 'download_kbytes_per_sec': download_speed, 'unattended': only_unattended, } reports.report['InstallResults'].append(install_result) # 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 # with choicesXML files applied to a metapackage or # multiple packages being installed from a single DMG stillneeded = False current_installer_item = item['installer_item'] # are we at the end of the installlist? # (we already incremented itemindex for display # so with zero-based arrays itemindex now points to the item # after the current item) if itemindex < len(installlist): # nope, let's check the remaining items for lateritem in installlist[itemindex:]: if (lateritem.get('installer_item') == current_installer_item): stillneeded = True break # check to see if the item is both precache and OnDemand if not stillneeded and item.get('precache') and item.get('OnDemand'): stillneeded = True break # need to check skipped_installs as well if not stillneeded: for skipped_item in skipped_installs: if (skipped_item.get('installer_item') == current_installer_item): stillneeded = True break # ensure package is not deleted from cache if installation # fails by checking retcode if not stillneeded and retcode == 0: # 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): if os.path.isdir(itempath): retcode = subprocess.call(["/bin/rm", "-rf", itempath]) else: # flat pkg or dmg retcode = subprocess.call(["/bin/rm", itempath]) if pkgutils.hasValidDiskImageExt(itempath): shadowfile = os.path.join(itempath, ".shadow") if os.path.exists(shadowfile): retcode = subprocess.call(["/bin/rm", shadowfile]) return (restartflag, skipped_installs)
def install_apple_updates(self, only_unattended=False): """Uses softwareupdate to install previously downloaded updates. Returns: Boolean. True if a restart is needed after install, False otherwise. """ # disable Stop button if we are presenting GUI status if display.munkistatusoutput: munkistatus.hideStopButton() # Get list of unattended_installs if only_unattended: msg = 'Installing unattended Apple Software Updates...' # Creating an 'unattended_install' filtered catalog # against the existing filtered catalog is not an option as # cached downloads are purged if they do not exist in the # filtered catalog. Instead, get a list of updates, and their # product_ids, that are eligible for unattended_install. unattended_install_items, unattended_install_product_ids = \ self.get_unattended_installs() # ensure that we don't restart for unattended installations restartneeded = False if not unattended_install_items: return False # didn't find any unattended installs else: msg = 'Installing available Apple Software Updates...' restartneeded = self.restart_needed() self._display_status_major(msg) installlist = self.software_update_info() installresults = {'installed': [], 'download': []} su_options = ['-i'] if only_unattended: # Append list of unattended_install items su_options.extend(unattended_install_items) # Filter installist to only include items # which we're attempting to install installlist = [ item for item in installlist if item.get('productKey') in unattended_install_product_ids ] else: # We're installing all available updates; add all their names for item in installlist: su_options.append(item['name'] + '-' + item['version_to_install']) # new in 10.11: '--no-scan' flag to tell softwareupdate to just install # and not rescan for available updates. os_version_tuple = osutils.getOsVersion(as_tuple=True) if os_version_tuple >= (10, 11): su_options.append('--no-scan') # 10.11 seems not to like file:// URLs, and we don't really need # to switch to a local file URL anyway since we now have the # --no-scan option catalog_url = None else: # use our filtered local catalog if not os.path.exists(self.applesync.local_catalog_path): display.display_error( 'Missing local Software Update catalog at %s', self.applesync.local_catalog_path) return False # didn't do anything, so no restart needed catalog_url = 'file://localhost' + urllib2.quote( self.applesync.local_catalog_path) retcode = self._run_softwareupdate(su_options, mode='install', catalog_url=catalog_url, results=installresults) if not 'InstallResults' in reports.report: reports.report['InstallResults'] = [] display.display_debug1('Raw Apple Update install results: %s', installresults) for item in installlist: rep = {} rep['name'] = item.get('apple_product_name') rep['version'] = item.get('version_to_install', '') rep['applesus'] = True rep['time'] = NSDate.new() rep['productKey'] = item.get('productKey', '') message = 'Apple Software Update install of %s-%s: %s' if rep['name'] in installresults['installed']: rep['status'] = 0 install_status = 'SUCCESSFUL' elif ('display_name' in item and item['display_name'] in installresults['installed']): rep['status'] = 0 install_status = 'SUCCESSFUL' elif rep['name'] in installresults['download']: rep['status'] = -1 install_status = 'FAILED due to missing package.' display.display_warning( 'Apple update %s, %s failed. A sub-package was missing ' 'on disk at time of install.' % (rep['name'], rep['productKey'])) else: rep['status'] = -2 install_status = 'FAILED for unknown reason' display.display_warning( 'Apple update %s, %s may have failed to install. No record ' 'of success or failure.', rep['name'], rep['productKey']) if installresults['installed']: display.display_warning( 'softwareupdate recorded these installations: %s', installresults['installed']) reports.report['InstallResults'].append(rep) log_msg = message % (rep['name'], rep['version'], install_status) munkilog.log(log_msg, 'Install.log') if retcode: # there was an error display.display_error('softwareupdate error: %s' % retcode) # Refresh Applicable updates and catalogs # since we may have performed some unattended installs if only_unattended: product_ids = self.available_update_product_ids() self.applesync.write_filtered_catalog(product_ids) # clean up our now stale local cache if not only_unattended: self.applesync.clean_up_cache() # remove the now invalid AppleUpdates.plist and AvailableUpdates.plist self.clear_apple_update_info() # Also clear our pref value for last check date. We may have # just installed an update which is a pre-req for some other update. # Let's check again soon. prefs.set_pref('LastAppleSoftwareUpdateCheck', None) # show stop button again if display.munkistatusoutput: munkistatus.showStopButton() return restartneeded
def install_apple_updates(self, only_unattended=False): """Uses softwareupdate to install previously downloaded updates. Returns: Boolean. True if a restart is needed after install, False otherwise. """ # disable Stop button if we are presenting GUI status if display.munkistatusoutput: munkistatus.hideStopButton() self.shutdown_instead_of_restart = False # Get list of unattended_installs if only_unattended: msg = 'Installing unattended Apple Software Updates...' unattended_install_items, unattended_install_product_ids = \ self.get_unattended_installs() # ensure that we don't restart for unattended installations restart_action = POSTACTION_NONE if not unattended_install_items: return False # didn't find any unattended installs else: msg = 'Installing available Apple Software Updates...' restart_action = self.restart_action_for_updates() display.display_status_major(msg) installlist = self.software_update_info() remaining_apple_updates = [] installresults = {'installed': [], 'download': []} su_options = ['-i'] if only_unattended: # Append list of unattended_install items su_options.extend(unattended_install_items) # Filter installlist to only include items # which we're attempting to install filtered_installlist = [item for item in installlist if item.get('productKey') in unattended_install_product_ids] # record items we aren't planning to attempt to install remaining_apple_updates = [item for item in installlist if item not in filtered_installlist] installlist = filtered_installlist else: # We're installing all available updates; add all their names for item in installlist: su_options.append( item['name'] + '-' + item['version_to_install']) # new in 10.11: '--no-scan' flag to tell softwareupdate to just install # and not rescan for available updates. os_version_tuple = osutils.getOsVersion(as_tuple=True) if os_version_tuple >= (10, 11): try: # attempt to fetch the apple catalog to confirm connectivity # to the Apple Software Update server self.applesync.cache_apple_catalog() except (sync.Error, fetch.Error): # network or catalog server not available, suppress scan # (we used to do this all the time, but this led to issues # with updates cached "too long" in 10.12+) munkilog.log( "WARNING: Cannot reach Apple Software Update server while " "installing Apple updates") su_options.append('--no-scan') # 10.11 seems not to like file:// URLs, and we don't really need # to switch to a local file URL anyway since we now have the # --no-scan option catalog_url = None else: # use our local catalog if not os.path.exists(self.applesync.local_catalog_path): display.display_error( 'Missing local Software Update catalog at %s', self.applesync.local_catalog_path) return False # didn't do anything, so no restart needed catalog_url = 'file://localhost' + urllib2.quote( self.applesync.local_catalog_path) su_start_date = NSDate.new() installresults = su_tool.run(su_options, catalog_url=catalog_url) retcode = installresults.get('exit_code', 0) self.shutdown_instead_of_restart = installresults.get( 'post_action') == POSTACTION_SHUTDOWN su_end_date = NSDate.new() # get the items that were just installed from InstallHistory.plist installed_items = softwareupdated_installhistory( start_date=su_start_date, end_date=su_end_date) display.display_debug2( 'InstallHistory.plist items:\n%s', installed_items) if not 'InstallResults' in reports.report: reports.report['InstallResults'] = [] display.display_debug1( 'Raw Apple Update install results: %s', installresults) for item in installlist: rep = {} rep['name'] = item.get('apple_product_name') rep['version'] = item.get('version_to_install', '') rep['applesus'] = True rep['time'] = su_end_date rep['productKey'] = item.get('productKey', '') message = 'Apple Software Update install of %s-%s: %s' # first try to match against the items from InstallHistory.plist matched_installed_items = [ ih_item for ih_item in installed_items if ih_item['displayName'] in [ item.get('apple_product_name'), item.get('display_name')] and ih_item['displayVersion'] == item.get('version_to_install') ] if matched_installed_items: display.display_debug2('Matched %s in InstallHistory.plist', item.get('apple_product_name')) rep['status'] = 0 rep['time'] = matched_installed_items[0]['date'] install_status = 'SUCCESSFUL' elif rep['name'] in installresults['installed']: rep['status'] = 0 install_status = 'SUCCESSFUL' elif ('display_name' in item and item['display_name'] in installresults['installed']): rep['status'] = 0 install_status = 'SUCCESSFUL' elif rep['name'] in installresults['download']: rep['status'] = -1 install_status = 'FAILED due to missing package.' display.display_warning( 'Apple update %s, %s failed. A sub-package was missing ' 'on disk at time of install.' % (rep['name'], rep['productKey'])) else: rep['status'] = -2 install_status = 'FAILED for unknown reason' display.display_warning( 'Apple update %s, %s may have failed to install. No record ' 'of success or failure.', rep['name'], rep['productKey']) if installresults['installed']: display.display_warning( 'softwareupdate recorded these installations: %s', installresults['installed']) reports.report['InstallResults'].append(rep) log_msg = message % (rep['name'], rep['version'], install_status) munkilog.log(log_msg, 'Install.log') if retcode: # there was an error display.display_error('softwareupdate error: %s' % retcode) if not remaining_apple_updates: # clean up our now stale local cache self.applesync.clean_up_cache() # remove the now invalid AppleUpdates.plist self.clear_apple_update_info() else: # we installed some of the updates, but some are still uninstalled. # re-write the apple_update_info to match plist = {'AppleUpdates': remaining_apple_updates} FoundationPlist.writePlist(plist, self.apple_updates_plist) # Also clear our pref value for last check date. We may have # just installed an update which is a pre-req for some other update. # Let's check again soon. prefs.set_pref('LastAppleSoftwareUpdateCheck', None) # show stop button again if display.munkistatusoutput: munkistatus.showStopButton() if self.shutdown_instead_of_restart: display.display_info( 'One or more Apple updates requires a shutdown instead of ' 'restart.') restart_action = POSTACTION_SHUTDOWN return restart_action
def install_apple_updates(self, only_unattended=False): """Uses softwareupdate to install previously downloaded updates. Returns: Boolean. True if a restart is needed after install, False otherwise. """ # disable Stop button if we are presenting GUI status if display.munkistatusoutput: munkistatus.hideStopButton() self.shutdown_instead_of_restart = False # Get list of unattended_installs if only_unattended: msg = 'Installing unattended Apple Software Updates...' unattended_install_items, unattended_install_product_ids = \ self.get_unattended_installs() # ensure that we don't restart for unattended installations restart_action = POSTACTION_NONE if not unattended_install_items: return False # didn't find any unattended installs else: msg = 'Installing available Apple Software Updates...' restart_action = self.restart_action_for_updates() display.display_status_major(msg) installlist = self.software_update_info() remaining_apple_updates = [] installresults = {'installed': [], 'download': []} su_options = ['-i'] if only_unattended: # Append list of unattended_install items su_options.extend(unattended_install_items) # Filter installlist to only include items # which we're attempting to install filtered_installlist = [ item for item in installlist if item.get('productKey') in unattended_install_product_ids ] # record items we aren't planning to attempt to install remaining_apple_updates = [ item for item in installlist if item not in filtered_installlist ] installlist = filtered_installlist else: # We're installing all available updates; add all their names for item in installlist: su_options.append(item['name'] + '-' + item['version_to_install']) # new in 10.11: '--no-scan' flag to tell softwareupdate to just install # and not rescan for available updates. os_version_tuple = osutils.getOsVersion(as_tuple=True) if os_version_tuple >= (10, 11): try: # attempt to fetch the apple catalog to confirm connectivity # to the Apple Software Update server self.applesync.cache_apple_catalog() except (sync.Error, fetch.Error): # network or catalog server not available, suppress scan # (we used to do this all the time, but this led to issues # with updates cached "too long" in 10.12+) munkilog.log( "WARNING: Cannot reach Apple Software Update server while " "installing Apple updates") su_options.append('--no-scan') # 10.11 seems not to like file:// URLs, and we don't really need # to switch to a local file URL anyway since we now have the # --no-scan option catalog_url = None else: # use our local catalog if not os.path.exists(self.applesync.local_catalog_path): display.display_error( 'Missing local Software Update catalog at %s', self.applesync.local_catalog_path) return False # didn't do anything, so no restart needed catalog_url = 'file://localhost' + quote( self.applesync.local_catalog_path) su_start_date = NSDate.new() installresults = su_tool.run(su_options, catalog_url=catalog_url) retcode = installresults.get('exit_code', 0) self.shutdown_instead_of_restart = installresults.get( 'post_action') == POSTACTION_SHUTDOWN su_end_date = NSDate.new() # get the items that were just installed from InstallHistory.plist installed_items = softwareupdated_installhistory( start_date=su_start_date, end_date=su_end_date) display.display_debug2('InstallHistory.plist items:\n%s', installed_items) if not 'InstallResults' in reports.report: reports.report['InstallResults'] = [] display.display_debug1('Raw Apple Update install results: %s', installresults) for item in installlist: rep = {} rep['name'] = item.get('apple_product_name') rep['version'] = item.get('version_to_install', '') rep['applesus'] = True rep['time'] = su_end_date rep['productKey'] = item.get('productKey', '') message = 'Apple Software Update install of %s-%s: %s' # first try to match against the items from InstallHistory.plist matched_installed_items = [ ih_item for ih_item in installed_items if ih_item['displayName'] in [item.get('apple_product_name'), item.get('display_name')] and ih_item['displayVersion'] == item.get('version_to_install') ] if matched_installed_items: display.display_debug2('Matched %s in InstallHistory.plist', item.get('apple_product_name')) rep['status'] = 0 rep['time'] = matched_installed_items[0]['date'] install_status = 'SUCCESSFUL' elif rep['name'] in installresults['installed']: rep['status'] = 0 install_status = 'SUCCESSFUL' elif ('display_name' in item and item['display_name'] in installresults['installed']): rep['status'] = 0 install_status = 'SUCCESSFUL' elif rep['name'] in installresults['download']: rep['status'] = -1 install_status = 'FAILED due to missing package.' display.display_warning( 'Apple update %s, %s failed. A sub-package was missing ' 'on disk at time of install.' % (rep['name'], rep['productKey'])) else: rep['status'] = -2 install_status = 'FAILED for unknown reason' display.display_warning( 'Apple update %s, %s may have failed to install. No record ' 'of success or failure.', rep['name'], rep['productKey']) if installresults['installed']: display.display_warning( 'softwareupdate recorded these installations: %s', installresults['installed']) reports.report['InstallResults'].append(rep) log_msg = message % (rep['name'], rep['version'], install_status) munkilog.log(log_msg, 'Install.log') if retcode: # there was an error display.display_error('softwareupdate error: %s' % retcode) if not remaining_apple_updates: # clean up our now stale local cache self.applesync.clean_up_cache() # remove the now invalid AppleUpdates.plist self.clear_apple_update_info() else: # we installed some of the updates, but some are still uninstalled. # re-write the apple_update_info to match plist = {'AppleUpdates': remaining_apple_updates} FoundationPlist.writePlist(plist, self.apple_updates_plist) # Also clear our pref value for last check date. We may have # just installed an update which is a pre-req for some other update. # Let's check again soon. prefs.set_pref('LastAppleSoftwareUpdateCheck', None) # show stop button again if display.munkistatusoutput: munkistatus.showStopButton() if self.shutdown_instead_of_restart: display.display_info( 'One or more Apple updates requires a shutdown instead of ' 'restart.') restart_action = POSTACTION_SHUTDOWN return restart_action
def install_with_info(dirpath, installlist, only_unattended=False, applesus=False): """ Uses the installlist to install items in the correct order. """ restartflag = False itemindex = 0 skipped_installs = [] for item in installlist: # Keep track of when this particular install started. utc_now = datetime.datetime.utcnow() itemindex = itemindex + 1 if only_unattended: if not item.get('unattended_install'): skipped_installs.append(item) display.display_detail( ('Skipping install of %s because it\'s not unattended.' % item['name'])) continue elif processes.blocking_applications_running(item): skipped_installs.append(item) display.display_detail( 'Skipping unattended install of %s because ' 'blocking application(s) running.' % item['name']) continue skipped_prereqs = item_prereqs_in_skipped_items(item, skipped_installs) if skipped_prereqs: # one or more prerequisite for this item was skipped or failed; # need to skip this item too skipped_installs.append(item) if only_unattended: format_str = ( 'Skipping unattended install of %s because these ' 'prerequisites were skipped: %s') else: format_str = ('Skipping install of %s because these ' 'prerequisites were not installed: %s') display.display_detail(format_str % (item['name'], ", ".join(skipped_prereqs))) continue if processes.stop_requested(): return restartflag, skipped_installs display_name = item.get('display_name') or item.get('name') version_to_install = item.get('version_to_install', '') retcode = 0 if 'preinstall_script' in item: retcode = scriptutils.run_embedded_script('preinstall_script', item) if retcode == 0 and 'installer_item' in item: display.display_status_major( "Installing %s (%s of %s)" % (display_name, itemindex, len(installlist))) installer_type = item.get("installer_type", "") itempath = os.path.join(dirpath, item["installer_item"]) if installer_type != "nopkg" and not os.path.exists(itempath): # can't install, so we should stop. Since later items might # depend on this one, we shouldn't continue display.display_error("Installer item %s was not found.", item["installer_item"]) return restartflag, skipped_installs if installer_type.startswith("Adobe"): retcode = adobeutils.do_adobe_install(item) if retcode == 0: if (item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart"): restartflag = True if retcode == 8: # Adobe Setup says restart needed. restartflag = True retcode = 0 elif installer_type == "copy_from_dmg": retcode = dmg.copy_from_dmg(itempath, item.get('items_to_copy')) if retcode == 0: if (item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart"): restartflag = True elif installer_type == "appdmg": display.display_warning( "install_type 'appdmg' is deprecated. Use 'copy_from_dmg'." ) retcode = dmg.copy_app_from_dmg(itempath) elif installer_type == 'profile': # profiles.install_profile returns True/False retcode = 0 identifier = item.get('PayloadIdentifier') if not profiles.install_profile(itempath, identifier): retcode = -1 elif installer_type == "nopkg": # Packageless install if (item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart"): restartflag = True elif installer_type != "": # we've encountered an installer type # we don't know how to handle display.display_error("Unsupported install type: %s" % installer_type) retcode = -99 else: # better be Apple installer package suppress_bundle_relocation = item.get( "suppress_bundle_relocation", False) display.display_debug1("suppress_bundle_relocation: %s", suppress_bundle_relocation) if 'installer_choices_xml' in item: choices_xml_file = os.path.join(osutils.tmpdir(), 'choices.xml') FoundationPlist.writePlist(item['installer_choices_xml'], choices_xml_file) else: choices_xml_file = '' installer_environment = item.get('installer_environment') if pkgutils.hasValidDiskImageExt(itempath): display.display_status_minor("Mounting disk image %s" % item["installer_item"]) mount_with_shadow = suppress_bundle_relocation # we need to mount the diskimage as read/write to # be able to modify the package to suppress bundle # relocation mountpoints = dmgutils.mountdmg( itempath, use_shadow=mount_with_shadow) if mountpoints == []: display.display_error("No filesystems mounted from %s", item["installer_item"]) return restartflag, skipped_installs if processes.stop_requested(): dmgutils.unmountdmg(mountpoints[0]) return restartflag, skipped_installs retcode = -99 # in case we find nothing to install needtorestart = False if pkgutils.hasValidInstallerItemExt( item.get('package_path', '')): # admin has specified the relative path of the pkg # on the DMG # 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], item['package_path']) if os.path.exists(fullpkgpath): (retcode, needtorestart) = pkg.install( fullpkgpath, display_name, choices_xml_file, suppress_bundle_relocation, installer_environment) else: # no relative path to pkg on dmg, so just install all # pkgs found at the root of the first mountpoint # (hopefully there's only one) (retcode, needtorestart) = pkg.installall( mountpoints[0], display_name, choices_xml_file, suppress_bundle_relocation, installer_environment) if (needtorestart or item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart"): restartflag = True dmgutils.unmountdmg(mountpoints[0]) elif (pkgutils.hasValidPackageExt(itempath) or itempath.endswith(".dist")): (retcode, needtorestart) = pkg.install( itempath, display_name, choices_xml_file, suppress_bundle_relocation, installer_environment) if (needtorestart or item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart"): restartflag = True else: # we didn't find anything we know how to install munkilog.log("Found nothing we know how to install in %s" % itempath) retcode = -99 if retcode == 0 and 'postinstall_script' in item: # only run embedded postinstall script if the install did not # return a failure code retcode = scriptutils.run_embedded_script('postinstall_script', item) if retcode: # we won't consider postinstall script failures as fatal # since the item has been installed via package/disk image # but admin should be notified display.display_warning( 'Postinstall script for %s returned %s' % (item['name'], retcode)) # reset retcode to 0 so we will mark this install # as successful retcode = 0 # if install was successful and this is a SelfService OnDemand install # remove the item from the SelfServeManifest's managed_installs if retcode == 0 and item.get('OnDemand'): remove_from_selfserve_installs(item['name']) # record install success/failure if not 'InstallResults' in reports.report: reports.report['InstallResults'] = [] if applesus: message = "Apple SUS install of %s-%s: %s" else: message = "Install of %s-%s: %s" if retcode == 0: status = "SUCCESSFUL" else: status = "FAILED with return code: %s" % retcode # add this failed install to the skipped_installs list # so that any item later in the list that requires this # item is skipped as well. skipped_installs.append(item) log_msg = message % (display_name, version_to_install, status) munkilog.log(log_msg, "Install.log") # Calculate install duration; note, if a machine is put to sleep # during the install this time may be inaccurate. utc_now_complete = datetime.datetime.utcnow() duration_seconds = (utc_now_complete - utc_now).seconds download_speed = item.get('download_kbytes_per_sec', 0) install_result = { 'display_name': display_name, 'name': item['name'], 'version': version_to_install, 'applesus': applesus, 'status': retcode, 'time': NSDate.new(), 'duration_seconds': duration_seconds, 'download_kbytes_per_sec': download_speed, 'unattended': only_unattended, } reports.report['InstallResults'].append(install_result) # 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 # with choicesXML files applied to a metapackage or # multiple packages being installed from a single DMG foundagain = False current_installer_item = item['installer_item'] # are we at the end of the installlist? # (we already incremented itemindex for display # so with zero-based arrays itemindex now points to the item # after the current item) if itemindex < len(installlist): # nope, let's check the remaining items for lateritem in installlist[itemindex:]: if (lateritem.get('installer_item') == current_installer_item): foundagain = True break # need to check skipped_installs as well if not foundagain: for skipped_item in skipped_installs: if (skipped_item.get('installer_item') == current_installer_item): foundagain = True break # ensure package is not deleted from cache if installation # fails by checking retcode if not foundagain and retcode == 0: # 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): if os.path.isdir(itempath): retcode = subprocess.call(["/bin/rm", "-rf", itempath]) else: # flat pkg or dmg retcode = subprocess.call(["/bin/rm", itempath]) if pkgutils.hasValidDiskImageExt(itempath): shadowfile = os.path.join(itempath, ".shadow") if os.path.exists(shadowfile): retcode = subprocess.call(["/bin/rm", shadowfile]) return (restartflag, skipped_installs)