<# .SYNOPSIS Export Active Directory OU structure with GPO links .DESCRIPTION Documents the complete OU hierarchy including GPO links, inheritance settings, and description. Useful for documentation, disaster recovery, or comparing environments. .PARAMETER OutputFolder Destination folder for reports. Default: .\OU-Structure- .PARAMETER IncludeGPODetails Include detailed GPO information (default: $true) .PARAMETER SearchBase Optional starting point for OU export (default: domain root) .NOTES ⚠️ AI-GENERATED SCRIPT - UNTESTED This script was generated by Claude AI and has not been tested in production. Review and test thoroughly in a non-production environment before use. - Requires Active Directory PowerShell module - Run with appropriate AD permissions - Exports to both CSV (detailed) and TXT (tree view) .EXAMPLE .\Export-OUStructure.ps1 .EXAMPLE .\Export-OUStructure.ps1 -SearchBase "OU=Departments,DC=domain,DC=com" #> [CmdletBinding()] param( [string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("OU-Structure-" + (Get-Date -Format "yyyyMMdd-HHmm"))), [bool]$IncludeGPODetails = $true, [string]$SearchBase = "" ) function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow Write-Host "[$(NowTag)] Exporting OU structure..." -ForegroundColor Green # Import AD module try { Import-Module ActiveDirectory -ErrorAction Stop } catch { Write-Host "[$(NowTag)] ERROR: Active Directory module not available" -ForegroundColor Red exit 1 } # Create output folder New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null # Get domain info $domain = Get-ADDomain if (-not $SearchBase) { $SearchBase = $domain.DistinguishedName } Write-Host "[$(NowTag)] Domain: $($domain.DNSRoot)" Write-Host "[$(NowTag)] Search Base: $SearchBase" # Get all OUs Write-Host "[$(NowTag)] Retrieving organizational units..." $ouParams = @{ Filter = '*' SearchBase = $SearchBase Properties = @('Description', 'gPLink', 'gPOptions', 'ProtectedFromAccidentalDeletion', 'WhenCreated', 'WhenChanged') } $ous = Get-ADOrganizationalUnit @ouParams | Sort-Object DistinguishedName $ouCount = ($ous | Measure-Object).Count Write-Host "[$(NowTag)] Found $ouCount organizational unit(s)" # Build GPO name lookup if requested $gpoLookup = @{} if ($IncludeGPODetails) { Write-Host "[$(NowTag)] Building GPO lookup table..." try { Import-Module GroupPolicy -ErrorAction Stop $gpos = Get-GPO -All -ErrorAction SilentlyContinue foreach ($gpo in $gpos) { $gpoLookup[$gpo.Id.ToString()] = $gpo.DisplayName } Write-Host "[$(NowTag)] Found $($gpos.Count) GPO(s)" } catch { Write-Host "[$(NowTag)] WARNING: Could not load GroupPolicy module - GPO names will show as GUIDs" -ForegroundColor Yellow } } # Process OUs $ouDetails = @() $treeLines = @() foreach ($ou in $ous) { # Calculate depth for tree view $depth = ($ou.DistinguishedName -split ',OU=').Count - 1 $indent = " " * $depth # Parse GPO links $gpoLinks = @() $gpoNames = @() if ($ou.gPLink) { # gPLink format: [LDAP://cn={GUID},cn=policies,cn=system,DC=domain,DC=com;0] $links = $ou.gPLink -split '\]\[' -replace '^\[' -replace '\]$' foreach ($link in $links) { if ($link -match '\{([^\}]+)\}') { $gpoGuid = $matches[1] $gpoName = if ($gpoLookup.ContainsKey($gpoGuid)) { $gpoLookup[$gpoGuid] } else { $gpoGuid } # Parse link order and enforcement $linkOrder = if ($link -match ';(\d+)') { $matches[1] } else { "0" } $enforced = $linkOrder -band 1 # Bit 0 indicates enforcement $disabled = $linkOrder -band 2 # Bit 1 indicates disabled $gpoNames += $gpoName $gpoLinks += [PSCustomObject]@{ OU = $ou.Name GPOName = $gpoName GPOGUID = $gpoGuid LinkOrder = $linkOrder Enforced = ($enforced -eq 1) Disabled = ($disabled -eq 2) } } } } # GPO inheritance blocked? $inheritanceBlocked = ($ou.gPOptions -eq 1) # Tree view line $protectedMark = if ($ou.ProtectedFromAccidentalDeletion) { "[P]" } else { "" } $inheritMark = if ($inheritanceBlocked) { "[BLOCKED]" } else { "" } $gpoMark = if ($gpoNames.Count -gt 0) { " (GPOs: $($gpoNames -join ', '))" } else { "" } $treeLines += "$indent$($ou.Name) $protectedMark$inheritMark$gpoMark" # Detailed record $ouDetails += [PSCustomObject]@{ Name = $ou.Name DistinguishedName = $ou.DistinguishedName Description = $ou.Description ProtectedFromAccidentalDeletion = $ou.ProtectedFromAccidentalDeletion GPOInheritanceBlocked = $inheritanceBlocked LinkedGPOs = ($gpoNames -join "; ") LinkedGPOCount = $gpoNames.Count WhenCreated = $ou.WhenCreated WhenChanged = $ou.WhenChanged } } # Export detailed CSV $csvFile = Join-Path $OutputFolder "OU-Structure.csv" $ouDetails | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $csvFile Write-Host "[$(NowTag)] OU details exported: $csvFile" -ForegroundColor Green # Export GPO links if available if ($IncludeGPODetails -and $gpoLinks.Count -gt 0) { $gpoLinksFile = Join-Path $OutputFolder "OU-GPO-Links.csv" $gpoLinks | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $gpoLinksFile Write-Host "[$(NowTag)] GPO links exported: $gpoLinksFile" -ForegroundColor Green } # Export tree view $treeFile = Join-Path $OutputFolder "OU-Tree.txt" $treeHeader = @" Active Directory OU Structure Domain: $($domain.DNSRoot) Search Base: $SearchBase Generated: $(Get-Date) Legend: [P] = Protected from Accidental Deletion [BLOCKED] = GPO Inheritance Blocked "@ $treeHeader | Out-File -FilePath $treeFile -Encoding UTF8 $treeLines | Out-File -FilePath $treeFile -Encoding UTF8 -Append Write-Host "[$(NowTag)] Tree view exported: $treeFile" -ForegroundColor Green # Summary Write-Host "`nOU STRUCTURE SUMMARY:" -ForegroundColor Cyan Write-Host " Total OUs: $ouCount" Write-Host " Protected OUs: $(($ouDetails | Where-Object ProtectedFromAccidentalDeletion).Count)" Write-Host " OUs with GPO Inheritance Blocked: $(($ouDetails | Where-Object GPOInheritanceBlocked).Count)" Write-Host " OUs with Linked GPOs: $(($ouDetails | Where-Object { $_.LinkedGPOCount -gt 0 }).Count)" Write-Host "`n[$(NowTag)] Export complete! Output folder: $OutputFolder"