powershell max route X server

################################################################################
# Script: Get-NetScalerServerRouteMapping.ps1
# Description: Analyse une config NetScaler et identifie quels serveurs 
#              utilisent quelle route basé sur le longest prefix match
################################################################################

param(
    [Parameter(Mandatory=$false)]
    [string]$ConfigPath = "c:\logs\ns.conf"
)

# Fonction pour convertir une IP en entier
function ConvertTo-IPInt {
    param([string]$IP)
    
    $octets = $IP.Split('.')
    if ($octets.Count -ne 4) {
        Write-Warning "IP invalide: $IP"
        return $null
    }
    
    try {
        [int64]$result = 0
        $result += [int64]$octets[0] * 16777216  # 256^3
        $result += [int64]$octets[1] * 65536     # 256^2
        $result += [int64]$octets[2] * 256       # 256^1
        $result += [int64]$octets[3]             # 256^0
        return $result
    }
    catch {
        Write-Warning "Erreur conversion IP: $IP"
        return $null
    }
}

# Fonction pour convertir un masque en CIDR
function ConvertTo-CIDR {
    param([string]$Netmask)
    
    $maskInt = ConvertTo-IPInt -IP $Netmask
    if ($null -eq $maskInt) { return 0 }
    
    # Compter les bits à 1
    $cidr = 0
    for ($i = 31; $i -ge 0; $i--) {
        if ($maskInt -band [Math]::Pow(2, $i)) {
            $cidr++
        }
        else {
            break
        }
    }
    return $cidr
}

# Fonction pour calculer le réseau à partir d'une IP et d'un masque
function Get-NetworkAddress {
    param(
        [int64]$IPInt,
        [int64]$MaskInt
    )
    return $IPInt -band $MaskInt
}

# Fonction pour parser les routes
function Get-Routes {
    param([string[]]$ConfigLines)
    
    $routes = @()
    
    foreach ($line in $ConfigLines) {
        if ($line -match '^\s*add\s+route\s+(\S+)\s+(\S+)\s+(\S+)') {
            $network = $matches[1]
            $netmask = $matches[2]
            $gateway = $matches[3]
            
            $networkInt = ConvertTo-IPInt -IP $network
            $maskInt = ConvertTo-IPInt -IP $netmask
            $cidr = ConvertTo-CIDR -Netmask $netmask
            
            if ($null -ne $networkInt -and $null -ne $maskInt) {
                $routes += [PSCustomObject]@{
                    Network = $network
                    Netmask = $netmask
                    Gateway = $gateway
                    NetworkInt = $networkInt
                    MaskInt = $maskInt
                    CIDR = $cidr
                    DisplayName = "$network/$cidr -> $gateway"
                }
            }
        }
    }
    
    return $routes
}

# Fonction pour parser les serveurs
function Get-Servers {
    param([string[]]$ConfigLines)
    
    $servers = @()
    
    foreach ($line in $ConfigLines) {
        if ($line -match '^\s*add\s+server\s+(\S+)\s+(\S+)(.*)') {
            $name = $matches[1]
            $ip = $matches[2]
            $remainder = $matches[3]
            
            # Extraire le commentaire s'il existe
            $comment = ""
            if ($remainder -match '-comment\s+"([^"]*)"') {
                $comment = $matches[1]
            }
            elseif ($remainder -match '-comment\s+(\S+)') {
                $comment = $matches[1]
            }
            
            $ipInt = ConvertTo-IPInt -IP $ip
            
            if ($null -ne $ipInt) {
                $servers += [PSCustomObject]@{
                    Name = $name
                    IP = $ip
                    IPInt = $ipInt
                    Comment = $comment
                }
            }
        }
    }
    
    return $servers
}

# Fonction pour trouver la route correspondante (longest prefix match)
function Find-MatchingRoute {
    param(
        [int64]$ServerIPInt,
        [array]$Routes
    )
    
    $bestMatch = $null
    $longestPrefix = -1
    
    foreach ($route in $Routes) {
        $serverNetwork = Get-NetworkAddress -IPInt $ServerIPInt -MaskInt $route.MaskInt
        
        if ($serverNetwork -eq $route.NetworkInt) {
            if ($route.CIDR -gt $longestPrefix) {
                $longestPrefix = $route.CIDR
                $bestMatch = $route
            }
        }
    }
    
    return $bestMatch
}

