Windows Server 2025 Active Directory Upgrade Pre-check

Windows Server 2025 Active Directory upgrade yapmadan önce mevcut durumunuzu kontrol etmek için aşağıdaki scripti kullanabilirsiniz.

# ================================================================
# VOLSYS - Windows Server 2025 Active Directory Upgrade Pre-Check
# Volkan Demirci - 31.03.2026 - Ankara - Ofis
# ================================================================
$Date = Get-Date -Format "yyyy-MM-dd_HH-mm"
$BaseFolder = "C:\Volsys\$Date"
$HtmlFile = "$BaseFolder\AD-Migration-Precheck.html"
$TxtFile = "$BaseFolder\AD-Migration-Precheck.txt"
$CsvFile = "$BaseFolder\AD-Migration-Precheck-Summary.csv"
$Transcript = "$BaseFolder\Transcript.txt"
New-Item -ItemType Directory -Path $BaseFolder -Force | Out-Null
Start-Transcript -Path $Transcript -Force
$HtmlContent = New-Object System.Text.StringBuilder
$TxtContent = New-Object System.Text.StringBuilder
$Findings = New-Object System.Collections.ArrayList
function Add-HtmlLine {
param([string]$Text)
[void]$HtmlContent.AppendLine($Text)
}
function Add-TxtLine {
param([string]$Text)
[void]$TxtContent.AppendLine($Text)
}
function Convert-ToSafeHtml {
param([string]$Text)
if ($null -eq $Text) { return "" }
return ($Text | Out-String) `
-replace '&', '&' `
-replace '<', '&lt;' `
-replace '>', '&gt;' `
-replace '"', '&quot;' `
-replace "'", '&#39;'
}
function Add-Finding {
param(
[string]$Category,
[string]$Check,
[string]$Status,
[string]$Details
)
[void]$Findings.Add([PSCustomObject]@{
Category = $Category
Check = $Check
Status = $Status
Details = $Details
})
}
function Write-Section {
param([string]$Title)
$SafeTitle = Convert-ToSafeHtml $Title
Add-HtmlLine "<div class='section-title'>$SafeTitle</div>"
Add-TxtLine ""
Add-TxtLine "============================================================"
Add-TxtLine $Title
Add-TxtLine "============================================================"
}
function Get-StatusClass {
param([string]$Status)
switch ($Status.ToUpper()) {
"PASS" { return "status-pass" }
"WARNING" { return "status-warning" }
"FAIL" { return "status-fail" }
default { return "status-info" }
}
}
function Add-ResultBadge {
param([string]$Status)
$Class = Get-StatusClass $Status
return "<span class='status-badge $Class'>$Status</span>"
}
function Run-Command {
param(
[string]$Title,
[string]$Command
)
$SafeTitle = Convert-ToSafeHtml $Title
$SafeCommand = Convert-ToSafeHtml $Command
Add-HtmlLine "<div class='command-block'>"
Add-HtmlLine "<div class='command-title'>$SafeTitle</div>"
Add-HtmlLine "<div class='command-text'><b>Command:</b> <code>$SafeCommand</code></div>"
Add-TxtLine ""
Add-TxtLine "----- $Title -----"
Add-TxtLine "Command: $Command"
Add-TxtLine ""
try {
$Result = Invoke-Expression $Command 2>&1 | Out-String
Add-HtmlLine "<pre>$(Convert-ToSafeHtml $Result)</pre>"
Add-HtmlLine "</div>"
Add-TxtLine $Result
return $Result
}
catch {
$ErrorText = $_.Exception.Message
Add-HtmlLine "<pre class='error'>ERROR: $(Convert-ToSafeHtml $ErrorText)</pre>"
Add-HtmlLine "</div>"
Add-TxtLine "ERROR: $ErrorText"
return "ERROR: $ErrorText"
}
}
function Run-AssessedCommand {
param(
[string]$Category,
[string]$Title,
[string]$Command,
[scriptblock]$AssessmentLogic
)
$SafeTitle = Convert-ToSafeHtml $Title
$SafeCommand = Convert-ToSafeHtml $Command
Add-HtmlLine "<div class='command-block'>"
Add-HtmlLine "<div class='command-title'>$SafeTitle</div>"
Add-HtmlLine "<div class='command-text'><b>Command:</b> <code>$SafeCommand</code></div>"
Add-TxtLine ""
Add-TxtLine "----- $Title -----"
Add-TxtLine "Command: $Command"
Add-TxtLine ""
try {
$Result = Invoke-Expression $Command 2>&1 | Out-String
$Assessment = & $AssessmentLogic $Result
$Status = $Assessment.Status
$Details = $Assessment.Details
Add-Finding -Category $Category -Check $Title -Status $Status -Details $Details
Add-HtmlLine "<div class='assessment-line'><b>Assessment:</b> $(Add-ResultBadge $Status) <span class='assessment-detail'>$(Convert-ToSafeHtml $Details)</span></div>"
Add-HtmlLine "<pre>$(Convert-ToSafeHtml $Result)</pre>"
Add-HtmlLine "</div>"
Add-TxtLine "Assessment: $Status - $Details"
Add-TxtLine ""
Add-TxtLine $Result
return $Result
}
catch {
$ErrorText = $_.Exception.Message
Add-Finding -Category $Category -Check $Title -Status "FAIL" -Details $ErrorText
Add-HtmlLine "<div class='assessment-line'><b>Assessment:</b> $(Add-ResultBadge 'FAIL') <span class='assessment-detail'>$(Convert-ToSafeHtml $ErrorText)</span></div>"
Add-HtmlLine "<pre class='error'>ERROR: $(Convert-ToSafeHtml $ErrorText)</pre>"
Add-HtmlLine "</div>"
Add-TxtLine "Assessment: FAIL - $ErrorText"
Add-TxtLine "ERROR: $ErrorText"
return "ERROR: $ErrorText"
}
}
function Add-ManualFindingFromResult {
param(
[string]$Category,
[string]$Check,
[hashtable]$Assessment
)
Add-Finding -Category $Category -Check $Check -Status $Assessment.Status -Details $Assessment.Details
}
# ----------------------------
# Assessment helpers
# ----------------------------
function Get-DcdiagStatus {
param([string]$Text)
$Lower = $Text.ToLowerInvariant()
if ($Lower -match "failed test" -or $Lower -match "fatal") {
@{ Status = "FAIL"; Details = "dcdiag output contains failed test or fatal pattern." }
}
elseif ($Lower -match "warning") {
@{ Status = "WARNING"; Details = "dcdiag output contains warning entries." }
}
elseif ($Lower -match "passed test") {
@{ Status = "PASS"; Details = "dcdiag output shows passed test patterns without obvious fatal issues." }
}
else {
@{ Status = "WARNING"; Details = "dcdiag output should be reviewed manually." }
}
}
function Get-RepadminSummaryStatus {
param([string]$Text)
$Lower = $Text.ToLowerInvariant()
if ($Lower -match "fails" -or $Lower -match "last error" -or $Lower -match "error") {
@{ Status = "FAIL"; Details = "Replication summary contains failure or error pattern." }
}
elseif ($Lower -match "largest delta") {
@{ Status = "WARNING"; Details = "Replication summary returned delta information; confirm there is no abnormal latency." }
}
else {
@{ Status = "PASS"; Details = "No obvious replication failure pattern detected." }
}
}
function Get-ShareStatus {
param([string]$Text)
$HasSysvol = $Text -match "(?im)^\s*SYSVOL\s+"
$HasNetlogon = $Text -match "(?im)^\s*NETLOGON\s+"
if ($HasSysvol -and $HasNetlogon) {
@{ Status = "PASS"; Details = "SYSVOL and NETLOGON shares are visible." }
}
elseif ($HasSysvol -or $HasNetlogon) {
@{ Status = "WARNING"; Details = "Only one of SYSVOL / NETLOGON appears in output." }
}
else {
@{ Status = "FAIL"; Details = "SYSVOL and NETLOGON shares were not detected." }
}
}
function Get-DfsrStateStatus {
param([string]$Text)
$Lower = $Text.ToLowerInvariant()
if ($Lower -match "eliminated") {
@{ Status = "PASS"; Details = "SYSVOL migration appears to be in Eliminated state." }
}
elseif ($Lower -match "redirected" -or $Lower -match "prepared" -or $Lower -match "start") {
@{ Status = "WARNING"; Details = "DFSR migration state is not final; verify before migration." }
}
else {
@{ Status = "WARNING"; Details = "Could not confidently verify DFSR migration final state." }
}
}
function Get-NslookupStatus {
param([string]$Text)
$Lower = $Text.ToLowerInvariant()
if ($Lower -match "non-existent domain" -or $Lower -match "can't find" -or $Lower -match "timed out" -or $Lower -match "server failed") {
@{ Status = "FAIL"; Details = "DNS lookup indicates missing record, timeout, or server failure." }
}
else {
@{ Status = "PASS"; Details = "DNS lookup returned a response without obvious failure text." }
}
}
function Get-W32tmStatus {
param([string]$Text)
$Lower = $Text.ToLowerInvariant()
if ($Lower -match "local cmos clock") {
@{ Status = "WARNING"; Details = "Time source appears to be Local CMOS Clock; verify authoritative time source configuration." }
}
elseif ($Lower -match "not synchronized" -or $Lower -match "error") {
@{ Status = "FAIL"; Details = "Time service output indicates synchronization issue." }
}
else {
@{ Status = "PASS"; Details = "No obvious time synchronization issue detected." }
}
}
function Get-EventCountStatus {
param([string]$Text)
$NonEmptyLines = ($Text -split "`r?`n" | Where-Object { $_.Trim() -ne "" }).Count
if ($NonEmptyLines -gt 30) {
@{ Status = "FAIL"; Details = "High number of returned error/warning entries detected." }
}
elseif ($NonEmptyLines -gt 5) {
@{ Status = "WARNING"; Details = "Some error/warning entries detected; review recommended." }
}
else {
@{ Status = "PASS"; Details = "Low number of returned error/warning entries detected." }
}
}
function Get-GenericErrorStatus {
param([string]$Text)
$Lower = $Text.ToLowerInvariant()
if ($Lower -match "error" -or $Lower -match "failed" -or $Lower -match "access is denied") {
@{ Status = "FAIL"; Details = "Command output contains error, failed, or access denied pattern." }
}
else {
@{ Status = "PASS"; Details = "No obvious failure pattern detected." }
}
}
function Get-Recommendation {
param(
[string]$Category,
[string]$Check,
[string]$Status
)
if ($Status -eq "PASS") {
return "No immediate action required."
}
switch ($Category) {
"DCDIAG" { "Review dcdiag failures in detail and remediate AD DS, DNS, Netlogon, Services, or SYSVOL issues before migration." }
"Replication" { "Resolve replication failures, investigate last error values, and confirm healthy inbound/outbound replication on all DCs." }
"DNS" { "Verify AD-integrated DNS zones, SRV records, conditional forwarders, forwarders, NS records, and client DNS configuration." }
"DNS Forwarders" { "Validate external and internal DNS forwarder design, reachability, and timeout behavior." }
"ConditionalForward" { "Review conditional forwarder necessity, target IPs, and replication scope." }
"SYSVOL" { "Confirm SYSVOL and NETLOGON shares are published and accessible. Repair SYSVOL replication before migration." }
"DFSR" { "Validate DFSR migration state and ensure SYSVOL replication is healthy and finalized." }
"Time" { "Correct time hierarchy, especially on the PDC Emulator, and ensure reliable authoritative time source usage." }
"GC" { "Confirm at least one healthy Global Catalog is available and discoverable." }
"Schema" { "Validate schema health, schema master accessibility, and readiness for schema extension or ADPrep operations." }
"Secure Channel" { "Repair trust or secure channel issues before introducing or demoting domain controllers." }
"Events" { "Review returned event logs, correlate repeated warnings/errors, and eliminate migration blockers before the change window." }
"FSMO" { "Verify FSMO role holder availability, health, backups, DNS registration, and replication consistency." }
"Trust" { "Review trust health, trust type, direction, authentication scope, and DNS resolution between trusted domains." }
"Firewall" { "Confirm firewall profile settings align with DC hardening baseline and required AD DS ports." }
"StrictReplication" { "Enable Strict Replication Consistency where missing to protect against lingering object issues." }
"Kerberos RC4" { "Inventory RC4-dependent principals or systems and move them to AES-capable configurations." }
"Privileged Groups" { "Review privileged group memberships and remove stale, excess, or legacy accounts." }
"Sites" { "Validate AD site topology, subnet mapping, and site link cost/interval design." }
"StaticDynamicDNS" { "Review stale dynamic records and validate static records that are business critical." }
"ADPrep" { "Validate schema master reachability, replication health, backups, and permissions before ADPrep." }
"Backup" { "Ensure recent system state backup exists for FSMO/DC recovery before migration." }
"Tombstone" { "Review tombstone lifetime and deleted object retention settings before migration and cleanup operations." }
"RecycleBin" { "Consider enabling Recycle Bin if not already enabled and operationally approved." }
"LDAP" { "Review LDAP signing, channel binding, and application compatibility before enforcing stricter settings." }
"NTLM" { "Reduce NTLM usage and validate NTLM restriction policy before migration hardening." }
default { "Review output manually and remediate findings before migration." }
}
}
# ----------------------------
# HTML header
# ----------------------------
Add-HtmlLine "<!DOCTYPE html>"
Add-HtmlLine "<html lang='en'>"
Add-HtmlLine "<head>"
Add-HtmlLine "<meta charset='UTF-8'>"
Add-HtmlLine "<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
Add-HtmlLine "<title>VOLSYS AD Migration Pre-Check</title>"
Add-HtmlLine "<style>"
Add-HtmlLine "body { font-family: Segoe UI, Arial, sans-serif; background:#f3f6fa; color:#1f2937; margin:0; padding:20px; }"
Add-HtmlLine ".container { max-width:1500px; margin:0 auto; background:#ffffff; padding:30px; border-radius:12px; box-shadow:0 4px 20px rgba(0,0,0,0.08); }"
Add-HtmlLine ".brandbar { background:linear-gradient(90deg,#0b3d91,#1d4ed8); color:#fff; padding:20px 24px; border-radius:12px; margin-bottom:20px; }"
Add-HtmlLine ".brandtitle { font-size:28px; font-weight:800; letter-spacing:.5px; }"
Add-HtmlLine ".brandsubtitle { font-size:14px; opacity:.95; margin-top:6px; }"
Add-HtmlLine ".meta { background:#eef4ff; border-left:5px solid #0b3d91; padding:15px; border-radius:6px; margin-bottom:25px; }"
Add-HtmlLine ".dashboard { display:flex; gap:15px; flex-wrap:wrap; margin:25px 0; }"
Add-HtmlLine ".card { flex:1; min-width:180px; padding:18px; border-radius:10px; color:#fff; font-weight:600; cursor:pointer; transition:transform .15s ease, box-shadow .15s ease, opacity .15s ease; }"
Add-HtmlLine ".card:hover { transform:translateY(-2px); box-shadow:0 6px 18px rgba(0,0,0,0.15); }"
Add-HtmlLine ".card.inactive { opacity:0.45; }"
Add-HtmlLine ".card-total { background:#1f2937; }"
Add-HtmlLine ".card-pass { background:#047857; }"
Add-HtmlLine ".card-warning { background:#b45309; }"
Add-HtmlLine ".card-fail { background:#b91c1c; }"
Add-HtmlLine ".card-score { background:#1d4ed8; cursor:default; }"
Add-HtmlLine ".card-score:hover { transform:none; box-shadow:none; }"
Add-HtmlLine ".card-title { font-size:14px; opacity:.95; }"
Add-HtmlLine ".card-value { font-size:30px; margin-top:8px; }"
Add-HtmlLine ".filter-note { margin-top:12px; font-size:13px; color:#374151; }"
Add-HtmlLine ".hidden-row { display:none; }"
Add-HtmlLine ".section-title { font-size:20px; font-weight:700; color:#fff; background:#0b3d91; padding:12px 15px; margin-top:30px; border-radius:8px; }"
Add-HtmlLine ".command-block { border:1px solid #dbe4ee; border-radius:10px; margin-top:15px; padding:16px; background:#fbfcfe; }"
Add-HtmlLine ".command-title { font-size:16px; font-weight:700; margin-bottom:8px; }"
Add-HtmlLine ".command-text { margin-bottom:10px; color:#374151; }"
Add-HtmlLine ".assessment-line { margin-bottom:12px; padding:10px; background:#f3f4f6; border-radius:8px; }"
Add-HtmlLine ".assessment-detail { margin-left:8px; color:#374151; }"
Add-HtmlLine ".status-badge { display:inline-block; padding:4px 10px; border-radius:999px; font-size:12px; font-weight:700; color:#fff; }"
Add-HtmlLine ".status-pass { background:#059669; }"
Add-HtmlLine ".status-warning { background:#d97706; }"
Add-HtmlLine ".status-fail { background:#dc2626; }"
Add-HtmlLine ".status-info { background:#2563eb; }"
Add-HtmlLine ".readiness-ready { color:#047857; font-weight:800; }"
Add-HtmlLine ".readiness-review { color:#b45309; font-weight:800; }"
Add-HtmlLine ".readiness-notready { color:#b91c1c; font-weight:800; }"
Add-HtmlLine "code { background:#eef2f7; padding:2px 6px; border-radius:4px; }"
Add-HtmlLine "pre { white-space:pre-wrap; word-wrap:break-word; background:#111827; color:#e5e7eb; padding:15px; border-radius:8px; overflow:auto; }"
Add-HtmlLine ".error { background:#7f1d1d; color:#fecaca; }"
Add-HtmlLine "table { width:100%; border-collapse:collapse; margin-top:15px; }"
Add-HtmlLine "th, td { border:1px solid #dbe4ee; padding:10px; text-align:left; vertical-align:top; }"
Add-HtmlLine "th { background:#0f172a; color:#fff; }"
Add-HtmlLine "tr:nth-child(even) { background:#f8fafc; }"
Add-HtmlLine ".info-box { background:#f8fafc; border-left:5px solid #1d4ed8; padding:14px; margin-top:18px; border-radius:6px; }"
Add-HtmlLine ".warning-box { background:#fff7ed; border-left:5px solid #ea580c; padding:14px; margin-top:18px; border-radius:6px; }"
Add-HtmlLine ".fail-box { background:#fef2f2; border-left:5px solid #dc2626; padding:14px; margin-top:18px; border-radius:6px; }"
Add-HtmlLine "</style>"
Add-HtmlLine "</head>"
Add-HtmlLine "<body>"
Add-HtmlLine "<div class='container'>"
Add-HtmlLine "<div class='brandbar'>"
Add-HtmlLine "<div class='brandtitle'>VOLSYS</div>"
Add-HtmlLine "<div class='brandsubtitle'>Windows Server 2025 Active Directory Migration Readiness Assessment - Ultimate</div>"
Add-HtmlLine "</div>"
Add-HtmlLine "<div class='meta'>"
Add-HtmlLine "<b>Server:</b> $(Convert-ToSafeHtml $env:COMPUTERNAME)<br>"
Add-HtmlLine "<b>Domain:</b> $(Convert-ToSafeHtml $env:USERDNSDOMAIN)<br>"
Add-HtmlLine "<b>Generated:</b> $(Convert-ToSafeHtml (Get-Date))<br>"
Add-HtmlLine "<b>HTML Report:</b> $(Convert-ToSafeHtml $HtmlFile)<br>"
Add-HtmlLine "<b>TXT Report:</b> $(Convert-ToSafeHtml $TxtFile)<br>"
Add-HtmlLine "<b>CSV Summary:</b> $(Convert-ToSafeHtml $CsvFile)<br>"
Add-HtmlLine "<b>Transcript:</b> $(Convert-ToSafeHtml $Transcript)"
Add-HtmlLine "</div>"
Add-TxtLine "VOLSYS - Windows Server 2025 Active Directory Migration Readiness Assessment - Ultimate"
Add-TxtLine "Generated : $(Get-Date)"
Add-TxtLine "Server : $env:COMPUTERNAME"
Add-TxtLine "Domain : $env:USERDNSDOMAIN"
Add-TxtLine "HTML : $HtmlFile"
Add-TxtLine "TXT : $TxtFile"
Add-TxtLine "CSV : $CsvFile"
Add-TxtLine "Transcript: $Transcript"
# ----------------------------
# Base checks
# ----------------------------
Write-Section "GENERAL SERVER INFORMATION"
Run-Command "Hostname" "hostname" | Out-Null
Run-Command "Current User" "whoami" | Out-Null
Run-Command "IP Configuration" "ipconfig /all" | Out-Null
Run-Command "System Information" "systeminfo" | Out-Null
Write-Section "DOMAIN AND FOREST INFORMATION"
Run-Command "Get-ADDomain" "Get-ADDomain" | Out-Null
Run-Command "Get-ADForest" "Get-ADForest" | Out-Null
Run-Command "Domain Controllers" "Get-ADDomainController -Filter *" | Out-Null
Run-Command "Domain Mode" "Get-ADDomain | Select Name,DomainMode,PDCEmulator,RIDMaster,InfrastructureMaster" | Out-Null
Run-Command "Forest Mode" "Get-ADForest | Select ForestMode,SchemaMaster,DomainNamingMaster" | Out-Null
Write-Section "FSMO ROLES"
Run-AssessedCommand "FSMO" "FSMO Roles" "netdom query fsmo" { param($r) Get-GenericErrorStatus $r } | Out-Null
Write-Section "DCDIAG HEALTH CHECKS"
Run-AssessedCommand "DCDIAG" "DCDIAG Verbose" "dcdiag /v" { param($r) Get-DcdiagStatus $r } | Out-Null
Run-AssessedCommand "DCDIAG" "DCDIAG DNS" "dcdiag /test:dns /v" { param($r) Get-DcdiagStatus $r } | Out-Null
Run-AssessedCommand "DCDIAG" "DCDIAG Replications" "dcdiag /test:replications" { param($r) Get-DcdiagStatus $r } | Out-Null
Run-AssessedCommand "DCDIAG" "DCDIAG Advertising" "dcdiag /test:advertising" { param($r) Get-DcdiagStatus $r } | Out-Null
Run-AssessedCommand "DCDIAG" "DCDIAG SYSVOL" "dcdiag /test:sysvolcheck" { param($r) Get-DcdiagStatus $r } | Out-Null
Run-AssessedCommand "DCDIAG" "DCDIAG Services" "dcdiag /test:services" { param($r) Get-DcdiagStatus $r } | Out-Null
Write-Section "REPLICATION CHECKS"
Run-AssessedCommand "Replication" "Replication Summary" "repadmin /replsummary" { param($r) Get-RepadminSummaryStatus $r } | Out-Null
Run-AssessedCommand "Replication" "Show Replication" "repadmin /showrepl" { param($r) Get-GenericErrorStatus $r } | Out-Null
Run-Command "Replication Queue" "repadmin /queue" | Out-Null
Run-Command "Replication Fail Cache" "repadmin /failcache" | Out-Null
Run-Command "Replication Latency" "repadmin /latency *" | Out-Null
Write-Section "DNS CHECKS"
Run-AssessedCommand "DNS" "LDAP SRV Record" "nslookup _ldap._tcp.dc._msdcs.$env:USERDNSDOMAIN" { param($r) Get-NslookupStatus $r } | Out-Null
Run-AssessedCommand "DNS" "Kerberos SRV Record" "nslookup _kerberos._tcp.dc._msdcs.$env:USERDNSDOMAIN" { param($r) Get-NslookupStatus $r } | Out-Null
Run-AssessedCommand "DNS" "DSGetDC" "nltest /dsgetdc:$env:USERDNSDOMAIN" { param($r) Get-GenericErrorStatus $r } | Out-Null
Run-Command "DNS Cache" "ipconfig /displaydns" | Out-Null
Run-Command "DC List" "nltest /dclist:$env:USERDNSDOMAIN" | Out-Null
Write-Section "SYSVOL AND DFSR CHECKS"
Run-AssessedCommand "SYSVOL" "Net Share" "net share" { param($r) Get-ShareStatus $r } | Out-Null
Run-AssessedCommand "DFSR" "DFSR Global State" "dfsrmig /getglobalstate" { param($r) Get-DfsrStateStatus $r } | Out-Null
Run-Command "DFSR Migration State" "dfsrmig /getmigrationstate" | Out-Null
Run-Command "DFSR Replication State" "dfsrdiag replicationstate" | Out-Null
Write-Section "TIME SYNCHRONIZATION CHECKS"
Run-AssessedCommand "Time" "W32Time Status" "w32tm /query /status" { param($r) Get-W32tmStatus $r } | Out-Null
Run-AssessedCommand "Time" "W32Time Source" "w32tm /query /source" { param($r) Get-W32tmStatus $r } | Out-Null
Run-Command "W32Time Configuration" "w32tm /query /configuration" | Out-Null
Run-Command "W32Time Monitor" "w32tm /monitor" | Out-Null
Write-Section "GLOBAL CATALOG CHECKS"
Run-Command "Global Catalog Servers" "Get-ADDomainController -Filter * | Select HostName,IsGlobalCatalog" | Out-Null
Run-AssessedCommand "GC" "GC Discovery" "nltest /dsgetdc:$env:USERDNSDOMAIN /GC" { param($r) Get-GenericErrorStatus $r } | Out-Null
Write-Section "FUNCTIONAL LEVEL AND SCHEMA CHECKS"
Run-Command "Domain Functional Level" "Get-ADDomain | Select Name,DomainMode" | Out-Null
Run-Command "Forest Functional Level" "Get-ADForest | Select Name,ForestMode" | Out-Null
Run-AssessedCommand "Schema" "Schema Version" "Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion" { param($r) Get-GenericErrorStatus $r } | Out-Null
Write-Section "KERBEROS AND NTLM CHECKS"
Run-Command "Kerberos Tickets" "klist" | Out-Null
Run-AssessedCommand "Secure Channel" "Secure Channel Query" "nltest /sc_query:$env:USERDNSDOMAIN" { param($r) Get-GenericErrorStatus $r } | Out-Null
Run-AssessedCommand "Secure Channel" "Secure Channel Verify" "nltest /sc_verify:$env:USERDNSDOMAIN" { param($r) Get-GenericErrorStatus $r } | Out-Null
Write-Section "EVENT LOG CHECKS"
Run-AssessedCommand "Events" "System Errors and Warnings" 'Get-WinEvent -LogName System -MaxEvents 200 | Where-Object {$_.LevelDisplayName -in "Error","Warning"}' { param($r) Get-EventCountStatus $r } | Out-Null
Run-AssessedCommand "Events" "Directory Service Errors and Warnings" 'Get-WinEvent -LogName "Directory Service" -MaxEvents 200 | Where-Object {$_.LevelDisplayName -in "Error","Warning"}' { param($r) Get-EventCountStatus $r } | Out-Null
Run-AssessedCommand "Events" "DNS Server Errors and Warnings" 'Get-WinEvent -LogName "DNS Server" -MaxEvents 200 | Where-Object {$_.LevelDisplayName -in "Error","Warning"}' { param($r) Get-EventCountStatus $r } | Out-Null
Run-AssessedCommand "Events" "DFSR Errors and Warnings" 'Get-WinEvent -LogName "DFS Replication" -MaxEvents 200 | Where-Object {$_.LevelDisplayName -in "Error","Warning"}' { param($r) Get-EventCountStatus $r } | Out-Null
Run-Command "RID Related Events" 'Get-WinEvent -LogName "Directory Service" | Where-Object {$_.Id -in 16644,16645,16650,16657} | Select TimeCreated,Id,LevelDisplayName,Message -First 20' | Out-Null
Run-Command "Replication Related Events" 'Get-WinEvent -LogName "Directory Service" | Where-Object {$_.Id -in 1311,1566,1865,2042,2087,2088,2092} | Select TimeCreated,Id,LevelDisplayName,Message -First 20' | Out-Null
Run-Command "DFSR Related Events" 'Get-WinEvent -LogName "DFS Replication" | Where-Object {$_.Id -in 2213,4012,4612,5002,5004} | Select TimeCreated,Id,LevelDisplayName,Message -First 20' | Out-Null
Run-Command "DNS Related Events" 'Get-WinEvent -LogName "DNS Server" | Where-Object {$_.Id -in 4000,4013,4015,5501} | Select TimeCreated,Id,LevelDisplayName,Message -First 20' | Out-Null
Write-Section "BACKUP CHECKS"
$BackupVersions = Run-Command "WBAdmin Versions" "wbadmin get versions"
Run-Command "WBAdmin Status" "wbadmin get status" | Out-Null
Run-Command "WBAdmin Items" "wbadmin get items" | Out-Null
# ----------------------------
# Requested extra checks
# ----------------------------
Write-Section "DOMAIN TRUST CHECKS"
$TrustOutput = Run-Command "AD Trusts" "Get-ADTrust -Filter * | Select Name,Source,Target,Direction,TrustType,ForestTransitive,SelectiveAuthentication,SIDFilteringForestAware"
if ($TrustOutput -match "ERROR:") {
Add-Finding "Trust" "AD Trusts" "WARNING" "Could not query trusts. Validate RSAT/permissions/module availability."
} elseif (($TrustOutput -split "`r?`n" | Where-Object { $_.Trim() -ne "" }).Count -le 3) {
Add-Finding "Trust" "AD Trusts" "PASS" "No trust objects returned or only header output present."
} else {
Add-Finding "Trust" "AD Trusts" "WARNING" "One or more domain/forest trusts detected. Review trust health and dependencies before migration."
}
Run-Command "Netdom Trust Verification" "netdom trust $env:USERDNSDOMAIN /domain:$env:USERDNSDOMAIN /verify" | Out-Null
Write-Section "WINDOWS FIREWALL CHECKS"
$FwOutput = Run-Command "Firewall Profiles" "Get-NetFirewallProfile | Select Name,Enabled,DefaultInboundAction,DefaultOutboundAction"
try {
$FwProfiles = Get-NetFirewallProfile
$EnabledCount = ($FwProfiles | Where-Object { $_.Enabled -eq 'True' -or $_.Enabled -eq 1 }).Count
if ($EnabledCount -eq 0) {
Add-Finding "Firewall" "Firewall Profiles" "WARNING" "All firewall profiles appear disabled."
} else {
Add-Finding "Firewall" "Firewall Profiles" "PASS" "One or more firewall profiles are enabled."
}
} catch {
Add-Finding "Firewall" "Firewall Profiles" "WARNING" "Firewall profile state could not be evaluated."
}
Write-Section "STRICT REPLICATION CONSISTENCY CHECKS"
$StrictOutput = Run-Command "Strict Replication Consistency" 'Get-ADDomainController -Filter * | ForEach-Object { $regPath="\\$($_.HostName)\HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters"; try { $value=(Get-ItemProperty -Path "Registry::$regPath" -Name "Strict Replication Consistency" -ErrorAction Stop)."Strict Replication Consistency"; [PSCustomObject]@{DC=$_.HostName;StrictReplicationConsistency=$value} } catch { [PSCustomObject]@{DC=$_.HostName;StrictReplicationConsistency="Unknown / AccessDenied / NotSet"} } }'
if ($StrictOutput -match " 1" -or $StrictOutput -match "StrictReplicationConsistency\s*:?\s*1") {
if ($StrictOutput -match "Unknown / AccessDenied / NotSet" -or $StrictOutput -match " 0") {
Add-Finding "StrictReplication" "Strict Replication Consistency" "WARNING" "Some DCs could not be verified or may not have Strict Replication Consistency enabled."
} else {
Add-Finding "StrictReplication" "Strict Replication Consistency" "PASS" "Strict Replication Consistency appears enabled on verified DCs."
}
} else {
Add-Finding "StrictReplication" "Strict Replication Consistency" "WARNING" "Could not confirm Strict Replication Consistency across all DCs."
}
Write-Section "DNS FORWARDER CHECKS"
$DnsFwdOutput = Run-Command "DNS Forwarders" "Get-DnsServerForwarder | Select IPAddress,Timeout,UseRootHint"
if ($DnsFwdOutput -match "ERROR:") {
Add-Finding "DNS Forwarders" "DNS Forwarders" "WARNING" "Could not query DNS forwarders. Run on DNS server with DnsServer module."
} elseif (($DnsFwdOutput -split "`r?`n" | Where-Object { $_.Trim() -ne '' }).Count -le 3) {
Add-Finding "DNS Forwarders" "DNS Forwarders" "WARNING" "No DNS forwarders returned. Validate whether root hints-only design is intentional."
} else {
Add-Finding "DNS Forwarders" "DNS Forwarders" "PASS" "DNS forwarders returned successfully."
}
$CondFwdOutput = Run-Command "DNS Conditional Forwarders" "Get-DnsServerZone | Where-Object {$_.ZoneType -eq 'Forwarder'} | Select ZoneName,MasterServers,ReplicationScope"
if ($CondFwdOutput -match "ERROR:") {
Add-Finding "ConditionalForward" "Conditional Forwarders" "WARNING" "Could not query conditional forwarders."
} elseif (($CondFwdOutput -split "`r?`n" | Where-Object { $_.Trim() -ne '' }).Count -le 3) {
Add-Finding "ConditionalForward" "Conditional Forwarders" "PASS" "No conditional forwarders detected."
} else {
Add-Finding "ConditionalForward" "Conditional Forwarders" "WARNING" "Conditional forwarders detected. Validate target master servers and replication scope."
}
$NsOutput = Run-Command "DNS Name Server Records" "Get-DnsServerResourceRecord -ZoneName $env:USERDNSDOMAIN -RRType NS | Select HostName,RecordType,Timestamp,RecordData"
if ($NsOutput -match "ERROR:") {
Add-Finding "DNS" "Name Server Records" "WARNING" "Could not query NS records for domain zone."
} else {
Add-Finding "DNS" "Name Server Records" "PASS" "NS records queried successfully."
}
Write-Section "KERBEROS RC4 CHECKS"
$RC4Output1 = Run-Command "Accounts with msDS-SupportedEncryptionTypes" "Get-ADObject -LDAPFilter '(|(objectClass=user)(objectClass=computer)(objectClass=msDS-GroupManagedServiceAccount))' -Properties msDS-SupportedEncryptionTypes | Select-Object DistinguishedName,msDS-SupportedEncryptionTypes -First 200"
$RC4Output2 = Run-Command "Kerberos RC4 Related Events" 'Get-WinEvent -LogName System -MaxEvents 300 | Where-Object {$_.ProviderName -match "Kerberos|Security-Kerberos|KDC"} | Select TimeCreated,Id,ProviderName,Message -First 50'
$RC4Output3 = Run-Command "DefaultDomainSupportedEncTypes Registry" 'reg query "HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters" /v DefaultDomainSupportedEncTypes'
if ($RC4Output1 -match "\b4\b" -or $RC4Output1 -match "\b20\b" -or $RC4Output1 -match "\b28\b") {
Add-Finding "Kerberos RC4" "Kerberos RC4 Usage Indicators" "WARNING" "Objects with encryption type values that may include RC4 were detected. Validate principals and service accounts."
} elseif ($RC4Output3 -match "0x18" -or $RC4Output3 -match "24") {
Add-Finding "Kerberos RC4" "Kerberos RC4 Hardening" "PASS" "Registry output suggests AES-focused encryption types."
} else {
Add-Finding "Kerberos RC4" "Kerberos RC4 Hardening" "WARNING" "Could not conclusively verify RC4 disablement. Review account encryption types and Kerberos policy."
}
Write-Section "PRIVILEGED GROUP MEMBERSHIP CHECKS"
Run-Command "Builtin Administrators Members" 'Get-ADGroupMember -Identity "Administrators" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-Null
Run-Command "Domain Admins Members" 'Get-ADGroupMember -Identity "Domain Admins" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-Null
Run-Command "Enterprise Admins Members" 'Get-ADGroupMember -Identity "Enterprise Admins" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-Null
Run-Command "Schema Admins Members" 'Get-ADGroupMember -Identity "Schema Admins" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-Null
Run-Command "Protected Users Members" 'Get-ADGroupMember -Identity "Protected Users" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-Null
try {
$DA = Get-ADGroupMember -Identity "Domain Admins" -Recursive -ErrorAction Stop
if ($DA.Count -gt 10) {
Add-Finding "Privileged Groups" "Domain Admins Membership" "WARNING" "Domain Admins membership count is high. Review privileged access model."
} else {
Add-Finding "Privileged Groups" "Domain Admins Membership" "PASS" "Domain Admins membership count is within a generally reviewable range."
}
} catch {
Add-Finding "Privileged Groups" "Domain Admins Membership" "WARNING" "Could not evaluate Domain Admins membership."
}
Write-Section "AD SITES AND SERVICES CHECKS"
$SitesOutput = Run-Command "AD Sites" "Get-ADReplicationSite -Filter * | Select Name"
$SiteLinksOutput = Run-Command "AD Site Links" "Get-ADReplicationSiteLink -Filter * | Select Name,Cost,ReplicationFrequencyInMinutes,InterSiteTransportProtocol,Options"
try {
$Sites = Get-ADReplicationSite -Filter * -ErrorAction Stop
$SiteLinks = Get-ADReplicationSiteLink -Filter * -ErrorAction Stop
if ($Sites.Count -le 1) {
Add-Finding "Sites" "AD Sites Count" "PASS" "Single-site or simple topology detected."
} else {
Add-Finding "Sites" "AD Sites Count" "WARNING" "Multiple AD sites detected. Validate subnet mapping and site-link topology carefully before migration."
}
if ($SiteLinks.Count -eq 0) {
Add-Finding "Sites" "Site Links" "WARNING" "No site links returned."
} else {
Add-Finding "Sites" "Site Links" "PASS" "Site links returned successfully."
}
} catch {
Add-Finding "Sites" "AD Sites and Links" "WARNING" "Could not fully evaluate AD sites and site links."
}
Write-Section "STATIC AND DYNAMIC DNS OBJECT CHECKS"
$StaticDynOutput = Run-Command "DNS Static vs Dynamic Sample Records" 'Get-DnsServerResourceRecord -ZoneName $env:USERDNSDOMAIN | Select-Object -First 200 HostName,RecordType,Timestamp,RecordData'
if ($StaticDynOutput -match "ERROR:") {
Add-Finding "StaticDynamicDNS" "Static vs Dynamic DNS Records" "WARNING" "Could not query DNS record sample."
} else {
Add-Finding "StaticDynamicDNS" "Static vs Dynamic DNS Records" "PASS" "DNS record sample collected. Review Timestamp values: null/empty often indicates static; populated timestamps indicate dynamic."
}
Write-Section "ADPREP / SCHEMA PREP / FOREST PREP READINESS"
$SchemaMasterOutput = Run-Command "Schema Master" "Get-ADForest | Select SchemaMaster,DomainNamingMaster"
$ForestModeOutput = Run-Command "Forest and Domain Mode" "Get-ADForest | Select ForestMode; Get-ADDomain | Select DomainMode"
$SchemaVersionOutput = Run-Command "Schema Version Detailed" "Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion | Select Name,objectVersion"
$SchemaReachOutput = Run-Command "Schema Master Reachability" 'nltest /server:$((Get-ADForest).SchemaMaster) /sc_query:$env:USERDNSDOMAIN'
if ($SchemaReachOutput -match "ERROR" -or $SchemaReachOutput -match "failed") {
Add-Finding "ADPrep" "ADPrep Readiness" "FAIL" "Schema master reachability verification indicates failure."
} else {
Add-Finding "ADPrep" "ADPrep Readiness" "PASS" "Schema master and naming master information returned. Review replication, backup, and permissions before ADPrep."
}
Write-Section "BACKUP AGE CHECK"
if ($BackupVersions -match "Version identifier:\s+([0-9\/:\-\s]+)") {
Add-Finding "Backup" "Backup Age" "PASS" "WBAdmin returned backup version information. Confirm backup date is recent enough for your change window."
} elseif ($BackupVersions -match "ERROR:" -or $BackupVersions -match "There are no backups") {
Add-Finding "Backup" "Backup Age" "FAIL" "No backup versions returned by WBAdmin."
} else {
Add-Finding "Backup" "Backup Age" "WARNING" "Backup information returned but backup age could not be confidently parsed."
}
Write-Section "FSMO HOLDER HEALTH CHECKS"
$FsmoList = Run-Command "FSMO Role Holders" "netdom query fsmo"
$FsmoDcdiag = Run-Command "FSMO Holder DCDIAG KnowsofRoleHolders" "dcdiag /test:knowsofroleholders"
if ($FsmoDcdiag.ToLowerInvariant() -match "failed test" -or $FsmoDcdiag.ToLowerInvariant() -match "error") {
Add-Finding "FSMO" "FSMO Holder Health" "FAIL" "FSMO holder awareness/health check shows error patterns."
} else {
Add-Finding "FSMO" "FSMO Holder Health" "PASS" "FSMO role holder awareness check returned without obvious failure text."
}
Write-Section "TOMBSTONE LIFETIME AND RECYCLE BIN CHECKS"
$TombstoneOutput = Run-Command "Tombstone Lifetime" 'Get-ADObject "CN=Directory Service,CN=Windows NT,CN=Services,$((Get-ADRootDSE).configurationNamingContext)" -Properties tombstoneLifetime,msDS-DeletedObjectLifetime | Select tombstoneLifetime,msDS-DeletedObjectLifetime'
$RecycleBinOutput = Run-Command "AD Recycle Bin" 'Get-ADOptionalFeature -Filter "name -like ''Recycle Bin Feature''" | Select Name,EnabledScopes'
if ($RecycleBinOutput -match "EnabledScopes" -and $RecycleBinOutput -notmatch "{}") {
Add-Finding "RecycleBin" "AD Recycle Bin" "PASS" "AD Recycle Bin appears enabled."
} else {
Add-Finding "RecycleBin" "AD Recycle Bin" "WARNING" "AD Recycle Bin does not appear enabled or could not be confirmed."
}
if ($TombstoneOutput -match "ERROR:") {
Add-Finding "Tombstone" "Tombstone Lifetime" "WARNING" "Could not query tombstone/deleted object lifetime."
} else {
Add-Finding "Tombstone" "Tombstone Lifetime" "PASS" "Tombstone lifetime information returned."
}
Write-Section "LDAP SIGNING / CHANNEL BINDING / NTLM HARDENING CHECKS"
$LDAPSignOutput = Run-Command "LDAP Signing Registry" 'reg query "HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" /v LDAPServerIntegrity'
$LDAPCBOutput = Run-Command "LDAP Channel Binding Registry" 'reg query "HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" /v LdapEnforceChannelBinding'
$NTLM1Output = Run-Command "NTLM Restriction - Lsa" 'reg query "HKLM\SYSTEM\CurrentControlSet\Control\Lsa"'
$NTLM2Output = Run-Command "NTLM Operational Events" 'Get-WinEvent -LogName "Microsoft-Windows-NTLM/Operational" -MaxEvents 100 | Select TimeCreated,Id,LevelDisplayName,Message'
if ($LDAPSignOutput -match "0x2" -or $LDAPSignOutput -match " 2") {
Add-Finding "LDAP" "LDAP Signing" "PASS" "LDAP signing appears configured as Require Signing."
} elseif ($LDAPSignOutput -match "0x1" -or $LDAPSignOutput -match " 1") {
Add-Finding "LDAP" "LDAP Signing" "WARNING" "LDAP signing appears configured as Negotiate Signing."
} else {
Add-Finding "LDAP" "LDAP Signing" "WARNING" "Could not confirm LDAP signing configuration."
}
if ($LDAPCBOutput -match "0x2" -or $LDAPCBOutput -match " 2") {
Add-Finding "LDAP" "LDAP Channel Binding" "PASS" "LDAP channel binding appears set to Always."
} elseif ($LDAPCBOutput -match "0x1" -or $LDAPCBOutput -match " 1") {
Add-Finding "LDAP" "LDAP Channel Binding" "WARNING" "LDAP channel binding appears set to When Supported."
} else {
Add-Finding "LDAP" "LDAP Channel Binding" "WARNING" "Could not confirm LDAP channel binding configuration."
}
if ($NTLM2Output -match "ERROR:" ) {
Add-Finding "NTLM" "NTLM Usage and Hardening" "WARNING" "Could not query NTLM operational log."
} elseif (($NTLM2Output -split "`r?`n" | Where-Object { $_.Trim() -ne "" }).Count -gt 5) {
Add-Finding "NTLM" "NTLM Usage and Hardening" "WARNING" "NTLM operational log contains entries. Review remaining NTLM dependencies."
} else {
Add-Finding "NTLM" "NTLM Usage and Hardening" "PASS" "Low visible NTLM operational event volume."
}
# ----------------------------
# Summary
# ----------------------------
$PassCount = ($Findings | Where-Object { $_.Status -eq "PASS" }).Count
$WarningCount = ($Findings | Where-Object { $_.Status -eq "WARNING" }).Count
$FailCount = ($Findings | Where-Object { $_.Status -eq "FAIL" }).Count
$TotalCount = $Findings.Count
function Get-CategoryWeight {
param([string]$Category)
switch ($Category) {
"DCDIAG" { return 10 }
"Replication" { return 10 }
"DNS" { return 8 }
"SYSVOL" { return 9 }
"DFSR" { return 9 }
"ADPrep" { return 10 }
"Backup" { return 10 }
"FSMO" { return 9 }
"Time" { return 8 }
"Schema" { return 8 }
"Secure Channel" { return 8 }
"Trust" { return 7 }
"GC" { return 7 }
"LDAP" { return 7 }
"NTLM" { return 6 }
"Kerberos RC4" { return 6 }
"StrictReplication" { return 7 }
"DNS Forwarders" { return 5 }
"ConditionalForward" { return 5 }
"Firewall" { return 4 }
"Privileged Groups" { return 4 }
"Sites" { return 4 }
"StaticDynamicDNS" { return 3 }
"RecycleBin" { return 3 }
"Tombstone" { return 3 }
"Events" { return 5 }
default { return 5 }
}
}
$Score = 100
foreach ($Finding in $Findings) {
$Weight = Get-CategoryWeight -Category $Finding.Category
switch ($Finding.Status) {
"FAIL" {
$Score -= $Weight
}
"WARNING" {
$Score -= [math]::Ceiling($Weight / 2)
}
default {
}
}
}
if ($Score -lt 0) { $Score = 0 }
if ($Score -gt 100) { $Score = 100 }
$CriticalFailCategories = @(
"DCDIAG",
"Replication",
"SYSVOL",
"DFSR",
"ADPrep",
"Backup",
"FSMO",
"DNS",
"Schema",
"Secure Channel"
)
$CriticalFailCount = (
$Findings |
Where-Object {
$_.Status -eq "FAIL" -and $_.Category -in $CriticalFailCategories
}
).Count
if ($CriticalFailCount -ge 1 -or $FailCount -ge 5 -or $Score -lt 40) {
$Readiness = "NOT READY"
$ReadinessClass = "readiness-notready"
}
elseif ($FailCount -gt 0 -or $WarningCount -ge 8 -or $Score -lt 75) {
$Readiness = "REVIEW REQUIRED"
$ReadinessClass = "readiness-review"
}
else {
$Readiness = "READY"
$ReadinessClass = "readiness-ready"
}
$SummaryHtml = New-Object System.Text.StringBuilder
[void]$SummaryHtml.AppendLine("<div class='dashboard' id='summary-dashboard'>")
[void]$SummaryHtml.AppendLine("<div class='card card-total' data-filter='ALL' onclick=""filterFindings('ALL')""><div class='card-title'>Total Assessed Checks</div><div class='card-value'>$TotalCount</div></div>")
[void]$SummaryHtml.AppendLine("<div class='card card-pass' data-filter='PASS' onclick=""filterFindings('PASS')""><div class='card-title'>PASS</div><div class='card-value'>$PassCount</div></div>")
[void]$SummaryHtml.AppendLine("<div class='card card-warning' data-filter='WARNING' onclick=""filterFindings('WARNING')""><div class='card-title'>WARNING</div><div class='card-value'>$WarningCount</div></div>")
[void]$SummaryHtml.AppendLine("<div class='card card-fail' data-filter='FAIL' onclick=""filterFindings('FAIL')""><div class='card-title'>FAIL</div><div class='card-value'>$FailCount</div></div>")
[void]$SummaryHtml.AppendLine("<div class='card card-score'><div class='card-title'>Readiness Score</div><div class='card-value'>$Score</div></div>")
[void]$SummaryHtml.AppendLine("</div>")
[void]$SummaryHtml.AppendLine("<div class='filter-note'>Click PASS / WARNING / FAIL to filter the findings table. Click Total Assessed Checks to show all results.</div>")
[void]$SummaryHtml.AppendLine("<div class='info-box'><b>Migration Readiness:</b> <span class='$ReadinessClass'>$Readiness</span></div>")
$CurrentHtml = $HtmlContent.ToString()
$InsertPoint = $CurrentHtml.IndexOf("</div>", $CurrentHtml.IndexOf("<div class='meta'>"))
if ($InsertPoint -gt 0) {
$InsertPoint += 6
$CurrentHtml = $CurrentHtml.Insert($InsertPoint, "`r`n" + $SummaryHtml.ToString())
$HtmlContent = New-Object System.Text.StringBuilder
[void]$HtmlContent.Append($CurrentHtml)
}
Write-Section "EXECUTIVE SUMMARY"
Add-HtmlLine "<div class='info-box'>"
Add-HtmlLine "<b>Overall Assessment:</b> $(Convert-ToSafeHtml $Readiness)<br>"
Add-HtmlLine "<b>Score:</b> $Score / 100<br>"
Add-HtmlLine "<b>Summary:</b> This environment returned $PassCount PASS, $WarningCount WARNING, and $FailCount FAIL findings across assessed pre-migration controls."
Add-HtmlLine "</div>"
Add-TxtLine ""
Add-TxtLine "EXECUTIVE SUMMARY"
Add-TxtLine "Overall Assessment : $Readiness"
Add-TxtLine "Readiness Score : $Score / 100"
Add-TxtLine "PASS : $PassCount"
Add-TxtLine "WARNING : $WarningCount"
Add-TxtLine "FAIL : $FailCount"
$Blockers = $Findings | Where-Object { $_.Status -eq "FAIL" }
Write-Section "MIGRATION BLOCKERS"
if ($Blockers.Count -gt 0) {
Add-HtmlLine "<div class='fail-box'><b>Blocking findings were detected. Migration should not proceed until these items are remediated.</b></div>"
Add-HtmlLine "<table>"
Add-HtmlLine "<tr><th>Category</th><th>Check</th><th>Status</th><th>Details</th><th>Recommended Action</th></tr>"
foreach ($Item in $Blockers) {
$Recommendation = Get-Recommendation -Category $Item.Category -Check $Item.Check -Status $Item.Status
Add-HtmlLine "<tr><td>$(Convert-ToSafeHtml $Item.Category)</td><td>$(Convert-ToSafeHtml $Item.Check)</td><td>$(Add-ResultBadge $Item.Status)</td><td>$(Convert-ToSafeHtml $Item.Details)</td><td>$(Convert-ToSafeHtml $Recommendation)</td></tr>"
Add-TxtLine ""
Add-TxtLine "BLOCKER: [$($Item.Category)] $($Item.Check)"
Add-TxtLine "Status : $($Item.Status)"
Add-TxtLine "Detail : $($Item.Details)"
Add-TxtLine "Action : $Recommendation"
}
Add-HtmlLine "</table>"
} else {
Add-HtmlLine "<div class='info-box'><b>No FAIL-level migration blockers were detected by the automated checks.</b></div>"
Add-TxtLine ""
Add-TxtLine "MIGRATION BLOCKERS"
Add-TxtLine "No FAIL-level migration blockers were detected by the automated checks."
}
Write-Section "RECOMMENDATIONS"
Add-HtmlLine "<table>"
Add-HtmlLine "<tr><th>Category</th><th>Check</th><th>Status</th><th>Recommended Action</th></tr>"
foreach ($Item in ($Findings | Where-Object { $_.Status -ne "PASS" })) {
$Recommendation = Get-Recommendation -Category $Item.Category -Check $Item.Check -Status $Item.Status
Add-HtmlLine "<tr><td>$(Convert-ToSafeHtml $Item.Category)</td><td>$(Convert-ToSafeHtml $Item.Check)</td><td>$(Add-ResultBadge $Item.Status)</td><td>$(Convert-ToSafeHtml $Recommendation)</td></tr>"
Add-TxtLine ""
Add-TxtLine "RECOMMENDATION: [$($Item.Category)] $($Item.Check)"
Add-TxtLine "Status : $($Item.Status)"
Add-TxtLine "Action : $Recommendation"
}
Add-HtmlLine "</table>"
Write-Section "CRITICAL FINDINGS SUMMARY"
Add-HtmlLine "<table id='critical-findings-table'>"
Add-HtmlLine "<thead><tr><th>Category</th><th>Check</th><th>Status</th><th>Details</th></tr></thead>"
Add-HtmlLine "<tbody>"
foreach ($Item in $Findings) {
$SafeCategory = Convert-ToSafeHtml $Item.Category
$SafeCheck = Convert-ToSafeHtml $Item.Check
$SafeDetails = Convert-ToSafeHtml $Item.Details
$SafeStatus = Convert-ToSafeHtml $Item.Status
Add-HtmlLine "<tr class='finding-row' data-status='$SafeStatus'>"
Add-HtmlLine "<td>$SafeCategory</td>"
Add-HtmlLine "<td>$SafeCheck</td>"
Add-HtmlLine "<td>$(Add-ResultBadge $Item.Status)</td>"
Add-HtmlLine "<td>$SafeDetails</td>"
Add-HtmlLine "</tr>"
}
Add-HtmlLine "</tbody>"
Add-HtmlLine "</table>"
Add-TxtLine ""
Add-TxtLine "CRITICAL FINDINGS SUMMARY"
foreach ($Item in $Findings) {
Add-TxtLine "[$($Item.Status)] [$($Item.Category)] $($Item.Check) - $($Item.Details)"
}
$CsvRows = foreach ($Item in $Findings) {
[PSCustomObject]@{
Server = $env:COMPUTERNAME
Domain = $env:USERDNSDOMAIN
Category = $Item.Category
Check = $Item.Check
Status = $Item.Status
Details = $Item.Details
Recommendation = (Get-Recommendation -Category $Item.Category -Check $Item.Check -Status $Item.Status)
Generated = (Get-Date)
Readiness = $Readiness
Score = $Score
}
}
$CsvRows | Export-Csv -Path $CsvFile -NoTypeInformation -Encoding UTF8
Add-HtmlLine "<div class='info-box'><b>Assessment Note:</b> This report is heuristic-based and should be validated with engineering review before the production migration window.</div>"
Add-HtmlLine "<script>"
Add-HtmlLine "function filterFindings(status) {"
Add-HtmlLine " const rows = document.querySelectorAll('#critical-findings-table tbody tr.finding-row');"
Add-HtmlLine " const cards = document.querySelectorAll('#summary-dashboard .card[data-filter]');"
Add-HtmlLine ""
Add-HtmlLine " rows.forEach(row => {"
Add-HtmlLine " const rowStatus = row.getAttribute('data-status');"
Add-HtmlLine " if (status === 'ALL' || rowStatus === status) {"
Add-HtmlLine " row.classList.remove('hidden-row');"
Add-HtmlLine " } else {"
Add-HtmlLine " row.classList.add('hidden-row');"
Add-HtmlLine " }"
Add-HtmlLine " });"
Add-HtmlLine ""
Add-HtmlLine " cards.forEach(card => {"
Add-HtmlLine " if (card.getAttribute('data-filter') === status) {"
Add-HtmlLine " card.classList.remove('inactive');"
Add-HtmlLine " } else {"
Add-HtmlLine " card.classList.add('inactive');"
Add-HtmlLine " }"
Add-HtmlLine " });"
Add-HtmlLine ""
Add-HtmlLine " if (status === 'ALL') {"
Add-HtmlLine " cards.forEach(card => card.classList.remove('inactive'));"
Add-HtmlLine " }"
Add-HtmlLine "}"
Add-HtmlLine "</script>"
Add-HtmlLine "</div>"
Add-HtmlLine "</body>"
Add-HtmlLine "</html>"
$HtmlContent.ToString() | Set-Content -Path $HtmlFile -Encoding UTF8
$TxtContent.ToString() | Set-Content -Path $TxtFile -Encoding UTF8
Stop-Transcript
Write-Host ""
Write-Host "====================================================" -ForegroundColor Green
Write-Host "VOLSYS AD Migration Ultimate Pre-Check completed." -ForegroundColor Green
Write-Host "HTML Report : $HtmlFile" -ForegroundColor Yellow
Write-Host "TXT Report : $TxtFile" -ForegroundColor Yellow
Write-Host "CSV Summary : $CsvFile" -ForegroundColor Yellow
Write-Host "Transcript : $Transcript" -ForegroundColor Yellow
Write-Host "Readiness : $Readiness" -ForegroundColor Cyan
Write-Host "Score : $Score / 100" -ForegroundColor Cyan
Write-Host "PASS : $PassCount" -ForegroundColor Green
Write-Host "WARNING : $WarningCount" -ForegroundColor DarkYellow
Write-Host "FAIL : $FailCount" -ForegroundColor Red
Write-Host "====================================================" -ForegroundColor Green