```powershell
<#
.SYNOPSIS
    Validates readiness for BitLocker Network Unlock on Windows Server 2022.

.DESCRIPTION
    This script performs non-destructive checks that help confirm a server is ready
    for BitLocker Network Unlock deployment via Group Policy.

    It checks:
    - Basic OS and PowerShell prerequisites
    - BitLocker status on the OS volume
    - Group Policy application and likely policy scope
    - Network reachability to a specified unlock service endpoint (optional)
    - Key warning conditions that can cause rollout failures

    The script does not change BitLocker state, modify Group Policy, or install components.

.NOTES
    Vendor-neutral and safe to run in read-only mode.
    Requires administrative privileges for full BitLocker and policy reporting.
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$Volume = 'C:',

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$UnlockServiceNameOrIP,

    [Parameter(Mandatory = $false)]
    [ValidateRange(1, 65535)]
    [int]$UnlockServicePort = 4011,

    [Parameter(Mandatory = $false)]
    [switch]$GenerateGpresultReport,

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$ReportPath = "$env:TEMP\bitlocker-network-unlock-validation.html"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Write-Section {
    param([string]$Text)
    Write-Host "`n=== $Text ===" -ForegroundColor Cyan
}

function Write-Result {
    param(
        [string]$Name,
        [bool]$Passed,
        [string]$Details
    )

    if ($Passed) {
        Write-Host "[PASS] $Name - $Details" -ForegroundColor Green
    }
    else {
        Write-Host "[WARN] $Name - $Details" -ForegroundColor Yellow
    }
}

$results = New-Object System.Collections.Generic.List[object]

Write-Section 'Prerequisite Checks'

# Basic OS check
$os = Get-CimInstance Win32_OperatingSystem
$isServer = $os.ProductType -ne 1
$results.Add([pscustomobject]@{
    Check   = 'Operating system is a server'
    Passed  = $isServer
    Details = "$($os.Caption)"
}) | Out-Null
Write-Result -Name 'Operating system is a server' -Passed $isServer -Details $os.Caption

# BitLocker module availability
$bitlockerModuleAvailable = [bool](Get-Module -ListAvailable -Name BitLocker)
$results.Add([pscustomobject]@{
    Check   = 'BitLocker module available'
    Passed  = $bitlockerModuleAvailable
    Details = if ($bitlockerModuleAvailable) { 'BitLocker module found' } else { 'BitLocker module not found' }
}) | Out-Null
Write-Result -Name 'BitLocker module available' -Passed $bitlockerModuleAvailable -Details (if ($bitlockerModuleAvailable) { 'BitLocker module found' } else { 'BitLocker module not found' })

# Volume status
try {
    $blv = Get-BitLockerVolume -MountPoint $Volume
    $encryptionStatus = $blv.VolumeStatus
    $protectionStatus = $blv.ProtectionStatus
    $protectionEnabled = $protectionStatus -eq 'On'
    $results.Add([pscustomobject]@{
        Check   = 'BitLocker enabled on target volume'
        Passed  = $protectionEnabled
        Details = "VolumeStatus=$encryptionStatus; ProtectionStatus=$protectionStatus"
    }) | Out-Null
    Write-Result -Name 'BitLocker enabled on target volume' -Passed $protectionEnabled -Details "VolumeStatus=$encryptionStatus; ProtectionStatus=$protectionStatus"

    $protectors = @($blv.KeyProtector)
    $hasProtectors = $protectors.Count -gt 0
    $results.Add([pscustomobject]@{
        Check   = 'BitLocker protectors present'
        Passed  = $hasProtectors
        Details = if ($hasProtectors) { "Protectors=$($protectors.KeyProtectorType -join ', ')" } else { 'No protectors detected' }
    }) | Out-Null
    Write-Result -Name 'BitLocker protectors present' -Passed $hasProtectors -Details (if ($hasProtectors) { "Protectors=$($protectors.KeyProtectorType -join ', ')" } else { 'No protectors detected' })
}
catch {
    $results.Add([pscustomobject]@{
        Check   = 'BitLocker volume query'
        Passed  = $false
        Details = $_.Exception.Message
    }) | Out-Null
    Write-Result -Name 'BitLocker volume query' -Passed $false -Details $_.Exception.Message
}