################################################################################
# MAIN
################################################################################

Write-Host "`n=== Analyse de la configuration NetScaler ===" -ForegroundColor Cyan
Write-Host "Fichier: $ConfigPath`n" -ForegroundColor Gray

# Vérifier que le fichier existe
if (-not (Test-Path $ConfigPath)) {
    Write-Error "Le fichier '$ConfigPath' n'existe pas"
    exit 1
}

# Lire la configuration
$configContent = Get-Content $ConfigPath

# Parser routes et serveurs
Write-Host "Parsing des routes..." -ForegroundColor Yellow
$routes = Get-Routes -ConfigLines $configContent
Write-Host "  -> $($routes.Count) routes trouvées" -ForegroundColor Green

Write-Host "Parsing des serveurs..." -ForegroundColor Yellow
$servers = Get-Servers -ConfigLines $configContent
Write-Host "  -> $($servers.Count) serveurs trouvés`n" -ForegroundColor Green

# Créer le mapping Route → Serveurs
$routeMapping = @{}

foreach ($route in $routes) {
    $routeMapping[$route.DisplayName] = @()
}

# Ajouter une catégorie pour les serveurs sans route
$noRouteKey = "[ATTENTION] Aucune route correspondante"
$routeMapping[$noRouteKey] = @()

# Associer chaque serveur à sa route
foreach ($server in $servers) {
    $matchingRoute = Find-MatchingRoute -ServerIPInt $server.IPInt -Routes $routes
    
    if ($null -ne $matchingRoute) {
        $routeMapping[$matchingRoute.DisplayName] += $server
    }
    else {
        $routeMapping[$noRouteKey] += $server
    }
}

# Afficher les résultats
Write-Host "=== Résultats : Serveurs par Route ===" -ForegroundColor Cyan
Write-Host ""

$routeCount = 0
$emptyRoutes = @()

# Afficher d'abord les routes avec des serveurs
foreach ($routeName in ($routeMapping.Keys | Sort-Object)) {
    $serversInRoute = $routeMapping[$routeName]
    
    if ($serversInRoute.Count -gt 0) {
        $routeCount++
        
        if ($routeName -eq $noRouteKey) {
            Write-Host "[$routeName]" -ForegroundColor Red
        }
        else {
            Write-Host "Route: $routeName" -ForegroundColor Green
        }
        
        Write-Host "  Nombre de serveurs: $($serversInRoute.Count)" -ForegroundColor Gray
        Write-Host ""
        
        foreach ($srv in ($serversInRoute | Sort-Object Name)) {
            $displayLine = "  - $($srv.Name) ($($srv.IP))"
            
            if ($srv.Comment) {
                $displayLine += " - `"$($srv.Comment)`""
            }
            
            Write-Host $displayLine
        }
        
        Write-Host ""
    }
    else {
        # Collecter les routes vides (sauf la catégorie noRouteKey)
        if ($routeName -ne $noRouteKey) {
            $emptyRoutes += $routeName
        }
    }
}

# Afficher les routes sans serveurs à la fin en rouge
if ($emptyRoutes.Count -gt 0) {
    Write-Host "=== Routes non bindees a aucun serveur ===" -ForegroundColor Red
    Write-Host ""
    
    foreach ($emptyRoute in ($emptyRoutes | Sort-Object)) {
        Write-Host "Route: $emptyRoute" -ForegroundColor Red
        Write-Host "  Aucun serveur n'utilise cette route" -ForegroundColor Gray
        Write-Host ""
    }
}

# Statistiques finales
Write-Host "=== Statistiques ===" -ForegroundColor Cyan
Write-Host "Total routes: $($routes.Count)" -ForegroundColor Gray
Write-Host "Total serveurs: $($servers.Count)" -ForegroundColor Gray
Write-Host ""
Write-Host "Routes avec serveurs: $routeCount" -ForegroundColor Gray
Write-Host "Routes sans serveurs: $($emptyRoutes.Count)" -ForegroundColor Gray
Write-Host "Serveurs sans route: $($routeMapping[$noRouteKey].Count)" -ForegroundColor Gray
Write-Host ""