import: ingested 160 files from C:\Users\howar\Clients

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>
This commit is contained in:
2026-04-16 19:43:58 -07:00
parent 6eaba02b71
commit 8d975c1b44
160 changed files with 16002 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
# Win11Debloat — Business PC App Removal Script
# For Cascades senior living staff machines
# Removes games, social media, entertainment, and other non-business apps
# Keeps: Calculator, Notepad, Snipping Tool, Photos, Paint, Camera, Store,
# Sticky Notes, Alarms, OneDrive, Outlook, Teams, Office Hub,
# Windows Terminal, Remote Desktop, Quick Assist
#
# Usage (run as Administrator in PowerShell):
#
# Option 1 — One-liner (downloads and runs automatically):
# & ([scriptblock]::Create((irm "https://debloat.raphi.re/"))) -RemoveApps -Apps "Microsoft.GamingApp, Microsoft.XboxApp, Microsoft.XboxGameOverlay, Microsoft.XboxGamingOverlay, Microsoft.XboxSpeechToTextOverlay, Microsoft.Xbox.TCUI, Microsoft.MicrosoftSolitaireCollection, king.com.CandyCrushSaga, king.com.CandyCrushSodaSaga, king.com.BubbleWitch3Saga, Asphalt8Airborne, CaesarsSlotsFreeCasino, COOKINGFEVER, DisneyMagicKingdoms, FarmVille2CountryEscape, HiddenCity, MarchofEmpires, RoyalRevolt, Facebook, Instagram, TikTok, Twitter, LinkedInforWindows, XING, Viber, Netflix, Spotify, Disney, HULULLC.HULUPLUS, AmazonVideo.PrimeVideo, SlingTV, PandoraMediaInc, iHeartRadio, TuneInRadio, Plex, Shazam, Flipboard, Microsoft.BingNews, Microsoft.News, Microsoft.BingWeather, Microsoft.BingSports, Microsoft.BingFinance, Microsoft.BingFoodAndDrink, Microsoft.BingHealthAndFitness, Microsoft.BingTranslator, Microsoft.BingTravel, Clipchamp.Clipchamp, Microsoft.Copilot, Microsoft.Windows.AIHub, Microsoft.549981C3F5F10, Microsoft.MicrosoftJournal, Microsoft.Whiteboard, Microsoft.Office.Sway, Microsoft.PowerAutomateDesktop, Microsoft.MicrosoftPowerBIForWindows, Microsoft.StartExperiencesApp, Microsoft.3DBuilder, Microsoft.Microsoft3DViewer, Microsoft.MSPaint, Microsoft.Print3D, Microsoft.MixedReality.Portal, Amazon.com.Amazon, Microsoft.WindowsFeedbackHub, Microsoft.Getstarted, Microsoft.M365Companions, Microsoft.YourPhone, Microsoft.BingSearch, Microsoft.ZuneVideo, Microsoft.SkypeApp, Microsoft.People, Microsoft.Messaging, Microsoft.OneConnect, Microsoft.NetworkSpeedTest, AutodeskSketchBook, DrawboardPDF, Duolingo-LearnLanguagesforFree, NYTCrossword, OneCalendar, PhototasticCollage, PicsArt-PhotoStudio, PolarrPhotoEditorAcademicEdition, WinZipUniversal, Wunderlist, AdobeSystemsIncorporated.AdobePhotoshopExpress, ActiproSoftwareLLC, CyberLinkMediaSuiteEssentials, fitbit, Sidia.LiveWallpaper"
#
# Option 2 — From downloaded copy (if Win11Debloat is already extracted):
# cd C:\path\to\Win11Debloat
# Set-ExecutionPolicy Unrestricted -Scope Process -Force
# .\Win11Debloat.ps1 -RemoveApps -Apps "<same app list>"
#
# Apps not present on the machine are silently skipped.
# Source: https://github.com/Raphire/Win11Debloat/wiki/App-Removal
# --- App list (one per line for readability) ---
$apps = @(
# Gaming / Xbox
"Microsoft.GamingApp"
"Microsoft.XboxApp"
"Microsoft.XboxGameOverlay"
"Microsoft.XboxGamingOverlay"
"Microsoft.XboxSpeechToTextOverlay"
"Microsoft.Xbox.TCUI"
"Microsoft.MicrosoftSolitaireCollection"
# Bloatware games
"king.com.CandyCrushSaga"
"king.com.CandyCrushSodaSaga"
"king.com.BubbleWitch3Saga"
"Asphalt8Airborne"
"CaesarsSlotsFreeCasino"
"COOKINGFEVER"
"DisneyMagicKingdoms"
"FarmVille2CountryEscape"
"HiddenCity"
"MarchofEmpires"
"RoyalRevolt"
# Social media
"Facebook"
"Instagram"
"TikTok"
"Twitter"
"LinkedInforWindows"
"XING"
"Viber"
# Entertainment / streaming
"Netflix"
"Spotify"
"Disney"
"HULULLC.HULUPLUS"
"AmazonVideo.PrimeVideo"
"SlingTV"
"PandoraMediaInc"
"iHeartRadio"
"TuneInRadio"
"Plex"
"Shazam"
"Flipboard"
# Bing / News / Weather
"Microsoft.BingNews"
"Microsoft.News"
"Microsoft.BingWeather"
"Microsoft.BingSports"
"Microsoft.BingFinance"
"Microsoft.BingFoodAndDrink"
"Microsoft.BingHealthAndFitness"
"Microsoft.BingTranslator"
"Microsoft.BingTravel"
# Unused Microsoft apps
"Clipchamp.Clipchamp"
"Microsoft.Copilot"
"Microsoft.Windows.AIHub"
"Microsoft.549981C3F5F10" # Cortana
"Microsoft.MicrosoftJournal"
"Microsoft.Whiteboard"
"Microsoft.Office.Sway"
"Microsoft.PowerAutomateDesktop"
"Microsoft.MicrosoftPowerBIForWindows"
"Microsoft.StartExperiencesApp" # Widgets
"Microsoft.WindowsFeedbackHub"
"Microsoft.Getstarted" # Get Started / Tips
"Microsoft.M365Companions"
"Microsoft.YourPhone" # Phone Link
"Microsoft.BingSearch"
"Microsoft.ZuneVideo" # Movies & TV
"Microsoft.SkypeApp"
"Microsoft.People"
"Microsoft.Messaging"
"Microsoft.OneConnect"
"Microsoft.NetworkSpeedTest"
# 3D / Creative (not needed)
"Microsoft.3DBuilder"
"Microsoft.Microsoft3DViewer"
"Microsoft.MSPaint" # Paint 3D (not regular Paint)
"Microsoft.Print3D"
"Microsoft.MixedReality.Portal"
# Third-party bloat
"Amazon.com.Amazon"
"AutodeskSketchBook"
"DrawboardPDF"
"Duolingo-LearnLanguagesforFree"
"NYTCrossword"
"OneCalendar"
"PhototasticCollage"
"PicsArt-PhotoStudio"
"PolarrPhotoEditorAcademicEdition"
"WinZipUniversal"
"Wunderlist"
"AdobeSystemsIncorporated.AdobePhotoshopExpress"
"ActiproSoftwareLLC"
"CyberLinkMediaSuiteEssentials"
"fitbit"
"Sidia.LiveWallpaper"
) -join ", "
# --- Run via one-liner (download + execute) ---
& ([scriptblock]::Create((irm "https://debloat.raphi.re/"))) -RemoveApps -Apps $apps

View File

