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