README.md

May 15, 2026 · View on GitHub

Powerview.py


version 2026.2.2 @aniqfakhrul on X @h0j3n on X


Installation | Basic Usage | Obfuscation | Modules | Logging | User Defined Rules | MCP | Plugins | Acknowledgements

Overview

PowerView.py is an alternative for the awesome original PowerView.ps1 script. Most of the modules used in PowerView are available here ( some of the flags are changed ). Main goal is to achieve interactive session without having to repeatedly authenticate to ldap.

Installation

https://github.com/aniqfakhrul/powerview.py/wiki/Installation-Guide

Basic Usage

Note

Note that some of the kerberos functions are still not functioning well just yet but it'll still do most of the works. Detailed usage can be found in Wiki section

  • Init connection
powerview range.net/lowpriv:Password123@192.168.86.192 [-k] [--use-ldap | --use-ldaps | --use-gc | --use-gc-ldaps | --use-adws]
  • Maintain persistent connection

Tip

Connection persistence is disabled by default. LDAP sessions timeout after inactivity. Use --keepalive-interval to send periodic queries maintaining session state.

  • Start web interface
powerview range.net/lowpriv:Password123@192.168.86.192 --web [--web-host 0.0.0.0] [--web-port 3000] [--web-auth user:password1234]

IMG_4602

  • Init connection with specific authentication. Note that --use-sign-and-seal and --use-channel-binding is only available if you install ldap3 library directly from this branch
powerview range.net/lowpriv:Password123@192.168.86.192 [--use-channel-binding | --use-sign-and-seal | --use-simple-auth]
  • Init with schannel. --pfx flag accept pfx formatted certificate file.

Note


powerview will try to load certificate without password on the first attempt. If it fails, it'll prompt for password. So, no password parameter needed

powerview 10.10.10.10 --pfx administrator.pfx

intro

  • Query for specific user
Get-DomainUser Administrator
Get-DomainUser -Identity Administrator
  • Specify search attributes
Get-DomainUser -Properties samaccountname,description
  • Filter results
Get-DomainUser -Where 'samaccountname [contains][in][eq] admins'
  • Count results
Get-DomainUser -Count
  • Output result to file
Get-DomainUser -OutFile ~/domain_user.txt
  • Format output in a table.
Get-DomainUser -Properties samaccountname,memberof -TableView
Get-DomainUser -Properties samaccountname,memberof -TableView [csv,md,html,latex]
  • Set module
Set-DomainObject -Identity "adminuser" -Set 'servicePrincipalname=http/web.ws.local'
Set-DomainObject -Identity "adminuser" -Append 'servicePrincipalname=http/web.ws.local'
Set-DomainObject -Identity "adminuser" -Clear 'servicePrincipalname'

# Reading from local file
Set-DomainObject -Identity "adminuser" -Set 'servicePrincipalname=@/path/to/local/file'
Set-DomainObject -Identity "adminuser" -Append 'servicePrincipalname=@/path/to/local/file'
  • Relay mode
powerview 10.10.10.10 --relay [--relay-host] [--relay-port] [--use-ldap | --use-ldaps]

relay

Note


This demonstration shows coerced authentication was made using printerbug.py. You may use other methods that coerce HTTP authentication.

Obfuscation

PowerView uses ldapx-py to obfuscate LDAP queries and related parameters, varying observable patterns while preserving query intent.

  • Enable via CLI (default chain)
powerview range.net/lowpriv:Password123@192.168.86.192 --obfuscate
  • Enable via CLI (custom chain codes)
powerview range.net/lowpriv:Password123@192.168.86.192 --obfuscate CZXANDR
  • Enable via Web

Tip

Toggle Obfuscate in Settings. This applies to subsequent queries for the active session.

What gets obfuscated

  • Filter: transforms attribute names, operators and values before sending
  • DN: mutates the search base distinguishedName
  • Attributes: mutates the requested attribute list

Default chains (when using --obfuscate with no value)

TargetChainTechniques
FilterCZNDRCase, Zeros, aNR, De Morgan, Reorder
BaseDNCXCase, heX encoding
AttributesCRCase, Reorder

Run ldapx codes --all to see all available obfuscation codes.

Note

Results are functionally equivalent in most cases, but approximation/wildcard expansions can broaden matches. Some codes (G, O, S) may not be compatible with ldap3 - use with caution.

Warning

Excessive obfuscation may be rejected by strict servers or intermediary tooling. Use only when needed.

Module available (so far?)