@@ -0,0 +1,161 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 0: Export all configuration snapshots from CS-SERVER before migration.
.DESCRIPTION
Exports AD users/computers/groups, DNS records, NPS config, and file share
permissions to D:\Shares\IT\Backups\. Run on CS-SERVER via ScreenConnect.
.NOTES
Run BEFORE making any changes. This is the safety net.
#>
$BackupRoot = "D:\Shares\IT\Backups"
$Timestamp = Get-Date -Format "yyyy-MM-dd_HHmm"
Write-Host "=== Phase 0: Configuration Export ===" -ForegroundColor Cyan
Write-Host "Timestamp: $Timestamp"
Write-Host ""
# --- Create backup directories ---
$dirs = @("AD", "DNS", "NPS", "Permissions", "pfSense", "SynologyDrive-Audit")
foreach ($d in $dirs) {
New-Item -Path "$BackupRoot\$d" -ItemType Directory -Force | Out-Null
}
Write-Host "[OK] Backup directories created at $BackupRoot" -ForegroundColor Green
# --- AD Export ---
Write-Host "`n--- Exporting Active Directory ---" -ForegroundColor Yellow
try {
Import-Module ActiveDirectory -ErrorAction Stop
Get-ADUser -Filter * -Properties * |
Export-Csv "$BackupRoot\AD\AD-Users_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] AD Users exported" -ForegroundColor Green
Get-ADComputer -Filter * -Properties * |
Export-Csv "$BackupRoot\AD\AD-Computers_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] AD Computers exported" -ForegroundColor Green
Get-ADGroup -Filter * | ForEach-Object {
$g = $_
Get-ADGroupMember $g -ErrorAction SilentlyContinue |
Select-Object @{N='Group';E={$g.Name}}, Name, SamAccountName
} | Export-Csv "$BackupRoot\AD\AD-Groups_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] AD Groups exported" -ForegroundColor Green
Get-ADOrganizationalUnit -Filter * -Properties * |
Export-Csv "$BackupRoot\AD\AD-OUs_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] AD OUs exported" -ForegroundColor Green
}
catch {
Write-Host "[ERROR] AD export failed: $_" -ForegroundColor Red
}
# --- DNS Export ---
Write-Host "`n--- Exporting DNS ---" -ForegroundColor Yellow
try {
Import-Module DnsServer -ErrorAction Stop
Get-DnsServerResourceRecord -ZoneName "cascades.local" |
Export-Csv "$BackupRoot\DNS\DNS-Records_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] DNS records exported" -ForegroundColor Green
Get-DnsServerZone |
Export-Csv "$BackupRoot\DNS\DNS-Zones_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] DNS zones exported" -ForegroundColor Green
Get-DnsServerForwarder |
Export-Csv "$BackupRoot\DNS\DNS-Forwarders_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] DNS forwarders exported" -ForegroundColor Green
}
catch {
Write-Host "[ERROR] DNS export failed: $_" -ForegroundColor Red
}
# --- NPS/RADIUS Export ---
Write-Host "`n--- Exporting NPS/RADIUS ---" -ForegroundColor Yellow
try {
$npsFile = "$BackupRoot\NPS\nps-config_$Timestamp.xml"
$result = netsh nps export filename="$npsFile" exportPSK=YES 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "[OK] NPS config exported" -ForegroundColor Green
} else {
Write-Host "[WARN] NPS export returned: $result" -ForegroundColor Yellow
}
}
catch {
Write-Host "[WARN] NPS export failed (may not be installed): $_" -ForegroundColor Yellow
}
# --- File Share Permissions Export ---
Write-Host "`n--- Exporting File Share Permissions ---" -ForegroundColor Yellow
try {
$shares = Get-SmbShare | Where-Object { $_.Path -like "D:\*" }
foreach ($s in $shares) {
$shareName = $s.Name -replace '[\\/:*?"<>|]', '_'
# NTFS permissions via icacls
icacls $s.Path /save "$BackupRoot\Permissions\${shareName}-icacls_$Timestamp.txt" /T /C 2>$null
Write-Host " [OK] NTFS permissions for $($s.Name)" -ForegroundColor Green
# SMB share-level permissions
Get-SmbShareAccess -Name $s.Name |
Export-Csv "$BackupRoot\Permissions\${shareName}-SMB_$Timestamp.csv" -NoTypeInformation
Write-Host " [OK] SMB permissions for $($s.Name)" -ForegroundColor Green
}
# Also export the share list itself
Get-SmbShare | Select-Object Name, Path, Description, CurrentUsers |
Export-Csv "$BackupRoot\Permissions\AllShares_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] Share list exported" -ForegroundColor Green
}
catch {
Write-Host "[ERROR] Share permissions export failed: $_" -ForegroundColor Red
}
# --- SynologyDrive Audit ---
Write-Host "`n--- Auditing SynologyDrive ---" -ForegroundColor Yellow
if (Test-Path "D:\Shares\SynologyDrive") {
Get-ChildItem "D:\Shares\SynologyDrive" -Depth 2 |
Select-Object FullName, Length, LastWriteTime, PSIsContainer |
Export-Csv "$BackupRoot\SynologyDrive-Audit\SynologyDrive-Contents_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] SynologyDrive structure exported" -ForegroundColor Green
# Summary
$stats = Get-ChildItem "D:\Shares\SynologyDrive" -Recurse -File -ErrorAction SilentlyContinue
Write-Host " Total files: $($stats.Count)" -ForegroundColor Cyan
Write-Host " Total size: $([math]::Round(($stats | Measure-Object Length -Sum).Sum / 1GB, 2)) GB" -ForegroundColor Cyan
} else {
Write-Host "[WARN] D:\Shares\SynologyDrive not found" -ForegroundColor Yellow
}
# --- GPO Export ---
Write-Host "`n--- Exporting GPO state ---" -ForegroundColor Yellow
try {
New-Item -Path "$BackupRoot\GPO" -ItemType Directory -Force | Out-Null
Get-GPO -All | ForEach-Object {
$_ | Get-GPOReport -ReportType XML -Path "$BackupRoot\GPO\$($_.DisplayName)_$Timestamp.xml"
}
Get-GPO -All | Select-Object DisplayName, Id, GpoStatus, CreationTime, ModificationTime |
Export-Csv "$BackupRoot\GPO\GPO-List_$Timestamp.csv" -NoTypeInformation
Write-Host "[OK] GPO reports exported" -ForegroundColor Green
}
catch {
Write-Host "[WARN] GPO export failed: $_" -ForegroundColor Yellow
}
# --- Summary ---
Write-Host "`n=== Export Complete ===" -ForegroundColor Cyan
Write-Host "All backups saved to: $BackupRoot" -ForegroundColor Green
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Yellow
Write-Host " 1. Download pfSense config XML manually (Diagnostics > Backup & Restore)"
Write-Host " 2. Save it to $BackupRoot\pfSense\"
Write-Host " 3. Review SynologyDrive audit output"
Write-Host " 4. Proceed to Phase 1 (Network Foundation)"

View File

