Skip to main content

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 lockoutTime to 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.conf configured

Installation

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 array
  • csv: 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)

Source distribution for msad 0.7.0
File Size Uploaded Provenance
msad-0.7.0.tar.gz 93.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for msad 0.7.0
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page