powershell max authent policies


# NetScaler Login Schema Analyzer
# Script spécialisé pour analyser l'utilisation réelle des Login Schemas
# v1.0 - Détection complète selon documentation Citrix
#
# Un loginSchema est utilisé si:
# 1. Via loginSchemaPolicy bindée à un vserver
# 2. Via policylabel avec -loginSchema qui est utilisé (nextFactor ou policies bindées)

$NS_CONF_PATH = "C:\logs\ns.conf"

Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan
Write-Host "                    Login Schema Usage 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
}

$configString = $configLines -join "`n"
Write-Host "Configuration loaded: $($configLines.Count) lines`n" -ForegroundColor Green

# Find all defined login schemas
Write-Host "Step 1: Finding all defined login schemas..." -ForegroundColor Yellow
$schemaPattern = "(?i)add authentication loginschema\s+(`"[^`"]+`"|\S+)"
$schemaMatches = [regex]::Matches($configString, $schemaPattern)
$definedSchemas = @{}

foreach ($match in $schemaMatches) {
    $schemaName = $match.Groups[1].Value.Trim('"')
    if ($schemaName -ne "LSCHEMA_INT" -and -not $schemaName.StartsWith("lschema_int")) {
        $definedSchemas[$schemaName] = @{
            Name = $schemaName
            UsedBy = @()
            IsUsed = $false
        }
    }
}

Write-Host "Found $($definedSchemas.Count) login schemas (excluding LSCHEMA_INT)`n" -ForegroundColor Green

# Check Method 1: loginSchemaPolicy
Write-Host "Step 2: Checking loginSchemaPolicy usage..." -ForegroundColor Yellow
$policyPattern = "(?i)add authentication loginSchemaPolicy\s+(`"[^`"]+`"|\S+).*?-action\s+(`"[^`"]+`"|\S+)"
$policyMatches = [regex]::Matches($configString, $policyPattern)

foreach ($match in $policyMatches) {
    $policyName = $match.Groups[1].Value.Trim('"')
    $schemaName = $match.Groups[2].Value.Trim('"')
    
    if ($definedSchemas.ContainsKey($schemaName)) {
        # Check if this policy is bound to any vserver
        $bindPattern = "(?i)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('"')
            $definedSchemas[$schemaName].UsedBy += "LoginSchemaPolicy: $policyName -> VServer: $vserver"
            $definedSchemas[$schemaName].IsUsed = $true
        }
        
        if ($bindMatches.Count -eq 0) {
            $definedSchemas[$schemaName].UsedBy += "LoginSchemaPolicy: $policyName (NOT BOUND to any vserver)"
        }
    }
}

# Check Method 2: PolicyLabel with -loginSchema
Write-Host "Step 3: Checking policylabel usage..." -ForegroundColor Yellow
# Pattern handles both -loginSchema and -loginschema (case insensitive)
$labelPattern = "(?i)add authentication policylabel\s+(`"[^`"]+`"|\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 ($definedSchemas.ContainsKey($schemaName)) {
        $labelUsed = $false
        $usageDetails = @()
        
        # Check if policylabel is used as nextFactor
        $nextFactorPattern = "(?i)-nextFactor\s+$([regex]::Escape($labelName))"
        $nextFactorMatches = [regex]::Matches($configString, $nextFactorPattern)
        
        if ($nextFactorMatches.Count -gt 0) {
            foreach ($nfMatch in $nextFactorMatches) {
                # Find which vserver or policy uses this as nextFactor
                $contextPattern = "(?i)bind authentication (?:vserver|policylabel)\s+(\S+).*?-nextFactor\s+$([regex]::Escape($labelName))"
                $contextMatches = [regex]::Matches($configString, $contextPattern)
                foreach ($cMatch in $contextMatches) {
                    $source = $cMatch.Groups[1].Value.Trim('"')
                    $usageDetails += "PolicyLabel: $labelName -> Used as nextFactor by: $source"
                    $labelUsed = $true
                }
            }
        }
        
        # Check if policylabel has bound policies that are used
        $boundPolicyPattern = "(?i)bind authentication policylabel\s+$([regex]::Escape($labelName)).*?-policyName\s+(`"[^`"]+`"|\S+)"
        $boundPolicyMatches = [regex]::Matches($configString, $boundPolicyPattern)
        
        foreach ($pMatch in $boundPolicyMatches) {
            $policyName = $pMatch.Groups[1].Value.Trim('"')
            
            # Check if this policy is bound to a vserver
            $vserverPattern = "(?i)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('"')
                $usageDetails += "PolicyLabel: $labelName -> Policy: $policyName -> VServer: $vserver"
                $labelUsed = $true
            }
        }
        
        if ($labelUsed) {
            $definedSchemas[$schemaName].UsedBy += $usageDetails
            $definedSchemas[$schemaName].IsUsed = $true
        } else {
            $definedSchemas[$schemaName].UsedBy += "PolicyLabel: $labelName (NOT USED - no nextFactor reference and no policies bound to vserver)"
        }
    }
}

# Display results
Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan
Write-Host "                        ANALYSIS RESULTS" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan

$usedCount = 0
$unusedCount = 0

# Display used schemas
Write-Host "`n[USED LOGIN SCHEMAS]" -ForegroundColor Green
foreach ($schemaName in ($definedSchemas.Keys | Sort-Object)) {
    if ($definedSchemas[$schemaName].IsUsed) {
        Write-Host "  [✓] $schemaName" -ForegroundColor Green
        foreach ($usage in $definedSchemas[$schemaName].UsedBy) {
            if ($usage -match "NOT BOUND" -or $usage -match "NOT USED") {
                Write-Host "      └─ $usage" -ForegroundColor Yellow
            } else {
                Write-Host "      └─ $usage" -ForegroundColor DarkGray
            }
        }
        $usedCount++
    }
}

# Display unused schemas
Write-Host "`n[UNUSED LOGIN SCHEMAS - Can be cleaned up]" -ForegroundColor Red
foreach ($schemaName in ($definedSchemas.Keys | Sort-Object)) {
    if (-not $definedSchemas[$schemaName].IsUsed) {
        Write-Host "  [X] $schemaName" -ForegroundColor Red
        if ($definedSchemas[$schemaName].UsedBy.Count -gt 0) {
            foreach ($usage in $definedSchemas[$schemaName].UsedBy) {
                Write-Host "      └─ $usage" -ForegroundColor DarkYellow
            }
        } else {
            Write-Host "      └─ Not referenced anywhere" -ForegroundColor DarkRed
        }
        $unusedCount++
    }
}

# Summary
Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan
Write-Host "                           SUMMARY" -ForegroundColor Yellow
Write-Host ("=" * 80) -ForegroundColor Cyan
Write-Host ""
Write-Host "Total Login Schemas: $($definedSchemas.Count)" -ForegroundColor White
Write-Host "  ├─ Used: $usedCount" -ForegroundColor Green
Write-Host "  └─ Unused: $unusedCount" -ForegroundColor Red

if ($unusedCount -gt 0) {
    $percentage = [math]::Round(($unusedCount / $definedSchemas.Count * 100), 1)
    Write-Host "`n[CLEANUP OPPORTUNITY] $unusedCount login schema(s) ($percentage%) can be removed" -ForegroundColor Yellow
}

Write-Host "`n" + ("=" * 80) -ForegroundColor Cyan
Write-Host "Analysis complete" -ForegroundColor Cyan
Write-Host ""
Write-Host "Note: LSCHEMA_INT (noschema) is excluded from this analysis" -ForegroundColor DarkGray
Write-Host "as it's a built-in schema used for factors without UI." -ForegroundColor DarkGray