Howard's personal MSP client documentation folder imported into shared
ClaudeTools repo via /import command. Scope:
Clients (structured MSP docs under clients/<name>/docs/):
- anaise (NEW) - 13 files
- cascades-tucson - 47 files merged (existing had only reports/)
- dataforth - 18 files merged (alongside incident reports)
- instrumental-music-center - 14 files merged
- khalsa (NEW) - 22 files, multi-site (camden, river)
- kittle (NEW) - 16 files incl. fix-pdf-preview, gpo-intranet-zone
- lens-auto-brokerage (NEW) - 3 files (name matches SOPS vault)
- _client_template - 13-file scaffold for new clients
MSP tooling (projects/msp-tools/):
- msp-audit-scripts/ - server_audit.ps1, workstation_audit.ps1, README
- utilities/ - clean_printer_ports, win11_upgrade,
screenconnect-toolbox-commands
Credential handling:
- Extracted 1 inline password (Anaise DESKTOP-O8GF4SD / david)
to SOPS vault: clients/anaise/desktop-o8gf4sd.sops.yaml
- Redacted overview.md with vault reference pattern
- Scanned all 160 files for keys/tokens/connection strings -
no other credentials found
Skipped:
- Cascades/.claude/settings.local.json (per-machine config)
- Source-root CLAUDE.md (personal, claudetools has its own)
- scripts/server_audit.ps1 and workstation_audit.ps1 at source root
(identical duplicates of msp-audit-scripts versions)
Memory updates:
- reference_client_docs_structure.md (layout, conventions, active list)
- reference_msp_audit_scripts.md (locations, ScreenConnect 80-char rule)
Session log: session-logs/2026-04-16-howard-client-docs-import.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
317 lines
12 KiB
PowerShell
317 lines
12 KiB
PowerShell
#Requires -RunAsAdministrator
|
|
<#
|
|
.SYNOPSIS
|
|
Phase 2.2: AD cleanup and preparation on CS-SERVER.
|
|
.DESCRIPTION
|
|
Fixes OU naming, creates Workstations OU, creates security groups with members,
|
|
removes stale/former accounts, fixes group issues, and moves computers.
|
|
Run on CS-SERVER via ScreenConnect.
|
|
.NOTES
|
|
PREREQUISITE: Run phase2-ou-cleanup.ps1 FIRST to remove duplicate root-level OUs.
|
|
Client has confirmed all account removals.
|
|
Set $DeleteAccounts = $true when ready to execute deletions.
|
|
#>
|
|
|
|
Import-Module ActiveDirectory -ErrorAction Stop
|
|
|
|
# --- SAFETY FLAG ---
|
|
$DeleteAccounts = $false
|
|
|
|
$Domain = "DC=cascades,DC=local"
|
|
|
|
Write-Host "=== Phase 2.2: AD Cleanup & Preparation ===" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# ============================================================
|
|
# STEP 1: Fix immediate security issues
|
|
# ============================================================
|
|
Write-Host "--- Fixing Security Issues ---" -ForegroundColor Yellow
|
|
|
|
# Remove disabled Monica.Ramirez from Domain Admins
|
|
try {
|
|
$monica = Get-ADUser -Identity "Monica.Ramirez" -ErrorAction SilentlyContinue
|
|
if ($monica) {
|
|
Remove-ADGroupMember -Identity "Domain Admins" -Members "Monica.Ramirez" -Confirm:$false -ErrorAction Stop
|
|
Write-Host " [OK] Removed Monica.Ramirez from Domain Admins" -ForegroundColor Green
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " [SKIP] Monica.Ramirez not in Domain Admins or not found" -ForegroundColor DarkGray
|
|
}
|
|
|
|
# Disable Haris.Durut (currently enabled, no longer employed)
|
|
try {
|
|
Disable-ADAccount -Identity "Haris.Durut" -ErrorAction Stop
|
|
Write-Host " [OK] Disabled Haris.Durut" -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Host " [SKIP] Haris.Durut already disabled or not found" -ForegroundColor DarkGray
|
|
}
|
|
|
|
# ============================================================
|
|
# STEP 2: Fix misspelled OU
|
|
# ============================================================
|
|
Write-Host "`n--- Fixing misspelled OU ---" -ForegroundColor Yellow
|
|
|
|
try {
|
|
$mgmtOU = Get-ADOrganizationalUnit -Filter 'Name -eq "Managment"' -ErrorAction SilentlyContinue
|
|
if ($mgmtOU) {
|
|
Rename-ADOrganizationalUnit -Identity $mgmtOU.DistinguishedName -NewName "Management"
|
|
Write-Host " [OK] Renamed 'Managment' -> 'Management'" -ForegroundColor Green
|
|
} else {
|
|
Write-Host " [SKIP] 'Managment' OU not found (already renamed?)" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " [ERROR] Failed to rename OU: $_" -ForegroundColor Red
|
|
}
|
|
|
|
# Fix "Quickboosk acccess" group name typo
|
|
try {
|
|
$qbGroup = Get-ADGroup -Filter 'Name -eq "Quickboosk acccess"' -ErrorAction SilentlyContinue
|
|
if ($qbGroup) {
|
|
Rename-ADObject -Identity $qbGroup.DistinguishedName -NewName "QuickBooks Access"
|
|
Set-ADGroup -Identity $qbGroup.DistinguishedName -DisplayName "QuickBooks Access"
|
|
Write-Host " [OK] Renamed 'Quickboosk acccess' -> 'QuickBooks Access'" -ForegroundColor Green
|
|
} else {
|
|
Write-Host " [SKIP] 'Quickboosk acccess' group not found" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " [ERROR] Failed to rename QuickBooks group: $_" -ForegroundColor Red
|
|
}
|
|
|
|
# Add lauren.hasselman to QuickBooks Access (she replaced Jeff Bristol)
|
|
try {
|
|
$qbGroupName = "QuickBooks Access"
|
|
# Try both names in case rename hasn't run yet
|
|
$qb = Get-ADGroup -Filter "Name -eq '$qbGroupName'" -ErrorAction SilentlyContinue
|
|
if (-not $qb) { $qb = Get-ADGroup -Filter 'Name -eq "Quickboosk acccess"' -ErrorAction SilentlyContinue }
|
|
if ($qb) {
|
|
Add-ADGroupMember -Identity $qb -Members "lauren.hasselman" -ErrorAction Stop
|
|
Write-Host " [OK] Added lauren.hasselman to $($qb.Name)" -ForegroundColor Green
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " [SKIP] lauren.hasselman already in QuickBooks group or error: $_" -ForegroundColor DarkGray
|
|
}
|
|
|
|
# ============================================================
|
|
# STEP 3: Create Workstations OU
|
|
# ============================================================
|
|
Write-Host "`n--- Creating Workstations OU ---" -ForegroundColor Yellow
|
|
|
|
$ous = @(
|
|
@{ Name = "Workstations"; Path = $Domain }
|
|
@{ Name = "Staff PCs"; Path = "OU=Workstations,$Domain" }
|
|
@{ Name = "Shared PCs"; Path = "OU=Workstations,$Domain" }
|
|
)
|
|
|
|
foreach ($ou in $ous) {
|
|
try {
|
|
$existing = Get-ADOrganizationalUnit -Filter "Name -eq '$($ou.Name)'" -SearchBase $ou.Path -SearchScope OneLevel -ErrorAction SilentlyContinue
|
|
if (-not $existing) {
|
|
New-ADOrganizationalUnit -Name $ou.Name -Path $ou.Path -ProtectedFromAccidentalDeletion $true
|
|
Write-Host " [OK] Created OU=$($ou.Name) in $($ou.Path)" -ForegroundColor Green
|
|
} else {
|
|
Write-Host " [SKIP] OU=$($ou.Name) already exists" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " [ERROR] Failed to create $($ou.Name): $_" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
# ============================================================
|
|
# STEP 4: Create security groups and populate members
|
|
# ============================================================
|
|
Write-Host "`n--- Creating & Populating Security Groups ---" -ForegroundColor Yellow
|
|
|
|
# Group definitions: Name -> array of AD SamAccountNames
|
|
$groupDefs = @{
|
|
"SG-Management-RW" = @(
|
|
"Meredith.Kuhn", "Ashley.Jensen", "Megan.Hiatt", "Crystal.Rodriguez",
|
|
"Tamra.Matthews", "britney.thompson", "Veronica.Feller", "strozzi",
|
|
"Alyssa.Brooks", "lauren.hasselman"
|
|
)
|
|
"SG-Sales-RW" = @(
|
|
"Megan.Hiatt", "Crystal.Rodriguez", "Tamra.Matthews"
|
|
)
|
|
"SG-Server-RW" = @(
|
|
"Ashley.Jensen", "britney.thompson", "Christina.DuPras",
|
|
"Veronica.Feller", "Meredith.Kuhn"
|
|
)
|
|
"SG-Chat-RW" = @(
|
|
"Ashley.Jensen", "britney.thompson", "Veronica.Feller"
|
|
)
|
|
"SG-Culinary-RW" = @(
|
|
"JD.Martin", "Ramon.Castaneda", "Alyssa.Brooks"
|
|
)
|
|
"SG-IT-RW" = @(
|
|
"howard", "sysadmin"
|
|
)
|
|
"SG-Receptionist-RW" = @(
|
|
"Cathy.Kingston", "Shontiel.Nunn", "Ray.Rai",
|
|
"Sebastian.Leon", "Michelle.Shestko"
|
|
)
|
|
"SG-Directory-RW" = @(
|
|
"Cathy.Kingston", "Shontiel.Nunn", "Christina.DuPras"
|
|
)
|
|
"SG-Public-RW" = @() # Empty — will use "Authenticated Users" at share level
|
|
"SG-AllShares-RO" = @() # Populated as needed
|
|
}
|
|
|
|
foreach ($g in $groupDefs.Keys) {
|
|
# Create group if it doesn't exist
|
|
try {
|
|
$existing = Get-ADGroup -Filter "Name -eq '$g'" -ErrorAction SilentlyContinue
|
|
if (-not $existing) {
|
|
New-ADGroup -Name $g -GroupScope DomainLocal -GroupCategory Security -Path $Domain `
|
|
-Description "File share access - created during migration"
|
|
Write-Host " [OK] Created group: $g" -ForegroundColor Green
|
|
} else {
|
|
Write-Host " [SKIP] Group $g already exists" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " [ERROR] Failed to create $g : $_" -ForegroundColor Red
|
|
continue
|
|
}
|
|
|
|
# Add members
|
|
foreach ($member in $groupDefs[$g]) {
|
|
try {
|
|
Add-ADGroupMember -Identity $g -Members $member -ErrorAction Stop
|
|
Write-Host " [OK] Added $member to $g" -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Host " [SKIP] $member -> $g (already member or not found)" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
}
|
|
|
|
# ============================================================
|
|
# STEP 5: Delete stale and former employee accounts
|
|
# ============================================================
|
|
Write-Host "`n--- Removing Stale & Former Employee Accounts ---" -ForegroundColor Yellow
|
|
|
|
# Already disabled — delete
|
|
$disabledToDelete = @(
|
|
"Anna.Pitzlin",
|
|
"Nela.Durut-Azizi",
|
|
"Jodi.Ramstack",
|
|
"Monica.Ramirez",
|
|
"jeff.bristol"
|
|
)
|
|
|
|
# Currently enabled — disable first, then delete (former employees not in HR)
|
|
$enabledToRemove = @(
|
|
"Haris.Durut",
|
|
"Nuria.Diaz",
|
|
"Cathy.Reece",
|
|
"Kelly.Wallace",
|
|
"alyssa.brooks",
|
|
"Isabella.Islas",
|
|
"ann.dery"
|
|
)
|
|
|
|
if ($DeleteAccounts) {
|
|
Write-Host " Deleting disabled accounts..." -ForegroundColor Yellow
|
|
foreach ($acct in $disabledToDelete) {
|
|
try {
|
|
Remove-ADUser -Identity $acct -Confirm:$false -ErrorAction Stop
|
|
Write-Host " [OK] Deleted: $acct" -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Host " [SKIP] $acct not found: $_" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
|
|
Write-Host " Disabling and deleting former employees..." -ForegroundColor Yellow
|
|
foreach ($acct in $enabledToRemove) {
|
|
try {
|
|
Disable-ADAccount -Identity $acct -ErrorAction SilentlyContinue
|
|
Remove-ADUser -Identity $acct -Confirm:$false -ErrorAction Stop
|
|
Write-Host " [OK] Disabled + Deleted: $acct" -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Host " [SKIP] $acct not found: $_" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
} else {
|
|
Write-Host " [WARN] 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 }
|
|
}
|
|
|
|
# ============================================================
|
|
# STEP 6: Remove shared/generic accounts from active use
|
|
# ============================================================
|
|
Write-Host "`n--- Shared Account Cleanup ---" -ForegroundColor Yellow
|
|
|
|
$sharedAccounts = @("Culinary", "Receptionist", "saleshare", "directoryshare")
|
|
Write-Host " The following shared accounts should be replaced with individual logins:" -ForegroundColor Yellow
|
|
foreach ($acct in $sharedAccounts) {
|
|
try {
|
|
$user = Get-ADUser -Identity $acct -Properties Enabled -ErrorAction SilentlyContinue
|
|
if ($user -and $user.Enabled) {
|
|
Write-Host " $acct — ENABLED (disable after users have individual accounts)" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " $acct — not found" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
|
|
# ============================================================
|
|
# STEP 7: Move computers to Workstations OU
|
|
# ============================================================
|
|
Write-Host "`n--- Moving Computers to Workstations OU ---" -ForegroundColor Yellow
|
|
|
|
$targetOU = "OU=Staff PCs,OU=Workstations,$Domain"
|
|
# Note: CS-QB stays in CN=Computers — it's a Hyper-V VM (VoIP server), not a staff PC
|
|
$computers = @("CRYSTAL-PC", "ACCT2-PC", "DESKTOP-H6QHRR7", "DESKTOP-1ISF081")
|
|
|
|
foreach ($pc in $computers) {
|
|
try {
|
|
$comp = Get-ADComputer -Identity $pc -ErrorAction SilentlyContinue
|
|
if ($comp) {
|
|
if ($comp.DistinguishedName -notlike "*$targetOU*") {
|
|
Move-ADObject -Identity $comp.DistinguishedName -TargetPath $targetOU
|
|
Write-Host " [OK] Moved $pc to Staff PCs OU" -ForegroundColor Green
|
|
} else {
|
|
Write-Host " [SKIP] $pc already in Staff PCs OU" -ForegroundColor DarkGray
|
|
}
|
|
} else {
|
|
Write-Host " [SKIP] $pc not found in AD" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " [ERROR] Failed to move $pc : $_" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
# ============================================================
|
|
# SUMMARY
|
|
# ============================================================
|
|
Write-Host "`n=== AD Setup Summary ===" -ForegroundColor Cyan
|
|
|
|
Write-Host "`nOU Structure:" -ForegroundColor Yellow
|
|
Get-ADOrganizationalUnit -Filter * | Select-Object Name, DistinguishedName | Format-Table -AutoSize -Wrap
|
|
|
|
Write-Host "Security Groups & Members:" -ForegroundColor Yellow
|
|
Get-ADGroup -Filter 'Name -like "SG-*"' | ForEach-Object {
|
|
$members = (Get-ADGroupMember $_ -ErrorAction SilentlyContinue | Select-Object -Expand Name) -join ", "
|
|
Write-Host " $($_.Name): $members" -ForegroundColor Cyan
|
|
}
|
|
|
|
Write-Host "`nEnabled User Count:" -ForegroundColor Yellow
|
|
$enabled = (Get-ADUser -Filter 'Enabled -eq $true').Count
|
|
Write-Host " $enabled enabled accounts" -ForegroundColor Cyan
|
|
|
|
Write-Host "`n=== AD Cleanup Complete ===" -ForegroundColor Cyan
|
|
Write-Host "Next: Run phase2-sync-synology.ps1 to sync data from NAS" -ForegroundColor Green
|