@@ -0,0 +1,135 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 0: Quick remote health checks on CS-SERVER.
.DESCRIPTION
Checks disk health (Dell OpenManage), identifies unknown listening ports,
audits IIS websites, and reports general server health.
Run on CS-SERVER via ScreenConnect.
.NOTES
Run during Phase 0 / Session 3 (2026-03-07) while backup is in progress.
#>
Write-Host "=== CS-SERVER Remote Health Checks ===" -ForegroundColor Cyan
Write-Host "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Host ""
# --- Disk Health (Dell OpenManage) ---
Write-Host "--- Disk Health ---" -ForegroundColor Yellow
$omreport = "C:\Program Files\Dell\SysMgt\oma\bin\omreport.exe"
if (Test-Path $omreport) {
Write-Host "[Physical Disks]" -ForegroundColor Cyan
& $omreport storage pdisk controller=0
Write-Host "`n[Virtual Disks]" -ForegroundColor Cyan
& $omreport storage vdisk controller=0
Write-Host "`n[Controller Battery]" -ForegroundColor Cyan
& $omreport storage battery controller=0
} else {
Write-Host "[WARN] Dell OpenManage CLI not found at: $omreport" -ForegroundColor Yellow
Write-Host "Try these alternatives:" -ForegroundColor Yellow
Write-Host " - OpenManage web UI: https://192.168.2.254:1311"
# Try alternate paths
$altPaths = @(
"C:\Program Files (x86)\Dell\SysMgt\oma\bin\omreport.exe",
"C:\Program Files\Dell\OpenManage\oma\bin\omreport.exe"
)
foreach ($p in $altPaths) {
if (Test-Path $p) {
Write-Host " - Found at: $p" -ForegroundColor Green
}
}
# Fallback: Windows disk health
Write-Host "`n[Windows Disk Health - Fallback]" -ForegroundColor Cyan
Get-PhysicalDisk | Select-Object DeviceId, FriendlyName, MediaType, Size, HealthStatus, OperationalStatus | Format-Table -AutoSize
Get-Volume | Where-Object { $_.DriveLetter } | Select-Object DriveLetter, FileSystemLabel, FileSystem, @{N='SizeGB';E={[math]::Round($_.Size/1GB,1)}}, @{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,1)}}, HealthStatus | Format-Table -AutoSize
}
# --- Unknown Listening Ports ---
Write-Host "`n--- Unknown Port Identification ---" -ForegroundColor Yellow
$unknownPorts = @(5504, 6783, 8019)
foreach ($port in $unknownPorts) {
$conns = Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue
if ($conns) {
foreach ($conn in $conns) {
$proc = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue
$svc = Get-CimInstance Win32_Service | Where-Object { $_.ProcessId -eq $conn.OwningProcess } | Select-Object -First 1
Write-Host "Port $port`:" -ForegroundColor Green -NoNewline
Write-Host " PID=$($conn.OwningProcess), Process=$($proc.ProcessName), Service=$($svc.Name)"
if ($proc.Path) { Write-Host " Path: $($proc.Path)" }
Write-Host " Local: $($conn.LocalAddress):$($conn.LocalPort) State: $($conn.State)"
}
} else {
Write-Host "Port $port`: No active listener" -ForegroundColor Yellow
}
}
# --- IIS Websites ---
Write-Host "`n--- IIS Websites ---" -ForegroundColor Yellow
try {
Import-Module WebAdministration -ErrorAction Stop
$sites = Get-Website
if ($sites) {
foreach ($site in $sites) {
Write-Host "Site: $($site.Name)" -ForegroundColor Green
Write-Host " State: $($site.State)"
Write-Host " Path: $($site.PhysicalPath)"
Write-Host " Bindings: $(($site.bindings.Collection | ForEach-Object { $_.bindingInformation }) -join ', ')"
Write-Host " App Pool: $($site.applicationPool)"
}
} else {
Write-Host "No websites configured in IIS" -ForegroundColor Yellow
}
} catch {
Write-Host "[WARN] WebAdministration module not available: $_" -ForegroundColor Yellow
}
# --- General Server Health ---
Write-Host "`n--- Server Health Summary ---" -ForegroundColor Yellow
$os = Get-CimInstance Win32_OperatingSystem
$uptime = (Get-Date) - $os.LastBootUpTime
Write-Host "Hostname: $env:COMPUTERNAME"
Write-Host "OS: $($os.Caption) $($os.Version)"
Write-Host "Last Boot: $($os.LastBootUpTime) (uptime: $($uptime.Days)d $($uptime.Hours)h $($uptime.Minutes)m)"
Write-Host "Timezone: $(Get-TimeZone | Select-Object -ExpandProperty DisplayName)"
# Memory
$totalMemGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1)
$freeMemGB = [math]::Round($os.FreePhysicalMemory / 1MB, 1)
$usedMemGB = [math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 1)
Write-Host "Memory: ${usedMemGB} GB used / ${totalMemGB} GB total (${freeMemGB} GB free)"
# Disk space
Write-Host "`nDisk Space:"
Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | ForEach-Object {
$totalGB = [math]::Round(($_.Used + $_.Free) / 1GB, 1)
$usedGB = [math]::Round($_.Used / 1GB, 1)
$freeGB = [math]::Round($_.Free / 1GB, 1)
$pct = [math]::Round($_.Used / ($_.Used + $_.Free) * 100, 0)
Write-Host " $($_.Name): ${usedGB} GB / ${totalGB} GB (${freeGB} GB free, ${pct}% used)"
}
# --- DNS Forwarder Verification ---
Write-Host "`n--- DNS Forwarder Verification ---" -ForegroundColor Yellow
try {
$fwd = Get-DnsServerForwarder
Write-Host "DNS Forwarders:" -ForegroundColor Green
$fwd.IPAddress | ForEach-Object { Write-Host " $_" }
if ($fwd.IPAddress -contains "192.168.0.1") {
Write-Host " [OK] pfSense (192.168.0.1) is configured as forwarder" -ForegroundColor Green
} else {
Write-Host " [WARN] pfSense (192.168.0.1) is NOT a forwarder!" -ForegroundColor Red
}
} catch {
Write-Host "[WARN] Could not check DNS forwarders: $_" -ForegroundColor Yellow
}
Write-Host "`n=== Checks Complete ===" -ForegroundColor Cyan

View File

@@ -0,0 +1,316 @@
#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

View File

@@ -0,0 +1,123 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 2.1: DNS cleanup on CS-SERVER.
.DESCRIPTION
Removes stale DNS records, fixes DomainDnsZones/ForestDnsZones,
enables scavenging, and creates reverse lookup zones.
Run on CS-SERVER via ScreenConnect.
#>
Import-Module DnsServer -ErrorAction Stop
Import-Module ActiveDirectory -ErrorAction Stop
$Zone = "cascades.local"
Write-Host "=== Phase 2.1: DNS Cleanup ===" -ForegroundColor Cyan
Write-Host ""
# --- Remove stale A records ---
Write-Host "--- Removing stale A records ---" -ForegroundColor Yellow
$staleRecords = @(
@{ Name = "@"; IP = "192.168.0.5" }
@{ Name = "@"; IP = "192.168.2.59" }
@{ Name = "CRYSTAL-PC"; IP = "192.168.5.115" }
@{ Name = "CS-QB"; IP = "192.168.5.29" }
@{ Name = "DESKTOP-1ISF081"; IP = "192.168.5.30" }
@{ Name = "DomainDnsZones"; IP = "192.168.0.5" }
@{ Name = "DomainDnsZones"; IP = "192.168.2.59" }
@{ Name = "ForestDnsZones"; IP = "192.168.0.5" }
@{ Name = "ForestDnsZones"; IP = "192.168.2.59" }
)
foreach ($rec in $staleRecords) {
try {
Remove-DnsServerResourceRecord -ZoneName $Zone -RRType "A" -Name $rec.Name -RecordData $rec.IP -Force -ErrorAction Stop
Write-Host " [OK] Removed $($rec.Name) -> $($rec.IP)" -ForegroundColor Green
}
catch {
Write-Host " [SKIP] $($rec.Name) -> $($rec.IP) not found or already removed" -ForegroundColor DarkGray
}
}
# --- Fix DomainDnsZones/ForestDnsZones ---
Write-Host "`n--- Fixing DomainDnsZones/ForestDnsZones ---" -ForegroundColor Yellow
try {
Add-DnsServerResourceRecordA -ZoneName $Zone -Name "DomainDnsZones" -IPv4Address "192.168.2.254" -ErrorAction Stop
Write-Host " [OK] Added DomainDnsZones -> 192.168.2.254" -ForegroundColor Green
}
catch {
Write-Host " [SKIP] DomainDnsZones -> 192.168.2.254 already exists" -ForegroundColor DarkGray
}
try {
Add-DnsServerResourceRecordA -ZoneName $Zone -Name "ForestDnsZones" -IPv4Address "192.168.2.254" -ErrorAction Stop
Write-Host " [OK] Added ForestDnsZones -> 192.168.2.254" -ForegroundColor Green
}
catch {
Write-Host " [SKIP] ForestDnsZones -> 192.168.2.254 already exists" -ForegroundColor DarkGray
}
# --- Enable scavenging ---
Write-Host "`n--- Enabling DNS Scavenging ---" -ForegroundColor Yellow
try {
Set-DnsServerScavenging -ScavengingState $true -ScavengingInterval 7.00:00:00 -ErrorAction Stop
Write-Host " [OK] Server-level scavenging enabled (7-day interval)" -ForegroundColor Green
}
catch {
Write-Host " [ERROR] Failed to enable scavenging: $_" -ForegroundColor Red
}
try {
Set-DnsServerZoneAging -Name $Zone -Aging $true -ErrorAction Stop
Write-Host " [OK] Zone aging enabled on $Zone" -ForegroundColor Green
}
catch {
Write-Host " [ERROR] Failed to enable zone aging: $_" -ForegroundColor Red
}
# --- Create reverse lookup zones ---
Write-Host "`n--- Creating Reverse Lookup Zones ---" -ForegroundColor Yellow
# 192.168.0.0/22 - covers 192.168.0.x through 192.168.3.x
# /22 means we need individual /24 reverse zones for each subnet
$reverseSubnets = @("192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24", "192.168.3.0/24")
foreach ($subnet in $reverseSubnets) {
try {
Add-DnsServerPrimaryZone -NetworkId $subnet -ReplicationScope "Domain" -DynamicUpdate "Secure" -ErrorAction Stop
Write-Host " [OK] Created reverse zone for $subnet" -ForegroundColor Green
}
catch {
Write-Host " [SKIP] Reverse zone for $subnet already exists or failed: $_" -ForegroundColor DarkGray
}
}
# 10.0.20.0/24 - INTERNAL VLAN
try {
Add-DnsServerPrimaryZone -NetworkId "10.0.20.0/24" -ReplicationScope "Domain" -DynamicUpdate "Secure" -ErrorAction Stop
Write-Host " [OK] Created reverse zone for 10.0.20.0/24" -ForegroundColor Green
}
catch {
Write-Host " [SKIP] Reverse zone for 10.0.20.0/24 already exists or failed: $_" -ForegroundColor DarkGray
}
# --- Verify ---
Write-Host "`n--- Verification ---" -ForegroundColor Yellow
Write-Host "`nCurrent A records for zone root:" -ForegroundColor Cyan
Get-DnsServerResourceRecord -ZoneName $Zone -Name "@" -RRType "A" | Format-Table -AutoSize
Write-Host "DomainDnsZones records:" -ForegroundColor Cyan
Get-DnsServerResourceRecord -ZoneName $Zone -Name "DomainDnsZones" -RRType "A" | Format-Table -AutoSize
Write-Host "ForestDnsZones records:" -ForegroundColor Cyan
Get-DnsServerResourceRecord -ZoneName $Zone -Name "ForestDnsZones" -RRType "A" | Format-Table -AutoSize
Write-Host "Reverse lookup zones:" -ForegroundColor Cyan
Get-DnsServerZone | Where-Object { $_.IsReverseLookupZone } | Format-Table ZoneName, ZoneType, DynamicUpdate -AutoSize
Write-Host "`n=== DNS Cleanup Complete ===" -ForegroundColor Cyan
Write-Host "Next: Run phase2-ad-setup.ps1" -ForegroundColor Green

