m365ctl
An admin CLI for Microsoft 365 — OneDrive, SharePoint, and Exchange Mail — built on Microsoft Graph.
Safe by default: dry-run, plan-file workflow, scope allow-lists, audit log, and undo on every mutation.
Table of contents
- Highlights
- Installation
- Quickstart
- Features
- Safety model
- Configuration
- Documentation
- Contributing
- License
- Disclaimer
Highlights
- Read-only by default. Mutating verbs require
--confirmand are logged. - Local catalog. DuckDB mirrors of files and mail enable offline queries.
- Plan / replay. Bulk operations produce reviewable plan files before execution.
- Undoable. Every mutation records before/after state for
m365ctl undo. - Scope guards.
allow_drives/allow_mailboxes/deny_pathsenforced on every call. - App-only auth. Certificate-based auth against your Entra app registration.
Installation
Requires Python 3.11+. Install with uv:
# Standalone CLI (recommended for end users):
uv tool install m365ctl
# Or as a project dependency:
uv add m365ctl
# Or with pipx / pip:
pipx install m365ctl
This installs the m365ctl console script. Verify with m365ctl --help.
For local development from source:
git clone https://github.com/aren13/m365ctl
cd m365ctl
uv sync --all-extras
# The repo's bin/*.sh shims (./bin/od-auth, ./bin/mail-list, ...) are
# convenience aliases for `uv run m365ctl <domain> <verb>`.
Quickstart
-
Register an Entra app and grant Graph permissions — see docs/setup/azure-app-registration.md.
-
Generate a client certificate — see docs/setup/certificate-auth.md.
-
Configure the CLI:
# Grab the template from the repo or copy from `m365ctl --help` output. curl -O https://raw.githubusercontent.com/aren13/m365ctl/main/config.toml.example mv config.toml.example config.toml # Edit config.toml: tenant id, app id, cert path, allow-lists.
-
Authenticate and verify:
m365ctl od auth login m365ctl od auth whoami m365ctl mail whoami
Full walkthrough: docs/setup/first-run.md (≤ 20 minutes from install to whoami).
Features
OneDrive and SharePoint
| Capability | Commands |
|---|---|
| Catalog mirror | od-catalog-refresh, od-catalog-status |
| Search | od-search (server and local) |
| Inventory | od-inventory (top-by-size, top-by-age, dups) |
| File operations | od-move, od-rename, od-copy |
| Sensitivity | od-label |
| Sharing audit | od-audit-sharing |
| Recycle bin | od-clean, od-delete |
| Download | od-download (bulk + resumable) |
| Capability | Commands |
|---|---|
| Read | mail-list, mail-get, mail-read, mail-search |
| Folders / labels | mail-folders, mail-categories, mail-categorize |
| Compose | mail-draft, mail-send, mail-reply, mail-forward |
| Move / flag | mail-move, mail-copy, mail-flag, mail-focus |
| Inbox rules | mail-rules (create, update, delete, import, export) |
| Mailbox settings | mail-settings, mail-ooo, mail-signature |
| Triage DSL | mail-triage (YAML rules over local catalog) |
| Catalog | mail-catalog-refresh, mail-catalog-status |
| Export | mail-export (EML, MBOX, attachments, full mailbox) |
| Hard delete | mail-clean, mail-empty (triple-gated) |
| Delegation | mail-delegate, --mailbox shared:<addr> routing |
| Send-as | mail-sendas (app-only, fully audited) |
| Scheduled send | mail-send --schedule-at <iso> |
| Convenience verbs | mail-digest, mail-archive, mail-snooze, mail-top-senders, mail-size-report, mail-unsubscribe |
Each verb supports --help. See docs/mail/convenience-commands.md for the convenience-verb reference.
Safety model
| Layer | Behavior |
|---|---|
| Dry-run default | Mutating verbs print a plan and exit unless --confirm is passed. |
| Plan files | Bulk operations write a plan file that can be reviewed before --replay. |
| Scope allow-lists | allow_drives / allow_mailboxes gate every API call; deny_paths / deny_folders are absolute. |
| Audit log | Each mutation records a before/after block to a tamper-resistant log. |
| Undo | m365ctl undo <op-id> (or od-undo / mail-undo) replays the inverse using the audit record. |
| Hard-delete gate | Irreversible deletes require --confirm, a TTY-typed phrase, and an extra escalation for large ops. |
Configuration
config.toml controls auth, scopes, paths, and feature flags. Start from config.toml.example:
[auth]
tenant_id = "..."
client_id = "..."
cert_path = "~/.m365ctl/cert.pem"
[scope]
allow_drives = ["..."]
allow_mailboxes = ["user@example.com"]
deny_paths = ["/Confidential"]
[mail]
schedule_send_enabled = false
Catalog files, plan files, and the audit log default to cache/, plans/, and logs/ under the project root — override per config.toml.
Documentation
| Topic | Link |
|---|---|
| Azure app registration | docs/setup/azure-app-registration.md |
| Certificate-based auth | docs/setup/certificate-auth.md |
| First-run walkthrough | docs/setup/first-run.md |
| PnP PowerShell prerequisites | docs/ops/pnp-powershell-setup.md |
| Mail convenience verbs | docs/mail/convenience-commands.md |
| Roadmap | docs/roadmap.md |
| Changelog | CHANGELOG.md |
| Contributing | CONTRIBUTING.md |
Contributing
Issues and pull requests welcome. Before opening a PR, please read CONTRIBUTING.md and run the test suite:
uv run pytest
uv run ruff check .
uv run mypy src
License
Apache-2.0. See LICENSE.
Disclaimer
This is an independent open-source project. Not affiliated with, endorsed by, or supported by Microsoft. "Microsoft 365", "OneDrive", "SharePoint", and "Exchange" are trademarks of Microsoft Corporation.
Metadata
Release files for m365ctl 1.15.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| m365ctl-1.15.0.tar.gz | 409.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| m365ctl-1.15.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 676.3 kB
Release files / m365ctl-1.15.0.tar.gz
| Download URL | m365ctl-1.15.0.tar.gz |
|---|---|
| Size | 409.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e46f783ed3f17417c4d8aca5dadd41997f900f431321f6b94a336ca95aba8f50
|
|
BLAKE2b-256 checksum How to use checksums |
33eca6ada001f90fadeb2c54dd5e1bf1c3bcc96efdc43a6dbd81622e14974a54
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 1, 2026.
Transparency logRelease files / m365ctl-1.15.0-py3-none-any.whl
| Download URL | m365ctl-1.15.0-py3-none-any.whl |
|---|---|
| Size | 267.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0fa30c85e068c994802b7699688e3c3301938477dd014f4ebc4c0cff592e0d99
|
|
BLAKE2b-256 checksum How to use checksums |
75b19339a02aa24b96034e31fefdffed6cac07d8c3e2740be3201097d8a7e014
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 1, 2026.
Transparency log