#!/usr/bin/env -S python3 -B
# coding: utf-8
# python: 3.x
# Copyright © 2025 Florent Claerhout.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Helper for macOS MDM profiles management."""
# stdlib:
import argparse
import importlib.metadata
import fnmatch
import logging
import pathlib
import subprocess
import sys
import time
class KnownError(Exception):
pass
def get_installed_profile_name(profile_identifier: str) -> str:
proc = subprocess.run(
["/usr/bin/profiles", "show", profile_identifier],
check=True,
stdout=subprocess.PIPE,
text=True,
)
for line in proc.stdout.splitlines():
if "attribute: name: " in line:
return line.split(":")[2].strip()
raise KnownError("failed to parse profile name")
def is_profile_installed(file_path: pathlib.Path) -> bool:
if file_path.suffix != ".mobileconfig":
raise KnownError(f"{file_path}: expected .mobileconfig extension")
proc = subprocess.run(
["/usr/bin/profiles", "list"],
check=True,
stdout=subprocess.PIPE,
text=True,
)
for line in proc.stdout.splitlines():
if "profileIdentifier: " in line:
profile_identifier = line.split(":")[2].strip()
profile_name = get_installed_profile_name(profile_identifier=profile_identifier)
if profile_name == file_path.stem:
return True
else:
return False
def install(file_path: pathlib.Path) -> None:
if is_profile_installed(file_path):
pass
else:
subprocess.run(["/usr/bin/open", file_path], check=True)
subprocess.run(["/usr/bin/open", "x-apple.systempreferences:com.apple.Profiles-Settings.extension"], check=True)
logging.info("waiting for user to confirm profile installation")
while not is_profile_installed(file_path):
time.sleep(1)
def main(args: list[str]) -> int:
logging.basicConfig(
datefmt="%Y/%m/%d-%H:%M:%S-%Z",
format="\033[90m%(asctime)s: +%(relativeCreated)dms: %(levelname)s: %(message)s\033[0m",
level=logging.INFO,
) # reminder: can be called once
##
## init parser
##
parser = argparse.ArgumentParser(
add_help=True, # Add a -h/--help option to the parser
allow_abbrev=True, # Allows long options to be abbreviated if the abbreviation is unambiguous
description=__doc__,
)
parser.add_argument(
"-v", "--verbose",
action="store_true",
help="increase output verbosity",
)
subparsers = parser.add_subparsers(
dest="subparser_name",
required=False,
title="commands",
)
if True:
subparser = subparsers.add_parser("is-installed", aliases=[], help="lorem ipsum")
subparser.add_argument(
"file_path",
help="lorem ipsum",
type=pathlib.Path,
)
if True:
subparser = subparsers.add_parser("install", aliases=[], help="lorem ipsum")
subparser.add_argument(
"file_path",
help="lorem ipsum",
type=pathlib.Path,
)
##
## parse
##
ns = parser.parse_args(args)
if ns.verbose:
logging.getLogger().setLevel(logging.DEBUG)
if ns.subparser_name == "is-installed":
if is_profile_installed(file_path=ns.file_path):
logging.info(f"{ns.file_path}: profile is installed")
return 0
else:
raise KnownError(f"{ns.file_path}: profile is not installed")
elif ns.subparser_name == "install":
install(file_path=ns.file_path)
else:
raise NotImplementedError
if __name__ == "__main__":
try:
raise SystemExit(main(sys.argv[1:]))
except KeyboardInterrupt:
raise SystemExit("User-cancelled")
except KnownError as exc:
raise SystemExit(f"Fatal: {exc}")