Profiles.py

#!/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}")