powershell max authent policies
# NetScaler Authentication Policy Analyzer
# Analyse spécialisée pour les objets AAA - Application Traffic
# v2.3 - BUGFIX: Login schemas dans labels vides correctement détectés
#
# IMPORTANT: Chain of authentication:
# Actions (define HOW) -> Policies (define WHEN) -> Bindings (define WHERE)
# Example: LDAP_Action -> Auth_Policy -> bound to AAA_vServer
#
# === CHANGELOG ===
# v2.3 (Current):
# - BUGFIX CRITIQUE: Les login schemas dans des labels sans policies
# sont maintenant correctement marqués comme NON utilisés (rouge)
# - FIX: Pattern corrigé pour -loginSchema (case insensitive: -login[Ss]chema)
# - FIX: Détection améliorée de nextFactor (cherche dans vserver ET policylabel)
# - FIX: Correction d'une erreur de syntaxe (accolades dupliquées)
# - Messages détaillés expliquant pourquoi un schema n'est pas utilisé :
# * "(in label: X but label has NO policies)" - Label vide
# * "(in label: X but policies not bound to vserver)" - Policies non bindées
# * "(not used anywhere)" - Pas dans un label du tout
#
# v2.2:
# - CORRECTION MAJEURE: Détection complète des login schemas
# - Vérification de la chaîne complète pour les schemas via policylabel
# - Schema -> PolicyLabel -> Policy -> vServer
# - Détection des schemas utilisés via loginschema policies
# - Détection des policylabels utilisés comme nextFactor
# - Messages détaillés sur pourquoi un schema n'est pas utilisé
#
# Exemple de détection login schema:
# - lschema_Auth_Role_Error -> APL_SAML_Role_Error (label) -> AAA_AUTH-POL_EPA (policy) -> VS-AAA (vserver)
# Le script vérifie TOUTE cette chaîne pour confirmer l'utilisation
#
# v2.1:
# - Réorganisation des objets par ordre logique de chaînage
# - Profile -> vServer -> Schema/Policy -> Label -> Action
# - Chemin par défaut changé vers C:\logs\ns.conf
# - Suppression des sections non essentielles pour clarté
#
# v2.0:
# - Ajout complet de l'analyse des Actions d'authentification
# - Analyse de la chaîne complète: Action -> Policy -> Binding
# - Support des 5 types d'actions: LDAP, OAUTH, SAML, RADIUS, EPA
# - Affichage avec code couleur spécifique pour les actions (cyan)
# - Détection des policies qui utilisent des actions mais ne sont pas bindées
#
# v1.3:
# - Correction du calcul des statistiques (Total = Bound + Unbound)
# - Ajout du mode debug pour diagnostic
# - Validation automatique des calculs
#
# === FEATURES ===
# - Analyse complète de 10 types d'objets AAA
# - Détection des bindings globaux, vserver, et labels
# - Vérification de la chaîne complète pour les actions
# - Détection approfondie des login schemas (3 chemins possibles)
# - Code couleur intuitif pour visualisation rapide
# - Statistiques détaillées et validation automatique
# - Diagramme de flux d'authentification
# - Idéal pour identifier les objets à nettoyer
#
# === OBJETS ANALYSÉS (dans l'ordre du flux) ===
# Niveau 1 - Point d'entrée:
# 1. Authentication Profiles → Attachés aux VPN/LB vServers
#
# Niveau 2 - Serveur d'authentification:
# 2. Authentication vServers → Référencés dans les profiles
#
# Niveau 3 - Configuration:
# 3. Authentication Login Schemas → Bindés aux vServers
# 4. Authentication Policies → Bindées aux vServers ou globalement
# 5. Authentication Policy Labels → Utilisés comme nextFactor
#
# Niveau 4 - Actions (endpoints):
# 6. LDAP Actions → Définissent comment authentifier
# 7. OAUTH Actions → Définissent l'auth OAuth
# 8. SAML Actions → Définissent l'auth SAML
# 9. RADIUS Actions → Définissent l'auth RADIUS
# 10. EPA Actions → Définissent les checks EPA
#
# === UTILISATION ===
# 1. Modifier $NS_CONF_PATH avec le chemin vers votre fichier ns.conf
# 2. Exécuter le script PowerShell
# 3. Analyser les résultats avec le code couleur
# 4. Activer $DEBUG_MODE = $true pour plus de détails
#
# === ATTENTION - BUG CORRIGÉ EN v2.3 ===
# Les versions précédentes marquaient incorrectement comme utilisés les
# login schemas présents dans des policy labels vides. Ce bug est corrigé.
# Un schema n'est maintenant marqué comme utilisé QUE si la chaîne complète
# est valide jusqu'à un vserver.
#
# Fonctionnalités:
# - Code couleur : VERT (bindé/utilisé), ROUGE (non bindé), GRIS (détails)
# - Support des bindings globaux (bind system global)
# - Affichage des priorités et des relations
$NS_CONF_PATH = "C:\logs\ns.conf"
$DEBUG_MODE = $false # Set to $true to see detailed calculation info
Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan
Write-Host " NetScaler Authentication (AAA - Application Traffic) Analysis" -ForegroundColor Yellow
Write-Host ("=" * 80) + "`n" -ForegroundColor Cyan
# Load configuration
function Load-NSConf {
param([string]$FilePath)
if (Test-Path $FilePath) {
return Get-Content -Path $FilePath -Encoding UTF8
} else {
Write-Host "ERROR: File $FilePath not found." -ForegroundColor Red
return @()
}
}
$configLines = Load-NSConf -FilePath $NS_CONF_PATH
if ($configLines.Count -eq 0) {
exit
}
Write-Host "Configuration loaded: $($configLines.Count) lines`n" -ForegroundColor Green
# ADC Interface paths
$policyPaths = @{
# Existing authentication objects
"authentication vserver" = "Security > AAA - Application Traffic > Authentication Virtual Servers"
"authentication authnprofile" = "Security > AAA - Application Traffic > Authentication Profile"
"authentication loginschema" = "Security > AAA - Application Traffic > Login Schema > Policies"
"authentication loginschemaprofile" = "Security > AAA - Application Traffic > Login Schema > Profiles"
"authentication policy" = "Security > AAA - Application Traffic > Policies > Authentication > Advanced Policies > Authentication Policies"
"authentication policylabel" = "Security > AAA - Application Traffic > Policies > Authentication > Advanced Policies > Authentication Policy Labels"
# Authentication Actions (for future analysis)
"authentication ldapAction" = "Security > AAA - Application Traffic > Policies > Authentication > Advanced Policies > Actions > LDAP Actions"
"authentication OAuthAction" = "Security > AAA - Application Traffic > Policies > Authentication > Advanced Policies > Actions > OAUTH Actions"
"authentication samlAction" = "Security > AAA - Application Traffic > Policies > Authentication > Advanced Policies > Actions > SAML Actions"
"authentication radiusAction" = "Security > AAA - Application Traffic > Policies > Authentication > Advanced Policies > Actions > RADIUS Actions"
"authentication epaAction" = "Security > AAA - Application Traffic > Policies > Authentication > Advanced Policies > Actions > Authentication EPA Action"
}
# Note: All Authentication Actions are now fully analyzed in v2.0
# The script checks if actions are used by policies and if those policies are bound
function Find-DefinedObjects {
param(
[string[]]$ConfigLines,
[string]$ObjectType
)
$configString = $ConfigLines -join "`n"
# Adjust the pattern for different object types
# Actions can have mixed case (ldapAction, OAuthAction, etc.)
if ($ObjectType -match "Action$") {
# For actions, be more flexible with case
$actualType = $ObjectType -replace "authentication ", ""
$pattern = "(?i)add authentication $actualType\s+(`"[^`"]+`"|\S+)"
} else {
$pattern = "(?i)add $ObjectType\s+(`"[^`"]+`"|\S+)"
}
$matches = [regex]::Matches($configString, $pattern)
$objects = @{}
foreach ($match in $matches) {
$objectName = $match.Groups[1].Value.Trim('"')
$objects[$objectName] = @{
Name = $objectName
Type = $ObjectType
BoundTo = @()
BindingDetails = @()
}
}
return $objects
}
function Find-BoundObjects {
param(
[string[]]$ConfigLines,
[string]$ObjectType,
[hashtable]$DefinedObjects
)
$configString = $ConfigLines -join "`n"
$boundObjects = @{}
switch ($ObjectType.ToLower()) {
"authentication policy" {
# Bound to authentication vservers
$directPattern = "(?is)bind authentication vserver\s+(\S+).*?-policy\s+(`"([^`"]+)`"|\S+)"
$matches = [regex]::Matches($configString, $directPattern)
foreach ($match in $matches) {
$vserver = $match.Groups[1].Value.Trim('"')
$policy = $match.Groups[2].Value.Trim('"')
if (-not $boundObjects.ContainsKey($policy)) {
$boundObjects[$policy] = @()
}
# Extract priority if available
if ($match.Groups[0].Value -match "-priority\s+(\d+)") {
$priority = $Matches[1]
$boundObjects[$policy] += "AUTH_VSERVER: $vserver (priority: $priority)"
} else {
$boundObjects[$policy] += "AUTH_VSERVER: $vserver"
}
}
# Bound to policy labels (using -policyName)
$labelPattern1 = "(?is)bind authentication policylabel\s+(\S+).*?-policyName\s+(`"([^`"]+)`"|\S+)"
$matches = [regex]::Matches($configString, $labelPattern1)
foreach ($match in $matches) {
$label = $match.Groups[1].Value.Trim('"')
$policy = $match.Groups[2].Value.Trim('"')
if (-not $boundObjects.ContainsKey($policy)) {
$boundObjects[$policy] = @()
}
# Extract priority if available
if ($match.Groups[0].Value -match "-priority\s+(\d+)") {
$priority = $Matches[1]
$boundObjects[$policy] += "POLICY_LABEL: $label (priority: $priority)"
} else {
$boundObjects[$policy] += "POLICY_LABEL: $label"
}
}
# Alternative pattern for policy labels (using -policy)
$labelPattern2 = "(?is)bind authentication policylabel\s+(\S+).*?-policy\s+(`"([^`"]+)`"|\S+)"
$matches = [regex]::Matches($configString, $labelPattern2)
foreach ($match in $matches) {
$label = $match.Groups[1].Value.Trim('"')
$policy = $match.Groups[2].Value.Trim('"')
if (-not $boundObjects.ContainsKey($policy)) {
$boundObjects[$policy] = @()
}
$bindingText = "POLICY_LABEL: $label"
if ($match.Groups[0].Value -match "-priority\s+(\d+)") {
$priority = $Matches[1]
$bindingText = "POLICY_LABEL: $label (priority: $priority)"
}
if ($boundObjects[$policy] -notcontains $bindingText) {
$boundObjects[$policy] += $bindingText
}
}
# Bound globally to system
$globalPattern = "(?is)bind system global\s+(`"([^`"]+)`"|\S+)"
$matches = [regex]::Matches($configString, $globalPattern)
foreach ($match in $matches) {
$policy = $match.Groups[1].Value.Trim('"')
# Check if this is actually an authentication policy
if ($DefinedObjects.ContainsKey($policy)) {
if (-not $boundObjects.ContainsKey($policy)) {
$boundObjects[$policy] = @()
}
# Extract priority and other details
if ($match.Groups[0].Value -match "-priority\s+(\d+)") {
$priority = $Matches[1]
$bindingText = "GLOBAL_SYSTEM (priority: $priority)"
# Check for gotoPriorityExpression
if ($match.Groups[0].Value -match "-gotoPriorityExpression\s+(\S+)") {
$goto = $Matches[1]
$bindingText += " -> $goto"
}
$boundObjects[$policy] += $bindingText
} else {
$boundObjects[$policy] += "GLOBAL_SYSTEM"
}
}
}
}
"authentication policylabel" {
# Policy labels are bound as nextFactor
$pattern = "(?is)bind authentication vserver\s+(\S+).*?-nextFactor\s+(\S+)"
$matches = [regex]::Matches($configString, $pattern)
foreach ($match in $matches) {
$vserver = $match.Groups[1].Value.Trim('"')
$label = $match.Groups[2].Value.Trim('"')
if (-not $boundObjects.ContainsKey($label)) {
$boundObjects[$label] = @()
}
$boundObjects[$label] += "AUTH_VSERVER: $vserver (as nextFactor)"
}
# Policy labels can also be in policy goto expressions
$gotoPattern = "(?is)bind authentication policylabel\s+\S+.*?-gotoPriorityExpression\s+.*?`"([^`"]+)`""
$matches = [regex]::Matches($configString, $gotoPattern)
foreach ($match in $matches) {
$label = $match.Groups[1].Value.Trim('"')
if ($DefinedObjects.ContainsKey($label)) {
if (-not $boundObjects.ContainsKey($label)) {
$boundObjects[$label] = @()
}
$boundObjects[$label] += "Referenced in gotoPriorityExpression"
}
}
}
"authentication authnprofile" {
# Profiles are bound to VPN vservers
$pattern = "(?is)add vpn vserver\s+(\S+).*?-authnProfile\s+(`"([^`"]+)`"|\S+)"
$matches = [regex]::Matches($configString, $pattern)
foreach ($match in $matches) {
$vserver = $match.Groups[1].Value.Trim('"')
$profile = $match.Groups[2].Value.Trim('"')
if (-not $boundObjects.ContainsKey($profile)) {
$boundObjects[$profile] = @()
}
$boundObjects[$profile] += "VPN_VSERVER: $vserver"
}
# Profiles can also be bound to LB vservers
$lbPattern = "(?is)add lb vserver\s+(\S+).*?-authnProfile\s+(`"([^`"]+)`"|\S+)"
$matches = [regex]::Matches($configString, $lbPattern)
foreach ($match in $matches) {
$vserver = $match.Groups[1].Value.Trim('"')
$profile = $match.Groups[2].Value.Trim('"')
if (-not $boundObjects.ContainsKey($profile)) {
$boundObjects[$profile] = @()
}
$boundObjects[$profile] += "LB_VSERVER: $vserver"
}
}
"authentication vserver" {
# AAA vservers are referenced in authnProfiles
$mapping = @{}
$patternMap = "(?i)add authentication authnProfile\s+(`"([^`"]+)`"|\S+).*?-authnVsName\s+(`"([^`"]+)`"|\S+)"
$matches = [regex]::Matches($configString, $patternMap)
foreach ($match in $matches) {
$profile = $match.Groups[1].Value.Trim('"')
$aaaVserver = $match.Groups[3].Value.Trim('"')
$mapping[$profile] = $aaaVserver
}
# Find where these profiles are used
$patternBound = "(?is)add vpn vserver\s+(\S+).*?-authnProfile\s+(`"([^`"]+)`"|\S+)"
$matches = [regex]::Matches($configString, $patternBound)
foreach ($match in $matches) {
$vserver = $match.Groups[1].Value.Trim('"')
$profile = $match.Groups[2].Value.Trim('"')
if ($mapping.ContainsKey($profile)) {
$aaaVserver = $mapping[$profile]
if (-not $boundObjects.ContainsKey($aaaVserver)) {
$boundObjects[$aaaVserver] = @()
}
$boundObjects[$aaaVserver] += "VPN_VSERVER: $vserver (via profile: $profile)"
}
}
# AAA vservers can also be referenced directly
$directPattern = "(?is)set vpn vserver\s+(\S+).*?-authentication\s+(\S+)"
$matches = [regex]::Matches($configString, $directPattern)
foreach ($match in $matches) {
$vserver = $match.Groups[1].Value.Trim('"')
$aaaVserver = $match.Groups[2].Value.Trim('"')
if ($DefinedObjects.ContainsKey($aaaVserver)) {
if (-not $boundObjects.ContainsKey($aaaVserver)) {
$boundObjects[$aaaVserver] = @()
}
$boundObjects[$aaaVserver] += "VPN_VSERVER: $vserver (direct)"
}
}
}
"authentication loginschema" {
# Login schemas can be used in 2 ways:
# 1. Direct binding to vserver via loginschema policy
# 2. Used in policylabel which must have policies that are bound to vservers
# Method 1: Check for loginschema policies that use this schema
$policyPattern = "(?is)add authentication loginschemapolicy\s+(`"[^`"]+`"|\S+).*?-action\s+(`"[^`"]+`"|\S+)"
$policyMatches = [regex]::Matches($configString, $policyPattern)
$schemaPolicies = @{} # Track which policies use which schemas
foreach ($match in $policyMatches) {
$policyName = $match.Groups[1].Value.Trim('"')
$schemaName = $match.Groups[2].Value.Trim('"')
if (-not $schemaPolicies.ContainsKey($schemaName)) {
$schemaPolicies[$schemaName] = @()
}
$schemaPolicies[$schemaName] += $policyName
}
# Now check if these policies are bound to vservers
foreach ($schemaName in $schemaPolicies.Keys) {
if ($DefinedObjects.ContainsKey($schemaName)) {
foreach ($policyName in $schemaPolicies[$schemaName]) {
# Check if this policy is bound to a vserver
$bindPattern = "(?is)bind authentication vserver\s+(\S+).*?-policy\s+$([regex]::Escape($policyName))"
$bindMatches = [regex]::Matches($configString, $bindPattern)
foreach ($bindMatch in $bindMatches) {
$vserver = $bindMatch.Groups[1].Value.Trim('"')
if (-not $boundObjects.ContainsKey($schemaName)) {
$boundObjects[$schemaName] = @()
}
$boundObjects[$schemaName] += "AUTH_VSERVER: $vserver (via policy: $policyName)"
}
}
}
}
# Method 2: Check for schemas used in policy labels
# Pattern handles both -loginSchema and -loginschema (case insensitive)
$labelPattern = "(?is)add authentication policylabel\s+(`"[^`"]+`"|\S+).*?-login[Ss]chema\s+(`"[^`"]+`"|\S+)"
$labelMatches = [regex]::Matches($configString, $labelPattern)
foreach ($match in $labelMatches) {
$labelName = $match.Groups[1].Value.Trim('"')
$schemaName = $match.Groups[2].Value.Trim('"')
if ($DefinedObjects.ContainsKey($schemaName)) {
# Check if this policylabel has policies bound to it
$labelPolicyPattern = "(?is)bind authentication policylabel\s+$([regex]::Escape($labelName)).*?-policyName\s+(`"[^`"]+`"|\S+)"
$labelPolicyMatches = [regex]::Matches($configString, $labelPolicyPattern)
$labelHasPolicies = $false
$policiesInLabel = @()
foreach ($policyMatch in $labelPolicyMatches) {
$policyInLabel = $policyMatch.Groups[1].Value.Trim('"')
$policiesInLabel += $policyInLabel
$labelHasPolicies = $true
}
if ($labelHasPolicies) {
# Now check if any of these policies are bound to vservers
$labelUsed = $false
foreach ($policy in $policiesInLabel) {
$vserverBindPattern = "(?is)bind authentication vserver\s+(\S+).*?-policy\s+$([regex]::Escape($policy))"
$vserverMatches = [regex]::Matches($configString, $vserverBindPattern)
foreach ($vMatch in $vserverMatches) {
$vserver = $vMatch.Groups[1].Value.Trim('"')
if (-not $boundObjects.ContainsKey($schemaName)) {
$boundObjects[$schemaName] = @()
}
$boundObjects[$schemaName] += "POLICY_LABEL: $labelName -> POLICY: $policy -> VSERVER: $vserver"
$labelUsed = $true
}
}
# If label has policies but they're not bound to vservers - DON'T mark as bound
if (-not $labelUsed) {
# Don't add to boundObjects - this is an unused chain
# We can track this for informational purposes but it's NOT a valid binding
if ($DEBUG_MODE) {
Write-Host "DEBUG: Schema $schemaName in label $labelName has policies but they're not bound" -ForegroundColor DarkYellow
}
}
} else {
# Label exists but has no policies - DON'T mark as bound
# This is definitely not a valid use of the schema
if ($DEBUG_MODE) {
Write-Host "DEBUG: Schema $schemaName in label $labelName has no policies" -ForegroundColor DarkYellow
}
}
}
}
# Also check for any nextFactor usage of policylabels that contain schemas
# (policylabels can be used as nextFactor even without direct policy bindings)
foreach ($match in $labelMatches) {
$labelName = $match.Groups[1].Value.Trim('"')
$schemaName = $match.Groups[2].Value.Trim('"')
if ($DefinedObjects.ContainsKey($schemaName)) {
# Check for nextFactor usage - more robust pattern
$nextFactorPattern = "(?is)bind authentication (?:vserver|policylabel)\s+(\S+).*?-nextFactor\s+$([regex]::Escape($labelName))"
$nextFactorMatches = [regex]::Matches($configString, $nextFactorPattern)
foreach ($nfMatch in $nextFactorMatches) {
$vserver = $nfMatch.Groups[1].Value.Trim('"')
if (-not $boundObjects.ContainsKey($schemaName)) {
$boundObjects[$schemaName] = @()
}
$binding = "POLICY_LABEL: $labelName -> VSERVER: $vserver (as nextFactor)"
if ($boundObjects[$schemaName] -notcontains $binding) {
$boundObjects[$schemaName] += $binding
}
}
}
}
}
{ $_ -in @("authentication ldapAction", "authentication OAuthAction",
"authentication samlAction", "authentication radiusAction",
"authentication epaAction") } {
# Actions are referenced in authentication policies with -action parameter
#
# Chain analysis for actions:
# 1. Find all policies that use this action
# 2. For each policy, check if the policy itself is bound somewhere
# 3. Report the complete chain: Action -> Policy -> Binding location
# Find policies that use this action
# Pattern captures: policy name (group 1) and action name (group 2)
$policyPattern = "(?is)add authentication policy\s+(`"[^`"]+`"|\S+).*?-action\s+(`"[^`"]+`"|\S+)"
$matches = [regex]::Matches($configString, $policyPattern)
foreach ($match in $matches) {
$policyName = $match.Groups[1].Value.Trim('"')
$actionName = $match.Groups[2].Value.Trim('"')
# Check if this action is one of our defined objects
if ($DefinedObjects.ContainsKey($actionName)) {
if (-not $boundObjects.ContainsKey($actionName)) {
$boundObjects[$actionName] = @()
}
# Now check if this policy is bound somewhere
$policyBound = $false
$bindingDetails = @()
# Check if policy is bound to vserver
$vserverPattern = "(?is)bind authentication vserver\s+(\S+).*?-policy\s+$([regex]::Escape($policyName))"
$vserverMatches = [regex]::Matches($configString, $vserverPattern)
foreach ($vMatch in $vserverMatches) {
$vserver = $vMatch.Groups[1].Value.Trim('"')
$bindingDetails += "via AUTH_VSERVER: $vserver"
$policyBound = $true
}
# Check if policy is bound to label
$labelPattern = "(?is)bind authentication policylabel\s+(\S+).*?-policyName\s+$([regex]::Escape($policyName))"
$labelMatches = [regex]::Matches($configString, $labelPattern)
foreach ($lMatch in $labelMatches) {
$label = $lMatch.Groups[1].Value.Trim('"')
$bindingDetails += "via POLICY_LABEL: $label"
$policyBound = $true
}
# Check if policy is bound globally
$globalPattern = "(?is)bind system global\s+$([regex]::Escape($policyName))"
if ([regex]::IsMatch($configString, $globalPattern)) {
$bindingDetails += "via GLOBAL_SYSTEM"
$policyBound = $true
}
# Add to bound objects with details
if ($policyBound) {
foreach ($detail in $bindingDetails) {
$boundObjects[$actionName] += "POLICY: $policyName ($detail)"
}
} else {
$boundObjects[$actionName] += "POLICY: $policyName (policy not bound)"
}
}
}
}
}
# Update defined objects with binding info
foreach ($objName in $boundObjects.Keys) {
if ($DefinedObjects.ContainsKey($objName)) {
$DefinedObjects[$objName].BoundTo = $boundObjects[$objName]
}
}
return $boundObjects
}
# Authentication object types to analyze (ordered by logical chain flow)
$authTypes = @(
# 1. Entry point - Profiles bound to VPN/LB vservers
"authentication authnprofile",
# 2. Authentication vServers referenced in profiles
"authentication vserver",
# 3. Login schemas bound to vservers
"authentication loginschema",
# 4. Policies bound to vservers or globally
"authentication policy",
# 5. Policy labels used as nextFactor
"authentication policylabel",
# 6. Actions used by policies (endpoints)
"authentication ldapAction",
"authentication OAuthAction",
"authentication samlAction",
"authentication radiusAction",
"authentication epaAction"
)
# Note: The order follows the authentication flow from entry point to endpoints
# Profile -> vServer -> Schema/Policy -> Label -> Action
Write-Host "Starting authentication policy analysis..." -ForegroundColor Yellow
Write-Host "Objects will be analyzed in logical flow order (entry point → endpoints)" -ForegroundColor DarkGray
Write-Host ("=" * 80) -ForegroundColor DarkGray
Write-Host ""
$allPolicies = @{}
$totalDefined = 0
$totalBound = 0
$totalUnbound = 0
foreach ($authType in $authTypes) {
Write-Progress -Activity "Analysis in progress" -Status "Processing: $authType" `
-PercentComplete (($authTypes.IndexOf($authType) + 1) / $authTypes.Count * 100)
$defined = Find-DefinedObjects -ConfigLines $configLines -ObjectType $authType
$bound = Find-BoundObjects -ConfigLines $configLines -ObjectType $authType -DefinedObjects $defined
# Store all objects for this type
if ($defined.Count -gt 0) {
# IMPORTANT: Calculate the actual number of bound objects
# $bound is a hashtable where keys are object names and values are arrays of bindings
# We need to count how many defined objects have at least one binding
# NOT the count of the $bound hashtable itself
$boundCount = 0
foreach ($obj in $defined.Keys) {
if ($bound.ContainsKey($obj) -and $bound[$obj].Count -gt 0) {
$boundCount++
}
}
if ($DEBUG_MODE) {
Write-Host "DEBUG [$authType]: Defined=$($defined.Count), Bound=$boundCount, Unbound=$($defined.Count - $boundCount)" -ForegroundColor DarkYellow
}
$allPolicies[$authType] = @{
Defined = $defined
Bound = $bound
BoundCount = $boundCount
UnboundCount = ($defined.Count - $boundCount)
}
$totalDefined += $defined.Count
$totalBound += $boundCount
$totalUnbound += ($defined.Count - $boundCount)
}
}
Write-Progress -Completed -Activity "Analysis in progress"
# Display results with color coding
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host " AUTHENTICATION OBJECTS - STATUS BY LOGICAL FLOW" -ForegroundColor Yellow
Write-Host " (Entry Point → Configuration → Actions)" -ForegroundColor DarkGray
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Legend: " -NoNewline
Write-Host "[✓] Bound/Used " -ForegroundColor Green -NoNewline
Write-Host "[✓] Actions " -ForegroundColor Cyan -NoNewline
Write-Host "[X] Unbound/Unused " -ForegroundColor Red -NoNewline
Write-Host "[GLOBAL_SYSTEM] Global" -ForegroundColor Cyan
Write-Host " Policy not bound warnings shown in " -NoNewline
Write-Host "Dark Yellow" -ForegroundColor DarkYellow
Write-Host ""
foreach ($authType in $authTypes) {
if ($allPolicies.ContainsKey($authType)) {
$policies = $allPolicies[$authType]
$defined = $policies.Defined
$bound = $policies.Bound
$boundCount = $policies.BoundCount
$unboundCount = $policies.UnboundCount
# Get path from policyPaths, handle case sensitivity
$path = $null
# Try exact match first
if ($policyPaths.ContainsKey($authType)) {
$path = $policyPaths[$authType]
} else {
# Try case-insensitive match
foreach ($key in $policyPaths.Keys) {
if ($key -ieq $authType) {
$path = $policyPaths[$key]
break
}
}
}
if (-not $path) {
$path = "Path not defined"
}
Write-Host "[OBJECT TYPE: $($authType.ToUpper())]" -ForegroundColor Yellow
Write-Host "Location: $path" -ForegroundColor DarkGray
# Validate calculation
if ($defined.Count -ne ($boundCount + $unboundCount)) {
Write-Host "ERROR: Calculation mismatch! Total != Bound + Unbound" -ForegroundColor Red
}
Write-Host "Statistics: Total=$($defined.Count) | " -NoNewline
Write-Host "Bound=$boundCount " -ForegroundColor Green -NoNewline
Write-Host "| " -NoNewline
Write-Host "Unbound=$unboundCount" -ForegroundColor Red
Write-Host ""
# Sort objects alphabetically
foreach ($objName in ($defined.Keys | Sort-Object)) {
if ($bound.ContainsKey($objName)) {
# Object is bound (used) - show in GREEN
# Use different color for Actions
if ($authType -match "Action$") {
Write-Host " [✓] $objName" -ForegroundColor Cyan
} else {
Write-Host " [✓] $objName" -ForegroundColor Green
}
# Show binding details in GRAY
foreach ($binding in $defined[$objName].BoundTo) {
if ($binding -match "GLOBAL_SYSTEM") {
Write-Host " └─ $binding" -ForegroundColor Cyan
} elseif ($binding -match "policy not bound") {
Write-Host " └─ $binding" -ForegroundColor DarkYellow
} else {
Write-Host " └─ $binding" -ForegroundColor Gray
}
}
} else {
# Object is unbound (unused) - show in RED
$message = " [X] $objName"
# For login schemas, check if they're in a label but unused
if ($authType -eq "authentication loginschema") {
# Check if schema is in a label
$labelPattern = "(?is)add authentication policylabel\s+(`"[^`"]+`"|\S+).*?-loginschema\s+$([regex]::Escape($objName))"
$labelMatches = [regex]::Matches(($ConfigLines -join "`n"), $labelPattern)
if ($labelMatches.Count -gt 0) {
$labelName = $labelMatches[0].Groups[1].Value.Trim('"')
# Check if label has policies
$labelPolicyPattern = "(?is)bind authentication policylabel\s+$([regex]::Escape($labelName)).*?-policyName\s+(`"[^`"]+`"|\S+)"
$policyMatches = [regex]::Matches(($ConfigLines -join "`n"), $labelPolicyPattern)
if ($policyMatches.Count -eq 0) {
$message += " (in label: $labelName but label has NO policies)"
} else {
$message += " (in label: $labelName but policies not bound to vserver)"
}
} else {
$message += " (not used anywhere)"
}
} else {
# Other object types - use original messages
switch ($authType) {
"authentication vserver" {
$message += " (not referenced in any profile or vserver)"
}
"authentication authnprofile" {
$message += " (not bound to any vserver)"
}
"authentication policylabel" {
$message += " (not used as nextFactor)"
}
"authentication policy" {
$message += " (not bound anywhere)"
}
{ $_ -in @("authentication ldapAction", "authentication OAuthAction",
"authentication samlAction", "authentication radiusAction",
"authentication epaAction") } {
$message += " (not used by any policy)"
}
}
}
Write-Host $message -ForegroundColor Red
}
}
Write-Host ""
}
}
# Summary
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host " SUMMARY" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Authentication Objects Analysis:" -ForegroundColor White
Write-Host " |"
Write-Host " |-- Total objects defined: $totalDefined"
# Validate global calculation
if ($totalDefined -eq ($totalBound + $totalUnbound)) {
Write-Host " | |-- " -NoNewline
Write-Host "Bound (in use): $totalBound" -ForegroundColor Green
Write-Host " | |-- " -NoNewline
Write-Host "Unbound (unused): $totalUnbound" -ForegroundColor $(if ($totalUnbound -gt 0) { "Red" } else { "Green" })
} else {
Write-Host " | |-- ERROR: Calculation mismatch!" -ForegroundColor Red
Write-Host " | |-- Bound: $totalBound, Unbound: $totalUnbound" -ForegroundColor Red
}
Write-Host " |"
Write-Host " |-- Object types analyzed: $($allPolicies.Count)" -ForegroundColor White
if ($totalUnbound -gt 0) {
$percentage = [math]::Round(($totalUnbound / $totalDefined * 100), 1)
Write-Host "`n[WARNING] $totalUnbound authentication object(s) ($percentage%) are not bound" -ForegroundColor Yellow
Write-Host "Review these objects for potential cleanup" -ForegroundColor Yellow
} else {
Write-Host "`n[EXCELLENT] All authentication objects are properly configured and in use!" -ForegroundColor Green
}
# Relationship diagram
Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan
Write-Host " AUTHENTICATION FLOW DIAGRAM" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Authentication Chain Flow (in analysis order):" -ForegroundColor White
Write-Host ""
Write-Host "How to read Action bindings:" -ForegroundColor DarkGray
Write-Host " Action → Used by Policy → Policy bound to (vserver/label/global)" -ForegroundColor DarkGray
Write-Host " Example: EUR_DC_LB → POLICY: LDAP_Admin_AUTH_POL (via GLOBAL_SYSTEM)" -ForegroundColor DarkGray
Write-Host ""
Write-Host "Complete example from your config:" -ForegroundColor DarkGray
Write-Host " EUR_DC_LB (ldapAction) → LDAP_Admin_AUTH_POL (policy) → System Global" -ForegroundColor DarkGray
Write-Host ""
Write-Host "Flow Diagram:" -ForegroundColor White
Write-Host ""
Write-Host "Login Schema Usage Paths:" -ForegroundColor DarkGray
Write-Host " Path 1: Schema -> LoginSchemaPolicy -> vServer" -ForegroundColor DarkGray
Write-Host " Path 2: Schema -> PolicyLabel -> Policy -> vServer" -ForegroundColor DarkGray
Write-Host " Path 3: Schema -> PolicyLabel -> nextFactor on vServer" -ForegroundColor DarkGray
Write-Host ""
Write-Host "1. VPN/LB Vserver" -ForegroundColor Cyan
Write-Host " |"
Write-Host " └─> 2. Authentication Profile (authnProfile)" -ForegroundColor Yellow
Write-Host " |"
Write-Host " └─> 3. Authentication vServer (authnVsName)" -ForegroundColor Yellow
Write-Host " |"
Write-Host " ├─> 4. Login Schema" -ForegroundColor Green
Write-Host " |"
Write-Host " └─> 5. Authentication Policies" -ForegroundColor Green
Write-Host " |"
Write-Host " ├─> 6. Policy Labels (nextFactor)" -ForegroundColor Green
Write-Host " | |"
Write-Host " | └─> More Policies..." -ForegroundColor DarkGray
Write-Host " |"
Write-Host " └─> 7-11. ACTIONS (endpoints)" -ForegroundColor Magenta
Write-Host " ├─> LDAP Actions" -ForegroundColor DarkMagenta
Write-Host " ├─> OAUTH Actions" -ForegroundColor DarkMagenta
Write-Host " ├─> SAML Actions" -ForegroundColor DarkMagenta
Write-Host " ├─> RADIUS Actions" -ForegroundColor DarkMagenta
Write-Host " └─> EPA Actions" -ForegroundColor DarkMagenta
Write-Host ""
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host "Analysis complete" -ForegroundColor Cyan