functions/System/Drift/Get-RDPSecurityDrift.ps1

function Get-RDPSecurityDrift {
    <#
    .SYNOPSIS
    Detects configuration drift in RDP security settings.

    .DESCRIPTION
    Comprehensive RDP security drift detection covering encryption level, NLA, port configuration,
    certificate requirements, and idle session timeout. Returns PSCustomObject array with drift findings.

    .PARAMETER MinRDPEncryptionLevel
    Minimum RDP encryption level (default: 3 = High/128-bit). Levels: 1=Low, 2=Medium, 3=High.

    .PARAMETER RequireNLA
    Whether RDP Network Level Authentication should be enabled (default: $true).

    .PARAMETER RequireCertificate
    Whether RDP certificate authentication should be required (default: $false).

    .PARAMETER MaxIdleTimeMinutes
    Maximum RDP idle timeout in minutes (default: 15). 0 means no timeout.

    .EXAMPLE
    $drifts = Get-RDPSecurityDrift
    if ($drifts.Count -gt 0) { $drifts | Write-Output }

    .EXAMPLE
    $drifts = Get-RDPSecurityDrift -MinRDPEncryptionLevel 3 -RequireNLA $true -MaxIdleTimeMinutes 10

    .NOTES
    DEPENDENCIES: Write-Log (Core)
    APPLIES TO: Windows Server 2016+ with RDP enabled
    #>

    param(
        [ValidateRange(1, 3)]
        [int]$MinRDPEncryptionLevel = 3,

        [bool]$RequireNLA = $true,

        [bool]$RequireCertificate = $false,

        [ValidateRange(0, 1440)]
        [int]$MaxIdleTimeMinutes = 15
    )

    $rdpPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp"
    $tsPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server"
    $findings = @()
    
    try {
        # Check RDP Service enabled
        $rdpEnabled = (Get-ItemProperty -Path $tsPath -Name fDenyTSConnections -ErrorAction SilentlyContinue).fDenyTSConnections
        if ($rdpEnabled -eq 1) {
            $findings += [PSCustomObject]@{
                Category = "RDP Security"
                Setting = "RDP Service Enabled"
                Expected = "Enabled"
                Actual = "Disabled"
                Status = "DRIFT"
                Severity = "HIGH"
            }
            Write-Log -Message "RDP Security drift: RDP service is disabled" -Level Warning -Caller $MyInvocation.MyCommand.Name
        }
    
        # Check RDP Encryption Level
        $encProperty = Get-ItemProperty -Path $rdpPath -Name MinEncryptionLevel -ErrorAction SilentlyContinue
        $rdpEncryption = $encProperty.MinEncryptionLevel
        if ($null -eq $rdpEncryption) {
            $rdpEncryption = 1
        }
        if ($rdpEncryption -lt $MinRDPEncryptionLevel) {
            $encLevelNames = @{ 1 = "Low"; 2 = "Medium"; 3 = "High (128-bit)" }
            $findings += [PSCustomObject]@{
                Category = "RDP Security"
                Setting = "Encryption Level"
                Expected = "$MinRDPEncryptionLevel ($($encLevelNames[$MinRDPEncryptionLevel]))"
                Actual = "$rdpEncryption ($($encLevelNames[$rdpEncryption]))"
                Status = "DRIFT"
                Severity = "HIGH"
            }
            Write-Log -Message "RDP Security drift: Encryption level is $rdpEncryption (expected $MinRDPEncryptionLevel)" -Level Warning -Caller $MyInvocation.MyCommand.Name
        }
    
        # Check RDP NLA
        $nlaProperty = Get-ItemProperty -Path $rdpPath -Name SecurityLayer -ErrorAction SilentlyContinue
        $rdpNLA = $nlaProperty.SecurityLayer
        if ($null -eq $rdpNLA) {
            $rdpNLA = 1
        }
        $nlaEnabled = $rdpNLA -eq 2
        if ($nlaEnabled -ne $RequireNLA) {
            $expectedNLA = if ($RequireNLA) {
                "2 (Enabled)"
            }
            else {
                "1 (Disabled)"
            }
            $nlaStatus = if ($nlaEnabled) {
                'enabled'
            }
            else {
                'disabled'
            }
            $nlaExpected = if ($RequireNLA) {
                'enabled'
            }
            else {
                'disabled'
            }
            $findings += [PSCustomObject]@{
                Category = "RDP Security"
                Setting = "Network Level Authentication"
                Expected = $expectedNLA
                Actual = "$rdpNLA ($nlaStatus)"
                Status = "DRIFT"
                Severity = "HIGH"
            }
            Write-Log -Message "RDP Security drift: NLA is $nlaStatus (expected $nlaExpected)" -Level Warning -Caller $MyInvocation.MyCommand.Name
        }
    
        # Check RDP Port
        $portProperty = Get-ItemProperty -Path $rdpPath -Name PortNumber -ErrorAction SilentlyContinue
        $rdpPort = $portProperty.PortNumber
        if ($null -eq $rdpPort) {
            $rdpPort = 3389
        }
        if ($rdpPort -ne 3389) {
            $findings += [PSCustomObject]@{
                Category = "RDP Security"
                Setting = "RDP Port"
                Expected = "3389 (Standard)"
                Actual = "$rdpPort (Non-standard)"
                Status = "DRIFT"
                Severity = "MEDIUM"
            }
            Write-Log -Message "RDP Security drift: RDP port is $rdpPort (expected 3389)" -Level Warning -Caller $MyInvocation.MyCommand.Name
        }
    
        # Check RDP Certificate requirement
        $certProperty = Get-ItemProperty -Path $rdpPath -Name SSLCertificateSHA1Hash -ErrorAction SilentlyContinue
        $hasCert = -not [string]::IsNullOrEmpty($certProperty.SSLCertificateSHA1Hash)
        if ($RequireCertificate -and -not $hasCert) {
            $findings += [PSCustomObject]@{
                Category = "RDP Security"
                Setting = "SSL Certificate"
                Expected = "Configured"
                Actual = "Not Configured"
                Status = "DRIFT"
                Severity = "MEDIUM"
            }
            Write-Log -Message "RDP Security drift: SSL certificate not configured" -Level Warning -Caller $MyInvocation.MyCommand.Name
        }
    
        # Check Idle Session Timeout
        $idleProperty = Get-ItemProperty -Path $rdpPath -Name MaxIdleTime -ErrorAction SilentlyContinue
        $maxIdleMs = $idleProperty.MaxIdleTime
        if ($null -eq $maxIdleMs) {
            $maxIdleMs = 0
        }
        $maxIdleMin = [math]::Floor($maxIdleMs / 60000)
        if ($MaxIdleTimeMinutes -gt 0 -and ($maxIdleMs -eq 0 -or $maxIdleMin -gt $MaxIdleTimeMinutes)) {
            $actualStatus = if ($maxIdleMs -eq 0) {
                "No timeout"
            }
            else {
                "$maxIdleMin minutes"
            }
            $findings += [PSCustomObject]@{
                Category = "RDP Security"
                Setting = "Idle Session Timeout"
                Expected = "$MaxIdleTimeMinutes minutes"
                Actual = $actualStatus
                Status = "DRIFT"
                Severity = "LOW"
            }
            Write-Log -Message "RDP Security drift: Idle timeout is $actualStatus (expected $MaxIdleTimeMinutes min)" -Level Warning -Caller $MyInvocation.MyCommand.Name
        }
    }
    catch {
        Write-Log -Message "Error checking RDP security: $_" -Level Error -Caller $MyInvocation.MyCommand.Name
        throw
    }
    
    return $findings
}