powershell max

# Analyse des objets non bindés dans ns.conf
# Version PowerShell

# NetScaler Configuration Analyzer - Version 3.0 FINAL
# Analyzes ns.conf to find unbound/unused objects
# Fixed: Authentication policy detection now uses correct -policy syntax

# Configuration
$NS_CONF_PATH = "C:\tempmax\ns.conf"

# ADC Interface paths for each object type
$objectPaths = @{
    "authentication policy" = "Security > AAA-Application Traffic > Policies > Authentication > Advanced Policies > Authentication Policies"
    "authentication policylabel" = "Security > AAA-Application Traffic > Policies > Authentication > Advanced Policies > Policy Labels"
    "authentication authnprofile" = "Security > AAA-Application Traffic > Authentication Profile"
    "authentication vserver" = "Security > AAA-Application Traffic > Authentication Virtual Servers"
    "authentication loginschema" = "Security > AAA-Application Traffic > Authentication > Login Schemas"
    "vpn sessionpolicy" = "Citrix Gateway > Policies > Session Policies"
    "vpn trafficpolicy" = "Citrix Gateway > Policies > Traffic Policies, Profiles and Form SSO Profiles"
    "rewrite policy" = "AppExpert > Rewrite > Rewrite Policy"
    "responder policy" = "AppExpert > Responder > Responder Policy"
    "cache policy" = "AppExpert > Cache > Cache Policy"
    "lb policy" = "Traffic Management > Load Balancing > Load Balancing Policies"
    "cs policy" = "AppExpert > Content Switching > Content Switching Policies"
    "aaa policy" = "Citrix Gateway > Policies > AAA > AAA Policies"
    "dns policy" = "Traffic Management > DNS > DNS Policies"
    "appfw policy" = "Security > Application Firewall > Policies"
    "videooptimization policy" = "Traffic Management > Video Optimization > Video Optimization Policies"
    "contentinspection policy" = "Security > Content Inspection > Policies"
    "servicegroup" = "Traffic Management > Load Balancing > Service Groups"
    "ssl certkey" = "Traffic Management > SSL > Certificates and Keys"
    "server" = "Traffic Management > Servers"
}

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 @()
    }
}