Write-Section 'Group Policy Checks'

try {
    $gpresultOk = $true
    if ($GenerateGpresultReport) {
        $reportDir = Split-Path -Path $ReportPath -Parent
        if (-not (Test-Path $reportDir)) {
            New-Item -ItemType Directory -Path $reportDir -Force | Out-Null
        }
        gpresult /h $ReportPath | Out-Null
        $reportExists = Test-Path $ReportPath
        $results.Add([pscustomobject]@{
            Check   = 'gpresult report generated'
            Passed  = $reportExists
            Details = if ($reportExists) { $ReportPath } else { 'Report not created' }
        }) | Out-Null
        Write-Result -Name 'gpresult report generated' -Passed $reportExists -Details (if ($reportExists) { $ReportPath } else { 'Report not created' })
    }
    else {
        Write-Host 'Skipping gpresult HTML report generation. Use -GenerateGpresultReport to create one.' -ForegroundColor DarkYellow
    }

    $gpservice = Get-Service -Name 'gpsvc' -ErrorAction Stop
    $results.Add([pscustomobject]@{
        Check   = 'Group Policy service running'
        Passed  = $gpservice.Status -eq 'Running'
        Details = "Status=$($gpservice.Status)"
    }) | Out-Null
    Write-Result -Name 'Group Policy service running' -Passed ($gpservice.Status -eq 'Running') -Details "Status=$($gpservice.Status)"
}
catch {
    $results.Add([pscustomobject]@{
        Check   = 'Group Policy checks'
        Passed  = $false
        Details = $_.Exception.Message
    }) | Out-Null
    Write-Result -Name 'Group Policy checks' -Passed $false -Details $_.Exception.Message
}

Write-Section 'Network Unlock Reachability'

if ($PSBoundParameters.ContainsKey('UnlockServiceNameOrIP')) {
    try {
        $ping = Test-Connection -ComputerName $UnlockServiceNameOrIP -Count 1 -Quiet -ErrorAction Stop
        $results.Add([pscustomobject]@{
            Check   = 'Unlock service hostname/IP reachable'
            Passed  = $ping
            Details = "$UnlockServiceNameOrIP responded to ICMP"
        }) | Out-Null
        Write-Result -Name 'Unlock service hostname/IP reachable' -Passed $ping -Details "$UnlockServiceNameOrIP responded to ICMP"

        $tcp = Test-NetConnection -ComputerName $UnlockServiceNameOrIP -Port $UnlockServicePort -WarningAction SilentlyContinue
        $results.Add([pscustomobject]@{
            Check   = 'Unlock service port reachable'
            Passed  = $tcp.TcpTestSucceeded
            Details = "Port=$UnlockServicePort; RemoteAddress=$($tcp.RemoteAddress)"
        }) | Out-Null
        Write-Result -Name 'Unlock service port reachable' -Passed $tcp.TcpTestSucceeded -Details "Port=$UnlockServicePort; RemoteAddress=$($tcp.RemoteAddress)"
    }
    catch {
        $results.Add([pscustomobject]@{
            Check   = 'Unlock service reachability'
            Passed  = $false
            Details = $_.Exception.Message
        }) | Out-Null
        Write-Result -Name 'Unlock service reachability' -Passed $false -Details $_.Exception.Message
    }
}
else {
    Write-Host 'No unlock service endpoint provided. Network reachability checks skipped.' -ForegroundColor DarkYellow
}

Write-Section 'Rollout Readiness Summary'

$passCount = ($results | Where-Object { $_.Passed }).Count
$warnCount = ($results | Where-Object { -not $_.Passed }).Count

Write-Host "Passed: $passCount" -ForegroundColor Green
Write-Host "Warnings/Failures: $warnCount" -ForegroundColor Yellow

if ($warnCount -gt 0) {
    Write-Host "`nReview warnings before production rollout. Common issues include unsupported boot paths, missing protectors, policy scope conflicts, and early-boot network reachability problems." -ForegroundColor Yellow
}
else {
    Write-Host "`nAll checks passed. The server appears ready for controlled BitLocker Network Unlock validation." -ForegroundColor Green
}

# Output structured results for automation if needed
$results
```