Scripts/Test-FortiClientFirewallBlocking.ps1

<#
.SYNOPSIS
    Checks whether Windows Firewall or third-party security software could be
    blocking FortiClient's IKEv2 traffic (UDP 500/4500) or its processes.
 
.DESCRIPTION
    Three modes:
 
    1) Static check (default) - firewall profile state, any rules touching
       Fortinet's binaries or ports 500/4500, and what security products are
       actually registered with Windows (catches HP Wolf Security / other
       OEM-bundled firewalls that aren't the Windows Defender Firewall you'd
       normally check).
 
    2) -EnableBlockAuditing - turns on Windows Filtering Platform "connection
       blocked" auditing (Security event ID 5157). This is the one that
       actually catches a SILENT drop live - run this before the connection
       attempt, alongside the capture scripts. A block that never shows up
       anywhere else will show up here, with the exact rule/filter that did it.
 
    3) -ShowBlockedEvents - reads back 5157 events from the Security log,
       filtered to ports 500/4500 or Fortinet process paths, since the time
       you specify. Run this after the attempt.
 
    4) -DisableBlockAuditing - turns auditing back off afterward (it does add
       Security log volume, so don't leave it on indefinitely).
 
    Part of the FortiClient Connection Troubleshooting Toolkit (see FCT-Toolkit.ps1 in this same
    folder for the menu-driven entry point) - also fully usable standalone.
 
.EXAMPLE
    # Before the attempt
    .\Test-FortiClientFirewallBlocking.ps1 -EnableBlockAuditing
 
.EXAMPLE
    # After the attempt
    .\Test-FortiClientFirewallBlocking.ps1 -ShowBlockedEvents -SinceMinutes 15
 
.EXAMPLE
    # Cleanup
    .\Test-FortiClientFirewallBlocking.ps1 -DisableBlockAuditing
#>


[CmdletBinding()]
param(
    [switch]$EnableBlockAuditing,
    [switch]$DisableBlockAuditing,
    [switch]$ShowBlockedEvents,
    [int]$SinceMinutes = 30
)

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

# ---------------------------------------------------------------------------
if ($EnableBlockAuditing) {
    Write-Section "Enabling WFP Block Auditing"
    auditpol /set /subcategory:"Filtering Platform Connection" /failure:enable | Out-Null
    auditpol /get /subcategory:"Filtering Platform Connection"
    Write-Host "`nAuditing enabled. Have the user attempt the connection now." -ForegroundColor Green
    Write-Host "Then run this script again with -ShowBlockedEvents." -ForegroundColor Green
    return
}

if ($DisableBlockAuditing) {
    Write-Section "Disabling WFP Block Auditing"
    auditpol /set /subcategory:"Filtering Platform Connection" /failure:disable | Out-Null
    auditpol /get /subcategory:"Filtering Platform Connection"
    Write-Host "`nAuditing disabled." -ForegroundColor Green
    return
}

if ($ShowBlockedEvents) {
    Write-Section "Blocked Connections - Security Log 5157 (last $SinceMinutes min)"
    $since = (Get-Date).AddMinutes(-$SinceMinutes)
    $events = Get-WinEvent -FilterHashtable @{
        LogName   = 'Security'
        Id        = 5157
        StartTime = $since
    } -ErrorAction SilentlyContinue

    if (-not $events) {
        Write-Host "No 5157 events found. Either nothing was blocked, or auditing wasn't enabled for this window."
        return
    }

    $events | ForEach-Object {
        $xml = [xml]$_.ToXml()
        $data = @{}
        foreach ($d in $xml.Event.EventData.Data) { $data[$d.Name] = $d.'#text' }

        $isRelevant = $data.DestPort -in '500','4500' -or
                      $data.SourcePort -in '500','4500' -or
                      $data.Application -match 'forti|ipsec'

        if ($isRelevant) {
            Write-Host "`n[$($_.TimeCreated)]"
            Write-Host " Application: $($data.Application)"
            Write-Host " Direction: $($data.Direction)"
            Write-Host " Src: $($data.SourceAddress):$($data.SourcePort)"
            Write-Host " Dst: $($data.DestAddress):$($data.DestPort)"
            Write-Host " Protocol: $($data.Protocol)"
            Write-Host " Filter ID: $($data.FilterRTID)"
            Write-Host " Layer: $($data.LayerName)"
        }
    }
    return
}

# ---------------------------------------------------------------------------
# DEFAULT: static check
# ---------------------------------------------------------------------------

Write-Section "Windows Firewall Profile State"
Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction, LogBlocked, LogAllowed, LogFileName |
    Format-Table -AutoSize

Write-Section "Registered Security Products (Security Center)"
try {
    Get-CimInstance -Namespace 'root\SecurityCenter2' -ClassName FirewallProduct -ErrorAction Stop |
        Select-Object displayName, productState | Format-Table -AutoSize
    Get-CimInstance -Namespace 'root\SecurityCenter2' -ClassName AntiVirusProduct -ErrorAction Stop |
        Select-Object displayName, productState | Format-Table -AutoSize
} catch {
    Write-Host "Could not query SecurityCenter2 (may require elevation or isn't populated on this SKU)."
}
Write-Host "(If anything other than 'Windows Defender Firewall' / 'Microsoft Defender Antivirus' shows up here - e.g. HP Wolf Security, McAfee, etc. - that product's OWN firewall/network filter is a real candidate and Windows Firewall rules below won't reflect it.)"

Write-Section "Firewall Rules Referencing Fortinet Binaries"
$fortiPaths = @(
    'fortivpn.exe','ipsecproxy.exe','ipsec.exe','FortiClient.exe','fcappdb.exe','FortiSSLVPNdaemon.exe'
)
Get-NetFirewallApplicationFilter | Where-Object {
    $ap = $_.Program
    $fortiPaths | Where-Object { $ap -match [regex]::Escape($_) }
} | ForEach-Object {
    $rule = $_ | Get-NetFirewallRule
    [PSCustomObject]@{
        RuleName  = $rule.DisplayName
        Enabled   = $rule.Enabled
        Direction = $rule.Direction
        Action    = $rule.Action
        Program   = $_.Program
    }
} | Format-Table -AutoSize

Write-Section "Firewall Rules Referencing UDP 500 / 4500"
Get-NetFirewallPortFilter | Where-Object {
    $_.Protocol -eq 'UDP' -and ($_.LocalPort -match '500|4500' -or $_.RemotePort -match '500|4500')
} | ForEach-Object {
    $rule = $_ | Get-NetFirewallRule
    [PSCustomObject]@{
        RuleName  = $rule.DisplayName
        Enabled   = $rule.Enabled
        Direction = $rule.Direction
        Action    = $rule.Action
        LocalPort = $_.LocalPort
        RemotePort= $_.RemotePort
    }
} | Format-Table -AutoSize

Write-Section "Built-in 'Core Networking - IPsec' Rule Group State"
Get-NetFirewallRule -Group '*IPsec*' -ErrorAction SilentlyContinue |
    Select-Object DisplayName, Enabled, Direction, Action | Format-Table -AutoSize

Write-Section "Non-Microsoft WFP Filter Providers (catches EDR/VPN agents that skip Security Center registration)"
try {
    $wfpFile = Join-Path $env:TEMP "wfpstate_$(Get-Date -Format 'yyyyMMdd_HHmmss').xml"
    netsh wfp show state file="$wfpFile" | Out-Null
    if (Test-Path $wfpFile) {
        [xml]$wfp = Get-Content $wfpFile
        $providers = $wfp.wfpdiag.providers.item |
            Where-Object { $_.providerKey.data -notmatch '^\{4b153735|^\{f3183a' } |  # common MS provider GUID prefixes, best-effort
            Select-Object -ExpandProperty name -Unique
        $providers | Where-Object { $_ -and $_ -notmatch 'Microsoft|Windows' } | ForEach-Object {
            Write-Host " Non-Microsoft filter provider: $_"
        }
        if (-not ($providers | Where-Object { $_ -notmatch 'Microsoft|Windows' })) {
            Write-Host " No non-Microsoft WFP providers found."
        }
        Remove-Item $wfpFile -ErrorAction SilentlyContinue
    }
} catch {
    Write-Host "Could not enumerate WFP state (needs elevation)."
}

Write-Section "Known EDR / VPN / SASE Agent Sweep (process + service names)"
$vendorPatterns = @(
    'CSFalcon','SentinelAgent','SentinelOne','CylanceSvc','Cb Defense','CarbonBlack',
    'TmCCSF','TrendMicro','McAfee','mfe','Symantec','sepMasterService','Sophos',
    'Trellix','Forcepoint','Netskope','nsdiag','ZSATunnel','Zscaler',
    'GlobalProtect','PanGP','CiscoAnyConnect','vpnagent','Umbrella','csc_',
    'CheckPoint','FortiEDR','wolf' # HP Wolf Security
)
$hits = Get-Process | Where-Object { $n = $_.ProcessName; $vendorPatterns | Where-Object { $n -match $_ } }
$hits += Get-Service | Where-Object { $_.Status -eq 'Running' } | Where-Object { $n = $_.DisplayName; $vendorPatterns | Where-Object { $n -match $_ } }
if ($hits) {
    $hits | Select-Object -Unique | Format-Table -AutoSize
    Write-Host "Any of these represent a network filter that Windows Firewall rules above won't show." -ForegroundColor Yellow
} else {
    Write-Host " No known EDR/VPN/SASE agents found running under checked name patterns."
    Write-Host " (This list isn't exhaustive - if the user's org is known to use a specific EDR product, check its process name directly.)"
}

Write-Host "`nNext step: run with -EnableBlockAuditing before the next attempt," -ForegroundColor Yellow
Write-Host "then -ShowBlockedEvents afterward to see any live silent drop." -ForegroundColor Yellow
Write-Host "For the most conclusive answer - does the packet even leave the NIC - use the" -ForegroundColor Yellow
Write-Host "companion packet capture script (Start-FortiClientPacketCapture.ps1)." -ForegroundColor Yellow