PV >
Add-ADComputer                 Get-ADObject                   Get-EventLogChannel            Login-As                       Set-ADObject
Add-ADUser                     Get-CA                         Get-EventLogPublisher          Logoff-Session                 Set-ADObjectDN
Add-CATemplate                 Get-CATemplate                 Get-ExchangeDatabase           Reboot-Computer                Set-CATemplate
Add-CATemplateAcl              Get-DMSA                       Get-ExchangeMailbox            Remove-ADComputer              Set-DomainCATemplate
Add-DMSA                       Get-Domain                     Get-ExchangeServer             Remove-ADObject                Set-DomainComputerPassword
Add-DomainCATemplate           Get-DomainCA                   Get-GMSA                       Remove-ADUser                  Set-DomainDNSRecord
Add-DomainCATemplateAcl        Get-DomainCATemplate           Get-GPOLocalGroup              Remove-CATemplate              Set-DomainObject
Add-DomainComputer             Get-DomainComputer             Get-GPOSettings                Remove-DMSA                    Set-DomainObjectDN
Add-DomainDMSA                 Get-DomainController           Get-LocalUser                  Remove-DomainCATemplate        Set-DomainObjectOwner
Add-DomainDNSRecord            Get-DomainDMSA                 Get-NamedPipes                 Remove-DomainComputer          Set-DomainRBCD
Add-DomainGMSA                 Get-DomainDNSRecord            Get-NetComputerInfo            Remove-DomainDMSA              Set-DomainUserPassword
Add-DomainGPO                  Get-DomainDNSZone              Get-NetLoggedOn                Remove-DomainDNSRecord         Set-NetService
Add-DomainGroup                Get-DomainForeignGroupMember   Get-NetProcess                 Remove-DomainGMSA              Set-ObjectOwner
Add-DomainGroupMember          Get-DomainForeignUser          Get-NetService                 Remove-DomainGroupMember       Set-RBCD
Add-DomainObjectAcl            Get-DomainGMSA                 Get-NetSession                 Remove-DomainObject            Set-ShadowCred
Add-DomainOU                   Get-DomainGPO                  Get-NetShare                   Remove-DomainObjectAcl         Set-ShadowCredential
Add-DomainUser                 Get-DomainGPOLocalGroup        Get-NetTerminalSession         Remove-DomainOU                Shutdown-Computer
Add-GMSA                       Get-DomainGPOSettings          Get-ObjectAcl                  Remove-DomainUser              Start-NetService
Add-GPLink                     Get-DomainGroup                Get-ObjectOwner                Remove-GMSA                    Stop-Computer
Add-GPO                        Get-DomainGroupMember          Get-RBCD                       Remove-GPLink                  Stop-NetProcess
Add-GroupMember                Get-DomainObject               Get-RegLoggedOn                Remove-GroupMember             Stop-NetService
Add-NetService                 Get-DomainObjectAcl            Get-SCCM                       Remove-NetService              Unlock-ADAccount
Add-ObjectAcl                  Get-DomainObjectOwner          Get-ShadowCred                 Remove-NetSession              clear
Add-OU                         Get-DomainOU                   Get-ShadowCredential           Remove-NetTerminalSession      exit
Clear-Cache                    Get-DomainRBCD                 Get-TrustKey                   Remove-ObjectAcl               get_pool_stats
ConvertFrom-SID                Get-DomainSCCM                 Get-WDS                        Remove-OU                      history
ConvertFrom-UACValue           Get-DomainTrust                Invoke-ASREPRoast              Remove-ShadowCred              taskkill
Disable-DomainDNSRecord        Get-DomainTrustKey             Invoke-DFSCoerce               Remove-ShadowCredential        tasklist
                               Get-DomainTrustMapping
Find-ForeignGroup              Get-DomainUser                 Invoke-Kerberoast              Restart-Computer
Find-ForeignUser               Get-DomainWDS                  Invoke-MessageBox              Restore-ADObject
Find-LocalAdminAccess          Get-EventLog                   Invoke-PrinterBug              Restore-DomainObject

Domain/LDAP Functions

