Skip to content

Instantly share code, notes, and snippets.

@childrss
Forked from sheagcraig/README.md
Last active November 6, 2018 22:03
Show Gist options
  • Save childrss/2f689c9151e17012a7d4ca24a949bb64 to your computer and use it in GitHub Desktop.
Save childrss/2f689c9151e17012a7d4ca24a949bb64 to your computer and use it in GitHub Desktop.
Uninstall Microsoft Lync

UninstallLync

According to Microsoft, Lync must be completely uninstalled, following the procedures documented at https://technet.microsoft.com/en-us/library/jj945448(v=office.14).aspx, at least for Calendar functionality to work correctly.

This repo contains a python script for removing all referenced Lync components for all normal users on a machine, i.e., with homes in /Users.

It is somewhat naive in that it assumes the users have not moved the Lync keychain items out of the Login keychain and into some other keychain. It handles the potential for multiple "your.email@domain.com" certificates in the login keychain, however. This is all done by running the security command as a subprocess. Improvements could probably be made to do this all with the Security Framework and the PyObjC bridge, but this gets the job done.

Using UninstallLync in Your Environment

You will need to edit the pattern global APP_PW_PATTERN at the top of the uninstall_lync.py script to look for your environment's email domain. Again, this could be made more generic by just building a pattern that accepts all email addresses as part of the OC_KeyContainer__<email address> construction that Lync uses, but it gets the job done. Feel free to send me a better implementation! If you're of the "get it done" mentality, just edit this pattern to use your domain. Examples are included of how to regex search for multiple email domains as well.

#!/bin/bash
makepkginfo --name="UninstallLync" \
--displayname="Uninstall Microsoft Lync" \
--description="Skype for Business requires a complete removal of all Microsoft Lync components and data. This uninstaller takes care of cleanly removing everything." \
--pkgvers="1.0" \
--installcheck_script="installcheck.sh" \
--postinstall_script="uninstall_lync.py" \
--nopkg \
--catalog="phase3"
#!/bin/sh
if [[ -d "/Applications/Microsoft Lync.app" ]]; then
echo "Lync exists, need to run Uninstall"
exit 0
else
if [[ $(pkgutil --packages | grep lync) != *com.microsoft.lync* ]]; then
echo "exit 1, Lync does not exist, do NOT need to run this uninstaller"
exit 1
else
echo "Lync receipt(s) exist in packages receipts (/var/db/recipts/), need to run this uninstaller"
exit 0
fi
fi
#!/usr/bin/python
# Based on:
# https://technet.microsoft.com/en-us/library/jj945448(v=office.14).aspx
#
# microsoft_lync_clean_uninstall
#
# https://gist.github.com/sheagcraig/4bfb6249faf1ac413508f23dec448cd3#file-uninstall_lync-py
#
# childrss@illinois.edu -- Now with new & improved receipts database removal!
# You will need to change search values for APP_PW_PATTERN for your org's email at approximately line 42
#
# For munki use this is intended to be run as a postinstall script (nopkg installer type) paired with an install-check script
# that should also be on github.
import glob
import os
import re
import shutil
import subprocess
PKG_RECEIPTS = subprocess.check_output(['pkgutil', '--packages']).splitlines()
PKG_RECEIPTS_TO_REMOVE = [s for s in PKG_RECEIPTS if "com.microsoft.lync" in s]
for receipt in PKG_RECEIPTS_TO_REMOVE:
print "pkgutil --forget", receipt
subprocess.call(["pkgutil", "--forget", receipt])
FILES_TO_REMOVE = (
"/Applications/Microsoft Lync.app",
"/Users/*/Library/Preferences/com.microsoft.Lync.plist",
"/Users/*/Library/Preferences/ByHost/MicrosoftLyncRegistrationDB.*.plist",
"/Users/*/Library/Logs/Microsoft-Lync-x.log",
"/Users/*/Library/Logs/Microsoft-Lync.log",
"/Users/*/Documents/Microsoft User Data/Microsoft Lync Data",
"/Users/*/Documents/Microsoft User Data/Microsoft Lync History",
"/Library/Internet Plug-Ins/MeetingJoinPlugin.plugin")
# Edit to include your company's email domain
# e.g.:
# APP_PW_PATTERN = r".*(OC_KeyContainer__.*@tacos.com).*"
# Multiple domains:
# APP_PW_PATTERN = r".*(OC_KeyContainer__.*@(?:tacos|burritos).com).*"
APP_PW_PATTERN = r".*(OC_KeyContainer__.*@(?:sas|spss).com).*"
def main():
# Get Lync unloaded
try:
subprocess.check_call(["killall", "Microsoft Lync"])
except subprocess.CalledProcessError:
pass
# Remove easy stuff
for removal in FILES_TO_REMOVE:
removals = glob.glob(removal)
for removal in removals:
if os.path.isdir(removal):
shutil.rmtree(removal, ignore_errors=True)
else:
try:
os.remove(removal)
except OSError:
pass
# Remove keychain stuff
keychains = glob.glob("/Users/*/Library/Keychains/login.keychain*")
for keychain in keychains:
try:
dump = subprocess.check_output(["security", "dump-keychain",
keychain])
except subprocess.CalledProcessError:
continue
lync_items = set()
for line in dump.splitlines():
match = re.search(APP_PW_PATTERN, line)
if match:
lync_items.add(match.group(1))
for item in lync_items:
print "Removing {} from {}.".format(item, keychain)
try:
subprocess.check_call(
["security", "delete-generic-password", "-a", item,
keychain])
except subprocess.CalledProcessError as err:
print err.message
email_address = None
if lync_items:
search_string = lync_items.pop()
email_address = search_string.split("__")[1]
certs_remaining = True
while certs_remaining:
try:
issuer_check = subprocess.check_output(
["security", "find-certificate", "-Z", "-c",
email_address, keychain])
except subprocess.CalledProcessError as err:
if err.returncode == 44:
certs_remaining = False
continue
if search_string in issuer_check:
print "Trying to delete the email addy cert"
# Get the hash to use in identifying the cert for removal.
search = [line for line in issuer_check.splitlines() if
"SHA-1 hash" in line]
if search:
cert_hash = search[0].partition(":")[2].strip()
try:
subprocess.check_call(
["security", "delete-certificate", "-Z", cert_hash,
keychain])
except subprocess.CalledProcessError as err:
print err.message
# Remove keychain db items
for item in glob.glob("/Users/*/Library/Keychains/OC_KeyContainer__*"):
print "Removing keychain folder item {}".format(item)
try:
os.remove(item)
except OSError:
pass
if os.path.exists("/usr/local/bin/dockutil"):
try:
subprocess.check_call(["dockutil", "--remove", "Microsoft Lync",
"--allhomes"])
except subprocess.CalledProcessError as err:
print err.message
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment