aboutsummaryrefslogtreecommitdiffstats
path: root/maucl.py
diff options
context:
space:
mode:
Diffstat (limited to 'maucl.py')
-rwxr-xr-xmaucl.py190
1 files changed, 190 insertions, 0 deletions
diff --git a/maucl.py b/maucl.py
new file mode 100755
index 0000000..62d5b42
--- /dev/null
+++ b/maucl.py
@@ -0,0 +1,190 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""Microsoft AutoUpdate Command Line Utility."""
+
+import os
+import sys
+import httplib
+import plistlib
+import tempfile
+import subprocess
+
+
+UUID = 'C1297A47-86C4-4C1F-97FA-950631F94777'
+NAMES = {
+ 'XCEL15': 'Excel',
+ 'ONMC15': 'OneNote',
+ 'OPIM15': 'Outlook',
+ 'PPT315': 'PowerPoint',
+ 'MSWD15': 'Word',
+ 'MSau03': 'AutoUpdate',
+ 'SLVT': 'Silverlight',
+}
+
+
+BASEURL = '/pr/%s/OfficeMac/' % UUID
+PREF_PATH = 'Library/Preferences/com.microsoft.autoupdate2.plist'
+
+
+def get_plist(path):
+ """Return plist dict regardless of format."""
+ plist = subprocess.check_output(['/usr/bin/plutil',
+ '-convert', 'xml1',
+ path, '-o', '-'])
+ return plistlib.readPlistFromString(plist)
+
+
+def set_pref(k='HowToCheck', v='Manual'):
+ """Set AU pref k to v."""
+ subprocess.call(['/usr/bin/defaults',
+ 'write',
+ 'com.microsoft.autoupdate2',
+ k,
+ v])
+
+
+def check(home=None, pref=PREF_PATH):
+ """Collect info about available updates."""
+ results = []
+
+ if home is None:
+ home = os.getenv('HOME')
+
+ pref = os.path.join(home, pref)
+
+ try:
+ plist = get_plist(os.path.expanduser(pref))
+ except Exception as e:
+ raise Exception('Failed to read MAU prefs: %s' % e)
+
+ apps = plist.get('Applications')
+
+ for a in apps.items():
+ path, info = a
+ app_id = info.get('Application ID')
+
+ # Skip Microsoft Error Reporting
+ if app_id == 'Merp2':
+ continue
+
+ try:
+ app_info = get_plist(os.path.join(path, 'Contents/Info.plist'))
+ except Exception as e:
+ continue
+
+ result = {'id': app_id, 'installed': app_info.get('CFBundleVersion')}
+ result['name'] = NAMES.get(app_id, app_id)
+ result['lcid'] = info.get('LCID')
+
+ # Lync (UCCP14) is special
+ filename = '0409%s.xml' if app_id == 'UCCP14' else '0409%s-chk.xml'
+
+ try:
+ conn = httplib.HTTPSConnection('officecdn.microsoft.com')
+ conn.request("GET", BASEURL + filename % app_id)
+ data = conn.getresponse().read()
+ except Exception as e:
+ raise Exception('Failed to check for updates: %s' % e)
+
+ try:
+ p = plistlib.readPlistFromString(data)
+
+ if app_id == 'UCCP14': # Lync being special again
+ p = p[0]
+ result['location'] = p.get('Location')
+ versions = p['Triggers']['Lync']['Versions']
+ result['needs_update'] = result['installed'] in versions
+ else:
+ result['date'] = p.get('Date')
+ result['type'] = p.get('Type')
+ result['available'] = p.get('Update Version')
+ result['needs_update'] = result['installed'] != result['available']
+
+ if result['needs_update']:
+ url = BASEURL + filename % app_id
+ conn.request("GET", url.replace('-chk', ''))
+ data = conn.getresponse().read()
+ # Fetch the update details
+ updates = plistlib.readPlistFromString(data)
+ for i in updates:
+ if i.get('Baseline Version') == result['installed'] or app_id == 'MSau03':
+ result['location'] = i.get('Location')
+ result['size'] = i.get('FullUpdaterSize')
+ results.append(result)
+ except Exception as e:
+ print('Failed to check %s' % app_id, e)
+
+ finally:
+ conn.close()
+
+ return results
+
+
+def download(url):
+ """Download url to temporary folder."""
+ temp = os.path.join(tempfile.gettempdir(), 'mowgli')
+
+ if not os.path.exists(temp):
+ os.mkdir(temp)
+
+ fn = url.split('/')[-1]
+ fp = os.path.join(temp, fn)
+
+ if not os.path.exists(fp):
+ subprocess.call(['/usr/bin/curl', url, '-o', fp])
+
+ return fp
+
+
+def install(pkg):
+ """Install a package."""
+ r = subprocess.call(['/usr/sbin/installer', '-pkg', pkg, '-target', '/'])
+ if r > 0:
+ raise Exception('Failed to install package %s' % pkg)
+
+ os.remove(pkg)
+
+
+if __name__ == '__main__':
+
+ if len(sys.argv) < 2:
+ print("usage: {0} [-l | -i [home]".format(os.path.basename(sys.argv[0])))
+ sys.exit(1)
+
+ if sys.argv[1] == 'enable':
+ set_pref('HowToCheck', 'Manual')
+ print('* Automatic updates enabled')
+ sys.exit(0)
+
+ if sys.argv[1] == 'disable':
+ set_pref('HowToCheck', 'Automatic')
+ print('* Automatic updates disabled')
+ sys.exit(0)
+
+ print("* Finding available software")
+
+ if len(sys.argv) > 2:
+ if not os.path.exists(sys.argv[2]):
+ raise Exception('Invalid MAU pref file %s' % sys.argv[2])
+ updates = check(sys.argv[2])
+ else:
+ updates = check()
+
+ updates = [u for u in updates if u['needs_update']]
+
+ if sys.argv[1] == '-l':
+ for u in updates:
+ print(" * {id}\n {name} {installed} - {available} [{type}]".format(**u))
+
+ if sys.argv[1] == '-ia':
+ for u in updates:
+ print('* Downloading %s %s' % (u['name'], u['available']))
+ pkg = download(u['location'])
+ print('* Installing %s' % pkg)
+ install(pkg)
+
+ if len(updates) < 1:
+ print("* No updates available")
+ sys.exit(1)
+
+ sys.exit(0)