View File

@@ -0,0 +1,233 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 2.4: Set up file share permissions on CS-SERVER.
.DESCRIPTION
Creates SMB shares for folders synced from Synology and sets NTFS
permissions using security groups from phase2-ad-setup.ps1.
Run on CS-SERVER via ScreenConnect AFTER phase2-sync-synology.ps1.
.NOTES
Permissions mapped from Synology NAS permission report.
Existing shares (Culinary, IT, Receptionist, directoryshare) are updated.
New shares (Management, SalesDept, Server, chat, Public, homes) are created.
SaleShare (old) is left alone — SalesDept is the real folder.
#>
Import-Module ActiveDirectory -ErrorAction Stop
Write-Host "=== Phase 2.4: File Share Permissions ===" -ForegroundColor Cyan
Write-Host ""
$DestRoot = "D:\Shares"
# --- Share definitions ---
# Each entry: share Name, folder Path, security group for RW, description
$shares = @(
# Synology-sourced shares (new on CS-SERVER)
@{
Name = "Management"
Path = "$DestRoot\Management"
Group = "CASCADES\SG-Management-RW"
Desc = "Management share (from Synology)"
},
@{
Name = "SalesDept"
Path = "$DestRoot\SalesDept"
Group = "CASCADES\SG-Sales-RW"
Desc = "Sales department (from Synology)"
},
@{
Name = "Server"
Path = "$DestRoot\Server"
Group = "CASCADES\SG-Server-RW"
Desc = "Server/office share (from Synology)"
},
@{
Name = "chat"
Path = "$DestRoot\chat"
Group = "CASCADES\SG-Chat-RW"
Desc = "Chat share (from Synology)"
},
@{
Name = "Public"
Path = "$DestRoot\Public"
Group = $null # Everyone gets access
Desc = "Public share (all users)"
},
# Existing shares on CS-SERVER (update permissions)
@{
Name = "Culinary"
Path = "$DestRoot\Culinary"
Group = "CASCADES\SG-Culinary-RW"
Desc = "Culinary department"
},
@{
Name = "IT"
Path = "$DestRoot\IT"
Group = "CASCADES\SG-IT-RW"
Desc = "IT department"
},
@{
Name = "Receptionist"
Path = "$DestRoot\Receptionist"
Group = "CASCADES\SG-Receptionist-RW"
Desc = "Receptionist/front desk"
},
@{
Name = "directoryshare"
Path = "$DestRoot\directoryshare"
Group = "CASCADES\SG-Directory-RW"
Desc = "Directory share"
}
)
# --- Process each share ---
foreach ($s in $shares) {
Write-Host "`n--- $($s.Name) ---" -ForegroundColor Yellow
# Check path exists
if (-not (Test-Path $s.Path)) {
Write-Host " [SKIP] Path not found: $($s.Path) — run phase2-sync-synology.ps1 first" -ForegroundColor Yellow
continue
}
# Set NTFS permissions
try {
$acl = Get-Acl $s.Path
$acl.SetAccessRuleProtection($true, $false) # Break inheritance
# SYSTEM: Full Control
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"SYSTEM", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)))
# Domain Admins: Full Control
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"CASCADES\Domain Admins", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)))
if ($s.Group) {
# Specific group: Modify
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
$s.Group, "Modify", "ContainerInherit,ObjectInherit", "None", "Allow"
)))
# Read-only group
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"CASCADES\SG-AllShares-RO", "ReadAndExecute", "ContainerInherit,ObjectInherit", "None", "Allow"
)))
} else {
# Public share: Authenticated Users get Modify
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"Authenticated Users", "Modify", "ContainerInherit,ObjectInherit", "None", "Allow"
)))
}
Set-Acl $s.Path $acl
Write-Host " [OK] NTFS permissions set" -ForegroundColor Green
}
catch {
Write-Host " [ERROR] NTFS permissions failed: $_" -ForegroundColor Red
continue
}
# Create or update SMB share
try {
$existingShare = Get-SmbShare -Name $s.Name -ErrorAction SilentlyContinue
if (-not $existingShare) {
New-SmbShare -Name $s.Name -Path $s.Path -FullAccess "Authenticated Users" -Description $s.Desc
Write-Host " [OK] Created SMB share: \\CS-SERVER\$($s.Name)" -ForegroundColor Green
} else {
Grant-SmbShareAccess -Name $s.Name -AccountName "Authenticated Users" -AccessRight Full -Force | Out-Null
Write-Host " [OK] Updated SMB share: \\CS-SERVER\$($s.Name)" -ForegroundColor Green
}
}
catch {
Write-Host " [ERROR] SMB share failed: $_" -ForegroundColor Red
}
}
# --- Set up homes share for folder redirection ---
Write-Host "`n--- homes (Folder Redirection) ---" -ForegroundColor Yellow
$homesPath = "$DestRoot\homes"
if (Test-Path $homesPath) {
# Special permissions for homes: users create their own folders
try {
$acl = Get-Acl $homesPath
$acl.SetAccessRuleProtection($true, $false)
# SYSTEM: Full Control
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"SYSTEM", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)))
# Domain Admins: Full Control
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"CASCADES\Domain Admins", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow"
)))
# Authenticated Users: Create folders only (at root level)
# This lets folder redirection auto-create user folders
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"Authenticated Users", "CreateDirectories", "ThisFolder", "None", "Allow"
)))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"Authenticated Users", "ListDirectory", "ThisFolder", "None", "Allow"
)))
# CREATOR OWNER: Full Control on subfolders/files only
# This gives each user full control of their own folder
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
"CREATOR OWNER", "FullControl", "ContainerInherit,ObjectInherit", "InheritOnly", "Allow"
)))
Set-Acl $homesPath $acl
Write-Host " [OK] NTFS permissions set (folder redirection ready)" -ForegroundColor Green
}
catch {
Write-Host " [ERROR] homes NTFS permissions failed: $_" -ForegroundColor Red
}
# Create SMB share
try {
$existingShare = Get-SmbShare -Name "homes" -ErrorAction SilentlyContinue
if (-not $existingShare) {
New-SmbShare -Name "homes" -Path $homesPath -FullAccess "Authenticated Users" `
-Description "User home folders (folder redirection)" `
-FolderEnumerationMode AccessBased # ABE: users only see their own folder
Write-Host " [OK] Created SMB share: \\CS-SERVER\homes (ABE enabled)" -ForegroundColor Green
} else {
Write-Host " [SKIP] homes share already exists" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] homes SMB share failed: $_" -ForegroundColor Red
}
} else {
Write-Host " [SKIP] $homesPath not found — run phase2-sync-synology.ps1 first" -ForegroundColor Yellow
}
# --- Update main Shares share ---
Write-Host "`n--- Updating main Shares ---" -ForegroundColor Yellow
try {
$sharesShare = Get-SmbShare -Name "Shares" -ErrorAction SilentlyContinue
if ($sharesShare) {
Grant-SmbShareAccess -Name "Shares" -AccountName "Authenticated Users" -AccessRight Full -Force | Out-Null
Write-Host " [OK] Updated Shares SMB permissions" -ForegroundColor Green
}
}
catch {
Write-Host " [WARN] Could not update Shares: $_" -ForegroundColor Yellow
}
# --- Summary ---
Write-Host "`n=== File Share Summary ===" -ForegroundColor Cyan
Write-Host "`nAll SMB Shares:" -ForegroundColor Yellow
Get-SmbShare | Where-Object { $_.Path -like "D:\*" } |
Select-Object Name, Path, Description |
Format-Table -AutoSize -Wrap
Write-Host "=== File Share Setup Complete ===" -ForegroundColor Cyan
Write-Host "Next: Run phase2-print-server.ps1" -ForegroundColor Green

