Cross-platform utility package for MDM deployment scripts (Jamf Pro, Intune).
Project description
pymdm
A Python utility package for macOS MDM deployment scripts, built for MacAdmins Python (#!/usr/local/bin/managed_python3) and Jamf Pro workflows. Windows/Intune support is also available for teams managing mixed-platform fleets.
Features
- ParamParser: Safe parsing of Jamf Pro script parameters 4-11 (macOS)
- Dialog: swiftDialog integration for user-facing dialogs and notifications (macOS)
- CommandRunner: Secure subprocess execution with credential sanitization and platform-aware run-as-user
- SystemInfo: System information helpers — serial number, console user, hostname
- MdmLogger: Structured logging with file output, rotation, and multiple log levels
- WebhookSender: Send logs and metadata to webhooks
- IntuneParamProvider: Env var and argv parameter parsing for Intune scripts (Windows)
Platform Support
| Feature | macOS (Jamf) | Windows (Intune) |
|---|---|---|
| ParamParser (Jamf) | Yes | — |
| Dialog (swiftDialog) | Yes | Graceful no-op |
| CommandRunner | Yes | Yes |
| SystemInfo | Yes | Yes |
| MdmLogger | Yes | Yes |
| WebhookSender | Yes | Yes |
| IntuneParamProvider | — | Yes |
Installation
From Source
uv pip install -e .
Development
make install # Install with dev dependencies
make test # Run tests
make format # Format code with ruff
Quick Start
Logging
from pymdm import MdmLogger
logger = MdmLogger(
debug=True,
output_path="/var/log/my_script.log"
)
logger.info("Script started")
logger.debug("Detailed information")
logger.warn("Warning message")
logger.error("Error occurred", exit_code=1)
Jamf Parameters (macOS)
from pymdm import ParamParser
# Get string parameter
webhook_url = ParamParser.get(4) # $4 in Jamf policy
# Get boolean parameter
debug_mode = ParamParser.get_bool(5) # "true", "1", "yes" -> True
# Get integer parameter
timeout = ParamParser.get_int(6, default=30)
Intune Parameters (Windows)
from pymdm.mdm import IntuneParamProvider
provider = IntuneParamProvider()
# Get from sys.argv
value = provider.get(1)
# Get from environment variable
webhook_url = provider.get("WEBHOOK_URL")
# Boolean from env var
debug = provider.get_bool("DEBUG_MODE")
Auto-Detect MDM Provider
from pymdm.mdm import get_provider
# Automatically selects Jamf on macOS, Intune on Windows
# Override with PYMDM_MDM_PROVIDER env var
provider = get_provider()
value = provider.get(4) # Jamf: sys.argv[4], Intune: sys.argv[4]
debug = provider.get_bool("DEBUG") # Intune: env var lookup
Command Execution
from pymdm import CommandRunner
runner = CommandRunner(logger=logger)
# Safe execution (list form)
output = runner.run(["/usr/bin/id", "-u", username])
# Shell execution (for pipes, etc.)
output = runner.run("ps aux | grep python", timeout=10)
# Run as logged-in user (platform-aware)
# macOS: uses launchctl asuser
# Windows: uses PowerShell Start-Process
runner = CommandRunner(logger=logger, username="jsmith", uid=501)
output = runner.run_as_user(["/usr/bin/open", "-a", "Safari"])
System Information
from pymdm import SystemInfo
# Get serial number
# macOS: system_profiler | Windows: PowerShell/wmic
serial = SystemInfo.get_serial_number()
# Get console user info
user_info = SystemInfo.get_console_user()
if user_info:
username, uid, home_path = user_info
# Get hostname
hostname = SystemInfo.get_hostname()
# Get full name
full_name = SystemInfo.get_user_full_name("jsmith")
Webhook Integration
from pymdm import WebhookSender, MdmLogger
logger = MdmLogger(output_path="/var/log/script.log")
webhook = WebhookSender(
url="https://hooks.tray.io/...",
logger=logger
)
# Send log with metadata
webhook.send(
hostname=SystemInfo.get_hostname(),
serial=SystemInfo.get_serial_number(),
script_name="my_deployment_script",
status="success"
)
Complete Example (macOS / Jamf Pro)
#!/usr/local/bin/managed_python3
"""Example Jamf Pro policy script."""
from pymdm import (
MdmLogger,
ParamParser,
CommandRunner,
SystemInfo,
WebhookSender,
)
# Setup
logger = MdmLogger(
debug=ParamParser.get_bool(4),
output_path="/var/log/my_script.log"
)
runner = CommandRunner(logger=logger)
logger.log_startup("my_script", version="1.0.0")
try:
# Get system info
serial = SystemInfo.get_serial_number()
hostname = SystemInfo.get_hostname()
logger.info(f"Running on {hostname} ({serial})")
# Execute command
output = runner.run(["/usr/bin/sw_vers", "-productVersion"])
logger.info(f"macOS version: {output}")
# Send results
webhook = WebhookSender(
url=ParamParser.get(5),
logger=logger
)
webhook.send(
hostname=hostname,
serial=serial,
status="success"
)
except Exception as e:
logger.log_exception("Script failed", e, exit_code=1)
Complete Example (Windows / Intune)
"""Example Intune deployment script."""
from pymdm import MdmLogger, CommandRunner, SystemInfo, WebhookSender
from pymdm.mdm import IntuneParamProvider
# Setup
params = IntuneParamProvider()
logger = MdmLogger(
debug=params.get_bool("DEBUG"),
output_path="C:\\ProgramData\\Scripts\\my_script.log"
)
runner = CommandRunner(logger=logger)
logger.log_startup("my_script", version="1.0.0")
try:
serial = SystemInfo.get_serial_number()
hostname = SystemInfo.get_hostname()
logger.info(f"Running on {hostname} ({serial})")
# Windows-specific command
output = runner.run(["powershell", "-Command", "Get-ComputerInfo | Select-Object OsVersion"])
logger.info(f"System info: {output}")
webhook_url = params.get("WEBHOOK_URL")
if webhook_url:
webhook = WebhookSender(url=webhook_url, logger=logger)
webhook.send(hostname=hostname, serial=serial, status="success")
except Exception as e:
logger.log_exception("Script failed", e, exit_code=1)
Platform Configuration
Environment Variables
| Variable | Purpose | Values |
|---|---|---|
PYMDM_PLATFORM |
Override platform auto-detection | darwin, win32 |
PYMDM_MDM_PROVIDER |
Override MDM provider auto-detection | jamf, intune |
Task Mapping: Jamf vs Intune
| Jamf Pro | Intune Equivalent | pymdm API |
|---|---|---|
ParamParser.get(4) |
IntuneParamProvider().get("PARAM_NAME") |
get_provider().get(...) |
ParamParser.get_bool(5) |
IntuneParamProvider().get_bool("FLAG") |
get_provider().get_bool(...) |
Script params via sys.argv[4-11] |
Env vars or sys.argv |
Provider-specific |
jamf recon |
Microsoft Graph API | Not in pymdm (use provider SDK) |
| swiftDialog | Windows toast/WPF | Dialog (macOS only) |
Migration Notes
From pymdm < 0.4.0
All existing imports and APIs are fully backward compatible. No changes required for macOS/Jamf scripts:
# These all work exactly as before
from pymdm import SystemInfo, CommandRunner, ParamParser, MdmLogger
For new Windows/Intune scripts, use the new provider APIs:
from pymdm.mdm import IntuneParamProvider, get_provider
from pymdm.platforms import get_platform
Architecture
pymdm/
├── platforms/ # OS-specific implementations
│ ├── darwin.py # macOS: system_profiler, launchctl
│ └── win32.py # Windows: PowerShell, wmic, runas
├── mdm/ # MDM provider implementations
│ ├── jamf.py # Jamf Pro: sys.argv[4-11] parsing
│ └── intune.py # Intune: env vars, flexible argv
├── command_runner.py # Cross-platform subprocess wrapper
├── dialog.py # swiftDialog integration (macOS)
├── logger.py # Structured logging
├── param_parser.py # Backward-compat Jamf parser facade
├── system_info.py # Cross-platform system info facade
└── webhook_sender.py # HTTP webhook sender
Requirements
- Python 3.12+
requests(included with MacAdmins Python)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pymdm-0.4.0.tar.gz.
File metadata
- Download URL: pymdm-0.4.0.tar.gz
- Upload date:
- Size: 43.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8dc18e1f0d765667af91429955b97abfed3c44ea0fce475990e011d08b0a342c
|
|
| MD5 |
0bdddcdae637e1800e2e617f03ebea7c
|
|
| BLAKE2b-256 |
53552e71d872881ad15c4e86e15625f8b1106f482a3e9f89605fbde35fcbd319
|
Provenance
The following attestation bundles were made for pymdm-0.4.0.tar.gz:
Publisher:
build-release.yml on liquidz00/pymdm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymdm-0.4.0.tar.gz -
Subject digest:
8dc18e1f0d765667af91429955b97abfed3c44ea0fce475990e011d08b0a342c - Sigstore transparency entry: 949072942
- Sigstore integration time:
-
Permalink:
liquidz00/pymdm@42fd9fa908273e2bd8fa373b40d60239c5e16dfc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/liquidz00
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-release.yml@42fd9fa908273e2bd8fa373b40d60239c5e16dfc -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pymdm-0.4.0-py3-none-any.whl.
File metadata
- Download URL: pymdm-0.4.0-py3-none-any.whl
- Upload date:
- Size: 35.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bda1b208950e2e6ecb8045f3595ac254e667faa5c24dbe105a146641e527ac38
|
|
| MD5 |
97c40f742ea0bfe792354eaf75cb4b9c
|
|
| BLAKE2b-256 |
f1e9213837aaf1fc2c3cc5068598b238843e5be67f8a40cd679c7af8cc96af99
|
Provenance
The following attestation bundles were made for pymdm-0.4.0-py3-none-any.whl:
Publisher:
build-release.yml on liquidz00/pymdm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymdm-0.4.0-py3-none-any.whl -
Subject digest:
bda1b208950e2e6ecb8045f3595ac254e667faa5c24dbe105a146641e527ac38 - Sigstore transparency entry: 949072977
- Sigstore integration time:
-
Permalink:
liquidz00/pymdm@42fd9fa908273e2bd8fa373b40d60239c5e16dfc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/liquidz00
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build-release.yml@42fd9fa908273e2bd8fa373b40d60239c5e16dfc -
Trigger Event:
workflow_dispatch
-
Statement type: