sync: auto-sync from HOWARD-HOME at 2026-04-22 17:39:56

Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-04-22 17:39:56
This commit is contained in:
2026-04-22 17:39:57 -07:00
parent 90d4f386aa
commit 6bd416657c
18 changed files with 1409 additions and 36 deletions

View File

@@ -109,6 +109,13 @@ Run `scripts/phase2-file-shares.ps1` on CS-SERVER (AFTER sync completes).
Creates SMB shares for synced folders and sets NTFS permissions matching Synology access. **HIPAA §164.312(b):** After shares are created, enable Advanced Audit Logging on all PHI-containing shares (Management, Server, homes) to track read/write/delete operations.
**Prerequisite (added 2026-04-22 per `docs/security/hipaa-review-2026-04-22.md` + user-rollout dependency):** Before this script runs, the Synology permission inventory must be captured and translated to AD security group memberships. See `docs/migration/phase4-synology.md` §6.0.16.0.2 for the discovery commands and `docs/migration/synology-permission-inventory.md` (to be created) for the mapping output.
**HIPAA-review additions (must be applied as part of this phase, not deferred):**
- `Set-SmbShare -EncryptData $true` on every share in the table — satisfies Addressable specs §164.312(a)(2)(iv) at-rest and §164.312(e)(2)(ii) in-transit encryption
- NTFS SACL (audit rule) set to audit Success + Failure for ReadData / WriteData / Delete / ChangePermissions on all PHI shares — satisfies Required spec §164.312(b) Audit Controls
- See `phase4-synology.md` §6.0.3 for the exact PowerShell
| Share | NTFS Access | SMB Share |
|---|---|---|
| Management | SG-Management-RW = Modify | `\\CS-SERVER\Management` |

View File

@@ -1,5 +1,86 @@
# Step 6: Synology Transition (~2 hours, remote)
**Pre-requisite before any of the 6.x steps below:** a permission inventory of the live Synology must be captured and translated into AD group memberships, and CS-SERVER's NTFS + SMB permissions on the synced shares must match before users' drives get remapped. That work lives in §6.0.
---
## 6.0 — Permission inventory + CS-SERVER ACL application (pre-cutover)
**Why:** Synology Drive Client has been live-syncing `\\cascadesds\*``D:\Shares\Main` on CS-SERVER since 2026-03-07 (per `phase2-server-prep.md` §4c). Sync copies the files but **not** the permissions — CS-SERVER's shares inherit whatever the script in `phase2-file-shares.ps1` applies, which currently assumes AD security group memberships that are correct for the target state. Those group memberships are empty or incomplete today.
Before users' mapped drives switch from Synology to CS-SERVER, the ACLs on CS-SERVER must replicate current Synology access rights. Do this in three steps.
### 6.0.1 — Discovery (non-destructive, no maintenance window needed)
SSH into `admin@192.168.0.120` (credentials at `clients/cascades-tucson/synology-cascadesds.sops.yaml`). Run:
```bash
sudo synogroup --list
sudo synouser --list
sudo cat /etc/synoinfo.conf | grep -iE 'share|mode'
for share in homes Management SalesDept Server chat Public Culinary IT Receptionist directoryshare; do
echo "=== $share ==="
sudo synoacltool -get /volume1/$share
sudo synoshare --get "$share" 2>/dev/null || true
done
```
Paste the output into a new file `docs/migration/synology-permission-inventory.md` with one section per share. Capture:
- Owner, group, mode (bitmask)
- Per-user ACL entries (read / write / delete / changeperm)
- Per-group ACL entries
- SMB-share-level permission overrides
- Inheritance flags
### 6.0.2 — Map Synology accounts to AD
Produce a translation table in the inventory doc:
| Synology identity | AD identity | Notes |
|---|---|---|
| `admin` | Domain Admins | keep |
| `meredith.kuhn` (Synology local) | `Meredith.Kuhn` (AD) | same human |
| Synology group `Management` | AD `SG-Management-RW` | direct 1:1 |
| Synology group `Marketing` | AD `SG-Sales-RW` | renamed |
| ... | ... | ... |
Any Synology account with no AD equivalent either (a) corresponds to a departed employee and gets dropped, or (b) is a new account we need to create in AD first (feeds back into the user rollout waves).
### 6.0.3 — Apply to CS-SERVER + HIPAA additions
Update `scripts/phase2-file-shares.ps1` inputs to reflect the mapping, then run on CS-SERVER. After shares + NTFS are applied, add HIPAA-review-required settings per `docs/security/hipaa-review-2026-04-22.md`:
```powershell
# Enable SMB3 encryption on every PHI-containing share (addresses 164.312(a)(2)(iv), (e)(2)(ii))
foreach ($share in 'homes','Management','SalesDept','Server','chat','Culinary','IT','Receptionist','directoryshare') {
Set-SmbShare -Name $share -EncryptData $true -Force
}
# Enable Object Access auditing on the PHI file-share root (addresses 164.312(b))
auditpol /set /subcategory:"File System" /success:enable /failure:enable
# NTFS SACL — audit reads+writes on D:\Shares and subtree
$acl = Get-Acl D:\Shares
$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule(
'Everyone','ReadData,WriteData,Delete,ChangePermissions','ContainerInherit,ObjectInherit','None','Success,Failure')
$acl.AddAuditRule($auditRule)
Set-Acl D:\Shares $acl
```
Verify with:
```powershell
Get-SmbShare | Select-Object Name, EncryptData
Get-Acl D:\Shares -Audit | Format-List
auditpol /get /subcategory:"File System"
```
### 6.0.4 — Validation before any user is cut over
Pick one pilot user (Sharon Edwards on DESKTOP-DLTAGOI — already our folder-redirection pilot). Map her CS-SERVER drives manually, open representative files from each share she currently uses on Synology, verify open/save/delete works. Do not touch the GPO drive maps or run §6.1 below until the pilot validates cleanly.
**Rollback at this stage:** no user impact — just don't push the GPO change, and the Synology shares remain authoritative.
---
## 6.1 — Verify drive mappings

View File

@@ -0,0 +1,212 @@
# ============================================================================
# Entra Connect Pre-flight Remediation - CS-SERVER
# ----------------------------------------------------------------------------
# Applies the three items from the 2026-04-22 readiness check:
# 1. Time sync (w32tm) - immediate, no reboot
# 2. TLS 1.2 enforcement (.NET + Schannel) - requires reboot
# 3. Install Windows Server Backup feature - no reboot
#
# Then schedules a reboot at 18:00 local time (Arizona) for Schannel changes
# to take effect. Snapshot of all pre-change state is written to
# D:\Backups\pre-entra-connect-<timestamp>\ for rollback.
#
# To cancel the scheduled reboot at any time before 18:00: shutdown /a
# ----------------------------------------------------------------------------
# Prepared: 2026-04-22
# Source: docs/migration/scripts/entra-connect-preflight-remediation.ps1
# ============================================================================
$ErrorActionPreference = 'Continue'
$TargetRebootHour = 18 # 6 PM local (Arizona)
function Section($n) {
Write-Output ''
Write-Output ('=' * 72)
Write-Output "== $n"
Write-Output ('=' * 72)
}
function Status($ok, $msg) {
$tag = if ($ok) { '[OK] ' } else { '[FAIL]' }
Write-Output "$tag $msg"
}
# ----------------------------------------------------------------------------
Section '0. Pre-flight snapshot (rollback material)'
# ----------------------------------------------------------------------------
$ts = Get-Date -Format 'yyyy-MM-dd-HHmm'
$backupDir = "D:\Backups\pre-entra-connect-$ts"
try {
New-Item -Path $backupDir -ItemType Directory -Force | Out-Null
Status $true "Backup dir: $backupDir"
} catch {
Status $false "Could not create $backupDir - $_"
exit 1
}
# Export registry keys we're about to modify
reg export "HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" "$backupDir\dotnet-64.reg" /y 2>&1 | Out-Null
reg export "HKLM\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319" "$backupDir\dotnet-32.reg" /y 2>&1 | Out-Null
reg export "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" "$backupDir\schannel-protocols-pre.reg" /y 2>&1 | Out-Null
Status $true 'Registry pre-state exported (.NET + Schannel Protocols)'
# Snapshot w32tm state
w32tm /query /configuration 2>&1 | Out-File "$backupDir\w32tm-config-pre.txt"
w32tm /query /status 2>&1 | Out-File "$backupDir\w32tm-status-pre.txt"
w32tm /query /peers 2>&1 | Out-File "$backupDir\w32tm-peers-pre.txt"
Status $true 'w32tm pre-state captured'
# Snapshot recent events (for post-reboot comparison)
try {
Get-WinEvent -LogName System -MaxEvents 200 -ErrorAction Stop |
Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, Message |
Export-Csv "$backupDir\events-system-pre.csv" -NoTypeInformation
Status $true "Event log snapshot saved (200 System events)"
} catch {
Status $false "Event log snapshot: $_"
}
Write-Output ''
Write-Output ("To roll back TLS changes after reboot: " +
"`nreg import $backupDir\dotnet-64.reg `nreg import $backupDir\dotnet-32.reg " +
"`nreg import $backupDir\schannel-protocols-pre.reg `nRestart-Computer")
# ----------------------------------------------------------------------------
Section '1. Time sync fix (w32tm) - no reboot'
# ----------------------------------------------------------------------------
try {
# PDC emulator should be an authoritative time source + sync from external pool
$ntpPeers = 'time.windows.com,0x8 pool.ntp.org,0x8 time.nist.gov,0x8'
& w32tm /config /manualpeerlist:$ntpPeers /syncfromflags:manual /reliable:yes /update | Out-Null
Status $true "Configured w32tm peer list: $ntpPeers"
Restart-Service w32time -Force
Status $true 'Restarted w32time service'
Start-Sleep 4
& w32tm /resync /force 2>&1 | Out-Null
Start-Sleep 3
$statusOut = & w32tm /query /status 2>&1
$statusOut | Out-File "$backupDir\w32tm-status-post.txt"
$statusOut | Select-Object -First 20 | ForEach-Object { Write-Output " $_" }
$refIdLine = $statusOut | Where-Object { $_ -match 'ReferenceId' } | Select-Object -First 1
if ($refIdLine -match 'LOCL') {
Status $false "ReferenceId still LOCL - w32tm has not synced yet. Check firewall for outbound UDP 123."
} else {
Status $true "ReferenceId: $refIdLine (no longer LOCL)"
}
} catch {
Status $false "Time sync fix: $_"
}
# ----------------------------------------------------------------------------
Section '2. TLS 1.2 enforcement (.NET + Schannel) - requires reboot'
# ----------------------------------------------------------------------------
# .NET framework: enable strong crypto + system default TLS versions for both
# 64-bit and 32-bit runtimes. These take effect immediately for new processes.
foreach ($p in 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319',
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319') {
try {
if (-not (Test-Path $p)) { New-Item -Path $p -Force | Out-Null }
New-ItemProperty -Path $p -Name 'SchUseStrongCrypto' -PropertyType DWord -Value 1 -Force | Out-Null
New-ItemProperty -Path $p -Name 'SystemDefaultTlsVersions' -PropertyType DWord -Value 1 -Force | Out-Null
Status $true ".NET TLS keys set at $p"
} catch {
Status $false ".NET TLS at $p - $_"
}
}
# Schannel: disable TLS 1.0 and 1.1 (both Client and Server). Enable TLS 1.2
# explicitly. Takes effect only after reboot.
$base = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols'
foreach ($proto in 'TLS 1.0','TLS 1.1') {
foreach ($role in 'Client','Server') {
$k = "$base\$proto\$role"
try {
if (-not (Test-Path $k)) { New-Item -Path $k -Force | Out-Null }
New-ItemProperty -Path $k -Name 'Enabled' -PropertyType DWord -Value 0 -Force | Out-Null
New-ItemProperty -Path $k -Name 'DisabledByDefault' -PropertyType DWord -Value 1 -Force | Out-Null
Status $true "Disabled $proto $role"
} catch {
Status $false "Disable $proto $role - $_"
}
}
}
foreach ($role in 'Client','Server') {
$k = "$base\TLS 1.2\$role"
try {
if (-not (Test-Path $k)) { New-Item -Path $k -Force | Out-Null }
New-ItemProperty -Path $k -Name 'Enabled' -PropertyType DWord -Value 1 -Force | Out-Null
New-ItemProperty -Path $k -Name 'DisabledByDefault' -PropertyType DWord -Value 0 -Force | Out-Null
Status $true "Enabled TLS 1.2 $role"
} catch {
Status $false "Enable TLS 1.2 $role - $_"
}
}
# Snapshot post-state for audit
reg export "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" "$backupDir\schannel-protocols-post.reg" /y 2>&1 | Out-Null
# ----------------------------------------------------------------------------
Section '3. Install Windows Server Backup feature - no reboot'
# ----------------------------------------------------------------------------
try {
$feature = Get-WindowsFeature -Name Windows-Server-Backup
if ($feature.InstallState -eq 'Installed') {
Status $true 'Windows-Server-Backup already installed'
} else {
$result = Install-WindowsFeature -Name Windows-Server-Backup -IncludeManagementTools
if ($result.Success) {
Status $true "Installed Windows-Server-Backup. RestartNeeded: $($result.RestartNeeded)"
} else {
Status $false "Install-WindowsFeature returned Success=False"
}
}
} catch {
Status $false "WSB install: $_"
}
# ----------------------------------------------------------------------------
Section "4. Schedule reboot for $TargetRebootHour`:00 local"
# ----------------------------------------------------------------------------
$now = Get-Date
$target = $now.Date.AddHours($TargetRebootHour)
if ($target -lt $now) { $target = $target.AddDays(1) }
$seconds = [int]($target - $now).TotalSeconds
Write-Output "Current time: $now"
Write-Output "Target reboot: $target"
Write-Output "Delay: $seconds seconds ($([math]::Round($seconds/60,1)) minutes)"
if ($seconds -lt 60) {
Status $false "Target reboot is less than 1 minute away - not scheduling. Run again closer to window."
} elseif ($seconds -gt 86400) {
Status $false "Target reboot more than 24 hours away - $seconds seconds. Rejecting."
} else {
# /r restart, /t timeout, /d p:4:2 = Planned / Application: Maintenance
# /c comment (<512 chars) shows in user popup + event log
# No /f - we want graceful shutdown of services (AD, Hyper-V, QB VSS)
$comment = 'Entra Connect preflight: TLS 1.2 + time sync enforcement. Cancel: shutdown /a'
shutdown /r /t $seconds /d p:4:2 /c $comment
if ($LASTEXITCODE -eq 0) {
Status $true "Reboot scheduled for $target (in $seconds sec). Cancel with: shutdown /a"
} else {
Status $false "shutdown command returned exit $LASTEXITCODE"
}
}
# ----------------------------------------------------------------------------
Section '5. Summary'
# ----------------------------------------------------------------------------
Write-Output "Backup/rollback dir: $backupDir"
Write-Output "Reboot scheduled: $target"
Write-Output 'Cancel reboot: shutdown /a'
Write-Output ''
Write-Output 'Post-reboot verification:'
Write-Output ' 1. w32tm /query /status - confirm ReferenceId is no longer LOCL'
Write-Output ' 2. nltest /dsgetdc:cascades.local - confirm DC is responding'
Write-Output ' 3. Re-run entra-connect-readiness.ps1 for full pass/fail sweep'
Write-Output ''
Write-Output "Completed at $(Get-Date)"

View File

@@ -0,0 +1,288 @@
# ============================================================================
# Entra Connect Readiness Check - CS-SERVER
# ----------------------------------------------------------------------------
# Read-only diagnostic. No state changes. No service restarts. No registry
# writes. Safe to run on a production DC at any time. Takes ~30-60 seconds.
#
# Output is structured text with section headers. Copy the full output back
# to Howard for analysis.
#
# Prepared: 2026-04-22
# Target: CS-SERVER (cascades.local single DC)
# Prerequisite check for: Entra Connect install per user-account-rollout-plan
# ============================================================================
$ErrorActionPreference = 'Continue'
$sep = '=' * 76
function Write-Section($title) {
Write-Output ''
Write-Output $sep
Write-Output "== $title"
Write-Output $sep
}
function Write-Check($label, $value, $status = '') {
$marker = switch ($status) {
'PASS' { '[OK] ' }
'WARN' { '[WARN] ' }
'FAIL' { '[FAIL] ' }
default { ' ' }
}
Write-Output ("{0}{1,-40}: {2}" -f $marker, $label, $value)
}
Write-Output "Entra Connect Readiness Check - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')"
Write-Output "Host: $env:COMPUTERNAME"
# ----------------------------------------------------------------------------
Write-Section '1. Operating System'
# ----------------------------------------------------------------------------
$os = Get-CimInstance Win32_OperatingSystem
Write-Check 'OS Caption' $os.Caption
Write-Check 'OS Version' $os.Version
Write-Check 'OS Build' $os.BuildNumber
Write-Check 'Architecture' $os.OSArchitecture
Write-Check 'Install Date' ($os.InstallDate)
Write-Check 'Last Boot' ($os.LastBootUpTime)
Write-Check 'Uptime (days)' ([math]::Round(((Get-Date) - $os.LastBootUpTime).TotalDays, 1))
$osMajor = [int]($os.Version -split '\.')[0]
$osBuild = [int]$os.BuildNumber
$osStatus = if ($osBuild -ge 14393) { 'PASS' } else { 'FAIL' }
Write-Check 'Server 2016+ required' "$($os.Caption) -> $osStatus" $osStatus
# ----------------------------------------------------------------------------
Write-Section '2. .NET Framework version'
# ----------------------------------------------------------------------------
# Release key lookup: https://learn.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
$netKey = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' -ErrorAction SilentlyContinue
if ($netKey) {
$release = $netKey.Release
$netName = switch ($true) {
($release -ge 533320) { '4.8.1' }
($release -ge 528040) { '4.8' }
($release -ge 461808) { '4.7.2' }
($release -ge 461308) { '4.7.1' }
($release -ge 460798) { '4.7' }
($release -ge 394802) { '4.6.2' }
default { "Older (release=$release)" }
}
$netStatus = if ($release -ge 461808) { 'PASS' } else { 'FAIL' }
Write-Check '.NET Framework version' $netName
Write-Check '.NET release key' $release
Write-Check '.NET 4.7.2+ required' "$netName -> $netStatus" $netStatus
} else {
Write-Check '.NET Framework v4 Full' 'NOT INSTALLED' 'FAIL'
}
# ----------------------------------------------------------------------------
Write-Section '3. PowerShell'
# ----------------------------------------------------------------------------
Write-Check 'PSVersion' $PSVersionTable.PSVersion
Write-Check 'PSEdition' $PSVersionTable.PSEdition
$psStatus = if ($PSVersionTable.PSVersion.Major -ge 5) { 'PASS' } else { 'FAIL' }
Write-Check 'PS 5.0+ required' "$($PSVersionTable.PSVersion) -> $psStatus" $psStatus
# ----------------------------------------------------------------------------
Write-Section '4. TLS 1.2 configuration'
# ----------------------------------------------------------------------------
# Entra Connect requires TLS 1.2 enforced for outbound sync
$net4 = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' -Name SchUseStrongCrypto,SystemDefaultTlsVersions -ErrorAction SilentlyContinue
$net4w = Get-ItemProperty 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319' -Name SchUseStrongCrypto,SystemDefaultTlsVersions -ErrorAction SilentlyContinue
Write-Check '.NET SchUseStrongCrypto (64-bit)' $net4.SchUseStrongCrypto
Write-Check '.NET SchUseStrongCrypto (32-bit)' $net4w.SchUseStrongCrypto
Write-Check '.NET SystemDefaultTlsVersions (64)' $net4.SystemDefaultTlsVersions
Write-Check '.NET SystemDefaultTlsVersions (32)' $net4w.SystemDefaultTlsVersions
foreach ($role in 'Client','Server') {
foreach ($proto in 'TLS 1.0','TLS 1.1','TLS 1.2') {
$k = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$proto\$role"
$v = Get-ItemProperty $k -ErrorAction SilentlyContinue
$enabled = if ($v) { "Enabled=$($v.Enabled) DisabledByDefault=$($v.DisabledByDefault)" } else { 'unset (OS default)' }
Write-Check "SCHANNEL $proto $role" $enabled
}
}
Write-Output '(Entra Connect requires TLS 1.2 client enabled; TLS 1.0/1.1 should be disabled per HIPAA best practice.)'
# ----------------------------------------------------------------------------
Write-Section '5. Disk space'
# ----------------------------------------------------------------------------
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
$free = [math]::Round($_.FreeSpace / 1GB, 1)
$total = [math]::Round($_.Size / 1GB, 1)
$pct = if ($_.Size) { [math]::Round(100 * $_.FreeSpace / $_.Size, 1) } else { 0 }
Write-Check "Drive $($_.DeviceID)" "$free GB free of $total GB ($pct% free)"
}
Write-Output '(Entra Connect needs ~5-20 GB free on install drive, typically C:.)'
# ----------------------------------------------------------------------------
Write-Section '6. AD Domain + FSMO roles'
# ----------------------------------------------------------------------------
try {
Import-Module ActiveDirectory -ErrorAction Stop
$dom = Get-ADDomain
$for = Get-ADForest
Write-Check 'Domain' $dom.DNSRoot
Write-Check 'NetBIOS name' $dom.NetBIOSName
Write-Check 'Forest mode' $for.ForestMode
Write-Check 'Domain mode' $dom.DomainMode
Write-Check 'Schema master' $for.SchemaMaster
Write-Check 'Domain naming master' $for.DomainNamingMaster
Write-Check 'PDC emulator' $dom.PDCEmulator
Write-Check 'RID master' $dom.RIDMaster
Write-Check 'Infrastructure master' $dom.InfrastructureMaster
$userCount = (Get-ADUser -Filter * -ResultSetSize $null | Measure-Object).Count
$groupCount = (Get-ADGroup -Filter * -ResultSetSize $null | Measure-Object).Count
$ouCount = (Get-ADOrganizationalUnit -Filter * | Measure-Object).Count
Write-Check 'AD user count' $userCount
Write-Check 'AD group count' $groupCount
Write-Check 'AD OU count' $ouCount
} catch {
Write-Check 'AD module' "FAIL: $_" 'FAIL'
}
# ----------------------------------------------------------------------------
Write-Section '7. AD Schema version'
# ----------------------------------------------------------------------------
try {
$schema = Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion
Write-Check 'Schema objectVersion' $schema.objectVersion
$schemaName = switch ($schema.objectVersion) {
88 { 'Windows Server 2019' }
87 { 'Windows Server 2016' }
69 { 'Windows Server 2012 R2' }
56 { 'Windows Server 2012' }
47 { 'Windows Server 2008 R2' }
default { "Unknown ($($schema.objectVersion))" }
}
Write-Check 'Schema version (mapped)' $schemaName
} catch {
Write-Check 'AD Schema' "FAIL: $_" 'FAIL'
}
# ----------------------------------------------------------------------------
Write-Section '8. Time sync'
# ----------------------------------------------------------------------------
Write-Output '(Entra Connect requires clock within ~5 min of Microsoft time.)'
$w32status = w32tm /query /status 2>&1
$w32status | Out-String | Write-Output
$w32peers = w32tm /query /peers 2>&1
$w32peers | Out-String | Write-Output
# ----------------------------------------------------------------------------
Write-Section '9. Internet connectivity to Microsoft sync endpoints'
# ----------------------------------------------------------------------------
$endpoints = @(
'login.microsoftonline.com',
'login.windows.net',
'secure.aadcdn.microsoftonline-p.com',
'management.azure.com',
'graph.windows.net',
'adminwebservice.microsoftonline.com',
'provisioningapi.microsoftonline.com'
)
foreach ($e in $endpoints) {
$r = Test-NetConnection -ComputerName $e -Port 443 -InformationLevel Quiet -WarningAction SilentlyContinue
$status = if ($r) { 'PASS' } else { 'FAIL' }
Write-Check "HTTPS 443 -> $e" $r $status
}
# ----------------------------------------------------------------------------
Write-Section '10. WinHTTP proxy'
# ----------------------------------------------------------------------------
netsh winhttp show proxy | Out-String | Write-Output
# ----------------------------------------------------------------------------
Write-Section '11. Existing Entra Connect / AAD Sync installations'
# ----------------------------------------------------------------------------
$svcs = Get-Service -Name 'ADSync','MIISERVER','Microsoft Azure AD Sync' -ErrorAction SilentlyContinue
if ($svcs) {
$svcs | ForEach-Object { Write-Check "Existing service $($_.Name)" "$($_.Status) - CONFLICT" 'WARN' }
} else {
Write-Check 'Entra Connect / AAD Sync service' 'Not found (expected)' 'PASS'
}
$aadApps = Get-CimInstance Win32_Product -Filter "Name LIKE '%Azure AD%' OR Name LIKE '%Entra%' OR Name LIKE '%AADSync%'" -ErrorAction SilentlyContinue |
Select-Object Name, Version, InstallDate
if ($aadApps) {
$aadApps | Format-Table -AutoSize | Out-String | Write-Output
} else {
Write-Check 'Azure/Entra installed products' 'none' 'PASS'
}
# ----------------------------------------------------------------------------
Write-Section '12. SQL services (LocalDB conflict check)'
# ----------------------------------------------------------------------------
# Entra Connect installs SQL Server 2019 LocalDB by default. Existing SQL
# instances are not a conflict (different service name), but worth noting.
$sql = Get-Service -Name 'MSSQL*' -ErrorAction SilentlyContinue
if ($sql) {
$sql | ForEach-Object { Write-Check "$($_.Name)" "$($_.Status) ($($_.DisplayName))" }
} else {
Write-Check 'Existing SQL services' 'none'
}
# ----------------------------------------------------------------------------
Write-Section '13. Key services running (potential conflicts / heavy load)'
# ----------------------------------------------------------------------------
$watch = 'NTDS','DNS','DHCPServer','W3SVC','vmms','Spooler','SynoDriveClient','QBIDPService','QuickBooksDB34','DattoAV','ScreenConnect','SyncroDeviceAgent','Splashtop Streamer'
Get-Service -Name $watch -ErrorAction SilentlyContinue | Sort-Object Name | ForEach-Object {
Write-Check "Service $($_.Name)" "$($_.Status) - $($_.DisplayName)"
}
# ----------------------------------------------------------------------------
Write-Section '14. Memory + CPU pressure (current)'
# ----------------------------------------------------------------------------
$mem = Get-CimInstance Win32_OperatingSystem
$memTotal = [math]::Round($mem.TotalVisibleMemorySize / 1MB, 1)
$memFree = [math]::Round($mem.FreePhysicalMemory / 1MB, 1)
$memUsed = [math]::Round($memTotal - $memFree, 1)
$memPct = [math]::Round(100 * $memUsed / $memTotal, 0)
Write-Check 'RAM total (GB)' $memTotal
Write-Check 'RAM in use (GB)' "$memUsed ($memPct%)"
Write-Check 'RAM free (GB)' $memFree
$cpu = Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average
Write-Check 'CPU load (avg %)' ([math]::Round($cpu.Average,1))
# ----------------------------------------------------------------------------
Write-Section '15. Event log health (last 24h)'
# ----------------------------------------------------------------------------
$since = (Get-Date).AddHours(-24)
foreach ($log in 'System','Application') {
$ev = Get-WinEvent -FilterHashtable @{LogName=$log; Level=1,2; StartTime=$since} -ErrorAction SilentlyContinue
$crit = ($ev | Where-Object Level -eq 1 | Measure-Object).Count
$err = ($ev | Where-Object Level -eq 2 | Measure-Object).Count
Write-Check "$log log errors/critical" "$err errors, $crit critical"
if ($ev) {
$ev | Group-Object ProviderName |
Sort-Object Count -Descending |
Select-Object -First 5 Count, Name |
Format-Table -AutoSize | Out-String | Write-Output
}
}
# ----------------------------------------------------------------------------
Write-Section '16. DC health (dcdiag subset)'
# ----------------------------------------------------------------------------
# Focused tests only - full dcdiag is ~2 minutes. Skip if too slow.
Write-Output '--- dcdiag /test:connectivity,fsmocheck,services,advertising /v (trimmed) ---'
$dc = dcdiag /test:connectivity /test:fsmocheck /test:services /test:advertising 2>&1
$dc | Where-Object { $_ -match 'Starting test|passed test|failed test|warning|error' } | Out-String | Write-Output
# ----------------------------------------------------------------------------
Write-Section '17. Windows Features (Server Backup etc.)'
# ----------------------------------------------------------------------------
$features = Get-WindowsFeature -Name 'Windows-Server-Backup','ADDS-AdminCenter','RSAT-AD-PowerShell','RSAT-AD-Tools' -ErrorAction SilentlyContinue
$features | Format-Table Name, InstallState -AutoSize | Out-String | Write-Output
# ----------------------------------------------------------------------------
Write-Section '18. Certificate expirations (local cert store, expires < 90d)'
# ----------------------------------------------------------------------------
Get-ChildItem Cert:\LocalMachine\My, Cert:\LocalMachine\Root -ErrorAction SilentlyContinue |
Where-Object { $_.NotAfter -lt (Get-Date).AddDays(90) -or $_.NotAfter -lt (Get-Date) } |
Sort-Object NotAfter |
Select-Object Subject, NotAfter, Thumbprint |
Format-Table -AutoSize -Wrap | Out-String | Write-Output
# ----------------------------------------------------------------------------
Write-Section 'Done'
# ----------------------------------------------------------------------------
Write-Output "Completed at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')"