ModuleAliasDescription
Get-DomainUserQuery for all users or specific user objects in AD
Get-DomainComputerQuery for all computers or specific computer objects in AD
Get-DomainGroupQuery for all groups or specific group objects in AD
Get-DomainGroupMemberQuery the members for specific domain group
Get-DomainOUQuery for all OUs or specific OU objects in AD
Get-DomainQuery for domain information
Get-DomainControllerQuery for available domain controllers
Get-DomainDNSRecordQuery for available records. It will recurse all DNS zones if doesn't specify -ZoneName
Get-DomainDNSZoneQuery for available DNS zones in the domain
Get-DomainObjectGet-ADObjectQuery for all or specified domain objects in AD
Get-DomainObjectAclGet-ObjectAclQuery ACLs for specified AD object
Get-DomainSCCMGet-SCCMQuery for SCCM
Get-DomainRBCDGet-RBCDFinds accounts that are configured for resource-based constrained delegation
Get-DomainObjectOwnerGet-ObjectOwnerQuery owner of the AD object
Get-DomainGMSAGet-GMSAQuery for Group Managed Service Accounts (gMSA) and retrieve their password blobs
Get-DomainDMSAGet-GDSAQuery for Delegated Managed Service Accounts (dMSA)
Remove-DomainGMSARemove-GMSADelete an existing Group Managed Service Account (GMSA) from the domain
Remove-DomainDMSARemove-DMSADelete an existing Delegated Managed Service Account (dMSA) from the domain
Remove-DomainDNSRecordRemove Domain DNS Record
Remove-DomainComputerRemove-ADComputerRemove Domain Computer
Remove-DomainGroupMemberRemove-GroupMemberRemove member of a specific Domain Group
Remove-DomainOURemove-OURemove OUs or specific OU objects in AD
Remove-DomainObjectAclRemove-ObjectAclRemove ACLs for specified AD object
Remove-DomainObjectRemove-ADObjectRemove specified Domain Object
Remove-DomainUserRemove-ADUserRemove specified Domain User in AD
Set-DomainDNSRecordSet Domain DNS Record
Set-DomainUserPasswordSet password for specified Domain User
Set-DomainComputerPasswordSet password for specified Domain Computer
Set-DomainObjectSet-ADObjectSet for specified domain objects in AD
Set-DomainObjectDNSet-ADObjectDNModify object's distinguishedName attribute as well as changing OU
Set-DomainObjectOwnerSet-ObjectOwnerSet owner of the AD object
Set-ShadowCredentialSet-ShadowCredManage msDS-KeyCredentialLink (Shadow Credentials) for a domain object
Get-ShadowCredentialGet-ShadowCredList shadow credentials (msDS-KeyCredentialLink) for a domain object or all objects
Remove-ShadowCredentialRemove-ShadowCredRemove shadow credentials by DeviceId or clear all from a domain object
Add-DomainDNSRecordAdd Domain DNS Record
Disable-DomainDNSRecordDisabling DNS Record by pointing to invalid address
Add-DomainGMSAAdd-GMSACreate a new Group Managed Service Account (gMSA) in the domain
Add-DomainDMSAAdd-GMSACreate a new Delegated Managed Service Account (dMSA) in the domain
Add-DomainUserAdd-ADUserAdd new Domain User in AD
Add-DomainComputerAdd-ADComputerAdd new Domain Computer in AD
Add-DomainGroupMemberAdd-GroupMemberAdd new member in specified Domain Group in AD
Add-DomainOUAdd-OUAdd new OU object in AD
Add-DomainGPOAdd-GPOAdd new GPO object in AD
Add-DomainObjectAclAdd-ObjectAclSupported rights so far are All, DCsync, RBCD, ShadowCred, WriteMembers
Clear-CacheClear cache

GPO Functions

ModuleAliasDescription
Get-DomainGPOQuery for domain group policy objects
Get-DomainGPOLocalGroupGet-GPOLocalGroupQuery all GPOs in a domain that modify local group memberships through Restricted Groups or Group Policy preferences
Add-GPLinkCreate new GPO link to an OU
Remove-GPLinkRemove GPO link from an OU

Computer Enumeration Functions

ModuleAliasDescription
Get-NetSession[MS-SRVS] Query session information for the local or a remote computer
Get-NetShareQuery open shares on the local or a remote computer
Get-NetLoggedOn[MS-WKST] Query logged on users on the local or a remote computer
Get-EventLog[MS-EVEN6] Query Windows Event Logs from a remote computer via RPC
Get-EventLogChannel[MS-EVEN6] List event log channels on a remote computer with security product detection
Get-EventLogPublisher[MS-EVEN6] List event log publishers on a remote computer with security product detection
Get-NetService[MS-SCMR] Query running services on the local or a remote computer
Stop-NetService[MS-SCMR] Stop a specific service on the local or a remote computer
Get-NetProcesstasklist[MS-TSTS] Query running processes on the local or a remote computer
Stop-NetProcesstaskkill[MS-TSTS] Terminate a specific process on the local or a remote computer
Get-NetTerminalSession[MS-TSTS] Query active terminal sessions on the local or a remote computer
Remove-NetTerminalSession[MS-TSTS] Terminate a specific terminal session on the local or a remote computer
Stop-ComputerShutdown-Computer[MS-TSTS] Shutdown a remote computer
Restart-ComputerReboot-Computer[MS-TSTS] Restart a remote computer

