Public/Test-GSAPrivateAccessRule.ps1

#
# Function: Test-GSAPrivateAccessRule
# Public function for the GSA client diagnostics toolkit.
#
function Test-GSAPrivateAccessRule
{
    <#
.SYNOPSIS
    Checks whether the locally cached GSA forwarding profile permits a target/port/protocol via Private Access.
.DESCRIPTION
    Reads the ForwardingProfile registry value and evaluates its policy.rules against the supplied
    FQDN or IPv4 address, port, and protocol - mirroring the client-side rule matching the GSA client
    performs itself. This validates the local client configuration only; it does not call Microsoft Graph
    or require sign-in, unlike checking the application/profile assignment in the Entra portal.
.PARAMETER FQDNorIP
    The target FQDN or IPv4 address to test.
.PARAMETER Port
    The target TCP/UDP port number.
.PARAMETER Protocol
    The protocol to test (TCP or UDP).
.PARAMETER RegistryValueName
    Registry value name containing the GSA JSON policy (default: ForwardingProfile).
.PARAMETER PassThru
    Return the $true/$false test result. By default nothing is returned - only logged - so calling
    this function interactively doesn't echo a stray True/False after the log output.
.EXAMPLE
    Test-GSAPrivateAccessRule -FQDNorIP fileserver.contoso.com -Port 445 -Protocol TCP
.EXAMPLE
    if (Test-GSAPrivateAccessRule -FQDNorIP fileserver.contoso.com -Port 445 -Protocol TCP -PassThru)
    { ... }
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$FQDNorIP,
        [Parameter(Mandatory)]
        [int]$Port,
        [Parameter(Mandatory)]
        [ValidateSet("TCP", "UDP")]
        [string]$Protocol,
        [string]$RegistryValueName = "ForwardingProfile",
        [switch]$PassThru,
        [ValidateSet("ERROR", "WARNING", "INFO", "DEBUG", "SUCCESS")]
        [string]$LogLevel
    )

    if ($PSBoundParameters.ContainsKey('LogLevel'))
    { $script:LogLevel = $LogLevel }

    $inputType = Test-IPorFQDN -FQDNorIP $FQDNorIP
    if (-not $inputType)
    {
        Write-Log "'$FQDNorIP' is not a valid IPv4 address or FQDN." "ERROR"
        if ($PassThru)
        { return $false }
        return
    }

    Write-Log "Checking Private Access rules in the local forwarding profile for ${FQDNorIP}:${Port}/${Protocol}..." "INFO"

    # Load the GSA forwarding profile from the registry and parse it as JSON.
    $jsonContent = Get-JsonFromRegistry -ValueName $RegistryValueName
    if (-not $jsonContent)
    {
        Write-Log "Failed to load GSA client configuration from the registry." "ERROR"
        if ($PassThru)
        { return $false }
        return
    }

    # Extract the Private Access rules from the parsed JSON content.
    $rules = @($jsonContent.policy.rules)
    if ($rules.Count -eq 0 -or -not $rules[0])
    {
        Write-Log "No Private Access rules found in the forwarding profile. Ensure Private Access is enabled and at least one application is configured in Entra portal." "ERROR"
        if ($PassThru)
        { return $false }
        return
    }

    # Find the matching Private Access rule for the given FQDN/IP, port, and protocol.
    $match = Get-GSAPrivateAccessRuleMatch -Rules $rules -FQDNorIP $FQDNorIP -InputType $inputType -Port $Port -Protocol $Protocol

    if (-not $match.AddressMatched)
    {
        Write-Log "'$FQDNorIP' is not covered by any Private Access rule in the local forwarding profile." "ERROR"
        Write-Log "Recommended action: Ensure '$FQDNorIP' is configured as a destination in a Private Access application (Entra > Global Secure Access > Applications) and that the profile has been re-downloaded (delete the ForwardingProfileTimestamp registry value and restart GlobalSecureAccessForwardingProfileService)." "WARNING"
        if ($PassThru)
        { return $false }
        return
    }
    Write-Log "'$FQDNorIP' is covered by rule '$($match.RuleId)' (App ID: $($match.AppId))." "SUCCESS"

    if (-not $match.PortMatched)
    {
        Write-Log "Port $Port is NOT configured for the matching Private Access application (App ID: $($match.AppId))." "ERROR"
        Write-Log "Recommended action: Add port $Port to the application segment for App ID: $($match.AppId)." "WARNING"
        if ($PassThru)
        { return $false }
        return
    }
    Write-Log "Port $Port is configured for the matching Private Access application." "SUCCESS"

    if (-not $match.ProtocolMatched)
    {
        Write-Log "Protocol $Protocol is NOT configured for port $Port on the matching Private Access application (App ID: $($match.AppId))." "ERROR"
        Write-Log "Recommended action: Add the $Protocol protocol to the matching application segment for App ID: $($match.AppId)." "WARNING"
        if ($PassThru)
        { return $false }
        return
    }

    Write-Log "Test passed: '$FQDNorIP' on port $Port/$Protocol is allowed by the local Private Access forwarding profile (Rule ID: $($match.RuleId), App ID: $($match.AppId))." "SUCCESS"
    if ($PassThru)
    { return $true }
}