View File

@@ -0,0 +1,354 @@
#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

View File

@@ -0,0 +1,123 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 2.4: Set up print server on CS-SERVER.
.DESCRIPTION
Creates TCP/IP printer ports for all managed printers.
Drivers must be installed manually before uncommenting the Add-Printer lines.
Run on CS-SERVER via ScreenConnect.
.NOTES
Download drivers from manufacturer websites FIRST:
- Epson ET-5800: https://epson.com/Support/Printers/All-In-Ones/ET-Series/Epson-ET-5800/s/SPT_C11CJ30201
- Canon MF455DW: https://www.usa.canon.com/support/p/imageclass-mf455dw
- Canon MF451CDW: https://www.usa.canon.com/support/p/imageclass-mf451dw
- Brother MFC-L8900CDW: https://www.brother-usa.com/support/mfcl8900cdw
- Konica Minolta Bizhub C368: https://www.konicaminolta.us/en-us/support/download-centre
Install drivers on CS-SERVER, then uncomment the Add-Printer section below.
#>
Write-Host "=== Phase 2.4: Print Server Setup ===" -ForegroundColor Cyan
Write-Host ""
# --- Ensure Print Server feature is installed ---
Write-Host "--- Checking Print Server role ---" -ForegroundColor Yellow
$printFeature = Get-WindowsFeature -Name Print-Server -ErrorAction SilentlyContinue
if ($printFeature -and -not $printFeature.Installed) {
Install-WindowsFeature -Name Print-Server -IncludeManagementTools
Write-Host " [OK] Print Server role installed" -ForegroundColor Green
} else {
Write-Host " [OK] Print Server role already installed" -ForegroundColor Green
}
# --- Define printers ---
$printers = @(
@{ Name = "Front Desk - Epson ET-5800"; IP = "192.168.2.147"; ShareName = "FrontDesk-Epson"; Driver = "EPSON ET-5800 Series" }
@{ Name = "Business Office - Canon MF455DW"; IP = "192.168.3.227"; ShareName = "BizOffice-Canon"; Driver = "Canon Generic Plus UFR II" }
@{ Name = "Marketing - Brother MFC-L8900CDW"; IP = "192.168.2.21"; ShareName = "Marketing-Brother"; Driver = "Brother MFC-L8900CDW series" }
@{ Name = "206 Health - Bizhub C368"; IP = "192.168.1.138"; ShareName = "Health206-Bizhub"; Driver = "KONICA MINOLTA Universal PCL" }
@{ Name = "206 Nurse Station - Brother MFC-L8900CDW"; IP = "10.0.20.69"; ShareName = "Health206-Brother"; Driver = "Brother MFC-L8900CDW series" }
@{ Name = "MemCare MedTech - Brother (model TBD)"; IP = "192.168.2.53"; ShareName = "MemCare-Brother"; Driver = "TBD" }
@{ Name = "MemCare Director - Canon MF451CDW"; IP = "192.168.3.52"; ShareName = "MemDir-Canon"; Driver = "Canon Generic Plus UFR II" }
@{ Name = "Kitchen Printer"; IP = "192.168.0.121"; ShareName = "Kitchen"; Driver = "TBD" }
)
# --- Create TCP/IP printer ports ---
Write-Host "`n--- Creating Printer Ports ---" -ForegroundColor Yellow
foreach ($p in $printers) {
$portName = "TCP_$($p.IP)"
try {
$existing = Get-PrinterPort -Name $portName -ErrorAction SilentlyContinue
if (-not $existing) {
Add-PrinterPort -Name $portName -PrinterHostAddress $p.IP
Write-Host " [OK] Created port: $portName ($($p.Name))" -ForegroundColor Green
} else {
Write-Host " [SKIP] Port $portName already exists" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] Failed to create port $portName : $_" -ForegroundColor Red
}
}
# --- Test connectivity to each printer ---
Write-Host "`n--- Testing Printer Connectivity ---" -ForegroundColor Yellow
foreach ($p in $printers) {
$result = Test-Connection -ComputerName $p.IP -Count 1 -Quiet -ErrorAction SilentlyContinue
if ($result) {
Write-Host " [OK] $($p.Name) ($($p.IP)) - reachable" -ForegroundColor Green
} else {
Write-Host " [WARN] $($p.Name) ($($p.IP)) - NOT reachable" -ForegroundColor Yellow
}
}
# --- Add shared printers (UNCOMMENT after installing drivers) ---
<#
Write-Host "`n--- Creating Shared Printers ---" -ForegroundColor Yellow
foreach ($p in $printers) {
if ($p.Driver -eq "TBD") {
Write-Host " [SKIP] $($p.Name) - driver not specified" -ForegroundColor Yellow
continue
}
$portName = "TCP_$($p.IP)"
try {
$existing = Get-Printer -Name $p.Name -ErrorAction SilentlyContinue
if (-not $existing) {
Add-Printer -Name $p.Name -DriverName $p.Driver -PortName $portName -Shared -ShareName $p.ShareName -Published
Write-Host " [OK] Created printer: $($p.Name) (\\CS-SERVER\$($p.ShareName))" -ForegroundColor Green
} else {
Write-Host " [SKIP] Printer $($p.Name) already exists" -ForegroundColor DarkGray
}
}
catch {
Write-Host " [ERROR] Failed to create $($p.Name): $_" -ForegroundColor Red
Write-Host " Verify driver '$($p.Driver)' is installed: Get-PrinterDriver" -ForegroundColor Yellow
}
}
#>
# --- Summary ---
Write-Host "`n=== Print Server Summary ===" -ForegroundColor Cyan
Write-Host "`nPrinter Ports:" -ForegroundColor Yellow
Get-PrinterPort | Where-Object { $_.Name -like "TCP_*" } |
Select-Object Name, PrinterHostAddress |
Format-Table -AutoSize
Write-Host "Installed Printer Drivers:" -ForegroundColor Yellow
Get-PrinterDriver | Select-Object Name | Format-Table -AutoSize
Write-Host "Shared Printers:" -ForegroundColor Yellow
Get-Printer | Where-Object { $_.Shared } |
Select-Object Name, PortName, ShareName, DriverName |
Format-Table -AutoSize
Write-Host "`n=== Print Server Setup Complete ===" -ForegroundColor Cyan
Write-Host "Next steps:" -ForegroundColor Green
Write-Host " 1. Download and install printer drivers (see .NOTES in script header)"
Write-Host " 2. Uncomment the 'Add shared printers' section and re-run"
Write-Host " 3. Print a test page from CS-SERVER to each printer"
Write-Host " 4. Create GPOs in GPMC (see phase2-server-prep.md section 2.5)"

View File