ADCS Functions

ModuleAliasDescription
Get-DomainCATemplateGet-CATemplateQuery for available CA templates. Supports filtering for vulnerable template
Get-DomainCAGet-CAQuery for Certificate Authority(CA)
Remove-DomainCATemplateRemove-CATemplateRemove specified Domain CA Template
Set-DomainCATemplateSet-CATemplateModify domain object's attributes of a CA Template
Add-DomainCATemplateAdd-CATemplateAdd new Domain CA Template
Add-DomainCATemplateAclAdd-CATemplateAclAdd ACL to a certificate template. Supported rights so far are All, Enroll, Write

Exchange Functions

ModuleAliasDescription
Get-ExchangeServerGet-ExchangeRetrieve list of available exchange servers in the domain

Domain Trust Functions

ModuleAliasDescription
Get-DomainTrustQuery all Domain Trusts
Get-DomainTrustMappingRecursively map all trusts reachable from the current domain. Hops into each trust partner and flattens results with SourceName / TargetName columns (mirrors PowerView.ps1's Get-DomainTrustMapping). Use -NoRecurse to limit to the starting domain.
Get-DomainForeignUserFind-ForeignUserQuery users who are in group outside of the user's domain
Get-DomainForeignGroupMemberFind-ForeignGroupQuery groups with users outside of group's domain and look for foreign member

Misc Functions

ModuleAliasDescription
ConvertFrom-SIDConvert a given security identifier (SID) to user/group name
ConvertFrom-UACValueConverts a UAC int value to human readable form
Get-NamedPipesList out Named Pipes for a specific computer
Invoke-DFSCoerceCoerces machine account authentication via MS-DFSNM NetrDfsRemoveStdRoot()
Invoke-KerberoastRequests kerberos ticket for a specified service principal name (SPN)
Invoke-PrinterBugTriggers the MS-RPRN RpcRemoteFindFirstPrinterChangeNotificationEx function to force a server to authenticate to a specified machine
Unlock-ADAccountUnlock domain accounts by modifying lockoutTime attribute
Find-LocalAdminAccessFinds computer on the local domain where the current has a Local Administrator access

Logging

We will never miss logging to keep track of the actions done. By default, powerview creates a .powerview folder in current user home directory (~). Each log file is generated based on current date. Example path: /root/.powerview/logs/bionic.local/2024-02-13.log

Vulnerability Detection

PowerView.py includes an integrated vulnerability detection system that automatically identifies common Active Directory security issues. When querying objects, vulnerabilities will be displayed in the output:

vulnerabilities: [VULN-026] Domain with high machine account quota (allows users to add computer accounts) (MEDIUM)
                 [VULN-029] Domain with weak minimum password length policy (less than 8 characters) (HIGH)

User Defined Rules

You can define custom vulnerability detection rules by modifying the vulns.json file located in the PowerView storage directory (~/.powerview/vulns.json).

Each vulnerability rule has the following structure:

"rule_name": {
    "description": "Human-readable description of the vulnerability",
    "rules": [
        {
            "attribute": "attributeName",
            "condition": "condition_type",
            "value": "value_to_check"
        },
        {
            "attribute": "anotherAttribute",
            "condition": "another_condition",
            "value": "another_value"
        }
    ],
    "exclusions": [
        {
            "attribute": "attributeName",
            "condition": "condition_type",
            "value": "value_to_exclude"
        }
    ],
    "severity": "low|medium|high|critical",
    "id": "VULN-XXX",
    "rule_operator": "AND|OR",
    "exclusion_operator": "AND|OR",
    "details": "Optional detailed explanation of the vulnerability and remediation steps"
}

Rule Components:

  • rules: List of conditions that must be met for the vulnerability to be detected
  • exclusions: List of conditions that, if met, will exclude an object from detection even if it matches the rules
  • rule_operator: How to combine multiple rules (default: "OR")
    • "AND": All rules must match
    • "OR": Any rule can match
  • exclusion_operator: How to combine multiple exclusions (default: "OR")
    • "OR": Any exclusion can match to exclude the object
    • "AND": All exclusions must match to exclude the object
  • negate: Optional boolean (True/False) that can be added to any rule to invert its result

Supported Conditions:

ConditionDescription
existsAttribute exists
not_existsAttribute does not exist
equalsExact match (case-insensitive)
not_equalsNot an exact match
containsSubstring match (case-insensitive)
not_containsNo substring match
startswithStarts with string (case-insensitive)
endswithEnds with string (case-insensitive)
older_thanDate is older than specified number of days
newer_thanDate is newer than specified number of days
greater_thanNumeric value is greater than specified
less_thanNumeric value is less than specified
greater_than_or_equalNumeric value is greater than or equal to specified
less_than_or_equalNumeric value is less than or equal to specified
has_flagBit flag is set in a numeric value
missing_flagBit flag is not set in a numeric value
any_flag_setAny of the specified flags are set
all_flags_setAll of the specified flags are set

Multiple Values:

You can specify multiple values for a condition using:

  1. Pipe-separated string: "value": "value1|value2|value3"
  2. List format: "value": ["value1", "value2", "value3"]

Example Rule:

"weak_password_policy": {
    "description": "Domain with weak minimum password length policy (less than 8 characters)",
    "rules": [
        {
            "attribute": "objectClass",
            "condition": "contains",
            "value": "domainDNS"
        },
        {
            "attribute": "minPwdLength",
            "condition": "less_than",
            "value": 8
        }
    ],
    "exclusions": [],
    "severity": "high",
    "id": "VULN-029",
    "rule_operator": "AND"
}

MCP

Note

This is not bundled in the base project installation. You may run pip3 install .[mcp] or pip3 install powerview[mcp] to include MCP functionalities.

This enables the Model Context Protocol server, allowing AI agents to interact with PowerView functionality through a standardized interface via Streamable HTTP transport. See the MCP documentation for more details.

  • Start MCP server
powerview domain.local/lowpriv:Password1234@10.10.10.10 --mcp [--mcp-host 0.0.0.0] [--mcp-port 8888] [--mcp-path powerview]

The MCP server exposes most of PowerView's functionality through a standardized tool interface. This includes the ability to:

  • Query and enumerate Active Directory objects (users, computers, groups, OUs)
  • Retrieve information about domain trusts, GPOs, and group memberships
  • Search for security vulnerabilities and misconfigurations
  • ...

Claude Desktop

Claude Desktop does not support yet support HTTP based transport Github. You may want to use mcp-proxy.

  • Install mcp-proxy
# Option 1: With uv (recommended)
uv tool install mcp-proxy

# Option 2: With pipx (alternative)
pipx install mcp-proxy
  • Modify %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "Powerview": {
        "command": "mcp-proxy",
        "args": ["http://10.10.10.10:5000/powerview"]
    }
  }
}

Cursor

You can modify this in cursor settings under MCP options button.

Tip

Enable YOLO mode to enable autonomous mode so you don't have to click on "Run Tool" button each time. Read more here

{
  "mcpServers": {
    "Powerview": {
      "url": "http://127.0.0.1:5000/powerview"
    }
  }
}

Warning

When using MCP with public AI models (like Claude, GPT, etc.), your Active Directory data may be transmitted to and logged by these services according to their data handling policies. Be mindful of sensitive information exposure when using these tools. We are not responsible for any data leakage or security implications resulting from connecting PowerView to third-party AI services. Self-hosted FTW!

Plugins

PowerView supports a decorator-based plugin system that lets you add new commands and hook before/after existing commands.

How It Works

                         ┌────────────────────────────────┐
                         │         Plugin Loader          │
                         │  Scans directories on startup  │
                         └──────────────┬─────────────────┘

                        ┌───────────────┴───────────────┐
                        ▼                               ▼
            ┌───────────────────┐           ┌───────────────────┐
            │ builtin/ plugins  │           │  ~/.powerview/    │
            │                   │           │  plugins/         │
            │  dehashed.py      │           │                   │
            │  highlight.py     │           │  my_plugin.py     │
            │  recon.py         │           │  ...              │
            └───────────────────┘           └───────────────────┘
                        │                               │
                        └───────────────┬───────────────┘

                            ┌──────────────────────┐
                            │   Sanity Validation  │
                            │  Check signatures &  │
                            │  required decorators │
                            └──────────┬───────────┘

                            ┌──────────────────────┐
                            │   Plugin Registry    │
                            │                      │
                            │  commands: {}        │
                            │  before_hooks: {}    │
                            │  after_hooks: {}     │
                            └──────────┬───────────┘


                ┌─────────────────────────────────────────────────┐
                │              Command Execution Flow             │
                │                                                 │
                │  User Input: "Get-DomainUser -Identity admin"   │
                │                       │                         │
                │                       ▼                         │
                │          ┌────────────────────────┐             │
                │          │   @before hooks        │             │
                │          │   (modify args)        │             │
                │          └───────────┬────────────┘             │
                │                      │                          │
                │                      ▼                          │
                │          ┌────────────────────────┐             │
                │          │   Core command OR      │             │
                │          │   @command plugin      │             │
                │          └───────────┬────────────┘             │
                │                      │                          │
                │                      ▼                          │
                │          ┌────────────────────────┐             │
                │          │   @after hooks         │             │
                │          │   (modify results)     │             │
                │          └───────────┬────────────┘             │
                │                      │                          │
                │                      ▼                          │
                │               Format & Display                  │
                └─────────────────────────────────────────────────┘

Plugins are single .py files discovered automatically from two paths:

PathPurpose
powerview/plugins/builtin/Ships with the package
~/.powerview/plugins/User-managed plugins

Built-in Plugins

PluginCommandsHooksDescription
ReconInvoke-DomainReconSingle-command domain reconnaissance summary
Highlightafter:Get-DomainUserHighlights passwords found in user descriptions

Plugin Management

Get-Plugin                          # List all plugins with status
Enable-Plugin -Name <plugin_name>   # Enable a disabled plugin
Disable-Plugin -Name <plugin_name>  # Disable a plugin at runtime

Writing a Plugin

Create a .py file in ~/.powerview/plugins/. Plugins use three decorators: @command to register new commands, @before to run logic before an existing command, and @after to run logic after.

# ~/.powerview/plugins/custom_enum.py
from powerview.plugins import command, before, after, PowerviewPlugin

plugin = PowerviewPlugin(
    name="CustomEnum",
    description="Custom enumeration helpers",
    author="yourname",
    version="1.0",
)

@command("Get-DomainAdminUser", args=["-Identity", "-Properties", "-SearchBase"],
         description="Find users with adminCount=1")
def get_domainadminuser(pv, args=None, identity=None, properties=None, searchbase=None):
    results = pv.get_domainuser(
        identity=identity or "*",
        properties=properties or ["sAMAccountName", "adminCount", "memberOf", "description"],
        searchbase=searchbase,
    )
    if results:
        return [r for r in results
                if str(r.get("attributes", r).get("adminCount", "")) == "1"]
    return results

@before("Get-DomainUser", priority=10)
def inject_enabled_filter(pv, args):
    """Automatically filter out disabled accounts."""
    if not hasattr(args, 'ldapfilter') or not args.ldapfilter:
        args.ldapfilter = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))"
    return args

@after("Get-DomainUser", priority=20)
def add_password_age(pv, args, results):
    """Enrich results with password age."""
    from datetime import datetime, timezone
    if not results:
        return results
    for entry in results:
        attrs = entry.get("attributes", entry)
        pwdlast = attrs.get("pwdLastSet")
        if isinstance(pwdlast, datetime):
            if pwdlast.tzinfo is None:
                pwdlast = pwdlast.replace(tzinfo=timezone.utc)
            age = (datetime.now(timezone.utc) - pwdlast).days
            attrs["PasswordAge"] = f"{age} days"
    return results

Decorators:

DecoratorSignatureDescription
@command(name, args, description)func(pv, args=None, **kwargs)Register a new verb-noun command
@before(command_name, priority)func(pv, args)Run before a command. Return modified args or None
@after(command_name, priority)func(pv, args, results)Run after a command. Return modified results or None

Note

@before and @after accept a single command name or a list (e.g., @after(["Get-DomainUser", "Get-DomainComputer"])). Lower priority values run first.

Advanced argument definitions:

Plugin args can be simple strings ("-Identity") or dicts for finer control:

@command("Get-CustomThing", args=[
    "-Identity",
    {"name": "-Days", "type": int, "default": 90, "help": "Threshold in days"},
    {"name": "-Verbose", "action": "store_true", "help": "Show details"},
])

Tip

All plugin commands automatically get -OutFile, -TableView, and -SortBy flags.

Credits