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-NullStart-Transcript -Path $Transcript -Force$HtmlContent = New-Object System.Text.StringBuilder$TxtContent = New-Object System.Text.StringBuilder$Findings = New-Object System.Collections.ArrayListfunction 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 '<', '<' ` -replace '>', '>' ` -replace '"', '"' ` -replace "'", '''}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-NullRun-Command "Current User" "whoami" | Out-NullRun-Command "IP Configuration" "ipconfig /all" | Out-NullRun-Command "System Information" "systeminfo" | Out-NullWrite-Section "DOMAIN AND FOREST INFORMATION"Run-Command "Get-ADDomain" "Get-ADDomain" | Out-NullRun-Command "Get-ADForest" "Get-ADForest" | Out-NullRun-Command "Domain Controllers" "Get-ADDomainController -Filter *" | Out-NullRun-Command "Domain Mode" "Get-ADDomain | Select Name,DomainMode,PDCEmulator,RIDMaster,InfrastructureMaster" | Out-NullRun-Command "Forest Mode" "Get-ADForest | Select ForestMode,SchemaMaster,DomainNamingMaster" | Out-NullWrite-Section "FSMO ROLES"Run-AssessedCommand "FSMO" "FSMO Roles" "netdom query fsmo" { param($r) Get-GenericErrorStatus $r } | Out-NullWrite-Section "DCDIAG HEALTH CHECKS"Run-AssessedCommand "DCDIAG" "DCDIAG Verbose" "dcdiag /v" { param($r) Get-DcdiagStatus $r } | Out-NullRun-AssessedCommand "DCDIAG" "DCDIAG DNS" "dcdiag /test:dns /v" { param($r) Get-DcdiagStatus $r } | Out-NullRun-AssessedCommand "DCDIAG" "DCDIAG Replications" "dcdiag /test:replications" { param($r) Get-DcdiagStatus $r } | Out-NullRun-AssessedCommand "DCDIAG" "DCDIAG Advertising" "dcdiag /test:advertising" { param($r) Get-DcdiagStatus $r } | Out-NullRun-AssessedCommand "DCDIAG" "DCDIAG SYSVOL" "dcdiag /test:sysvolcheck" { param($r) Get-DcdiagStatus $r } | Out-NullRun-AssessedCommand "DCDIAG" "DCDIAG Services" "dcdiag /test:services" { param($r) Get-DcdiagStatus $r } | Out-NullWrite-Section "REPLICATION CHECKS"Run-AssessedCommand "Replication" "Replication Summary" "repadmin /replsummary" { param($r) Get-RepadminSummaryStatus $r } | Out-NullRun-AssessedCommand "Replication" "Show Replication" "repadmin /showrepl" { param($r) Get-GenericErrorStatus $r } | Out-NullRun-Command "Replication Queue" "repadmin /queue" | Out-NullRun-Command "Replication Fail Cache" "repadmin /failcache" | Out-NullRun-Command "Replication Latency" "repadmin /latency *" | Out-NullWrite-Section "DNS CHECKS"Run-AssessedCommand "DNS" "LDAP SRV Record" "nslookup _ldap._tcp.dc._msdcs.$env:USERDNSDOMAIN" { param($r) Get-NslookupStatus $r } | Out-NullRun-AssessedCommand "DNS" "Kerberos SRV Record" "nslookup _kerberos._tcp.dc._msdcs.$env:USERDNSDOMAIN" { param($r) Get-NslookupStatus $r } | Out-NullRun-AssessedCommand "DNS" "DSGetDC" "nltest /dsgetdc:$env:USERDNSDOMAIN" { param($r) Get-GenericErrorStatus $r } | Out-NullRun-Command "DNS Cache" "ipconfig /displaydns" | Out-NullRun-Command "DC List" "nltest /dclist:$env:USERDNSDOMAIN" | Out-NullWrite-Section "SYSVOL AND DFSR CHECKS"Run-AssessedCommand "SYSVOL" "Net Share" "net share" { param($r) Get-ShareStatus $r } | Out-NullRun-AssessedCommand "DFSR" "DFSR Global State" "dfsrmig /getglobalstate" { param($r) Get-DfsrStateStatus $r } | Out-NullRun-Command "DFSR Migration State" "dfsrmig /getmigrationstate" | Out-NullRun-Command "DFSR Replication State" "dfsrdiag replicationstate" | Out-NullWrite-Section "TIME SYNCHRONIZATION CHECKS"Run-AssessedCommand "Time" "W32Time Status" "w32tm /query /status" { param($r) Get-W32tmStatus $r } | Out-NullRun-AssessedCommand "Time" "W32Time Source" "w32tm /query /source" { param($r) Get-W32tmStatus $r } | Out-NullRun-Command "W32Time Configuration" "w32tm /query /configuration" | Out-NullRun-Command "W32Time Monitor" "w32tm /monitor" | Out-NullWrite-Section "GLOBAL CATALOG CHECKS"Run-Command "Global Catalog Servers" "Get-ADDomainController -Filter * | Select HostName,IsGlobalCatalog" | Out-NullRun-AssessedCommand "GC" "GC Discovery" "nltest /dsgetdc:$env:USERDNSDOMAIN /GC" { param($r) Get-GenericErrorStatus $r } | Out-NullWrite-Section "FUNCTIONAL LEVEL AND SCHEMA CHECKS"Run-Command "Domain Functional Level" "Get-ADDomain | Select Name,DomainMode" | Out-NullRun-Command "Forest Functional Level" "Get-ADForest | Select Name,ForestMode" | Out-NullRun-AssessedCommand "Schema" "Schema Version" "Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion" { param($r) Get-GenericErrorStatus $r } | Out-NullWrite-Section "KERBEROS AND NTLM CHECKS"Run-Command "Kerberos Tickets" "klist" | Out-NullRun-AssessedCommand "Secure Channel" "Secure Channel Query" "nltest /sc_query:$env:USERDNSDOMAIN" { param($r) Get-GenericErrorStatus $r } | Out-NullRun-AssessedCommand "Secure Channel" "Secure Channel Verify" "nltest /sc_verify:$env:USERDNSDOMAIN" { param($r) Get-GenericErrorStatus $r } | Out-NullWrite-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-NullRun-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-NullRun-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-NullRun-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-NullRun-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-NullRun-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-NullRun-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-NullRun-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-NullWrite-Section "BACKUP CHECKS"$BackupVersions = Run-Command "WBAdmin Versions" "wbadmin get versions"Run-Command "WBAdmin Status" "wbadmin get status" | Out-NullRun-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-NullWrite-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-NullRun-Command "Domain Admins Members" 'Get-ADGroupMember -Identity "Domain Admins" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-NullRun-Command "Enterprise Admins Members" 'Get-ADGroupMember -Identity "Enterprise Admins" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-NullRun-Command "Schema Admins Members" 'Get-ADGroupMember -Identity "Schema Admins" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-NullRun-Command "Protected Users Members" 'Get-ADGroupMember -Identity "Protected Users" -Recursive | Select Name,ObjectClass,SamAccountName' | Out-Nulltry { $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.Countfunction 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 = 100foreach ($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 }).Countif ($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 UTF8Add-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 UTF8Stop-TranscriptWrite-Host ""Write-Host "====================================================" -ForegroundColor GreenWrite-Host "VOLSYS AD Migration Ultimate Pre-Check completed." -ForegroundColor GreenWrite-Host "HTML Report : $HtmlFile" -ForegroundColor YellowWrite-Host "TXT Report : $TxtFile" -ForegroundColor YellowWrite-Host "CSV Summary : $CsvFile" -ForegroundColor YellowWrite-Host "Transcript : $Transcript" -ForegroundColor YellowWrite-Host "Readiness : $Readiness" -ForegroundColor CyanWrite-Host "Score : $Score / 100" -ForegroundColor CyanWrite-Host "PASS : $PassCount" -ForegroundColor GreenWrite-Host "WARNING : $WarningCount" -ForegroundColor DarkYellowWrite-Host "FAIL : $FailCount" -ForegroundColor RedWrite-Host "====================================================" -ForegroundColor Green