@@ -0,0 +1,147 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 2.3: Sync data from Synology NAS to CS-SERVER.
.DESCRIPTION
Uses robocopy to pull all shared folder data from the Synology NAS
(192.168.0.120) to D:\Shares on CS-SERVER. Creates missing folders
and SMB shares. Run on CS-SERVER via ScreenConnect.
.NOTES
- Synology must be accessible at \\192.168.0.120
- Run BEFORE phase2-file-shares.ps1 (permissions are set after sync)
- First run may take hours depending on data size
- Subsequent runs are incremental (robocopy only copies changes)
- /MIR is NOT used to avoid accidental deletion — uses /E instead
#>
$NAS = "192.168.0.120"
$DestRoot = "D:\Shares"
Write-Host "=== Phase 2.3: Synology Data Sync ===" -ForegroundColor Cyan
Write-Host ""
# --- Test NAS connectivity ---
Write-Host "--- Testing NAS connectivity ---" -ForegroundColor Yellow
$ping = Test-Connection -ComputerName $NAS -Count 2 -Quiet -ErrorAction SilentlyContinue
if (-not $ping) {
Write-Host "[FAIL] Cannot reach $NAS — aborting" -ForegroundColor Red
exit 1
}
Write-Host " [OK] $NAS is reachable" -ForegroundColor Green
# Test SMB access
try {
$testPath = Test-Path "\\$NAS\Public" -ErrorAction Stop
if ($testPath) {
Write-Host " [OK] SMB access to \\$NAS works" -ForegroundColor Green
} else {
Write-Host " [WARN] \\$NAS\Public not accessible — may need credentials" -ForegroundColor Yellow
Write-Host " Run: net use \\$NAS /user:admin <password>" -ForegroundColor Yellow
}
}
catch {
Write-Host " [WARN] SMB access test failed: $_" -ForegroundColor Yellow
Write-Host " You may need to map the NAS first:" -ForegroundColor Yellow
Write-Host " net use \\$NAS /user:admin <password>" -ForegroundColor Yellow
}
# --- Define sync jobs ---
# Source (Synology share name) -> Destination folder on CS-SERVER
$syncJobs = @(
@{ Name = "Management"; Source = "\\$NAS\Management"; Dest = "$DestRoot\Management" }
@{ Name = "SalesDept"; Source = "\\$NAS\SalesDept"; Dest = "$DestRoot\SalesDept" }
@{ Name = "Server"; Source = "\\$NAS\Server"; Dest = "$DestRoot\Server" }
@{ Name = "chat"; Source = "\\$NAS\chat"; Dest = "$DestRoot\chat" }
@{ Name = "Public"; Source = "\\$NAS\Public"; Dest = "$DestRoot\Public" }
@{ Name = "homes"; Source = "\\$NAS\homes"; Dest = "$DestRoot\homes" }
)
# --- Create destination folders ---
Write-Host "`n--- Creating destination folders ---" -ForegroundColor Yellow
foreach ($job in $syncJobs) {
if (-not (Test-Path $job.Dest)) {
New-Item -Path $job.Dest -ItemType Directory -Force | Out-Null
Write-Host " [OK] Created $($job.Dest)" -ForegroundColor Green
} else {
Write-Host " [SKIP] $($job.Dest) already exists" -ForegroundColor DarkGray
}
}
# --- Sync each share ---
Write-Host "`n--- Syncing data (this may take a while) ---" -ForegroundColor Yellow
Write-Host ""
$logDir = "$DestRoot\IT\Backups\SyncLogs"
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
foreach ($job in $syncJobs) {
Write-Host "=== Syncing: $($job.Name) ===" -ForegroundColor Cyan
Write-Host " From: $($job.Source)" -ForegroundColor DarkGray
Write-Host " To: $($job.Dest)" -ForegroundColor DarkGray
# Check if source is accessible
if (-not (Test-Path $job.Source)) {
Write-Host " [SKIP] Source not accessible: $($job.Source)" -ForegroundColor Yellow
continue
}
$logFile = "$logDir\sync-$($job.Name)-$(Get-Date -Format 'yyyy-MM-dd_HHmm').log"
# Robocopy options:
# /E = copy subdirectories including empty ones
# /Z = restartable mode (resume on network interruption)
# /COPY:DAT = copy Data, Attributes, Timestamps (no permissions — we set those separately)
# /R:3 = retry 3 times on failure
# /W:5 = wait 5 seconds between retries
# /MT:8 = 8 threads for parallel copy
# /XD = exclude directories (@ prefixed Synology system dirs)
# /LOG = log to file
# /NP = no progress percentage (cleaner log)
# /TEE = output to console AND log file
$roboArgs = @(
$job.Source,
$job.Dest,
"/E", "/Z",
"/COPY:DAT",
"/R:3", "/W:5",
"/MT:8",
"/XD", "@eaDir", "@tmp", "#recycle", "#snapshot",
"/XF", "Thumbs.db", ".DS_Store", "desktop.ini",
"/LOG:$logFile",
"/NP", "/TEE"
)
$startTime = Get-Date
& robocopy @roboArgs
$exitCode = $LASTEXITCODE
$elapsed = (Get-Date) - $startTime
# Robocopy exit codes: 0=no change, 1=files copied, 2=extras, 4=mismatches, 8+=errors
if ($exitCode -lt 8) {
Write-Host " [OK] $($job.Name) synced in $([math]::Round($elapsed.TotalMinutes, 1)) minutes (exit code: $exitCode)" -ForegroundColor Green
} else {
Write-Host " [ERROR] $($job.Name) had errors (exit code: $exitCode) — check $logFile" -ForegroundColor Red
}
Write-Host ""
}
# --- Summary ---
Write-Host "=== Sync Complete ===" -ForegroundColor Cyan
Write-Host ""
Write-Host "Folder sizes:" -ForegroundColor Yellow
foreach ($job in $syncJobs) {
if (Test-Path $job.Dest) {
$size = (Get-ChildItem $job.Dest -Recurse -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum
$sizeGB = [math]::Round($size / 1GB, 2)
$fileCount = (Get-ChildItem $job.Dest -Recurse -File -ErrorAction SilentlyContinue).Count
Write-Host " $($job.Name): $sizeGB GB ($fileCount files)" -ForegroundColor Cyan
}
}
Write-Host ""
Write-Host "Sync logs saved to: $logDir" -ForegroundColor Green
Write-Host "Next: Run phase2-file-shares.ps1 to set permissions and create SMB shares" -ForegroundColor Green

View File

@@ -0,0 +1,151 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 3.1: Domain join a workstation.
.DESCRIPTION
Documents current state, creates local admin backup, verifies DNS,
and joins the machine to cascades.local domain.
Run on each target workstation via ScreenConnect.
.NOTES
Order: DESKTOP-KQSL232 -> CHEF-PC -> SALES4-PC -> MDIRECTOR-PC
Machine will REBOOT after domain join.
#>
param(
[string]$LocalAdminPassword = ""
)
$DomainName = "cascades.local"
$OUPath = "OU=Staff PCs,OU=Workstations,DC=cascades,DC=local"
$MigrationDir = "C:\IT-Migration"
Write-Host "=== Phase 3.1: Domain Join - $env:COMPUTERNAME ===" -ForegroundColor Cyan
Write-Host ""
# --- Step 1: Document current state ---
Write-Host "--- Step 1: Documenting current state ---" -ForegroundColor Yellow
New-Item -Path $MigrationDir -ItemType Directory -Force | Out-Null
$infoFile = "$MigrationDir\pre-join-info_$(Get-Date -Format 'yyyy-MM-dd_HHmm').txt"
"=== Pre-Join State for $env:COMPUTERNAME ===" | Out-File $infoFile
"Timestamp: $(Get-Date)" | Out-File $infoFile -Append
"" | Out-File $infoFile -Append
"--- SYSTEM INFO ---" | Out-File $infoFile -Append
systeminfo | Out-File $infoFile -Append
"--- IP CONFIG ---" | Out-File $infoFile -Append
ipconfig /all | Out-File $infoFile -Append
"--- PRINTERS ---" | Out-File $infoFile -Append
Get-Printer -ErrorAction SilentlyContinue | Format-List | Out-File $infoFile -Append
"--- MAPPED DRIVES ---" | Out-File $infoFile -Append
net use | Out-File $infoFile -Append
"--- LOCAL USERS ---" | Out-File $infoFile -Append
Get-LocalUser | Format-Table | Out-File $infoFile -Append
"--- INSTALLED SOFTWARE ---" | Out-File $infoFile -Append
Get-WmiObject Win32_Product | Select-Object Name, Version | Sort-Object Name | Format-Table | Out-File $infoFile -Append
Write-Host " [OK] State documented at $infoFile" -ForegroundColor Green
# --- Step 2: Create local admin backup ---
Write-Host "`n--- Step 2: Creating MSPAdmin local account ---" -ForegroundColor Yellow
$localAdmin = Get-LocalUser -Name "Localadmin" -ErrorAction SilentlyContinue
if (-not $localAdmin) {
if (-not $LocalAdminPassword) {
Write-Host " [INPUT NEEDED] Enter password for Localadmin account:" -ForegroundColor Yellow
$securePassword = Read-Host -AsSecureString
} else {
$securePassword = ConvertTo-SecureString $LocalAdminPassword -AsPlainText -Force
}
New-LocalUser -Name "Localadmin" -Password $securePassword -PasswordNeverExpires -AccountNeverExpires -Description "Local emergency admin - migration rollback" | Out-Null
Add-LocalGroupMember -Group "Administrators" -Member "Localadmin" | Out-Null
Write-Host " [OK] Localadmin local admin created" -ForegroundColor Green
} else {
Write-Host " [SKIP] Localadmin already exists" -ForegroundColor DarkGray
}
# --- Step 3: Verify DNS ---
Write-Host "`n--- Step 3: Verifying DNS resolution ---" -ForegroundColor Yellow
try {
$dns = Resolve-DnsName "cs-server.cascades.local" -ErrorAction Stop
Write-Host " [OK] cs-server.cascades.local resolves to: $($dns.IPAddress -join ', ')" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] Cannot resolve cs-server.cascades.local" -ForegroundColor Red
$currentDns = (Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object { $_.ServerAddresses.Count -gt 0 })
Write-Host " Current DNS servers:" -ForegroundColor Yellow
foreach ($adapter in $currentDns) {
Write-Host " $($adapter.InterfaceAlias): $($adapter.ServerAddresses -join ', ')" -ForegroundColor Yellow
}
Write-Host ""
Write-Host " Options:" -ForegroundColor Cyan
Write-Host " [F] Fix DNS — set primary DNS to 192.168.2.254 (CS-SERVER) and retry" -ForegroundColor Cyan
Write-Host " [A] Abort — exit and fix manually" -ForegroundColor Cyan
$choice = Read-Host " Enter F or A"
if ($choice -eq 'F') {
# Get the active adapter (the one with a default gateway)
$activeAdapter = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null } | Select-Object -First 1
if (-not $activeAdapter) {
Write-Host " [FAIL] No active adapter with a gateway found. Fix manually." -ForegroundColor Red
exit 1
}
$ifIndex = $activeAdapter.InterfaceIndex
$ifName = $activeAdapter.InterfaceAlias
Write-Host " Setting DNS on '$ifName' to 192.168.2.254 (CS-SERVER)..." -ForegroundColor Yellow
Set-DnsClientServerAddress -InterfaceIndex $ifIndex -ServerAddresses @('192.168.2.254')
Clear-DnsClientCache
# Retry resolution
Start-Sleep -Seconds 2
try {
$dns = Resolve-DnsName "cs-server.cascades.local" -ErrorAction Stop
Write-Host " [OK] DNS fixed. cs-server.cascades.local resolves to: $($dns.IPAddress -join ', ')" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] Still cannot resolve after DNS change. Check network connectivity to 192.168.2.254" -ForegroundColor Red
exit 1
}
}
else {
Write-Host " [ABORT] Fix DNS and re-run the script." -ForegroundColor Yellow
exit 1
}
}
# --- Step 4: Confirm and join ---
Write-Host "`n--- Step 4: Domain Join ---" -ForegroundColor Yellow
Write-Host " Computer: $env:COMPUTERNAME" -ForegroundColor Cyan
Write-Host " Domain: $DomainName" -ForegroundColor Cyan
Write-Host " OU: $OUPath" -ForegroundColor Cyan
Write-Host ""
Write-Host " Machine will REBOOT after joining." -ForegroundColor Yellow
Write-Host ""
$confirm = Read-Host " Type 'JOIN' to proceed (anything else to abort)"
if ($confirm -ne "JOIN") {
Write-Host " [ABORT] Domain join cancelled by user" -ForegroundColor Yellow
exit 0
}
try {
Write-Host " Enter domain admin credentials..." -ForegroundColor Yellow
Add-Computer -DomainName $DomainName -OUPath $OUPath -Credential (Get-Credential) -Restart -Force
Write-Host " [OK] Domain join initiated - machine is restarting..." -ForegroundColor Green
}
catch {
Write-Host " [ERROR] Domain join failed: $_" -ForegroundColor Red
Write-Host " Troubleshooting:" -ForegroundColor Yellow
Write-Host " - Verify credentials (domain admin)" -ForegroundColor Yellow
Write-Host " - Check firewall rules allow AD ports from this subnet" -ForegroundColor Yellow
Write-Host " - Run phase3-pre-join-verify.ps1 for diagnostics" -ForegroundColor Yellow
exit 1
}

