Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Uninstall Microsoft Lync


According to Microsoft, Lync must be completely uninstalled, following the procedures documented at, 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 "" 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 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.

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="" \
--postinstall_script="" \
--nopkg \
if [[ -e "/Applications/Microsoft" ]]; then
exit 0
exit 1
# Based on:
import glob
import os
import re
import shutil
import subprocess
"/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__.**"
# Multiple domains:
# APP_PW_PATTERN = r".*(OC_KeyContainer__.*@(?:tacos|burritos).com).*"
APP_PW_PATTERN = r".*(OC_KeyContainer__.*@(?:sas|jmp).com).*"
def main():
# Get Lync unloaded
subprocess.check_call(["killall", "Microsoft Lync"])
except subprocess.CalledProcessError:
# 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)
except OSError:
# Remove keychain stuff
keychains = glob.glob("/Users/*/Library/Keychains/login.keychain*")
for keychain in keychains:
dump = subprocess.check_output(["security", "dump-keychain",
except subprocess.CalledProcessError:
lync_items = set()
for line in dump.splitlines():
match =, line)
if match:
for item in lync_items:
print "Removing {} from {}.".format(item, keychain)
["security", "delete-generic-password", "-a", item,
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:
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
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()
["security", "delete-certificate", "-Z", cert_hash,
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)
except OSError:
if os.path.exists("/usr/local/bin/dockutil"):
subprocess.check_call(["dockutil", "--remove", "Microsoft Lync",
except subprocess.CalledProcessError as err:
print err.message
if __name__ == "__main__":

This comment has been minimized.

Copy link

@childrss childrss commented Nov 6, 2018

Hey Shea --

Thanks so much for the code sharing. I noticed that it did not delete the package installer receipts out of /var/db/receipts so I added that code as well as code in the to clean up those systems where this had already ran but had not gotten the receipts.

Unfortunately there is no pull request on gist.github, so if you could take a look at my fork and copy and and paste it in. For the, all of the additions are between the last import command and FILES_TO_REMOVE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment