<# .SYNOPSIS Read-only Active Directory structure/health audit using the RSAT ActiveDirectory module. .DESCRIPTION Domain-joined-machine counterpart to ad_audit.py (the LDAP/ldap3 version). Produces the same data points -- OUs, users, computers, groups, object type counts -- using Get-AD* cmdlets instead of raw LDAP. Requires the ActiveDirectory PowerShell module (RSAT) and only performs reads; no changes are made to the directory. .PARAMETER Server Domain controller to query. Defaults to the domain of the current user's logon. .PARAMETER SearchBase Distinguished name to scope the search to. Defaults to the domain root. .PARAMETER StaleDays Days of inactivity (LastLogonDate) before an enabled account is flagged stale. Default 90. .PARAMETER Credential Optional PSCredential to bind with. If omitted, uses the current logon session (typical when run interactively on a domain-joined machine as a normal user). .PARAMETER OutDir Directory to write the Markdown report and raw JSON into. Default .\reports relative to the current working directory. .EXAMPLE .\Invoke-ADAudit.ps1 .EXAMPLE .\Invoke-ADAudit.ps1 -Server dc01.corp.example.com -SearchBase "OU=Corp,DC=corp,DC=example,DC=com" -StaleDays 120 .EXAMPLE .\Invoke-ADAudit.ps1 -Credential (Get-Credential) #> [CmdletBinding()] param( [string]$Server, [string]$SearchBase, [int]$StaleDays = 90, [System.Management.Automation.PSCredential]$Credential, [string]$OutDir = ".\reports" ) $ErrorActionPreference = "Stop" if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) { Write-Error "ActiveDirectory module not found. Install RSAT: Add-WindowsCapability -Online -Name 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0'" exit 1 } Import-Module ActiveDirectory -ErrorAction Stop $adParams = @{} if ($Server) { $adParams["Server"] = $Server } if ($Credential) { $adParams["Credential"] = $Credential } if (-not $SearchBase) { $domain = Get-ADDomain @adParams $SearchBase = $domain.DistinguishedName } Write-Host "Auditing base: $SearchBase" if ($Server) { Write-Host "Domain controller: $Server" } # --- UserAccountControl bit flags --- $UAC_PASSWD_NOTREQD = 0x0020 $UAC_DONT_EXPIRE_PASSWD = 0x10000 $UAC_SMARTCARD_REQUIRED = 0x40000 $UAC_TRUSTED_FOR_DELEGATION = 0x80000 $UAC_DONT_REQ_PREAUTH = 0x400000 $now = Get-Date $staleCutoff = $now.AddDays(-$StaleDays) function ConvertTo-Rfc3339($DateTime) { if (-not $DateTime) { return $null } return $DateTime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") } # AD attributes (descriptions, sAMAccountName, OS strings, DNs, ...) are # directory content, not report-generated text -- they can contain anything # a writer or an attacker put there, including raw HTML. Markdown doesn't # escape inline HTML by default, so an unescaped '