View File

@@ -0,0 +1,142 @@
<#
.SYNOPSIS
Phase 3.1 Step 5: Post-domain-join verification.
.DESCRIPTION
Verifies GPO application, drive mappings, printer deployment, and network
connectivity after a workstation has been joined to cascades.local.
Run on the joined machine after reboot, logged in with a domain account.
#>
Write-Host "=== Phase 3: Post-Join Verification - $env:COMPUTERNAME ===" -ForegroundColor Cyan
Write-Host "Logged in as: $env:USERDOMAIN\$env:USERNAME"
Write-Host ""
$issues = @()
# --- Domain membership ---
Write-Host "--- Domain Membership ---" -ForegroundColor Yellow
$cs = Get-WmiObject Win32_ComputerSystem
if ($cs.PartOfDomain) {
Write-Host " [OK] Domain: $($cs.Domain)" -ForegroundColor Green
} else {
Write-Host " [FAIL] Not joined to domain!" -ForegroundColor Red
$issues += "Not domain-joined"
}
# --- DC locator ---
Write-Host "`n--- Domain Controller ---" -ForegroundColor Yellow
$nltest = nltest /dsgetdc:cascades.local 2>&1
if ($LASTEXITCODE -eq 0) {
$dcLine = $nltest | Select-String "DC:"
Write-Host " [OK] DC found: $dcLine" -ForegroundColor Green
} else {
Write-Host " [FAIL] Cannot locate domain controller" -ForegroundColor Red
$issues += "Cannot locate DC"
}
# --- GPO ---
Write-Host "`n--- Group Policy ---" -ForegroundColor Yellow
$gpresult = gpresult /r 2>&1
$appliedGPOs = $gpresult | Select-String "CSC -"
if ($appliedGPOs) {
foreach ($gpo in $appliedGPOs) {
Write-Host " [OK] Applied: $($gpo.Line.Trim())" -ForegroundColor Green
}
} else {
Write-Host " [WARN] No CSC GPOs detected - may need gpupdate /force" -ForegroundColor Yellow
$issues += "No CSC GPOs applied"
}
# --- Drive Mappings ---
Write-Host "`n--- Drive Mappings ---" -ForegroundColor Yellow
$expectedDrives = @("S:")
$mappedDrives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.DisplayRoot -like "\\*" }
foreach ($d in $mappedDrives) {
Write-Host " [OK] $($d.Name): -> $($d.DisplayRoot)" -ForegroundColor Green
}
if (-not $mappedDrives) {
Write-Host " [WARN] No mapped drives found - check GPO and logoff/logon" -ForegroundColor Yellow
$issues += "No mapped drives"
}
# SMB access test
try {
$testPath = Test-Path "\\192.168.2.254\Shares" -ErrorAction Stop
if ($testPath) {
Write-Host " [OK] \\CS-SERVER\Shares accessible" -ForegroundColor Green
} else {
Write-Host " [FAIL] \\CS-SERVER\Shares not accessible" -ForegroundColor Red
$issues += "Cannot access \\CS-SERVER\Shares"
}
}
catch {
Write-Host " [FAIL] SMB access error: $_" -ForegroundColor Red
$issues += "SMB access error"
}
# --- Printers ---
Write-Host "`n--- Printers ---" -ForegroundColor Yellow
$printers = Get-Printer -ErrorAction SilentlyContinue
$networkPrinters = $printers | Where-Object { $_.Type -eq "Connection" }
if ($networkPrinters) {
foreach ($p in $networkPrinters) {
Write-Host " [OK] $($p.Name) ($($p.PortName))" -ForegroundColor Green
}
} else {
Write-Host " [WARN] No network printers deployed - check GPO" -ForegroundColor Yellow
$issues += "No network printers"
}
# --- Network ---
Write-Host "`n--- Network Connectivity ---" -ForegroundColor Yellow
# Internet
$internet = Test-Connection -ComputerName "8.8.8.8" -Count 1 -Quiet -ErrorAction SilentlyContinue
if ($internet) {
Write-Host " [OK] Internet: working" -ForegroundColor Green
} else {
Write-Host " [FAIL] Internet: NOT working" -ForegroundColor Red
$issues += "No internet"
}
# DNS
try {
$dns = Resolve-DnsName "cs-server.cascades.local" -ErrorAction Stop
Write-Host " [OK] DNS: cs-server.cascades.local -> $($dns.IPAddress -join ', ')" -ForegroundColor Green
}
catch {
Write-Host " [FAIL] DNS: cannot resolve cs-server.cascades.local" -ForegroundColor Red
$issues += "DNS resolution failed"
}
# Ping DC
$ping = Test-Connection -ComputerName "192.168.2.254" -Count 1 -Quiet -ErrorAction SilentlyContinue
if ($ping) {
Write-Host " [OK] Ping CS-SERVER: reachable" -ForegroundColor Green
} else {
Write-Host " [WARN] Ping CS-SERVER: no response (ICMP may be filtered)" -ForegroundColor Yellow
}
# --- Summary ---
Write-Host "`n========================================" -ForegroundColor Cyan
if ($issues.Count -eq 0) {
Write-Host "ALL CHECKS PASSED" -ForegroundColor Green
} else {
Write-Host "ISSUES FOUND ($($issues.Count)):" -ForegroundColor Red
foreach ($i in $issues) {
Write-Host " - $i" -ForegroundColor Red
}
Write-Host "`nTroubleshooting:" -ForegroundColor Yellow
Write-Host " - Run: gpupdate /force" -ForegroundColor Yellow
Write-Host " - Log off and log on again (for user-level GPOs)" -ForegroundColor Yellow
Write-Host " - Check: gpresult /r (for GPO details)" -ForegroundColor Yellow
}
Write-Host "========================================" -ForegroundColor Cyan

