msAD
msad is a library and command line tool for working with an Active Directory / LDAP server from Unix, Linux and macOS systems.
It supports authentication with user/password and Kerberos, and pagination (it can retrieve more than 2000 objects, a limit of AD).
Features:
- search objects (users, groups, computers, ...) with a raw LDAP filter
- search users by field (name, surname, mail, sAMAccountName, department)
- search groups by name / cn / sAMAccountName / displayName
- search computers by name / dNSHostName / operatingSystem
- search organizational units (OUs) and list their contents
- find inactive users / stale computers (last logon older than N days)
- audit reads: domain info, default password policy, privileged groups
- find users whose password violates the domain policy
- test AD connectivity (health check)
- get a single user or group
- fetch any entry directly by its DN (resolve
manager/managedBy) - list group members (direct or recursive/nested)
- list a user's groups (direct or recursive/nested)
- check whether a user is a (nested) member of a group
- add/remove members to/from AD groups using DN or sAMAccountName
- change AD passwords
- unlock a locked-out user account (reset
lockoutTimeto 0) - check if a user is disabled, locked, or has an expired/never-expiring password
- LDAP filter escaping to prevent injection
By default, user lookups return a useful attribute set — identity fields plus
manager (the DN of the user's manager), memberOf (direct group DNs),
userAccountControl, accountExpires, lastLogonTimestamp and whenCreated.
Group lookups include managedBy (owner DN), member, groupType and
whenCreated. Pass --attributes to override the defaults.
Prerequisites
Python >= 3.11
For Kerberos authentication:
- krb5 library and tools (like
kinit, ...) - a keytab file or
krb5.confconfigured
Installation
With uv (recommended)
Install as an isolated tool on your PATH:
uv tool install msad
Run it directly without installing (ephemeral, great for one-off use):
uvx msad --help
# uvx is shorthand for: uv tool run msad
Upgrade or uninstall the installed tool:
uv tool upgrade msad
uv tool uninstall msad
With pipx
pipx install msad
Configuration
msad reads a TOML file, $HOME/.msad.toml by default (override with
--config-file <path>). The init command creates that file from a
ready-to-edit template (it will not overwrite an existing file unless you pass
--force):
msad init
# creates ~/.msad.toml (warns if it already exists)
# then edit ~/.msad.toml
The template looks like this (abridged):
[defaults]
# Domain used when --domain is not passed on the command line.
domain = "mydomain"
[domains.mydomain]
host = "dc.example.com"
base = "dc=example,dc=com"
port = 636 # 389 for plain LDAP
use_ssl = true # false for plain LDAP
# Omit user/password to authenticate with Kerberos (SASL); get a ticket
# first with `kinit`. Set BOTH to authenticate with user/password:
# user = "svc_account"
# password = "..."
You can declare multiple domains under [domains.<name>] and select one at
runtime with --domain <name>.
Multiple domains
Declare as many domains as you need; each can use its own authentication
(Kerberos or user/password). [defaults].domain is used when --domain is
not given:
[defaults]
domain = "corp"
# Kerberos (SASL): omit user/password, run `kinit` first.
[domains.corp]
host = "dc.corp.example.com"
base = "dc=corp,dc=example,dc=com"
port = 636
use_ssl = true
# A second domain using user/password authentication.
[domains.lab]
host = "dc.lab.example.com"
base = "dc=lab,dc=example,dc=com"
port = 636
use_ssl = true
user = "svc_reader"
password = "s3cr3t"
Then target a domain per command (defaults to [defaults].domain when omitted):
msad user-get matteo # uses the default domain (corp)
msad user-get jdoe --domain lab # runs against the lab domain
msad test-connection --domain lab # check connectivity to a specific domain
Usage
msad --help
python -m msad --help
For Kerberos authentication, first obtain a ticket:
kinit # or: kinit myaduser
Verify connectivity and authentication before running other commands:
# Prints an ok/target/auth/error status; exits non-zero on failure
msad test-connection
Searching
# Raw LDAP filter (all attributes as JSON)
msad search "(samaccountname=matteo)" --out-format json
# Restrict returned attributes
msad search "(cn=redaelli*)" --attributes mail --attributes sAMAccountName --out-format json
# Find users by field (criteria are ANDed; values may contain *)
msad user-search --surname "Rossi" --department "IT"
# Get a single user (exact match on sAMAccountName / UPN / mail / cn)
msad user-get matteo
# Find groups (supports * wildcards)
msad group-search "qlik_*"
# Get a single group
msad group-get qlik_analyzer_users
# Find computers (name / dNSHostName / operatingSystem, values may contain *)
msad computer-search --name "PC0*" --os "Windows Server*"
# Get a single computer (sAMAccountName with or without trailing $, cn, or FQDN)
msad computer-get PC001
# Restrict any search to a specific OU subtree with --base
msad user-search --surname "Rossi" --base "OU=Sales,DC=group,DC=example,DC=com"
# Find organizational units (name matches the ou attribute; may contain *)
msad ou-search --name "Sales*"
# Get a single OU by its ou name or full DN
msad ou-get "OU=Sales,DC=group,DC=example,DC=com"
# List everything under an OU (optionally filter by objectClass)
msad ou-contents "OU=Sales,DC=group,DC=example,DC=com"
msad ou-contents "OU=Sales,DC=group,DC=example,DC=com" --object-class user
# Find users who haven't logged in for 90 days (default)
msad inactive-users
# Inactive users in a specific OU, last 30 days
msad inactive-users --days 30 --base "OU=Staff,DC=group,DC=example,DC=com"
# Find computers that haven't logged on in 180 days
msad stale-computers --days 180
# Include accounts that have never logged on (no lastLogonTimestamp)
msad inactive-users --days 90 --include-never
msad stale-computers --days 180 --include-never
# Fetch any entry by its DN (e.g. resolve a user's manager)
msad get-by-dn "CN=Anna Bianchi,OU=Staff,DC=group,DC=example,DC=com"
Groups and membership
# Direct members of a group
msad group-members qlik_analyzer_users
# All (nested) members
msad group-members qlik_analyzer_users --nested
# A user's groups (direct, or nested)
msad user-groups matteo
msad user-groups matteo --nested
# Is a user a (nested) member of a group? Prints true/false
msad is-member qlik_analyzer_users matteo
# Add / remove a member (by DN or sAMAccountName)
msad group-add-member qlik_analyzer_users matteo
msad group-remove-member qlik_analyzer_users matteo
Auditing (read-only)
# Domain object: security-relevant settings and metadata
msad domain-info
# Default domain password policy (maxPwdAge, minPwdLength, lockout, ...)
msad password-policy
# Users whose password violates the policy (expired, or must change at logon)
msad password-policy-violations
# Well-known privileged groups with member counts
msad privileged-groups
# Include the actual members (optionally expand nested membership)
msad privileged-groups --with-members
msad privileged-groups --with-members --nested
Account checks
# Individual checks (print true / false / not found)
msad is-disabled matteo
msad is-locked matteo
msad has-expired-password matteo --max-age 90
msad has-never-expires-password matteo
# Run all checks at once, optionally checking group memberships
msad check-user matteo --group qlik_analyzer_users --group domain_admins
Passwords
# Change a user's password (prompts interactively)
msad change-password matteo
Output formats
Most commands accept --out-format:
jsonl(default): one JSON object per line (JSON Lines / NDJSON)json: a single JSON arraycsv: tab-separated, list values joined with|
Development
This project uses uv. A Makefile wraps the common
tasks:
make sync # install/refresh the dev environment
make test # run the test suite with coverage
make lint # ruff check + format check + pyrefly (strict)
make format # apply ruff autofixes and formatting
make build # build wheel and sdist into dist/
make publish # upload to the package index (needs credentials)
make clean # remove build artifacts and caches
Equivalent raw commands:
uv sync --group dev
uv run pytest
uv run ruff check src/ tests/
uv run pyrefly check
uv build
The library ships type hints (py.typed) and its public API is declared in
msad.__all__, so it can be imported and type-checked by downstream projects.
License
Copyright © 2021 - 2025 Matteo Redaelli
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Metadata
Release files for msad 0.7.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 | Provenance | |
|---|---|---|---|---|
| msad-0.7.0.tar.gz | 93.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| msad-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 134.6 kB
Release files / msad-0.7.0.tar.gz
| Download URL | msad-0.7.0.tar.gz |
|---|---|
| Size | 93.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f33a2c5e1e4de63b886caa2b6edc5a35371e920b0d3c149150d76689bae813a7
|
|
BLAKE2b-256 checksum How to use checksums |
82d6d14dcff075bd5f51675e070e680cb520c17e8a8dcdbf8d25c7a137217dc0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / msad-0.7.0-py3-none-any.whl
| Download URL | msad-0.7.0-py3-none-any.whl |
|---|---|
| Size | 40.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
747857423438185c4f84d7683dc2167deef9ec4d985c8eb4a14b1bcafd8ec460
|
|
BLAKE2b-256 checksum How to use checksums |
c50de848a3cc0e14e631a67f1a10793077bcf629c2634e6d6030dcd6a88814d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for msad 0.7.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.