⚠️ IMPORTANT: These scripts are AI-GENERATED and UNTESTED Exchange Scripts (5): - Get-MailboxPermissions.ps1: Audit delegate access permissions - Get-InactiveMailboxes.ps1: Identify stale mailboxes - Compare-MailboxDatabases.ps1: Database health comparison - Export-DistributionGroups.ps1: Distribution group inventory - Get-MailflowStats.ps1: Transport log analysis Active Directory Scripts (3): - Get-ADUserLastLogon.ps1: True LastLogon across all DCs - Export-OUStructure.ps1: OU hierarchy with GPO links - Compare-ADGroupMemberships.ps1: Compare user group memberships System Maintenance Scripts (4): - Get-ServerInventory.ps1: Hardware/software inventory report - Monitor-DiskSpace.ps1: Disk space monitoring with alerts - Backup-ExchangeCertificates.ps1: Certificate backup to PFX - Test-ExchangeHealth.ps1: Aggregated Exchange health checks Documentation: - Updated CLAUDE.md with AI-generated scripts section - Added AI-GENERATED-SCRIPTS.md with warnings and testing guide All scripts include prominent warnings and follow established patterns from existing scripts. Require thorough testing before production use. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
202 lines
6.8 KiB
PowerShell
202 lines
6.8 KiB
PowerShell
<#
|
|
.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-<date>
|
|
|
|
.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"
|