Public/activedirectory/Get-ADDomainInfo.ps1

#Requires -Version 5.1

function Get-ADDomainInfo {
    <#
    .SYNOPSIS
        Retrieves a comprehensive summary of an Active Directory domain
 
    .DESCRIPTION
        Returns the identity card of an Active Directory domain including functional levels,
        FSMO role holders, domain controller inventory, site topology, object counts (users,
        computers, groups, OUs), and password policy. Designed as the first command to run
        when discovering an unfamiliar domain. All data is gathered from a single domain
        context using standard AD cmdlets.
 
    .PARAMETER Server
        Specifies the Active Directory Domain Services instance to connect to. When omitted,
        the current domain is used.
 
    .PARAMETER Credential
        Specifies the credentials to use for the Active Directory queries.
 
    .EXAMPLE
        Get-ADDomainInfo
 
        Returns a full summary of the current domain.
 
    .EXAMPLE
        Get-ADDomainInfo -Server 'dc01.contoso.com'
 
        Returns domain information from a specific domain controller.
 
    .EXAMPLE
        Get-ADDomainInfo -Server 'child.contoso.com' -Credential (Get-Credential)
 
        Returns domain information for a child domain using alternate credentials.
 
    .OUTPUTS
        PSWinOps.ADDomainInfo
        Returns a single object with domain identity, functional levels, FSMO holders,
        DC list, site names, object counts, and default password policy.
 
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-04-04
        Requires: PowerShell 5.1+ / Windows only
        Requires: ActiveDirectory module (RSAT)
 
    .LINK
        https://github.com/k9fr4n/PSWinOps
 
    .LINK
        https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-addomain
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Server,

        [Parameter()]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]$Credential
    )

    begin {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting"

        try {
            Import-Module -Name 'ActiveDirectory' -ErrorAction Stop -Verbose:$false
        }
        catch {
            Write-Error -Message "[$($MyInvocation.MyCommand)] ActiveDirectory module is not available: $_"
            return
        }

        $adSplat = @{}
        if ($PSBoundParameters.ContainsKey('Server')) {
            $adSplat['Server'] = $Server
        }
        if ($PSBoundParameters.ContainsKey('Credential')) {
            $adSplat['Credential'] = $Credential
        }
    }

    process {
        try {
            # --- Domain ---
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying domain"
            $domain = Get-ADDomain -ErrorAction Stop @adSplat

            # --- Forest ---
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying forest"
            $forest = Get-ADForest -ErrorAction Stop @adSplat

            # --- Domain Controllers ---
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying domain controllers"
            $domainControllers = Get-ADDomainController -Filter * -ErrorAction Stop @adSplat

            $dcList = $domainControllers | ForEach-Object {
                [PSCustomObject]@{
                    HostName        = $_.HostName
                    Site            = $_.Site
                    IPv4Address     = $_.IPv4Address
                    IsGlobalCatalog = $_.IsGlobalCatalog
                    IsReadOnly      = $_.IsReadOnly
                    OperatingSystem = $_.OperatingSystem
                }
            }

            # --- Sites ---
            $sites = ($domainControllers | Select-Object -ExpandProperty 'Site' -Unique | Sort-Object) -join ', '

            # --- Object counts ---
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] Counting domain objects"
            $domainDN = $domain.DistinguishedName

            $countSplat = @{ SearchBase = $domainDN; ErrorAction = 'Stop' }

            $enabledUsers = @(Get-ADUser -Filter "Enabled -eq `$true" @countSplat @adSplat).Count
            $disabledUsers = @(Get-ADUser -Filter "Enabled -eq `$false" @countSplat @adSplat).Count
            $enabledComputers = @(Get-ADComputer -Filter "Enabled -eq `$true" @countSplat @adSplat).Count
            $disabledComputers = @(Get-ADComputer -Filter "Enabled -eq `$false" @countSplat @adSplat).Count
            $groupCount = @(Get-ADGroup -Filter * @countSplat @adSplat).Count
            $ouCount = @(Get-ADOrganizationalUnit -Filter * @countSplat @adSplat).Count

            # --- Default Password Policy ---
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying default password policy"
            $passwordPolicy = Get-ADDefaultDomainPasswordPolicy -ErrorAction Stop @adSplat

            # --- Fine-Grained Password Policies count ---
            $fgppCount = 0
            try {
                $fgppCount = @(Get-ADFineGrainedPasswordPolicy -Filter * -ErrorAction Stop @adSplat).Count
            }
            catch {
                Write-Verbose -Message "[$($MyInvocation.MyCommand)] Could not query FGPP: $_"
            }

            # --- Recycle Bin ---
            $recycleBinEnabled = $false
            try {
                $optionalFeatures = Get-ADOptionalFeature -Filter "Name -eq 'Recycle Bin Feature'" -ErrorAction Stop @adSplat
                if ($optionalFeatures -and $optionalFeatures.EnabledScopes.Count -gt 0) {
                    $recycleBinEnabled = $true
                }
            }
            catch {
                Write-Verbose -Message "[$($MyInvocation.MyCommand)] Could not query optional features: $_"
            }

            [PSCustomObject]@{
                PSTypeName                  = 'PSWinOps.ADDomainInfo'
                DomainName                  = $domain.DNSRoot
                NetBIOSName                 = $domain.NetBIOSName
                DistinguishedName           = $domain.DistinguishedName
                DomainFunctionalLevel       = $domain.DomainMode.ToString()
                ForestFunctionalLevel       = $forest.ForestMode.ToString()
                ForestName                  = $forest.Name
                # FSMO Roles
                PDCEmulator                 = $domain.PDCEmulator
                RIDMaster                   = $domain.RIDMaster
                InfrastructureMaster        = $domain.InfrastructureMaster
                SchemaMaster                = $forest.SchemaMaster
                DomainNamingMaster          = $forest.DomainNamingMaster
                # Domain Controllers
                DomainControllerCount       = $domainControllers.Count
                DomainControllers           = $dcList
                Sites                       = $sites
                # Object counts
                EnabledUsers                = $enabledUsers
                DisabledUsers               = $disabledUsers
                TotalUsers                  = $enabledUsers + $disabledUsers
                EnabledComputers            = $enabledComputers
                DisabledComputers           = $disabledComputers
                TotalComputers              = $enabledComputers + $disabledComputers
                GroupCount                  = $groupCount
                OUCount                     = $ouCount
                # Password Policy
                MinPasswordLength           = $passwordPolicy.MinPasswordLength
                MaxPasswordAgeDays          = [math]::Round($passwordPolicy.MaxPasswordAge.TotalDays)
                MinPasswordAgeDays          = [math]::Round($passwordPolicy.MinPasswordAge.TotalDays)
                PasswordHistoryCount        = $passwordPolicy.PasswordHistoryCount
                ComplexityEnabled           = $passwordPolicy.ComplexityEnabled
                ReversibleEncryption        = $passwordPolicy.ReversibleEncryptionEnabled
                LockoutThreshold            = $passwordPolicy.LockoutThreshold
                LockoutDurationMinutes      = [math]::Round($passwordPolicy.LockoutDuration.TotalMinutes)
                LockoutObservationMinutes   = [math]::Round($passwordPolicy.LockoutObservationWindow.TotalMinutes)
                FineGrainedPolicyCount      = $fgppCount
                # Features
                RecycleBinEnabled           = $recycleBinEnabled
                Timestamp                   = Get-Date -Format 'o'
            }
        }
        catch {
            Write-Error -Message "[$($MyInvocation.MyCommand)] Failed to retrieve domain information: $_"
        }
    }

    end {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed"
    }
}