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:
@@ -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
|
||||
@@ -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)"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)"
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user