View File

@@ -0,0 +1,121 @@
<#
.SYNOPSIS
Phase 3.0: Pre-join verification from an INTERNAL VLAN machine.
.DESCRIPTION
Tests DNS resolution, network connectivity, and SMB access to CS-SERVER.
Run from any machine on INTERNAL VLAN (10.0.20.0/24) before domain joining.
ALL tests must pass before proceeding with domain join.
#>
Write-Host "=== Phase 3.0: Pre-Join Verification ===" -ForegroundColor Cyan
Write-Host "Running from: $env:COMPUTERNAME ($((Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.IPAddress -notlike '127.*'}).IPAddress -join ', '))"
Write-Host ""
$allPassed = $true
# --- DNS Resolution ---
Write-Host "--- DNS Tests ---" -ForegroundColor Yellow
$dnsTests = @(
@{ Name = "cs-server.cascades.local"; Expected = "192.168.2.254" }
@{ Name = "_ldap._tcp.cascades.local"; Expected = "" }
)
foreach ($test in $dnsTests) {
try {
$result = Resolve-DnsName $test.Name -ErrorAction Stop
if ($test.Expected -and $result.IPAddress -notcontains $test.Expected) {
Write-Host " [WARN] $($test.Name) resolved but not to $($test.Expected): $($result.IPAddress -join ', ')" -ForegroundColor Yellow
} else {
Write-Host " [OK] $($test.Name) resolved: $($result.IPAddress -join ', ')" -ForegroundColor Green
}
}
catch {
Write-Host " [FAIL] $($test.Name) - DNS resolution failed" -ForegroundColor Red
$allPassed = $false
}
}
# --- Network Connectivity ---
Write-Host "`n--- Network Connectivity ---" -ForegroundColor Yellow
$pingTargets = @(
@{ Name = "CS-SERVER"; IP = "192.168.2.254" }
@{ Name = "pfSense"; IP = "192.168.0.1" }
)
foreach ($target in $pingTargets) {
$result = Test-Connection -ComputerName $target.IP -Count 2 -Quiet -ErrorAction SilentlyContinue
if ($result) {
Write-Host " [OK] $($target.Name) ($($target.IP)) - reachable" -ForegroundColor Green
} else {
Write-Host " [FAIL] $($target.Name) ($($target.IP)) - NOT reachable" -ForegroundColor Red
$allPassed = $false
}
}
# --- Port Connectivity ---
Write-Host "`n--- Port Connectivity to CS-SERVER ---" -ForegroundColor Yellow
$ports = @(
@{ Port = 53; Desc = "DNS" }
@{ Port = 88; Desc = "Kerberos" }
@{ Port = 135; Desc = "RPC" }
@{ Port = 389; Desc = "LDAP" }
@{ Port = 445; Desc = "SMB" }
@{ Port = 636; Desc = "LDAPS" }
@{ Port = 3268; Desc = "Global Catalog" }
)
foreach ($p in $ports) {
try {
$result = Test-NetConnection -ComputerName "192.168.2.254" -Port $p.Port -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
if ($result.TcpTestSucceeded) {
Write-Host " [OK] Port $($p.Port) ($($p.Desc)) - open" -ForegroundColor Green
} else {
Write-Host " [FAIL] Port $($p.Port) ($($p.Desc)) - CLOSED/FILTERED" -ForegroundColor Red
$allPassed = $false
}
}
catch {
Write-Host " [FAIL] Port $($p.Port) ($($p.Desc)) - test failed" -ForegroundColor Red
$allPassed = $false
}
}
# --- SMB Access ---
Write-Host "`n--- SMB Share Access ---" -ForegroundColor Yellow
try {
$shares = net view \\192.168.2.254 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host " [OK] net view \\192.168.2.254 succeeded" -ForegroundColor Green
} else {
Write-Host " [FAIL] net view \\192.168.2.254 failed: $shares" -ForegroundColor Red
$allPassed = $false
}
}
catch {
Write-Host " [FAIL] SMB access test failed: $_" -ForegroundColor Red
$allPassed = $false
}
# --- Internet ---
Write-Host "`n--- Internet Access ---" -ForegroundColor Yellow
$internet = Test-Connection -ComputerName "8.8.8.8" -Count 1 -Quiet -ErrorAction SilentlyContinue
if ($internet) {
Write-Host " [OK] Internet connectivity works" -ForegroundColor Green
} else {
Write-Host " [WARN] No internet connectivity" -ForegroundColor Yellow
}
# --- Result ---
Write-Host "`n========================================" -ForegroundColor Cyan
if ($allPassed) {
Write-Host "ALL TESTS PASSED - Safe to proceed with domain join" -ForegroundColor Green
} else {
Write-Host "SOME TESTS FAILED - Fix issues before domain joining" -ForegroundColor Red
Write-Host "Check firewall rules (Phase 1.3) and DNS (Phase 1.4)" -ForegroundColor Yellow
}
Write-Host "========================================" -ForegroundColor Cyan

View File

@@ -0,0 +1,71 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Phase 4.5: Archive the SynologyDrive folder on CS-SERVER.
.DESCRIPTION
Renames the SynologyDrive share folder to indicate it's archived and
should be deleted after 30 days. Run ONLY after all users are confirmed
working with mapped drives to CS-SERVER shares.
#>
$SourcePath = "D:\Shares\SynologyDrive"
$ArchiveName = "_SynologyDrive_ARCHIVE_DeleteAfter30Days"
$ArchivePath = "D:\Shares\$ArchiveName"
$DeleteDate = (Get-Date).AddDays(30).ToString("yyyy-MM-dd")
Write-Host "=== Phase 4.5: Archive SynologyDrive ===" -ForegroundColor Cyan
Write-Host ""
if (-not (Test-Path $SourcePath)) {
Write-Host "[SKIP] $SourcePath does not exist (already archived?)" -ForegroundColor Yellow
if (Test-Path $ArchivePath) {
Write-Host " Archive exists at: $ArchivePath" -ForegroundColor DarkGray
}
exit 0
}
# Check if there's an SMB share pointing to SynologyDrive
$share = Get-SmbShare | Where-Object { $_.Path -eq $SourcePath } -ErrorAction SilentlyContinue
if ($share) {
Write-Host "Removing SMB share: $($share.Name)" -ForegroundColor Yellow
Remove-SmbShare -Name $share.Name -Force
Write-Host " [OK] SMB share removed" -ForegroundColor Green
}
# Get size info
$stats = Get-ChildItem $SourcePath -Recurse -File -ErrorAction SilentlyContinue
$sizeGB = [math]::Round(($stats | Measure-Object Length -Sum).Sum / 1GB, 2)
Write-Host ""
Write-Host "SynologyDrive stats:" -ForegroundColor Cyan
Write-Host " Files: $($stats.Count)" -ForegroundColor Cyan
Write-Host " Size: $sizeGB GB" -ForegroundColor Cyan
Write-Host ""
# Rename
Write-Host "Archiving..." -ForegroundColor Yellow
try {
Rename-Item $SourcePath $ArchiveName
Write-Host "[OK] Renamed to: $ArchivePath" -ForegroundColor Green
Write-Host ""
Write-Host "IMPORTANT:" -ForegroundColor Yellow
Write-Host " Safe to delete after: $DeleteDate" -ForegroundColor Yellow
Write-Host " Set a calendar reminder!" -ForegroundColor Yellow
# Create a readme in the archive
@"
THIS FOLDER IS ARCHIVED
========================
Archived on: $(Get-Date -Format "yyyy-MM-dd HH:mm")
Safe to delete after: $DeleteDate
Reason: Migrated to CS-SERVER mapped drives (Phase 4 of network migration)
If you need to restore:
Rename-Item "$ArchivePath" "SynologyDrive"
"@ | Out-File "$ArchivePath\_ARCHIVE_README.txt"
}
catch {
Write-Host "[ERROR] Failed to archive: $_" -ForegroundColor Red
}
Write-Host "`n=== Archive Complete ===" -ForegroundColor Cyan