function Find-DefinedObjects {
    param(
        [string[]]$ConfigLines,
        [string]$ObjectType
    )
    
    $configString = $ConfigLines -join "`n"
    $pattern = "(?i)add $ObjectType\s+(`"[^`"]+`"|\S+)"
    
    $matches = [regex]::Matches($configString, $pattern)
    $objects = New-Object System.Collections.Generic.HashSet[string]
    
    foreach ($match in $matches) {
        $objectName = $match.Groups[1].Value.Trim('"')
        [void]$objects.Add($objectName)
    }
    
    # Always return a valid HashSet
    if ($null -eq $objects) {
        $objects = New-Object System.Collections.Generic.HashSet[string]
    }
    
    return $objects
}

function Find-BoundObjects {
    param(
        [string[]]$ConfigLines,
        [string]$ObjectType
    )
    
    $configString = $ConfigLines -join "`n"
    $boundObjects = New-Object System.Collections.Generic.HashSet[string]
    
    # Ensure we always have a valid HashSet
    if ($null -eq $boundObjects) {
        $boundObjects = New-Object System.Collections.Generic.HashSet[string]
    }
    
    switch ($ObjectType.ToLower()) {
        "authentication authnprofile" {
            # Find profiles used in VPN vservers
            $pattern = "(?is)add vpn vserver\s+\S+.*?-authnProfile\s+(`"([^`"]+)`"|\S+)"
            $matches = [regex]::Matches($configString, $pattern)
            foreach ($match in $matches) {
                $name = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($name)
            }
        }
        
        "authentication vserver" {
            # Map authnProfile to AAA vserver
            $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 used profiles
            $patternBound = "(?is)add vpn vserver\s+\S+.*?-authnProfile\s+(`"([^`"]+)`"|\S+)"
            $matches = [regex]::Matches($configString, $patternBound)
            foreach ($match in $matches) {
                $profile = $match.Groups[1].Value.Trim('"')
                if ($mapping.ContainsKey($profile)) {
                    [void]$boundObjects.Add($mapping[$profile])
                }
            }
        }
        
        "authentication policy" {
            # FIXED: Authentication policies use -policy (not -policyName)
            # 1. Policies bound directly to authentication vservers
            $directPattern = "(?is)bind authentication vserver\s+\S+.*?-policy\s+(`"([^`"]+)`"|\S+)"
            $matches = [regex]::Matches($configString, $directPattern)
            foreach ($match in $matches) {
                $policy = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($policy)
            }
            
            # 2. Policies bound to policylabels
            $labelPattern = "(?is)bind authentication policylabel\s+\S+.*?-policy\s+(`"([^`"]+)`"|\S+)"
            $matches = [regex]::Matches($configString, $labelPattern)
            foreach ($match in $matches) {
                $policy = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($policy)
            }
        }
        
        "authentication policylabel" {
            # Labels referenced via -nextFactor
            $pattern = "(?is)bind authentication vserver\s+\S+.*?-nextFactor\s+(\S+)"
            $matches = [regex]::Matches($configString, $pattern)
            foreach ($match in $matches) {
                $label = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($label)
            }
        }
        
        "responder policy" {
            # Responder policies in VPN vserver bindings
            $pattern = "(?is)bind vpn vserver\s+\S+.*?-policy\s+(`"([^`"]+)`"|\S+)"
            $matches = [regex]::Matches($configString, $pattern)
            foreach ($match in $matches) {
                $policy = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($policy)
            }
        }
        
        { $_ -in @("vpn sessionpolicy", "vpn trafficpolicy") } {
            # VPN policies
            $pattern = "(?is)bind vpn vserver\s+\S+.*?-policy\s+(`"([^`"]+)`"|\S+)"
            $matches = [regex]::Matches($configString, $pattern)
            foreach ($match in $matches) {
                $policy = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($policy)
            }
        }
        
        "ssl certkey" {
            # SSL certificates
            $pattern = "(?is)bind ssl (?:service|vserver)\s+\S+.*?-certkeyName\s+(`"([^`"]+)`"|\S+)"
            $matches = [regex]::Matches($configString, $pattern)
            foreach ($match in $matches) {
                $cert = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($cert)
            }
        }
        
        { $_ -in @("rewrite policy", "cache policy", "cs policy", "aaa policy", 
                   "dns policy", "appfw policy", "videooptimization policy", 
                   "contentinspection policy", "lb policy") } {
            # Most other policies use -policyName
            $pattern = "(?i)-policyName\s+(`"([^`"]+)`"|\S+)"
            $matches = [regex]::Matches($configString, $pattern)
            foreach ($match in $matches) {
                $policy = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($policy)
            }
        }
        
        default {
            # Generic bind patterns
            $patternDirect = "(?i)bind $ObjectType (`"[^`"]+`"|\S+)"
            $matches = [regex]::Matches($configString, $patternDirect)
            foreach ($match in $matches) {
                $obj = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($obj)
            }
            
            # Policylabel bindings
            $patternLabel = "(?i)bind $ObjectType policylabel [^\s]+ -policyName (`"[^`"]+`"|\S+)"
            $matches = [regex]::Matches($configString, $patternLabel)
            foreach ($match in $matches) {
                $obj = $match.Groups[1].Value.Trim('"')
                [void]$boundObjects.Add($obj)
            }
        }
    }
    
    # Always return a valid HashSet
    if ($null -eq $boundObjects) {
        $boundObjects = New-Object System.Collections.Generic.HashSet[string]
    }
    
    return $boundObjects
}

function Find-ServersBoundToServiceGroups {
    param([string[]]$ConfigLines)
    
    $configString = $ConfigLines -join "`n"
    $boundServers = New-Object System.Collections.Generic.HashSet[string]
    
    # Servers in serviceGroups
    $patternSG = "(?i)bind serviceGroup\s+(`"[^`"]+`"|\S+)\s+(`"[^`"]+`"|\S+)"
    $matches = [regex]::Matches($configString, $patternSG)
    foreach ($match in $matches) {
        $server = $match.Groups[2].Value.Trim('"')
        [void]$boundServers.Add($server)
    }
    
    # Servers in lb vservers
    $patternVS = "(?i)bind lb vserver\s+(`"[^`"]+`"|\S+)\s+(`"[^`"]+`"|\S+)"
    $matches = [regex]::Matches($configString, $patternVS)
    foreach ($match in $matches) {
        $server = $match.Groups[2].Value.Trim('"')
        [void]$boundServers.Add($server)
    }
    
    # Always return a valid HashSet
    if ($null -eq $boundServers) {
        $boundServers = New-Object System.Collections.Generic.HashSet[string]
    }
    
    return $boundServers
}

function Find-UnboundObjects {
    param(
        [string]$FilePath,
        [string[]]$ObjectTypes
    )
    
    $configLines = Load-NSConf -FilePath $FilePath
    if ($configLines.Count -eq 0) {
        return @{}
    }
    
    $unbound = @{}
    
    foreach ($objType in $ObjectTypes) {
        Write-Progress -Activity "Analysis in progress" -Status "Processing: $objType" `
                      -PercentComplete (($ObjectTypes.IndexOf($objType) + 1) / $ObjectTypes.Count * 100)
        
        $defined = Find-DefinedObjects -ConfigLines $configLines -ObjectType $objType
        $bound = Find-BoundObjects -ConfigLines $configLines -ObjectType $objType
        
        # Safety checks
        if ($null -eq $defined) {
            $defined = New-Object System.Collections.Generic.HashSet[string]
        }
        if ($null -eq $bound) {
            $bound = New-Object System.Collections.Generic.HashSet[string]
        }
        
        # Find unbound objects
        $unboundSet = New-Object System.Collections.Generic.HashSet[string]
        foreach ($obj in $defined) {
            if (-not $bound.Contains($obj)) {
                [void]$unboundSet.Add($obj)
            }
        }
        
        $unbound[$objType] = $unboundSet
    }
    
    # Special handling for servers
    if ($unbound.ContainsKey("server")) {
        $serversBound = Find-ServersBoundToServiceGroups -ConfigLines $configLines
        if ($null -eq $serversBound) {
            $serversBound = New-Object System.Collections.Generic.HashSet[string]
        }
        $newServerSet = New-Object System.Collections.Generic.HashSet[string]
        foreach ($server in $unbound["server"]) {
            if (-not $serversBound.Contains($server)) {
                [void]$newServerSet.Add($server)
            }
        }
        $unbound["server"] = $newServerSet
    }
    
    Write-Progress -Completed -Activity "Analysis in progress"
    
    return $unbound
}

# Main function
function Main {
    $objectTypes = @(
        "authentication policy",
        "authentication policylabel",
        "authentication authnprofile",
        "authentication vserver",
        "authentication loginschema",
        "vpn sessionpolicy",
        "vpn trafficpolicy",
        "rewrite policy",
        "responder policy",
        "cache policy",
        "lb policy",
        "cs policy",
        "aaa policy",
        "dns policy",
        "appfw policy",
        "videooptimization policy",
        "contentinspection policy",
        "servicegroup",
        "ssl certkey",
        "server"
    )
    
    Write-Host "`nAnalyzing unbound objects in ns.conf..." -ForegroundColor Cyan
    Write-Host ("=" * 60) -ForegroundColor Cyan
    
    $unbound = Find-UnboundObjects -FilePath $NS_CONF_PATH -ObjectTypes $objectTypes
    
    $hasUnbound = $false
    $totalUnbound = 0
    
    foreach ($objType in $objectTypes) {
        $objs = $unbound[$objType]
        if ($objs.Count -eq 0) {
            continue
        }
        
        $hasUnbound = $true
        $totalUnbound += $objs.Count
        $path = $objectPaths[$objType.ToLower()]
        if (-not $path) {
            $path = "Path not defined"
        }
        
        Write-Host "`nLocation: $path" -ForegroundColor Yellow
        Write-Host "Object type: $objType" -ForegroundColor Green
        Write-Host "  Unbound objects:" -ForegroundColor White
        
        $sortedObjs = $objs | Sort-Object
        foreach ($obj in $sortedObjs) {
            if ($objType.ToLower() -eq "authentication vserver") {
                Write-Host "    - $obj (check - not bound to any VPN Vserver)" -ForegroundColor Gray
            } else {
                Write-Host "    - $obj" -ForegroundColor Gray
            }
        }
    }
    
    if (-not $hasUnbound) {
        Write-Host "`nNo unbound objects found!" -ForegroundColor Green
    } else {
        Write-Host "`n" + ("=" * 60) -ForegroundColor Cyan
        Write-Host "Total unbound objects: $totalUnbound" -ForegroundColor Yellow
    }
    
    Write-Host ("=" * 60) -ForegroundColor Cyan
    Write-Host "Analysis complete." -ForegroundColor Cyan
}

# Execute
Main