├── .gitignore ├── README.md ├── build-info.plist ├── payload └── Library │ ├── LaunchAgents │ └── com.grahamgilbert.chrome_update_notifier.plist │ └── Management │ ├── chrome_update_notifier.py │ └── show_chrome_update_message.sh └── scripts └── postinstall /.gitignore: -------------------------------------------------------------------------------- 1 | # .DS_Store files! 2 | .DS_Store 3 | 4 | # our build directory 5 | build/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chrome Update Notifier 2 | 3 | ## Requirements 4 | 5 | * [Yo](https://github.com/sheagcraig/yo) (a customized version with your organization's logo is even cooler) 6 | 7 | This is a script that is run via a Launch Agent periodically that will ensure that the currently running version of Google Chrome is the same as is on disk. If these differ, it will post a notification via Yo to ask the user to relaunch Google Chrome gracefully (preserving their precious tabs!). 8 | -------------------------------------------------------------------------------- /build-info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | distribution_style 6 | 7 | identifier 8 | com.grahamgilbert.chrome_update_notifier 9 | install_location 10 | / 11 | name 12 | chrome_update_notifier-${version}.pkg 13 | ownership 14 | recommended 15 | postinstall_action 16 | none 17 | suppress_bundle_relocation 18 | 19 | version 20 | 1.0.1 21 | 22 | 23 | -------------------------------------------------------------------------------- /payload/Library/LaunchAgents/com.grahamgilbert.chrome_update_notifier.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | com.grahamgilbert.chrome_update_notifier 7 | Program 8 | /Library/Management/chrome_update_notifier.py 9 | RunAtLoad 10 | 11 | StartInterval 12 | 300 13 | 14 | 15 | -------------------------------------------------------------------------------- /payload/Library/Management/chrome_update_notifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | This will notifify the user via Yo that they need to update Chrome if: 5 | * It is installed 6 | * We haven't already told them about this version 7 | """ 8 | 9 | import subprocess 10 | import time 11 | import plistlib 12 | import os 13 | import sys 14 | import getpass 15 | import string 16 | from distutils.version import LooseVersion 17 | from Foundation import \ 18 | CFPreferencesSetAppValue, \ 19 | CFPreferencesAppSynchronize, \ 20 | CFPreferencesCopyAppValue, \ 21 | NSDate 22 | 23 | BUNDLE_ID = 'com.grahamgilbert.chrome_notifier' 24 | 25 | TESTING = False 26 | 27 | 28 | def run_yo(): 29 | """ 30 | Like, runs Yo 31 | """ 32 | cmd = [ 33 | '/Library/Management/show_chrome_update_message.sh' 34 | ] 35 | 36 | subprocess.call(cmd) 37 | 38 | 39 | def set_pref(pref_name, pref_value): 40 | """ 41 | Sets a preference 42 | """ 43 | # pylint: disable=E0602 44 | CFPreferencesSetAppValue(pref_name, pref_value, BUNDLE_ID) 45 | CFPreferencesAppSynchronize(BUNDLE_ID) 46 | 47 | 48 | def pref(pref_name): 49 | """ 50 | Reads a preference, setting a default if we set any 51 | """ 52 | default_prefs = { 53 | # 'last_shown' : NSDate.new(), 54 | } 55 | 56 | pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID) 57 | if pref_value is None: 58 | pref_value = default_prefs.get(pref_name) 59 | # we're using a default value. We'll write it out to 60 | # /Library/Preferences/.plist for admin 61 | # discoverability 62 | set_pref(pref_name, pref_value) 63 | if isinstance(pref_value, NSDate): 64 | # convert NSDate/CFDates to strings 65 | pref_value = str(pref_value) 66 | return pref_value 67 | 68 | 69 | def already_notified(new_version): 70 | """ 71 | Have we notified on this version before? 72 | """ 73 | last_version = pref('last_notified') 74 | if last_version is None: 75 | # Never notified 76 | return False 77 | if LooseVersion(new_version) > LooseVersion(last_version): 78 | # new version is bigger, hasn't been notified 79 | return False 80 | else: 81 | # New version isn't bigger, we have been notified 82 | return True 83 | 84 | 85 | def set_last_version_notified(version): 86 | """ 87 | This is the last version we notified on 88 | """ 89 | set_pref('last_notified', version) 90 | 91 | 92 | def chrome_installed(): 93 | """ 94 | Checks if Google Chrome is installed 95 | """ 96 | chrome = '/Applications/Google Chrome.app' 97 | return bool(os.path.exists(chrome)) 98 | 99 | 100 | def get_chrome_version(): 101 | """ 102 | Returns the on disk chrome version 103 | """ 104 | chrome_info = plistlib.readPlist('/Applications/Google Chrome.app/Contents/Info.plist') 105 | if 'CFBundleShortVersionString' in chrome_info: 106 | return chrome_info['CFBundleShortVersionString'] 107 | else: 108 | return '0' 109 | 110 | 111 | def running_chrome_version(): 112 | proc = subprocess.Popen(['/bin/ps', '-axo' 'command='], 113 | shell=False, stdin=subprocess.PIPE, 114 | stdout=subprocess.PIPE, 115 | stderr=subprocess.PIPE) 116 | (output, dummy_err) = proc.communicate() 117 | if proc.returncode == 0: 118 | proc_lines = [item for item in output.splitlines()] 119 | for line in proc_lines: 120 | if line.startswith('/Applications/Google Chrome.app/Contents/Versions/'): 121 | line = line.replace('/Applications/Google Chrome.app/Contents/Versions/', '') 122 | if line: 123 | sep = '/' 124 | line = line.split(sep, 1)[0] 125 | return line 126 | 127 | 128 | def main(): 129 | """ 130 | gimmie some main 131 | """ 132 | 133 | running = False 134 | 135 | running_version = running_chrome_version() 136 | if not running_version: 137 | print 'Chrome is not running' 138 | time.sleep(30) 139 | sys.exit(0) 140 | 141 | if TESTING is True: 142 | running = True 143 | 144 | if TESTING is True: 145 | sleep_seconds = 0 146 | else: 147 | sleep_seconds = 20 148 | 149 | if not chrome_installed(): 150 | print 'Chrome not installed' 151 | time.sleep(30) 152 | sys.exit(0) 153 | 154 | current_chrome_version = get_chrome_version() 155 | 156 | if current_chrome_version == '0': 157 | print 'Could not get current chrome version' 158 | time.sleep(30) 159 | sys.exit(0) 160 | 161 | if LooseVersion(running_version) < LooseVersion(current_chrome_version): 162 | print "running version is {}".format(running_version) 163 | print "on disk version is {}".format(current_chrome_version) 164 | running = True 165 | 166 | if already_notified(get_chrome_version()): 167 | print 'Already notified for this version' 168 | time.sleep(30) 169 | sys.exit(0) 170 | 171 | if running is True or TESTING is True: 172 | print 'We are running...' 173 | run_yo() 174 | time.sleep(sleep_seconds) 175 | set_last_version_notified(get_chrome_version()) 176 | 177 | 178 | if __name__ == '__main__': 179 | main() 180 | -------------------------------------------------------------------------------- /payload/Library/Management/show_chrome_update_message.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /usr/local/bin/yo_scheduler --title "Google Chrome Update" --info "Google Chrome needs to be restarted so it can be updated. Tabs will be restored." -B "osascript -e 'tell application \"Google Chrome\" to open location \"chrome://restart\"'" --action-btn "Restart" 4 | -------------------------------------------------------------------------------- /scripts/postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # get console UID 4 | consoleuser=`/usr/bin/stat -f "%Su" /dev/console | /usr/bin/xargs /usr/bin/id -u` 5 | 6 | /bin/launchctl bootout gui/$consoleuser /Library/LaunchAgents/com.grahamgilbert.chrome_update_notifier.plist 7 | /bin/launchctl bootstrap gui/$consoleuser /Library/LaunchAgents/com.grahamgilbert.chrome_update_notifier.plist 8 | 9 | exit 0 10 | --------------------------------------------------------------------------------