#Requires -RunAsAdministrator <# .SYNOPSIS Phase 2.1: OU Structure Cleanup on CS-SERVER. .DESCRIPTION Audits and removes duplicate/empty root-level OUs, fixes misspelling, handles CN=Users account cleanup. Run BEFORE phase2-ad-setup.ps1. Run on CS-SERVER via ScreenConnect. .NOTES Step 1: Read-only audit (always runs) Step 2: Delete duplicate root OUs (requires $DeleteOUs = $true) Step 3: Delete empty Managment/MemCare/Sales OUs (requires $DeleteOUs = $true) Step 4: Delete/disable stale accounts in CN=Users (requires $DeleteAccounts = $true) Step 5: Flag Lupe.Sanchez for review #> Import-Module ActiveDirectory -ErrorAction Stop # --- SAFETY FLAGS --- $DeleteOUs = $false # Set $true to delete empty root-level OUs $DeleteAccounts = $false # Set $true to delete/disable stale accounts in CN=Users $Domain = "DC=cascades,DC=local" Write-Host "=== Phase 2.1: OU Structure Cleanup ===" -ForegroundColor Cyan Write-Host "" # ============================================================ # STEP 1: Audit root-level duplicate OUs (READ-ONLY) # ============================================================ Write-Host "--- Step 1: Auditing Root-Level Duplicate OUs ---" -ForegroundColor Yellow Write-Host "These OUs exist at root AND under Departments. Root copies should be empty." -ForegroundColor DarkGray Write-Host "" $rootDuplicateOUs = @( "OU=Administrative,$Domain", "OU=Care-Assisted Living,$Domain", "OU=Care-Memorycare,$Domain", "OU=Culinary,$Domain", "OU=Housekeeping,$Domain", "OU=Life Enrichment,$Domain", "OU=Maintenance,$Domain", "OU=Marketing,$Domain", "OU=Resident Services,$Domain", "OU=Transportation,$Domain" ) $allEmpty = $true $ouResults = @{} foreach ($ou in $rootDuplicateOUs) { $ouName = ($ou -split ',')[0] -replace 'OU=','' try { $objects = Get-ADObject -SearchBase $ou -SearchScope OneLevel -Filter * -ErrorAction Stop $props = Get-ADOrganizationalUnit $ou -Properties gPLink, ProtectedFromAccidentalDeletion -ErrorAction Stop Write-Host " === $ouName (root) ===" -ForegroundColor White if ($objects) { $allEmpty = $false $ouResults[$ou] = "HAS_OBJECTS" foreach ($obj in $objects) { Write-Host " $($obj.ObjectClass): $($obj.Name)" -ForegroundColor Red } } else { $ouResults[$ou] = "EMPTY" Write-Host " Empty" -ForegroundColor Green } $gpLink = if ($props.gPLink) { $props.gPLink } else { "(none)" } Write-Host " GPLink: $gpLink" -ForegroundColor DarkGray Write-Host " Protected: $($props.ProtectedFromAccidentalDeletion)" -ForegroundColor DarkGray Write-Host "" } catch { Write-Host " === $ouName (root) ===" -ForegroundColor White Write-Host " NOT FOUND — may already be deleted" -ForegroundColor DarkGray $ouResults[$ou] = "NOT_FOUND" Write-Host "" } } # Also audit Managment, MemCare, Sales Write-Host "--- Auditing Managment, MemCare, Sales Root OUs ---" -ForegroundColor Yellow Write-Host "" $miscRootOUs = @( "OU=Managment,$Domain", "OU=MemCare,$Domain", "OU=Sales,$Domain" ) foreach ($ou in $miscRootOUs) { $ouName = ($ou -split ',')[0] -replace 'OU=','' try { $objects = Get-ADObject -SearchBase $ou -SearchScope OneLevel -Filter * -ErrorAction Stop $props = Get-ADOrganizationalUnit $ou -Properties gPLink, ProtectedFromAccidentalDeletion -ErrorAction Stop Write-Host " === $ouName (root) ===" -ForegroundColor White if ($objects) { $allEmpty = $false $ouResults[$ou] = "HAS_OBJECTS" foreach ($obj in $objects) { Write-Host " $($obj.ObjectClass): $($obj.Name)" -ForegroundColor Red } } else { $ouResults[$ou] = "EMPTY" Write-Host " Empty" -ForegroundColor Green } $gpLink = if ($props.gPLink) { $props.gPLink } else { "(none)" } Write-Host " GPLink: $gpLink" -ForegroundColor DarkGray Write-Host " Protected: $($props.ProtectedFromAccidentalDeletion)" -ForegroundColor DarkGray Write-Host "" } catch { Write-Host " === $ouName (root) ===" -ForegroundColor White Write-Host " NOT FOUND — may already be deleted" -ForegroundColor DarkGray $ouResults[$ou] = "NOT_FOUND" Write-Host "" } } if ($allEmpty) { Write-Host " All audited OUs are EMPTY — safe to delete." -ForegroundColor Green } else { Write-Host " WARNING: Some OUs contain objects! Review output above before deleting." -ForegroundColor Red } Write-Host "" # ============================================================ # STEP 2: Delete root-level duplicate OUs (if empty) # ============================================================ Write-Host "--- Step 2: Delete Root-Level Duplicate Department OUs ---" -ForegroundColor Yellow if ($DeleteOUs) { foreach ($ou in $rootDuplicateOUs) { $ouName = ($ou -split ',')[0] -replace 'OU=','' if ($ouResults[$ou] -eq "EMPTY") { try { # Remove accidental deletion protection Set-ADOrganizationalUnit $ou -ProtectedFromAccidentalDeletion $false -ErrorAction Stop # Delete the OU Remove-ADOrganizationalUnit $ou -Confirm:$false -ErrorAction Stop Write-Host " [OK] Deleted root-level OU: $ouName" -ForegroundColor Green } catch { Write-Host " [ERROR] Failed to delete $ouName : $_" -ForegroundColor Red } } elseif ($ouResults[$ou] -eq "HAS_OBJECTS") { Write-Host " [SKIP] $ouName has objects — move them to Departments\$ouName first!" -ForegroundColor Red } elseif ($ouResults[$ou] -eq "NOT_FOUND") { Write-Host " [SKIP] $ouName already deleted" -ForegroundColor DarkGray } } } else { Write-Host " [WARN] Deletion SKIPPED — set `$DeleteOUs = `$true after reviewing audit output" -ForegroundColor Yellow } Write-Host "" # ============================================================ # STEP 3: Delete empty Managment, MemCare, Sales root OUs # ============================================================ Write-Host "--- Step 3: Delete Managment, MemCare, Sales Root OUs ---" -ForegroundColor Yellow if ($DeleteOUs) { foreach ($ou in $miscRootOUs) { $ouName = ($ou -split ',')[0] -replace 'OU=','' if ($ouResults[$ou] -eq "EMPTY") { try { Set-ADOrganizationalUnit $ou -ProtectedFromAccidentalDeletion $false -ErrorAction Stop Remove-ADOrganizationalUnit $ou -Confirm:$false -ErrorAction Stop Write-Host " [OK] Deleted root-level OU: $ouName" -ForegroundColor Green } catch { Write-Host " [ERROR] Failed to delete $ouName : $_" -ForegroundColor Red } } elseif ($ouResults[$ou] -eq "HAS_OBJECTS") { Write-Host " [SKIP] $ouName has objects — review and move/delete contents first!" -ForegroundColor Red } elseif ($ouResults[$ou] -eq "NOT_FOUND") { Write-Host " [SKIP] $ouName already deleted" -ForegroundColor DarkGray } } } else { Write-Host " [WARN] Deletion SKIPPED — set `$DeleteOUs = `$true after reviewing audit output" -ForegroundColor Yellow } Write-Host "" # ============================================================ # STEP 4: Handle stale accounts in CN=Users # ============================================================ Write-Host "--- Step 4: CN=Users Account Cleanup ---" -ForegroundColor Yellow # First, show what's in CN=Users Write-Host " Current accounts in CN=Users:" -ForegroundColor DarkGray $usersContainer = "CN=Users,$Domain" $cnUsers = Get-ADUser -SearchBase $usersContainer -SearchScope OneLevel -Filter * -Properties Enabled, Description, LastLogonDate foreach ($u in $cnUsers | Sort-Object Enabled, Name) { $status = if ($u.Enabled) { "Enabled " } else { "Disabled" } $lastLogon = if ($u.LastLogonDate) { $u.LastLogonDate.ToString("yyyy-MM-dd") } else { "Never" } Write-Host " [$status] $($u.SamAccountName) — Last logon: $lastLogon" -ForegroundColor $(if ($u.Enabled) { "White" } else { "DarkGray" }) } Write-Host "" # Already disabled — delete immediately $disabledToDelete = @( "Anna.Pitzlin", "Nela.Durut-Azizi", "Jodi.Ramstack", "Monica.Ramirez" ) # Enabled but former employees — disable then delete $enabledToRemove = @( "alyssa.brooks", "ann.dery", "Cathy.Reece", "Haris.Durut", "Isabella.Islas", "Kelly.Wallace", "Nuria.Diaz" ) if ($DeleteAccounts) { Write-Host " Deleting disabled accounts from CN=Users..." -ForegroundColor Yellow foreach ($acct in $disabledToDelete) { try { # Remove from all groups first (especially Domain Admins — Monica.Ramirez!) $user = Get-ADUser $acct -Properties MemberOf -ErrorAction Stop foreach ($group in $user.MemberOf) { $groupName = (Get-ADGroup $group).Name if ($groupName -ne "Domain Users") { Remove-ADGroupMember -Identity $group -Members $acct -Confirm:$false -ErrorAction SilentlyContinue Write-Host " [OK] Removed $acct from $groupName" -ForegroundColor Green } } Remove-ADUser -Identity $acct -Confirm:$false -ErrorAction Stop Write-Host " [OK] Deleted: $acct" -ForegroundColor Green } catch { Write-Host " [SKIP] $acct not found or error: $_" -ForegroundColor DarkGray } } Write-Host "" Write-Host " Disabling + deleting former employee accounts..." -ForegroundColor Yellow foreach ($acct in $enabledToRemove) { try { # Disable first Disable-ADAccount -Identity $acct -ErrorAction SilentlyContinue # Remove from all groups $user = Get-ADUser $acct -Properties MemberOf -ErrorAction Stop foreach ($group in $user.MemberOf) { $groupName = (Get-ADGroup $group).Name if ($groupName -ne "Domain Users") { Remove-ADGroupMember -Identity $group -Members $acct -Confirm:$false -ErrorAction SilentlyContinue Write-Host " [OK] Removed $acct from $groupName" -ForegroundColor Green } } Remove-ADUser -Identity $acct -Confirm:$false -ErrorAction Stop Write-Host " [OK] Disabled + Deleted: $acct" -ForegroundColor Green } catch { Write-Host " [SKIP] $acct not found or error: $_" -ForegroundColor DarkGray } } } else { Write-Host " [WARN] Account deletion SKIPPED — set `$DeleteAccounts = `$true to execute" -ForegroundColor Yellow Write-Host "" Write-Host " Disabled accounts to delete:" -ForegroundColor Yellow foreach ($acct in $disabledToDelete) { Write-Host " - $acct" -ForegroundColor DarkGray } Write-Host " Enabled accounts to disable + delete (NOT in HR):" -ForegroundColor Yellow foreach ($acct in $enabledToRemove) { Write-Host " - $acct" -ForegroundColor DarkGray } } Write-Host "" # ============================================================ # STEP 5: Flag Lupe.Sanchez for review # ============================================================ Write-Host "--- Step 5: Lupe.Sanchez Review ---" -ForegroundColor Yellow try { $lupe = Get-ADUser -Identity "Lupe.Sanchez" -Properties Enabled, LastLogonDate, Description, DistinguishedName -ErrorAction Stop $guadalupe = Get-ADUser -Identity "Guadalupe.Sanchez" -Properties Enabled, LastLogonDate, Description, DistinguishedName -ErrorAction SilentlyContinue Write-Host " Lupe.Sanchez:" -ForegroundColor White Write-Host " DN: $($lupe.DistinguishedName)" -ForegroundColor DarkGray Write-Host " Enabled: $($lupe.Enabled)" -ForegroundColor $(if ($lupe.Enabled) { "Yellow" } else { "DarkGray" }) Write-Host " Last Logon: $($lupe.LastLogonDate)" -ForegroundColor DarkGray Write-Host "" if ($guadalupe) { Write-Host " Guadalupe.Sanchez (possible duplicate):" -ForegroundColor White Write-Host " DN: $($guadalupe.DistinguishedName)" -ForegroundColor DarkGray Write-Host " Enabled: $($guadalupe.Enabled)" -ForegroundColor DarkGray Write-Host " Last Logon: $($guadalupe.LastLogonDate)" -ForegroundColor DarkGray Write-Host "" Write-Host " ** REVIEW: Lupe.Sanchez may be a duplicate of Guadalupe.Sanchez (Housekeeping)." -ForegroundColor Red Write-Host " ** Both accounts exist. Check with client which to keep." -ForegroundColor Red } } catch { Write-Host " Lupe.Sanchez not found in AD" -ForegroundColor DarkGray } Write-Host "" # ============================================================ # STEP 6: Accounts that should STAY in CN=Users # ============================================================ Write-Host "--- Accounts staying in CN=Users (system/service) ---" -ForegroundColor Yellow $keepInUsers = @("Administrator", "Guest", "krbtgt", "localadmin", "sysadmin", "QBDataServiceUser34") foreach ($acct in $keepInUsers) { try { $user = Get-ADUser $acct -Properties Enabled -ErrorAction SilentlyContinue if ($user) { $status = if ($user.Enabled) { "Enabled" } else { "Disabled" } Write-Host " [$status] $acct — Staying in CN=Users (system/service account)" -ForegroundColor DarkGray } } catch {} } Write-Host "" # Accounts needing client decision Write-Host "--- Accounts needing client decision ---" -ForegroundColor Yellow Write-Host " Receptionist — shared account, currently in CN=Users. Move to Departments\Resident Services?" -ForegroundColor Yellow Write-Host " directoryshare — shared account, currently in CN=Users. Keep as service account?" -ForegroundColor Yellow Write-Host " Lupe.Sanchez — see review above. Possible duplicate of Guadalupe.Sanchez." -ForegroundColor Yellow Write-Host "" # ============================================================ # SUMMARY: Final OU structure # ============================================================ Write-Host "=== Final OU Structure ===" -ForegroundColor Cyan Write-Host "" $allOUs = Get-ADOrganizationalUnit -Filter * -Properties ProtectedFromAccidentalDeletion | Sort-Object DistinguishedName | Select-Object Name, DistinguishedName, ProtectedFromAccidentalDeletion foreach ($ou in $allOUs) { # Calculate depth for indentation $depth = ($ou.DistinguishedName -split ',' | Where-Object { $_ -match '^OU=' }).Count - 1 $indent = " " * $depth $protected = if ($ou.ProtectedFromAccidentalDeletion) { "" } else { " [UNPROTECTED]" } Write-Host " $indent$($ou.Name)$protected" -ForegroundColor White } Write-Host "" Write-Host "=== OU Cleanup Complete ===" -ForegroundColor Cyan Write-Host "Next: Run phase2-ad-setup.ps1 (security fixes, groups, computer moves)" -ForegroundColor Green