From d03b2ba11fe2ad336d1fc861f5d16a203bd2a00a Mon Sep 17 00:00:00 2001 From: Justin McWilliams Date: Thu, 4 Aug 2011 19:23:19 +0000 Subject: [PATCH] Merging forced_install_after_date branch to trunk. This adds Force Install Notifications support to the MSU GUI, and logouthelper support to managedsoftwareupdate/launchd. Documentation on using the pkginfo force_install_after_date key to come.... This merge also includes localization fixes and on-the-fly updating of the MSU GUI when managedsoftwareupdate runs in the background while the GUI is open, changing InstallInfo. With this merge, the Munki version is increased to 0.8.0 and MSU GUI version to 3.2. git-svn-id: http://munki.googlecode.com/svn/trunk@1270 a4e17f2e-e282-11dd-95e1-755cbddbdd66 --- code/Managed Software Update/Info.plist | 4 +- code/Managed Software Update/Localize.py | 80 ++++---- .../Managed Software Update/MSUAppDelegate.py | 178 ++++++++++++++++-- .../MSUStatusWindowController.py | 15 +- .../MSUupdatesViewController.py | 3 +- .../project.pbxproj | 8 +- code/Managed Software Update/exclamation.tif | Bin 0 -> 536 bytes code/Managed Software Update/munki.py | 78 +++++++- code/client/logouthelper | 165 ++++++++++++++++ code/client/managedsoftwareupdate | 59 +++--- code/client/munkilib/munkicommon.py | 23 +-- code/client/munkilib/version.plist | 2 +- code/tools/make_munki_mpkg.sh | 2 +- 13 files changed, 510 insertions(+), 107 deletions(-) create mode 100644 code/Managed Software Update/exclamation.tif create mode 100755 code/client/logouthelper diff --git a/code/Managed Software Update/Info.plist b/code/Managed Software Update/Info.plist index 124717c7..c6963972 100644 --- a/code/Managed Software Update/Info.plist +++ b/code/Managed Software Update/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.1.2 + 3.2 CFBundleSignature ???? CFBundleVersion - 3.1.2 + 3.2 LSHasLocalizedDisplayName NSMainNibFile diff --git a/code/Managed Software Update/Localize.py b/code/Managed Software Update/Localize.py index e52f0e2b..cb7406bc 100755 --- a/code/Managed Software Update/Localize.py +++ b/code/Managed Software Update/Localize.py @@ -8,24 +8,24 @@ a new localized nib is created. Based on Philippe Casgrain's 'Automatically localize your nibs when building' http://developer.casgrain.com/?p=94 - + And Wil Shipley's 'Pimp My Code, Part 17: Lost in Translations' http://wilshipley.com/blog/2009/10/pimp-my-code-part-17-lost-in.html Written by David Keegan for Murky http://bitbucket.org/snej/murky -Usage: +Usage: Localize.py -help - + Localize nibs: Localize.py --from English --to "French|German" --nibs "MainMenu|Projects|Repo" - + Generate Strings: Localize.py --to English --genstrings "./**/*.[hm]" - + Use the '--utf8' flag to convert the strings files from utf-16 to utf-8. - + The MIT License Copyright David Keegan 2009-1010 @@ -74,11 +74,11 @@ def detectEncoding(filepath): If its not utf-16 assume it's utf-8, this should work for ascii files becuase the first 128 characters are the same... ''' - + f = open(filepath, 'r') firstBytes = f.read(2) f.close() - + if firstBytes == codecs.BOM_UTF16_BE: return 'utf_16_be' elif firstBytes == codecs.BOM_UTF16_LE: @@ -100,33 +100,33 @@ def fileToUtf8(stringFile): toFile = codecs.open(tempStrings, 'w', 'utf_8') for eachLine in fromFile: toFile.write(eachLine) - - toFile.close() + + toFile.close() fromFile.close() - + os.remove(stringFile) os.rename(tempStrings, stringFile) - + def runCommand(command, args): '''Run shell commands''' commandAndArgs = '%s %s' % (command, args) proc = subprocess.Popen(commandAndArgs, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() - if stderr != '': + if proc.returncode: raise LocalizationError(commandAndArgs + ' : ' + stderr) return stdout - + def md5(file): '''Get the md5 checksum of a file''' md5Sum = runCommand('/usr/bin/openssl md5', file) return md5Sum.split('=')[1].strip() - + def langProjName(language): return language.strip()+'.lproj' - + def nibToStringFileName(nibFile): return nibFile.rstrip('.xib')+'.strings' - + def ibtoolsGenerateStringsFile(nibFile, utf8=False): ''' Generate a .strings file from a nib @@ -134,22 +134,22 @@ def ibtoolsGenerateStringsFile(nibFile, utf8=False): ''' nibFileStrings = nibToStringFileName(nibFile) runCommand('ibtool', '--generate-strings-file %s %s' % (nibFileStrings, nibFile)) - + if utf8: fileToUtf8(nibFileStrings) - + print ' ', nibFileStrings, 'updated' - + def ibtoolsWriteNib(fromFile, toFile, utf8=False): '''convert one localized nib from one language to another''' toStrings = nibToStringFileName(toFile) runCommand('ibtool', '--strings-file %s --write %s %s' % (toStrings, toFile, fromFile)) - + if utf8: fileToUtf8(toStrings) - + print ' ', toFile, 'updated' - + def genStrings(toLangs, globString, utf8=False): for eachToLang in toLangs: toLangLproj = langProjName(eachToLang) @@ -157,7 +157,7 @@ def genStrings(toLangs, globString, utf8=False): localizableStrings = os.path.join(toLangLproj, 'Localizable.strings') if utf8: fileToUtf8(localizableStrings) - + print ' ', localizableStrings, 'updated' def getDict(): @@ -165,7 +165,7 @@ def getDict(): localizeDict = {} if not os.path.isfile(k_localizePath): return localizeDict - + with open(k_localizePath, 'rU') as localizeFile: for line in localizeFile: line = line.strip() @@ -173,39 +173,39 @@ def getDict(): if match: localizeDict[match.group('key')] = match.group('value') return localizeDict - + def writeDict(dict): '''Write a dictionary to Localize.ini''' with open(k_localizePath, 'w') as localizeFile: for key, value in sorted(dict.iteritems()): localizeFile.write('%s=%s\n' % (key, value)) - + def localizeNibs(fromLang, toLangs, nibs=None, utf8=False, ignore=False): '''Localize nibs from one language to others''' - + #get the data from the ini file iniData = getDict() - + fromLangLproj = langProjName(fromLang) - + #if nibs is none, get all the nibs in the from language project if nibs is None: nibs = [] for eachNib in glob.glob('%s/*.xib' % fromLangLproj): nibs.append(eachNib.lstrip(fromLangLproj+'/').rstrip('.xib')) - + for eachNib in nibs: eachNib = eachNib.strip() if not eachNib.endswith('.xib'): eachNib += '.xib' fromNib = os.path.join(fromLangLproj, eachNib) - + #get md5 and update the ini data fromNibMd5 = md5(fromNib) #check if the strings for the fromNib need to the updated if not os.path.isfile(nibToStringFileName(fromNib)) or fromNib not in iniData or iniData[fromNib] != fromNibMd5: ibtoolsGenerateStringsFile(fromNib, utf8) - + #write the localized nibs for eachToLang in toLangs: toLangLproj = langProjName(eachToLang) @@ -220,16 +220,16 @@ def localizeNibs(fromLang, toLangs, nibs=None, utf8=False, ignore=False): toStrings not in iniData or iniData[toStrings] != toStringsMd5): ibtoolsWriteNib(fromNib, toNib, utf8) iniData[toStrings] = toStringsMd5 - + iniData[fromNib] = fromNibMd5 - + #update Localize.ini writeDict(iniData) - + if __name__ == '__main__': '''Command line options''' startTime = time.time() - + opts = OptionParser() opts.add_option('--from', '-f', dest='fromLang', help='The language to localize from.', metavar='LANG') opts.add_option('--to', '-t', dest='toLangs', help="An array of languages to localize to, separated by '|'.", metavar='LANGS') @@ -238,8 +238,8 @@ if __name__ == '__main__': opts.add_option('--ignore', '-i', dest='ignore', help='If this flag is present the md5 checksums will be ignored.', action="store_true", default=False) opts.add_option('--genstrings', '-g', dest='genstrings', help='File name or glob string. If this argument is present the genstrings command line will be called.', metavar='GLOB', default=None) options, arguments = opts.parse_args() - - if options.genstrings != None: + + if options.genstrings != None: genStrings(options.toLangs.split('|'), options.genstrings, options.utf8) print 'Strings updated in %.2f seconds' % (time.time()-startTime) else: @@ -247,4 +247,4 @@ if __name__ == '__main__': if nibs != None: nibs = options.nibs.split('|') localizeNibs(options.fromLang, options.toLangs.split('|'), nibs, options.utf8, options.ignore) - print 'Nibs updated in %.2f seconds' % (time.time()-startTime) + print 'Nibs updated in %.2f seconds' % (time.time()-startTime) diff --git a/code/Managed Software Update/MSUAppDelegate.py b/code/Managed Software Update/MSUAppDelegate.py index da744a06..ab45176a 100644 --- a/code/Managed Software Update/MSUAppDelegate.py +++ b/code/Managed Software Update/MSUAppDelegate.py @@ -40,8 +40,10 @@ class MSUAppDelegate(NSObject): _emptyImage = NSImage.imageNamed_("Empty.png") _restartImage = NSImage.imageNamed_("RestartReq.tif") _logoutImage = NSImage.imageNamed_("LogOutReq.tif") + _exclamationImage = NSImage.imageNamed_("exclamation.tif") _listofupdates = [] _optionalInstalls = [] + _currentAlert = None restart_required = False logout_required = False @@ -58,10 +60,30 @@ class MSUAppDelegate(NSObject): self.runmode = runmode NSLog("Runmode: %s" % runmode) - # Prevent automatic relaunching at login on Lion + # Prevent automatic relaunching at login on Lion if NSApp.respondsToSelector_('disableRelaunchOnLogin'): NSApp.disableRelaunchOnLogin() + # register for notification messages so we can be told if available updates + # change while we are open + notification_center = NSDistributedNotificationCenter.defaultCenter() + notification_center.addObserver_selector_name_object_suspensionBehavior_( + self, + self.updateAvailableUpdates, + 'com.googlecode.munki.ManagedSoftwareUpdate.update', + None, + NSNotificationSuspensionBehaviorDeliverImmediately) + + # register for notification messages so we can be told to + # display a logout warning + notification_center = NSDistributedNotificationCenter.defaultCenter() + notification_center.addObserver_selector_name_object_suspensionBehavior_( + self, + self.forcedLogoutWarning, + 'com.googlecode.munki.ManagedSoftwareUpdate.logoutwarn', + None, + NSNotificationSuspensionBehaviorDeliverImmediately) + consoleuser = munki.getconsoleuser() if consoleuser == None or consoleuser == u"loginwindow": # Status Window only @@ -73,7 +95,12 @@ class MSUAppDelegate(NSObject): # user may have launched the app manually, or it may have # been launched by /usr/local/munki/managedsoftwareupdate # to display available updates - lastcheck = NSDate.dateWithString_(munki.pref('LastCheckDate')) + if munki.thereAreUpdatesToBeForcedSoon(hours=2): + # skip the check and just display the updates + # by pretending the lastcheck is now + lastcheck = NSDate.date() + else: + lastcheck = NSDate.dateWithString_(munki.pref('LastCheckDate')) if not lastcheck or lastcheck.timeIntervalSinceNow() < -60: # it's been more than a minute since the last check self.checkForUpdates() @@ -87,12 +114,24 @@ class MSUAppDelegate(NSObject): # no updates available. Should we check for some? self.checkForUpdates() + def updateAvailableUpdates(self): + NSLog(u"Managed Software Update got update notification") + if self.mainWindowController.theWindow.isVisible(): + self.getAvailableUpdates() + self.buildUpdateTableData() + self.getOptionalInstalls() + self.buildOptionalInstallsData() + + def displayUpdatesWindow(self): self.buildUpdateTableData() if self._optionalInstalls: self.buildOptionalInstallsData() self.mainWindowController.theWindow.makeKeyAndOrderFront_(self) - NSApp.requestUserAttention_(NSCriticalRequest) + if munki.thereAreUpdatesToBeForcedSoon(hours=2): + NSApp.activateIgnoringOtherApps_(True) + else: + NSApp.requestUserAttention_(NSCriticalRequest) def munkiStatusSessionEnded_(self, socketSessionResult): consoleuser = munki.getconsoleuser() @@ -259,6 +298,17 @@ class MSUAppDelegate(NSObject): installinfo = munki.getInstallInfo() if installinfo: updatelist = installinfo.get("managed_installs", []) + for update in updatelist: + force_install_after_date = update.get('force_install_after_date') + if force_install_after_date: + # insert installation deadline into description + local_date = munki.discardTimeZoneFromDate(force_install_after_date) + date_str = munki.stringFromDate(local_date) + forced_date_text = NSLocalizedString(u"This item must be installed by ", None) + description = update["description"] + # prepend deadline info to description. This will fail if the description is HTML... + update["description"] = forced_date_text + date_str + "\n\n" + description + if installinfo.get("removals"): removallist = installinfo.get("removals") restartNeeded = False @@ -379,6 +429,8 @@ class MSUAppDelegate(NSObject): elif item.get("RestartAction") == "RequireLogout" or item.get("RestartAction") == "RecommendLogout": row['image'] = self._logoutImage self.logout_required = True + if item.get("force_install_after_date"): + row['image'] = self._exclamationImage else: row['image'] = self._emptyImage row['name'] = item.get("display_name") or item.get("name","") @@ -405,6 +457,81 @@ class MSUAppDelegate(NSObject): self.update_view_controller.restartImageFld.setImage_(self._logoutImage) + def forcedLogoutWarning(self, notification_obj): + NSApp.activateIgnoringOtherApps_(True) + info = notification_obj.userInfo() + moreText = NSLocalizedString( + u"\nAll pending updates will be installed. Unsaved work will be lost.\nYou may avoid the forced logout by logging out now.", None) + logout_time = None + if info: + logout_time = info.get('logout_time') + elif munki.thereAreUpdatesToBeForcedSoon(): + logout_time = munki.earliestForceInstallDate() + if not logout_time: + return + time_til_logout = int(logout_time.timeIntervalSinceNow() / 60) + if time_til_logout > 55: + deadline_str = munki.stringFromDate(logout_time) + munki.log("user", "forced_logout_warning_initial") + infoText = NSLocalizedString(u"A logout will be forced at approximately %s.", None) % deadline_str + moreText + elif time_til_logout > 0: + munki.log("user", "forced_logout_warning_%s" % time_til_logout) + infoText = NSLocalizedString(u"A logout will be forced in less than %s minutes.", None) % time_til_logout + moreText + else: + munki.log("user", "forced_logout_warning_final") + infoText = NSLocalizedString(u"A logout will be forced in less than a minute.\nAll pending updates will be installed. Unsaved work will be lost.", None) + + # Set the OK button to default, unless less than 5 minutes to logout + # in which case only the Logout button should be displayed. + self._force_warning_logout_btn = NSLocalizedString( + u"Logout and update now", None) + self._force_warning_ok_btn = NSLocalizedString(u"OK", None) + if time_til_logout > 5: + self._force_warning_btns = { + NSAlertDefaultReturn: self._force_warning_ok_btn, + NSAlertAlternateReturn: self._force_warning_logout_btn, + } + else: + self._force_warning_btns = { + NSAlertDefaultReturn: self._force_warning_logout_btn, + NSAlertAlternateReturn: objc.nil, + } + + if self._currentAlert: + NSApp.endSheet_(self._currentAlert.window()) + self._currentAlert = None + alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( + NSLocalizedString(u"Forced Logout for Mandatory Install", None), + self._force_warning_btns[NSAlertDefaultReturn], + self._force_warning_btns[NSAlertAlternateReturn], + objc.nil, + infoText) + self._currentAlert = alert + alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( + self.mainWindowController.theWindow, self, self.forceLogoutWarningDidEnd_returnCode_contextInfo_, objc.nil) + + def laterBtnClicked(self): + if munki.thereAreUpdatesToBeForcedSoon(): + deadline = munki.earliestForceInstallDate() + time_til_logout = deadline.timeIntervalSinceNow() + if time_til_logout > 0: + deadline_str = munki.stringFromDate(deadline) + infoText = NSLocalizedString("One or more updates must be installed by %s. A logout may be forced if you wait too long to update.", None) % deadline_str + else: + infoText = NSLocalizedString("One or more mandatory updates are overdue for installation. A logout will be forced soon.", None) + alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( + NSLocalizedString(u"Manadatory Updates Pending", None), + NSLocalizedString(u"Show updates", None), + NSLocalizedString(u"Update later", None), + objc.nil, + infoText) + self._currentAlert = alert + alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( + self.mainWindowController.theWindow, self, self.confirmLaterAlertDidEnd_returnCode_contextInfo_, objc.nil) + else: + munki.log("user", "exit_later_clicked") + NSApp.terminate_(self) + def confirmInstallUpdates(self): if self.mainWindowController.theWindow.isVisible() == objc.NO: return @@ -415,33 +542,37 @@ class MSUAppDelegate(NSObject): objc.nil, objc.nil, NSLocalizedString("There are other users logged into this computer.\nUpdating now could cause other users to lose their work.\n\nPlease try again later after the other users have logged out.", None)) + self._currentAlert = alert alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.mainWindowController.theWindow, self, self.multipleUserAlertDidEnd_returnCode_contextInfo_, objc.nil) elif self.restart_required: alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Restart Required", None), - NSLocalizedString(u"Log out and update", None), + NSLocalizedString(u"Logout and update", None), NSLocalizedString(u"Cancel", None), objc.nil, - NSLocalizedString(u"A restart is required after updating. Please be patient as there may be a short delay at the login window. Log out and update now?", None)) + NSLocalizedString(u"A restart is required after updating. Please be patient as there may be a short delay at the login window. Logout and update now?", None)) + self._currentAlert = alert alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.mainWindowController.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil) elif self.logout_required or munki.installRequiresLogout(): alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Logout Required", None), - NSLocalizedString(u"Log out and update", None), + NSLocalizedString(u"Logout and update", None), NSLocalizedString(u"Cancel", None), objc.nil, - NSLocalizedString(u"A logout is required before updating. Please be patient as there may be a short delay at the login window. Log out and update now?", None)) + NSLocalizedString(u"A logout is required before updating. Please be patient as there may be a short delay at the login window. Logout and update now?", None)) + self._currentAlert = alert alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.mainWindowController.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil) else: alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Logout Recommended", None), - NSLocalizedString(u"Log out and update", None), + NSLocalizedString(u"Logout and update", None), NSLocalizedString(u"Cancel", None), NSLocalizedString(u"Update without logging out", None), - NSLocalizedString(u"A logout is recommended before updating. Please be patient as there may be a short delay at the login window. Log out and update now?", None)) + NSLocalizedString(u"A logout is recommended before updating. Please be patient as there may be a short delay at the login window. Logout and update now?", None)) + self._currentAlert = alert alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.mainWindowController.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil) @@ -465,6 +596,7 @@ class MSUAppDelegate(NSObject): objc.nil, NSLocalizedString(u"You must quit the following applications before proceeding with installation:\n\n%s", None) % '\n'.join(running_apps)) munki.log("MSU", "conflicting_apps", ','.join(running_apps)) + self._currentAlert = alert alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.mainWindowController.theWindow, self, self.blockingAppsRunningAlertDidEnd_returnCode_contextInfo_, objc.nil) return True @@ -479,17 +611,19 @@ class MSUAppDelegate(NSObject): objc.nil, NSLocalizedString(u"There is a configuration problem with the managed software installer. Could not start the install session. Contact your systems administrator.", None)) munki.log("MSU", "cannot_start") + self._currentAlert = alert alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil) @PyObjCTools.AppHelper.endSheetMethod def logoutAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo): + self._currentAlert = None if returncode == 0: NSLog("User cancelled") munki.log("user", "cancelled") elif returncode == 1: - NSLog("User chose to log out") + NSLog("User chose to logout") munki.log("user", "install_with_logout") result = munki.logoutAndUpdate() if result: @@ -514,14 +648,34 @@ class MSUAppDelegate(NSObject): @PyObjCTools.AppHelper.endSheetMethod def blockingAppsRunningAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo): - pass + self._currentAlert = None @PyObjCTools.AppHelper.endSheetMethod def multipleUserAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo): - pass + self._currentAlert = None + + @PyObjCTools.AppHelper.endSheetMethod + def confirmLaterAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo): + self._currentAlert = None + if returncode == 0: + munki.log("user", "exit_later_clicked") + NSApp.terminate_(self) + else: + pass + + @PyObjCTools.AppHelper.endSheetMethod + def forceLogoutWarningDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo): + self._currentAlert = None + btn_pressed = self._force_warning_btns[returncode] + if btn_pressed == self._force_warning_logout_btn: + munki.log("user", "install_with_logout") + result = munki.logoutAndUpdate() + else: + munki.log("user", "dismissed_forced_logout_warning") @PyObjCTools.AppHelper.endSheetMethod def quitAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo): + self._currentAlert = None if returncode == 1: munki.log("user", "quit") NSApp.terminate_(self) diff --git a/code/Managed Software Update/MSUStatusWindowController.py b/code/Managed Software Update/MSUStatusWindowController.py index b113ef12..01e65e94 100644 --- a/code/Managed Software Update/MSUStatusWindowController.py +++ b/code/Managed Software Update/MSUStatusWindowController.py @@ -214,7 +214,7 @@ class MSUStatusWindowController(NSObject): del pool def socketEnded_(self, socketSessionResult): - NSApp.delegate().munkiStatusSessionEnded_(socketSessionResult) + NSApp.delegate().munkiStatusSessionEnded_(socketSessionResult) def processSocketMsg_(self, message): if message.startswith(u"ACTIVATE: "): @@ -227,11 +227,10 @@ class MSUStatusWindowController(NSObject): self.window.orderFront_(self) return "" if message.startswith(u"TITLE: "): - self.window.setTitle_(NSLocalizedString(message[7:], None)) + self.window.setTitle_(message[7:]) return "" if message.startswith(u"MESSAGE: "): - self.messageFld.setStringValue_( - NSLocalizedString(message[9:], None)) + self.messageFld.setStringValue_(message[9:]) return "" if message.startswith(u"DETAIL: "): self.detailFld.setStringValue_(message[8:]) @@ -280,10 +279,10 @@ class MSUStatusWindowController(NSObject): def doRestartAlert(self): self.restartAlertDismissed = 0 alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( - NSLocalizedString(u"Restart Required", None), - NSLocalizedString(u"Restart", None), - objc.nil, - objc.nil, + NSLocalizedString(u"Restart Required", None), + NSLocalizedString(u"Restart", None), + objc.nil, + objc.nil, NSLocalizedString(u"Software installed or removed requires a restart. You will have a chance to save open documents.", None)) alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.window, self, self.alertDidEnd_returnCode_contextInfo_, objc.nil) diff --git a/code/Managed Software Update/MSUupdatesViewController.py b/code/Managed Software Update/MSUupdatesViewController.py index 152f2676..d422d8d1 100644 --- a/code/Managed Software Update/MSUupdatesViewController.py +++ b/code/Managed Software Update/MSUupdatesViewController.py @@ -58,8 +58,7 @@ class MSUupdatesViewController(NSViewController): @IBAction def laterBtnClicked_(self, sender): - munki.log("user", "exit_later_clicked") - NSApp.terminate_(self) + NSApp.delegate().laterBtnClicked() @IBAction def updateNowBtnClicked_(self, sender): diff --git a/code/Managed Software Update/Managed Software Update.xcodeproj/project.pbxproj b/code/Managed Software Update/Managed Software Update.xcodeproj/project.pbxproj index 6f59b146..acd74c28 100644 --- a/code/Managed Software Update/Managed Software Update.xcodeproj/project.pbxproj +++ b/code/Managed Software Update/Managed Software Update.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ C09D9A2D11E6DC7800B3E573 /* MSUupdatesViewController.py in Resources */ = {isa = PBXBuildFile; fileRef = C09D9A2C11E6DC7800B3E573 /* MSUupdatesViewController.py */; }; C0C548FD12B6E52B002C76CD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C0C548F812B6E52B002C76CD /* Localizable.strings */; }; C0C5493212B9691B002C76CD /* MainMenu.strings in Resources */ = {isa = PBXBuildFile; fileRef = C0C5493012B9691B002C76CD /* MainMenu.strings */; }; + C0E5AFC413B4E2550046074F /* exclamation.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0E5AFC313B4E2550046074F /* exclamation.tif */; }; C0E9E82411EFB1EB003CE81A /* Solid Aqua Blue.png in Resources */ = {isa = PBXBuildFile; fileRef = C0E9E82311EFB1EB003CE81A /* Solid Aqua Blue.png */; }; /* End PBXBuildFile section */ @@ -57,6 +58,7 @@ C0458B4E11EB88AD001F1172 /* BorderlessWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BorderlessWindow.h; sourceTree = ""; }; C0458B4F11EB88AD001F1172 /* ScaledImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScaledImageView.h; sourceTree = ""; }; C0458B5011EB88C0001F1172 /* MSUStatusWindowController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUStatusWindowController.py; sourceTree = ""; }; + C054583B13D4FDD000ED615D /* English */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; }; C062868811E677820009B44B /* MSUOptionalInstallsViewController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUOptionalInstallsViewController.py; sourceTree = ""; }; C082286E12C3CF5D007BAD36 /* ManagedInstalls.manifest */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = ManagedInstalls.manifest; sourceTree = ""; }; C0828C7F1123DBC5003D5807 /* MSUWebViewPolicyDelegate.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUWebViewPolicyDelegate.py; sourceTree = ""; }; @@ -78,6 +80,7 @@ C0C5492D12B96869002C76CD /* German */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = German; path = German.lproj/InfoPlist.strings; sourceTree = ""; }; C0C5493112B9691B002C76CD /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/MainMenu.strings; sourceTree = ""; }; C0C5493312B9693E002C76CD /* German */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = German; path = German.lproj/MainMenu.strings; sourceTree = ""; }; + C0E5AFC313B4E2550046074F /* exclamation.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = exclamation.tif; sourceTree = ""; }; C0E9E82311EFB1EB003CE81A /* Solid Aqua Blue.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Solid Aqua Blue.png"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -165,6 +168,7 @@ 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( + C0E5AFC313B4E2550046074F /* exclamation.tif */, C0C5493012B9691B002C76CD /* MainMenu.strings */, 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 77C8C1F70C07829500965286 /* MainMenu.xib */, @@ -270,6 +274,7 @@ C0C548FD12B6E52B002C76CD /* Localizable.strings in Resources */, C0C5493212B9691B002C76CD /* MainMenu.strings in Resources */, C082286F12C3CF5D007BAD36 /* ManagedInstalls.manifest in Resources */, + C0E5AFC413B4E2550046074F /* exclamation.tif in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -302,7 +307,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# generate localizable strings\n./Localize.py --to English --genstrings \"./**/*.[hm]\" --utf8\n\n# localize nibs\n./Localize.py --from English --to German --utf8"; + shellScript = "# generate localizable strings\n./Localize.py --to English --genstrings \"./*.{h,m,py}\" --utf8\n\n# localize nibs\n./Localize.py --from English --to German --utf8"; }; /* End PBXShellScriptBuildPhase section */ @@ -342,6 +347,7 @@ isa = PBXVariantGroup; children = ( C0C548F912B6E52B002C76CD /* German */, + C054583B13D4FDD000ED615D /* English */, ); name = Localizable.strings; path = German.lproj; diff --git a/code/Managed Software Update/exclamation.tif b/code/Managed Software Update/exclamation.tif new file mode 100644 index 0000000000000000000000000000000000000000..6590dadfc428f2141d92a2a3a378d86ae725eb4c GIT binary patch literal 536 zcmebEWzb?^U<_(tPzbP4;Rs`8;^B9=5TzI;%)TM~U_f_ZtU%&?#{*ju_6e9aok&<9 zA;9vCmsKG;v0?Vr6Ovr^Pd`h}IhANOWBbznjtE^L=EeXOcTFdzZ1>r_``C{Ee%N)S ze$Qpk&ZA*jHNA@-G&Lv)MBV3Ml{e{Ja&K-x{jJ=v*{Y#O4eELlnTzk_iso*+`L=xb zk$YLq?94)0KbnPpr_Q*tYTE8=a;r9Ru}Hk(SR5~FTCrHrd{6Fg+m8ATNxiKdJ_^T9 zpS{Qa`0m?x+oP^-{u(;ZbwYjb;;qKwo&t^4S6`Rsf3dKUm@amR%}=uVv&=K$j{EO6 z&9wdZT$-mWigU#c9#%O=^%Xs3>E=Dlzr9-FXB6bZu#n+3!Gb9 z%D@XG7?IdaU^WW_0}}%yGgO=vsD=&7W&*MW8G!OYy$lQ-jG|C+pa`QFOq{8VQ5?o* w^kS4^0Ga?&&&0tf4QC55%0Tr3Wf(P(%nw3{1N8xIfkLi74GawG0t`?Z0LE&Yr2qf` literal 0 HcmV?d00001 diff --git a/code/Managed Software Update/munki.py b/code/Managed Software Update/munki.py index 481adc47..7951e090 100644 --- a/code/Managed Software Update/munki.py +++ b/code/Managed Software Update/munki.py @@ -27,11 +27,14 @@ import stat import subprocess import random import FoundationPlist +import Foundation +from Foundation import NSDate from Foundation import NSFileManager from Foundation import CFPreferencesCopyAppValue from Foundation import CFPreferencesAppSynchronize +INSTALLATLOGOUTFILE = "/private/tmp/com.googlecode.munki.installatlogout" UPDATECHECKLAUNCHFILE = \ "/private/tmp/.com.googlecode.munki.updatecheck.launchd" MSULOGDIR = \ @@ -183,6 +186,67 @@ def getInstallInfo(): return plist +def thereAreUpdatesToBeForcedSoon(hours=72): + '''Return True if any updates need to be installed within the next + X hours, false otherwise''' + installinfo = getInstallInfo() + if installinfo: + now = NSDate.date() + now_xhours = NSDate.dateWithTimeIntervalSinceNow_(hours * 3600) + for item in installinfo.get('managed_installs', []): + force_install_after_date = item.get('force_install_after_date') + if force_install_after_date: + force_install_after_date = discardTimeZoneFromDate( + force_install_after_date) + if now_xhours >= force_install_after_date: + return True + return False + + +def earliestForceInstallDate(): + """Check installable packages for force_install_after_dates + Returns None or earliest force_install_after_date converted to local time + """ + earliest_date = None + + installinfo = getInstallInfo() + + for install in installinfo.get('managed_installs', []): + this_force_install_date = install.get('force_install_after_date') + + if this_force_install_date: + this_force_install_date = discardTimeZoneFromDate( + this_force_install_date) + if not earliest_date or this_force_install_date < earliest_date: + earliest_date = this_force_install_date + + return earliest_date + + +def discardTimeZoneFromDate(the_date): + """Input: NSDate object + Output: NSDate object with same date and time as the UTC. + In PDT, '2011-06-20T12:00:00Z' becomes '2011-06-20 12:00:00 -0700'""" + # get local offset + (unused_date, unused_time, offset) = str(the_date).split() + hour_offset = int(offset[0:3]) + minute_offset = int(offset[0] + offset[3:]) + seconds_offset = 60 * 60 * hour_offset + 60 * minute_offset + # return new NSDate minus local_offset + return the_date.dateByAddingTimeInterval_(-seconds_offset) + + +def stringFromDate(nsdate): + """Input: NSDate object + Output: unicode object, date and time formatted per system locale. + """ + df = Foundation.NSDateFormatter.alloc().init() + df.setFormatterBehavior_(Foundation.NSDateFormatterBehavior10_4) + df.setDateStyle_(Foundation.kCFDateFormatterLongStyle) + df.setTimeStyle_(Foundation.kCFDateFormatterShortStyle) + return unicode(df.stringForObjectValue_(nsdate)) + + def startUpdateCheck(): '''Does launchd magic to run managedsoftwareupdate as root.''' result = call(["/usr/bin/touch", UPDATECHECKLAUNCHFILE]) @@ -202,6 +266,7 @@ def getAppleUpdates(): pass return plist + def humanReadable(kbytes): """Returns sizes in human-readable units.""" units = [(" KB", 2**10), (" MB", 2**20), (" GB", 2**30), (" TB", 2**40)] @@ -276,13 +341,14 @@ end ignoring def logoutAndUpdate(): '''Touch a flag so the process that runs after logout knows it's OK to install everything''' - cmd = ["/usr/bin/touch", - "/private/tmp/com.googlecode.munki.installatlogout"] - result = call(cmd) - if result == 0: + + try: + if not os.path.exists(INSTALLATLOGOUTFILE): + f = open(INSTALLATLOGOUTFILE, 'w') + f.close() logoutNow() - else: - return result + except (OSError, IOError): + return 1 def justUpdate(): diff --git a/code/client/logouthelper b/code/client/logouthelper new file mode 100755 index 00000000..25054dc7 --- /dev/null +++ b/code/client/logouthelper @@ -0,0 +1,165 @@ +#!/usr/bin/python +# encoding: utf-8 +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +logouthelper + +Created by Greg Neagle on 2011-06-21. + +A helper tool for forced logouts to allow munki to force install items by +a certain deadline. + + +""" + +import os +import time +from munkilib import munkicommon +from munkilib import FoundationPlist +from munkilib.updatecheck import discardTimeZoneFromDate +from Foundation import NSDate +from Foundation import NSDictionary +from Foundation import NSDistributedNotificationCenter +from Foundation import NSNotificationDeliverImmediately +from Foundation import NSNotificationPostToAllSessions + + +NOTIFICATION_MINS = [240, 180, 120, 90, 60, 45, 30, 15, 10, 5] +MINIMUM_NOTIFICATION_MINS = 60 + + +def earliestForceInstallDate(): + '''Check installable packages for force_install_after_dates + Returns None or earliest force_install_after_date converted to local time + ''' + earliest_date = None + + ManagedInstallDir = munkicommon.pref('ManagedInstallDir') + installinfopath = os.path.join(ManagedInstallDir, 'InstallInfo.plist') + + try: + installinfo = FoundationPlist.readPlist(installinfopath) + except FoundationPlist.NSPropertyListSerializationException: + return None + + for install in installinfo.get('managed_installs', []): + this_force_install_date = install.get('force_install_after_date') + + if this_force_install_date: + this_force_install_date = discardTimeZoneFromDate( + this_force_install_date) + if not earliest_date or this_force_install_date < earliest_date: + earliest_date = this_force_install_date + + return earliest_date + + +def alertUserOfForcedLogout(info=None): + '''Uses Managed Software Update.app to notify the user of an + upcoming forced logout. + + Args: + info: dict of data to send with the notification. + ''' + consoleuser = munkicommon.getconsoleuser() + if not munkicommon.findProcesses( + exe="/Applications/Utilities/Managed Software Update.app", + user=consoleuser): + # Managed Software Update.app isn't running. + # Use our LaunchAgent to start + # Managed Software Update.app in the user context. + launchfile = '/var/run/com.googlecode.munki.ManagedSoftwareUpdate' + f = open(launchfile, 'w') + f.close() + time.sleep(0.1) + if os.path.exists(launchfile): + os.unlink(launchfile) + # now wait a bit for it to launch before proceeding + # because if we don't, sending the logoutwarn notification + # may fall on deaf ears. + time.sleep(4) + + # if set, convert Python dictionary to NSDictionary. + if info is not None: + info = NSDictionary.dictionaryWithDictionary_(info) + # cause MSU.app to display the Forced Logout warning + dnc = NSDistributedNotificationCenter.defaultCenter() + dnc.postNotificationName_object_userInfo_options_( + 'com.googlecode.munki.ManagedSoftwareUpdate.logoutwarn', + None, info, + NSNotificationDeliverImmediately + NSNotificationPostToAllSessions) + + # make sure flag is in place to cause munki to install at logout + f = open('/private/tmp/com.googlecode.munki.installatlogout', 'w') + f.close() + + +def main(): + '''Check for logged-in users and upcoming forced installs; + notify the user if needed; sleep a minute and do it again.''' + ID = 'com.googlecode.munki.logouthelper' + munkicommon.log('%s invoked' % ID) + sent_notifications = [] + logout_time_override = False + minimum_notifications_logout_time = NSDate.date().addTimeInterval_( + 60 * MINIMUM_NOTIFICATION_MINS + 30) + while True: + if not munkicommon.currentGUIusers(): + # no-one is logged in, so bail + munkicommon.log('%s: no-one logged in' % ID) + time.sleep(10) # makes launchd happy + munkicommon.log('%s exited' % ID) + exit(0) + + if not logout_time_override: + logout_time = earliestForceInstallDate() + if not logout_time: + # no forced logout needed, so bail + munkicommon.log('%s: no forced installs found' % ID) + time.sleep(10) # makes launchd happy + munkicommon.log('%s exited' % ID) + exit(0) + elif logout_time < minimum_notifications_logout_time: + if MINIMUM_NOTIFICATION_MINS not in sent_notifications: + # logout time is in the past, and the minimum notification + # has not been sent, so reset the logout_time to the future. + munkicommon.log('%d minute notification not sent.' % ( + MINIMUM_NOTIFICATION_MINS)) + logout_time = minimum_notifications_logout_time + munkicommon.log('Reset logout_time to: %s' % logout_time) + logout_time_override = True + + minutes_until_logout = int(logout_time.timeIntervalSinceNow() / 60) + info = {'logout_time': logout_time} + if minutes_until_logout in NOTIFICATION_MINS: + sent_notifications.append(minutes_until_logout) + munkicommon.log( + '%s: Warning user of %s minutes until forced logout' % + (ID, minutes_until_logout)) + alertUserOfForcedLogout(info) + elif minutes_until_logout < 1: + munkicommon.log('%s: Forced logout in 60 seconds' % ID) + alertUserOfForcedLogout(info) + + time.sleep(60) + if minutes_until_logout < 1: + break + + if munkicommon.currentGUIusers() and earliestForceInstallDate(): + munkicommon.log('%s: Beginning forced logout' % ID) + munkicommon.forceLogoutNow() + munkicommon.log('%s exited' % ID) + exit(0) + +if __name__ == '__main__': + main() diff --git a/code/client/managedsoftwareupdate b/code/client/managedsoftwareupdate index bb8c014f..679001c6 100755 --- a/code/client/managedsoftwareupdate +++ b/code/client/managedsoftwareupdate @@ -31,6 +31,9 @@ import traceback # Do not place any imports with ObjC bindings above this! try: from Foundation import NSDate + from Foundation import NSDistributedNotificationCenter + from Foundation import NSNotificationDeliverImmediately + from Foundation import NSNotificationPostToAllSessions except: # Python is missing ObjC bindings. Run external report script. from munkilib import utils @@ -195,9 +198,14 @@ def doInstallTasks(only_unattended=False): return need_to_restart -def doLogout(): - """Handle the need for a logout.""" - munkicommon.forceLogoutNow() +def startLogoutHelper(): + """Handle the need for a forced logout. Start our logouthelper""" + cmd = ['/bin/launchctl', 'start', 'com.googlecode.munki.logouthelper'] + result = subprocess.call(cmd) + if result: + # some problem with the launchd job + munkicommon.display_error( + 'Could not start com.googlecode.munki.logouthelper') def doRestart(): @@ -212,8 +220,14 @@ def doRestart(): else: munkicommon.display_info(restartMessage) - if not munkicommon.currentGUIusers(): - # no-one is logged in and we're at the loginwindow + # TODO: temporary fix for forced logout problem where we've killed + # loginwindow sessions, but munkicommon.currentGUIusers() still returns + # users. Need to find a better solution, though. + #if not munkicommon.currentGUIusers(): + # # no-one is logged in and we're at the loginwindow + consoleuser = munkicommon.getconsoleuser() + if not consoleuser or consoleuser == u'loginwindow': + # no-one is logged in or we're at the loginwindow time.sleep(5) unused_retcode = subprocess.call(['/sbin/shutdown', '-r', 'now']) else: @@ -253,6 +267,16 @@ def recordUpdateCheckResult(result): munkicommon.set_pref('LastCheckResult', result) +def sendUpdateNotification(): + '''Sends an update notification via NSDistributedNotificationCenter + MSU.app registers to receive these events.''' + dnc = NSDistributedNotificationCenter.defaultCenter() + dnc.postNotificationName_object_userInfo_options_( + 'com.googlecode.munki.ManagedSoftwareUpdate.update', + None, None, + NSNotificationDeliverImmediately + NSNotificationPostToAllSessions) + + def notifyUserOfUpdates(force=False): """Notify the logged-in user of available updates. @@ -282,25 +306,13 @@ def notifyUserOfUpdates(force=False): if force or now.timeIntervalSinceDate_(nextNotifyDate) >= 0: # record current notification date munkicommon.set_pref('LastNotifiedDate', now) - - # Kill Managed Software Update.app if it's already - # open so it will update its display - # using subprocess.Popen instead of subprocess.call - # so stderr doesn't get output to the terminal - # when there is no Managed Software Update process running - cmd = ['/usr/bin/killall', 'Managed Software Update'] - proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - (unused_output, unused_err) = proc.communicate() # notify user of available updates using LaunchAgent to start # Managed Software Update.app in the user context. launchfile = '/var/run/com.googlecode.munki.ManagedSoftwareUpdate' cmd = ['/usr/bin/touch', launchfile] unused_retcode = subprocess.call(cmd) - - time.sleep(0.1) + time.sleep(1) if os.path.exists(launchfile): os.unlink(launchfile) user_was_notified = True @@ -585,7 +597,7 @@ def main(): if updatecheckresult is not None: recordUpdateCheckResult(updatecheckresult) - + updatesavailable = munkiUpdatesAvailable() appleupdatesavailable = False if (not updatesavailable and not options.installonly and @@ -619,6 +631,9 @@ def main(): munkicommon.display_error(traceback.format_exc()) munkicommon.savereport() exit(-1) + + # send a notification event so MSU can update its display if needed + sendUpdateNotification() mustrestart = False mustlogout = False @@ -667,6 +682,8 @@ def main(): pass elif force_action: notifyUserOfUpdates(force=True) + time.sleep(2) + startLogoutHelper() elif not munkicommon.pref('SuppressUserNotification'): notifyUserOfUpdates() else: @@ -719,8 +736,8 @@ def main(): munkicommon.cleanUpTmpDir() if mustrestart: doRestart() - elif mustlogout: - doLogout() + #elif mustlogout: + # doForcedLogout() # indirectly done via logouthelper elif munkicommon.munkistatusoutput: munkistatus.quit() diff --git a/code/client/munkilib/munkicommon.py b/code/client/munkilib/munkicommon.py index a8d05f7c..6c659077 100644 --- a/code/client/munkilib/munkicommon.py +++ b/code/client/munkilib/munkicommon.py @@ -98,7 +98,7 @@ def set_file_nonblock(f, non_blocking=True): class Popen(subprocess.Popen): - '''Subclass of subprocess.Popen to add support for + '''Subclass of subprocess.Popen to add support for timeouts for some operations.''' def timed_readline(self, f, timeout): """Perform readline-like operation with timeout. @@ -1144,7 +1144,7 @@ def getOnePackageInfo(pkgpath): #if bomlist: # pkginfo['apps'] = [os.path.basename(item) for item in bomlist # if item.endswith('.app')] - + else: # look for old-style .info files! infopath = os.path.join(pkgpath, 'Contents', 'Resources', @@ -1371,7 +1371,7 @@ def nameAndVersion(aString): version = aString[index:] return (aString[0:index].rstrip(' .-_v'), version) else: - # no version number found, + # no version number found, # just return original string and empty string return (aString, '') @@ -1856,24 +1856,23 @@ def listdir(path): return os.listdir(path) -def findProcesses(user=None, exe=None, args=None): +def findProcesses(user=None, exe=None): """Find processes in process list. Args: user: str, optional, username owning process exe: str, optional, executable name of process - args: str, optional, string arguments to match to process Returns: dictionary of pids = { pid: { 'user': str, username owning process, - 'args': str, string executable and arguments of process, + 'exe': str, string executable of process, } } list of pids, or {} if none """ - argv = ['/bin/ps', '-x', '-w', '-w', '-a', '-o', 'pid=,user=,args='] + argv = ['/bin/ps', '-x', '-w', '-w', '-a', '-o', 'pid=,user=,comm='] p = subprocess.Popen( argv, @@ -1889,20 +1888,17 @@ def findProcesses(user=None, exe=None, args=None): lines = stdout.splitlines() for proc in lines: - (p_pid, p_user, p_args) = proc.split(None, 2) + (p_pid, p_user, p_comm) = proc.split(None, 2) if exe is not None: - if not p_args.startswith(exe): - continue - if args is not None: - if not p_args.find(args) > -1: + if not p_comm.startswith(exe): continue if user is not None: if p_user != user: continue pids[int(p_pid)] = { 'user': p_user, - 'args': p_args, + 'exe': p_comm, } except (ValueError, TypeError, IndexError): @@ -1911,6 +1907,7 @@ def findProcesses(user=None, exe=None, args=None): return pids + def forceLogoutNow(): """Force the logout of interactive GUI users and spawn MSU.""" try: diff --git a/code/client/munkilib/version.plist b/code/client/munkilib/version.plist index b3b088ec..1fbdc703 100644 --- a/code/client/munkilib/version.plist +++ b/code/client/munkilib/version.plist @@ -3,6 +3,6 @@ CFBundleShortVersionString - 0.7.3 + 0.8.0 diff --git a/code/tools/make_munki_mpkg.sh b/code/tools/make_munki_mpkg.sh index 39dd4597..c0d7bcb1 100755 --- a/code/tools/make_munki_mpkg.sh +++ b/code/tools/make_munki_mpkg.sh @@ -261,7 +261,7 @@ mkdir -p "$COREROOT/usr/local/munki/munkilib" chmod -R 755 "$COREROOT/usr" # Copy command line utilities. # edit this if list of tools changes! -for TOOL in launchapp managedsoftwareupdate supervisor +for TOOL in launchapp logouthelper managedsoftwareupdate supervisor do cp -X "$MUNKIROOT/code/client/$TOOL" "$COREROOT/usr/local/munki/" 2>&1 done