ActiveDirectory/Get-ADDomainReport.ps1
|
<#
.SYNOPSIS Reports Active Directory domain and forest topology information. .DESCRIPTION Collects domain, forest, site, and trust relationship details from Active Directory. Provides FSMO role holder locations, functional levels, site/subnet topology, and trust configurations. Designed for IT consultants performing AD assessments on SMB environments (10-500 users). All operations are read-only. Requires the ActiveDirectory module (available via RSAT or on domain controllers). .PARAMETER OutputPath Optional path to export results as CSV. If not specified, results are returned to the pipeline. .EXAMPLE PS> .\ActiveDirectory\Get-ADDomainReport.ps1 Returns domain, forest, site, and trust information as PSCustomObjects. .EXAMPLE PS> .\ActiveDirectory\Get-ADDomainReport.ps1 -OutputPath '.\ad-domain-report.csv' Exports the domain topology report to CSV. .EXAMPLE PS> . .\Common\Connect-Service.ps1 PS> Connect-Service -Service Graph -Scopes 'Organization.Read.All' PS> .\ActiveDirectory\Get-ADDomainReport.ps1 Can be combined with Graph-connected scripts for a hybrid assessment. #> [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string]$OutputPath ) $ErrorActionPreference = 'Stop' # ------------------------------------------------------------------ # Verify ActiveDirectory module is available # ------------------------------------------------------------------ if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) { Write-Error "The ActiveDirectory module is not installed. Install RSAT or run from a domain controller." return } Import-Module -Name ActiveDirectory -ErrorAction Stop $report = [System.Collections.Generic.List[PSCustomObject]]::new() # ------------------------------------------------------------------ # Domain information # ------------------------------------------------------------------ try { Write-Verbose "Querying domain information..." $domain = Get-ADDomain $report.Add([PSCustomObject]@{ RecordType = 'Domain' Name = $domain.DNSRoot DistinguishedName = $domain.DistinguishedName NetBIOSName = $domain.NetBIOSName FunctionalLevel = $domain.DomainMode PDCEmulator = $domain.PDCEmulator RIDMaster = $domain.RIDMaster InfrastructureMaster = $domain.InfrastructureMaster Detail = '' }) Write-Verbose "Domain: $($domain.DNSRoot) ($($domain.DomainMode))" } catch { Write-Error "Failed to query AD domain: $_" return } # ------------------------------------------------------------------ # Forest information # ------------------------------------------------------------------ try { Write-Verbose "Querying forest information..." $forest = Get-ADForest $globalCatalogs = if ($forest.GlobalCatalogs) { ($forest.GlobalCatalogs -join '; ') } else { '' } $sites = if ($forest.Sites) { ($forest.Sites -join '; ') } else { '' } $domains = if ($forest.Domains) { ($forest.Domains -join '; ') } else { '' } $detail = @( "SchemaMaster=$($forest.SchemaMaster)" "DomainNamingMaster=$($forest.DomainNamingMaster)" "GlobalCatalogs=$globalCatalogs" "Domains=$domains" "Sites=$sites" ) -join '; ' $report.Add([PSCustomObject]@{ RecordType = 'Forest' Name = $forest.Name DistinguishedName = $forest.RootDomain NetBIOSName = '' FunctionalLevel = $forest.ForestMode PDCEmulator = '' RIDMaster = '' InfrastructureMaster = '' Detail = $detail }) Write-Verbose "Forest: $($forest.Name) ($($forest.ForestMode))" } catch { Write-Warning "Failed to query AD forest: $_" } # ------------------------------------------------------------------ # Site information # ------------------------------------------------------------------ try { Write-Verbose "Querying AD sites..." $adSites = Get-ADReplicationSite -Filter * foreach ($site in $adSites) { $subnets = try { $siteSubnets = Get-ADReplicationSubnet -Filter "Site -eq '$($site.DistinguishedName)'" -ErrorAction SilentlyContinue if ($siteSubnets) { ($siteSubnets | ForEach-Object { $_.Name }) -join '; ' } else { '' } } catch { '' } $report.Add([PSCustomObject]@{ RecordType = 'Site' Name = $site.Name DistinguishedName = $site.DistinguishedName NetBIOSName = '' FunctionalLevel = '' PDCEmulator = '' RIDMaster = '' InfrastructureMaster = '' Detail = if ($subnets) { "Subnets=$subnets" } else { 'Subnets=None' } }) } Write-Verbose "Found $(@($adSites).Count) AD site(s)" } catch { Write-Warning "Failed to query AD sites: $_" } # ------------------------------------------------------------------ # Trust relationships # ------------------------------------------------------------------ try { Write-Verbose "Querying trust relationships..." $trusts = Get-ADTrust -Filter * if ($trusts) { foreach ($trust in $trusts) { $direction = switch ($trust.Direction) { 0 { 'Disabled' } 1 { 'Inbound' } 2 { 'Outbound' } 3 { 'Bidirectional' } default { $trust.Direction } } $trustType = switch ($trust.TrustType) { 1 { 'Downlevel (Windows NT)' } 2 { 'Uplevel (Windows 2000+)' } 3 { 'MIT (Kerberos)' } 4 { 'DCE' } default { $trust.TrustType } } $detail = @( "Direction=$direction" "TrustType=$trustType" "SelectiveAuth=$($trust.SelectiveAuthentication)" "ForestTransitive=$($trust.ForestTransitive)" ) -join '; ' $report.Add([PSCustomObject]@{ RecordType = 'Trust' Name = $trust.Name DistinguishedName = $trust.DistinguishedName NetBIOSName = '' FunctionalLevel = '' PDCEmulator = '' RIDMaster = '' InfrastructureMaster = '' Detail = $detail }) } Write-Verbose "Found $(@($trusts).Count) trust relationship(s)" } else { Write-Verbose "No trust relationships found" } } catch { Write-Warning "Failed to query AD trusts: $_" } # ------------------------------------------------------------------ # Export or return # ------------------------------------------------------------------ $results = @($report) Write-Verbose "Collected $($results.Count) AD domain topology records" if ($OutputPath) { $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 Write-Output "Exported $($results.Count) AD domain topology records to $OutputPath" } else { Write-Output $results } |