Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
show_unused_munki_packages.py
#!/usr/bin/env python
"""
Name: show_unused_munki_packages.py
Description: Given a path to a Munki repo, this script will tell you
which package names are unused in any manifests. Useful for
detecting things that were imported into Munki but never
added to a manifest.
Author: Elliot Jordan <elliot@lindegroup.com>
Created: 2015-07-30
Last Modified: 2015-08-03
Version: 1.0.7
"""
from xml.parsers.expat import ExpatError
import os
import plistlib
from pprint import pprint
import sys
def process_manifest(manifest, packages_in_manifests):
for item in ("managed_installs", "managed_uninstalls", "managed_updates", "optional_installs"):
try:
for package in manifest[item]:
if package not in packages_in_manifests:
packages_in_manifests.append(package)
except Exception:
continue
try:
if manifest["conditional_items"]:
for conditional_item in manifest["conditional_items"]:
process_manifest(conditional_item, packages_in_manifests)
except Exception:
pass
def main():
munki_repo = raw_input(
"Please enter the path to your Munki repo [defaults to /Users/Shared/munki_repo]: ")
if munki_repo == "":
munki_repo = "/Users/Shared/munki_repo"
packages_in_repo = []
requirements_in_repo = []
for dirpath, dirnames, filenames in os.walk(os.path.join(munki_repo, "pkgsinfo")):
for dirname in dirnames:
# Skip directories that start with a period (e.g. ".git")
if dirname.startswith("."):
dirnames.remove(dirname)
for filename in filenames:
# Skip files that start with a period (e.g. ".DS_Store")
if filename.startswith("."):
continue
filepath = os.path.join(dirpath, filename)
try:
pkginfo = plistlib.readPlist(filepath)
try:
if pkginfo["name"] not in packages_in_repo:
if "update_for" not in pkginfo:
if "installer_type" not in pkginfo:
packages_in_repo.append(pkginfo["name"])
elif pkginfo["installer_type"] != "apple_update_metadata":
packages_in_repo.append(pkginfo["name"])
if "requires" in pkginfo:
for requirement in pkginfo["requires"]:
if requirement not in requirements_in_repo:
requirements_in_repo.append(requirement)
except KeyError:
continue
except ExpatError:
print >> sys.stderr, "Could not parse %s" % os.path.join(
dirpath, filename)
packages_in_manifests = []
for dirpath, dirnames, filenames in os.walk(os.path.join(munki_repo, "manifests")):
for dirname in dirnames:
# Skip directories that start with a period (e.g. ".git")
if dirname.startswith("."):
dirnames.remove(dirname)
for filename in filenames:
# Skip files that start with a period (e.g. ".DS_Store")
if filename.startswith("."):
continue
filepath = os.path.join(dirpath, filename)
try:
manifest = plistlib.readPlist(filepath)
process_manifest(manifest, packages_in_manifests)
except ExpatError:
print >> sys.stderr, "Could not parse %s" % os.path.join(
dirpath, filename)
unused_packages = list(set(packages_in_repo) - set(requirements_in_repo) - set(packages_in_manifests))
print "\n UNUSED PACKAGES:\n"
pprint(unused_packages)
if __name__ == '__main__':
main()
@gregneagle

This comment has been minimized.

Copy link

gregneagle commented Jul 31, 2015

I get an exception: xml.parsers.expat.ExpatError: not well-formed (invalid token): line 1, column 0 at
File "show_unused_munki_packages.py", line 61, in main
manifest = plistlib.readPlist(os.path.join(directory, file))

There's probably a bad manifest in my repo.

I suggest wrapping that bit in a try/except:

# need to add a couple of imports
import sys
from xml.parsers.expat import ExpatError

# lines 61-62 replaced with:
try:
    manifest = plistlib.readPlist(os.path.join(directory, file))
    process_manifest(manifest, packages_in_manifests)
except ExpatError, err:
    print >> sys.stderr, "Could not parse %s" % os.path.join(directory, file)
@gregneagle

This comment has been minimized.

Copy link

gregneagle commented Jul 31, 2015

After making that change, I get the following errors:

Could not parse /Users/Shared/munki_repo/manifests/.DS_Store
Could not parse /Users/Shared/munki_repo/manifests/.git/....

and lots more lines for things in .git.

So you might want to do some filtering on filenames before trying to feed them to plistlib.readPlist.

@gregneagle

This comment has been minimized.

Copy link

gregneagle commented Jul 31, 2015

It identified all my apple_update_metadata items as unused packages -- these should never be added to a manifest, so they should probably be filtered out of the list.

@homebysix

This comment has been minimized.

Copy link
Owner Author

homebysix commented Jul 31, 2015

@gregneagle - Give it another try. I updated the script above, and tested it with my own git-tracked manifests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.