Windows Server 2019 : géolocaliser l’origine des requêtes HTTP/HTTPS (IIS, PowerShell, GeoIP)

Vous cherchez à savoir de quels pays proviennent les requêtes HTTP/HTTPS adressées à votre Windows Server 2019 ? Voici une méthode fiable reposant sur les journaux (IIS/pare‑feu), l’enrichissement GeoIP et PowerShell — sans passer par Performance Monitor.

Sommaire

Vue d’ensemble

L’objectif est d’identifier, agréger et visualiser la provenance géographique du trafic Web (HTTP/HTTPS) sur un serveur Windows Server 2019 Datacenter, qu’il soit frontal (IIS) ou derrière un proxy/équilibreur de charge.

Pourquoi PerfMon n’est pas la bonne solution

Performance Monitor (PerfMon) ne sait pas géolocaliser des IP. Il expose des compteurs système et applicatifs (CPU, mémoire, E/S disque, file d’attente IIS, etc.), mais pas l’origine géographique du trafic. Pour obtenir la géolocalisation, il faut s’appuyer sur des journaux contenant l’IP source puis enrichir ces IP avec une base de données GeoIP.

Approche recommandée (résumé actionnable)

  1. Activer/collecter les journaux de la brique exposée (IIS, reverse‑proxy, pare‑feu). Chaque entrée doit contenir l’IP source (ou un en‑tête client réel).
  2. Extraire les IP (PowerShell, Log Parser 2.2, SIEM).
  3. Résoudre IP → Pays via une base GeoIP (p. ex. MaxMind GeoLite2, IP2Location LITE, ipinfo.io pour un service API).
  4. Corréler et visualiser (Excel, Power BI, Grafana, SIEM) : trafic par pays, par plage horaire, top 10, etc.

Architecture cible (du log à la carte des pays)

Client (IP publique)
     │  HTTP/HTTPS
     ▼
[WAF / Proxy / LB] ──> (X-Forwarded-For / True-Client-IP)
     │  HTTP/HTTPS
     ▼
[IIS sur Windows Server 2019] ──> Journaux W3C (c-ip + en-têtes)
     │
     ├─> Extraction des IP (PowerShell / SIEM)
     │
     ├─> Enrichissement GeoIP (GeoLite2/GeoIP2)
     │
     └─> Agrégation & Visualisation (CSV/Excel/Power BI/SIEM)

Configurer les sources de journaux

IIS 10.0 (Windows Server 2019)

Activez le journal W3C et consignez les bons champs.

  1. Ouvrez Gestionnaire des services Internet (IIS) > sélectionnez le site concerné > Logging (Journalisation).
  2. Format : W3C. Type d’horodatage : UTC (par défaut).
  3. Planification du fichier : Daily. Dossier : par défaut C:\inetpub\logs\LogFiles\W3SVC<ID>.
  4. Cliquez sur Select Fields… et assurez-vous d’inclure au minimum :
    • date, time
    • c-ip (IP cliente vue par IIS)
    • cs-method, cs-uri-stem, cs-uri-query
    • sc-status, time-taken, cs(User-Agent)
  5. Si votre serveur est derrière un proxy/WAF (Azure App Gateway, Cloudflare, F5, Nginx, etc.), ajoutez les champs personnalisés suivants :
    • Name : X-Forwarded-ForSource Type : Request HeaderSource : X-Forwarded-For
    • (Optionnel) True-Client-IP ou CF-Connecting-IP selon votre fournisseur
    Dans la fenêtre Select Fields…, cliquez sur Add Field…, puis choisissez Request Header et saisissez le nom de l’en‑tête exact.
  6. Appliquez et recyclez le site si nécessaire.

Champs IIS recommandés pour l’analyse GeoIP

CatégorieChamps W3CUtilité
Identification clientc-ip, champ personnalisé X-Forwarded-ForIP source (ou client réel si proxy)
Requêtecs-method, cs-uri-stem, cs-uri-query, cs(User-Agent)Typologie du trafic et bots
Réponsesc-status, time-takenQualité de service par pays
Horodatagedate, timeAnalyse temporelle & corrélation

Journal Windows Defender Firewall

Utile si IIS n’est pas activé partout ou pour analyser les connexions avant IIS.

  1. Ouvrez wf.msc > Windows Defender Firewall with Advanced Security > Properties.
  2. Dans chaque profil (Domain, Private, Public) : Logging > activez Log dropped packets et Log successful connections.
  3. Chemin par défaut : C:\Windows\System32\LogFiles\Firewall\pfirewall.log.
  4. Filtrage ultérieur sur protocole TCP et ports 80/443.

Extraction des IP (PowerShell)

Les journaux IIS au format W3C sont auto‑documentés : une ligne #Fields: énumère l’ordre des colonnes. Le script ci‑dessous lit ce header, gère les custom fields (p. ex. X-Forwarded-For) et extrait les IP utiles.

# Chemins à adapter
$IisLogRoot = "C:\inetpub\logs\LogFiles"
$OutDir     = "C:\GeoIP\outputs"
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null

function Read-W3CLog {
    param([string]$Path)
    $header = $null
    foreach ($line in [System.IO.File]::ReadLines($Path)) {
        if ($line.StartsWith("#Fields:")) {
            $header = $line.Substring(8).Trim().Split(" ")
            continue
        }
        if ($line.StartsWith("#") -or [string]::IsNullOrWhiteSpace($line)) { continue }
        if (-not $header) { continue }
        $cols = $line.Split(" ")
        if ($cols.Count -ne $header.Count) { continue }
        $obj = [ordered]@{}
        for ($i=0; $i -lt $header.Count; $i++) { $obj[$header[$i]] = $cols[$i] }
        [pscustomobject]$obj
    }
}

function Get-ClientIP {
    param($record)
    # Priorité : X-Forwarded-For (première IP) puis True-Client-IP, sinon c-ip
    $xff = $record | Select-Object -ExpandProperty "x-forwarded-for" -ErrorAction SilentlyContinue
    if ($xff -and $xff -ne "-") {
        return ($xff.Split(",")[0].Trim())
    }
    $tci = $record | Select-Object -ExpandProperty "true-client-ip" -ErrorAction SilentlyContinue
    if ($tci -and $tci -ne "-") { return $tci }
    return $record."c-ip"
}

# Parcours de tous les fichiers IIS
$records = foreach ($siteDir in Get-ChildItem -Path $IisLogRoot -Directory) {
    foreach ($f in Get-ChildItem -Path $siteDir.FullName -Filter *.log) {
        Read-W3CLog -Path $f.FullName
    }
}

# Filtre HTTP/HTTPS (si vous n'avez que IIS, ce filtre est facultatif)
$httpRecords = $records | Where-Object {
    $_."cs-uri-stem" -and $_."cs-uri-stem" -ne "-" # rudimentaire
}

$ipList = $httpRecords | ForEach-Object { Get-ClientIP $_ } | Where-Object { $_ -and $_ -ne "-" }

# Enregistrer la liste d'IP uniques
$UniqueIPsPath = Join-Path $OutDir "ips_uniques.txt"
$ipList | Sort-Object -Unique | Set-Content -Encoding UTF8 $UniqueIPsPath
Write-Host "IP uniques : $((Get-Content $UniqueIPsPath).Count) -&gt; $UniqueIPsPath"

Variante : extraire depuis le journal du pare‑feu

Le journal du pare‑feu est aussi en W3C. L’exemple ci‑dessous retient les connexions TCP vers 80/443 et sort la colonne src-ip.

$FwLog = "C:\Windows\System32\LogFiles\Firewall\pfirewall.log"
function Read-W3CFirewall {
    param([string]$Path)
    $header = $null
    foreach ($line in [System.IO.File]::ReadLines($Path)) {
        if ($line.StartsWith("#Fields:")) { $header = $line.Substring(8).Trim().Split(" "); continue }
        if ($line.StartsWith("#") -or [string]::IsNullOrWhiteSpace($line)) { continue }
        if (-not $header) { continue }
        $cols = $line.Split(" ")
        if ($cols.Count -ne $header.Count) { continue }
        $obj = [ordered]@{}
        for ($i=0; $i -lt $header.Count; $i++) { $obj[$header[$i]] = $cols[$i] }
        [pscustomobject]$obj
    }
}

$fwRecords = Read-W3CFirewall -Path $FwLog |
Where-Object { $*.protocol -eq "TCP" -and ($*.{"dst-port"} -in @("80","443")) -and $_.action -eq "ALLOW" }

$fwIps = $fwRecords | Select-Object -ExpandProperty "src-ip" | Sort-Object -Unique
$FwIpsPath = Join-Path $OutDir "ips_firewall_uniques.txt"
$fwIps | Set-Content -Encoding UTF8 $FwIpsPath 

Résolution IP → Pays (GeoIP)

Deux grandes options :

  • Base locale (recommandé pour volume/contrôle) : installez une base GeoIP (p. ex. GeoLite2 Country) et la bibliothèque .NET MaxMind.GeoIP2.
  • Service API (rapide, mais limité par quotas/dépendance réseau) : résolvez les IP à la volée via un fournisseur (ipinfo.io, etc.).

Méthode locale (MaxMind GeoLite2 + .NET)

Préparez un dossier, par exemple C:\GeoIP, contenant :

  • GeoLite2-Country.mmdb (base à jour — renouvelez‑la chaque semaine).
  • MaxMind.GeoIP2.dll (bibliothèque .NET).

Script d’enrichissement et d’agrégation (pays + comptages) :

$GeoDir   = "C:\GeoIP"
$DbPath   = Join-Path $GeoDir "GeoLite2-Country.mmdb"
$DllPath  = Join-Path $GeoDir "MaxMind.GeoIP2.dll"
$IpsFile  = Join-Path $OutDir "ips_uniques.txt"
$OutCsv   = Join-Path $OutDir "trafic_par_pays.csv"

# Chargement de la DLL

if (-not (Test-Path $DllPath)) { throw "DLL MaxMind.GeoIP2 introuvable : $DllPath" }
[void][System.Reflection.Assembly]::LoadFrom($DllPath)

# Ouvre la base

$reader = [MaxMind.GeoIP2.DatabaseReader]::new($DbPath)

# Dictionnaire de cache IP->pays pour accélérer (thread-safe si besoin)

$cache = New-Object 'System.Collections.Generic.Dictionary[string,string]'

function Get-CountryIso2 {
param([string]$ip)
if ($cache.ContainsKey($ip)) { return $cache[$ip] }
try {
$resp = $reader.Country($ip)
$iso  = $resp.Country.IsoCode
if (-not $iso) { $iso = "ZZ" } # inconnu
} catch {
$iso = "ZZ"
}
$cache[$ip] = $iso
return $iso
}

$agg = @{}
Get-Content $IpsFile | ForEach-Object {
$iso = Get-CountryIso2 $_
if (-not $agg.ContainsKey($iso)) { $agg[$iso] = 0 }
$agg[$iso]++
}

# Sortie CSV ordonnée

$rows = $agg.GetEnumerator() | Sort-Object -Property Value -Descending | ForEach-Object {
[pscustomobject]@{ CountryISO = $*.Key; Count = $*.Value }
}
$rows | Export-Csv -NoTypeInformation -Encoding UTF8 $OutCsv
Write-Host "Résultats : $OutCsv" 

Option API (aperçu)

Si vous préférez une API, utilisez un jeton applicatif et mettez en place un throttling/cache côté PowerShell. Évitez d’appeler le service pour la même IP plus d’une fois. Attention aux limites quotidiennes et à la confidentialité des adresses IP.

Corrélation temporelle et analyses détaillées

Pour l’analyse par heure et par pays, enrichissez les lignes (pas seulement les IP uniques). Le fragment ci‑dessous joint pays, horodatage et code HTTP :

$OutDetail = Join-Path $OutDir "detail_pays_horodatage.csv"

# Relecture des logs IIS pour enrichissement ligne à ligne

$reader = [MaxMind.GeoIP2.DatabaseReader]::new($DbPath)
$detailRows = foreach ($siteDir in Get-ChildItem -Path $IisLogRoot -Directory) {
foreach ($f in Get-ChildItem -Path $siteDir.FullName -Filter *.log) {
foreach ($r in (Read-W3CLog -Path $f.FullName)) {
$ip = Get-ClientIP $r
if (-not $ip) { continue }
try { $iso = ($reader.Country($ip)).Country.IsoCode } catch { $iso = "ZZ" }
[pscustomobject]@{
DateTimeUTC = [datetime]::Parse($r.date + " " + $r.time)
CountryISO  = $iso
Method      = $r."cs-method"
UriStem     = $r."cs-uri-stem"
Status      = $r."sc-status"
TimeTakenMs = $r."time-taken"
}
}
}
}
$detailRows | Export-Csv -NoTypeInformation -Encoding UTF8 $OutDetail 

Ensuite, importez ce CSV dans Excel/Power BI : créez un visuel « Carte remplie » par CountryISO, un histogramme par heure (DateTimeUTC arrondi à l’heure), un top 10 des pays par volume, et corrélez avec sc-status pour repérer des anomalies régionales.

Automatiser (Planificateur de tâches)

Placez vos scripts dans C:\GeoIP\scripts\ puis créez une tâche planifiée quotidienne :

SCHTASKS /Create /SC DAILY /ST 02:30 /RU "SYSTEM" `
  /TN "GeoIP-IIS-Aggregation" /TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\GeoIP\scripts\geoip_iis.ps1"

Conseils d’automatisation :

  • Rotation & rétention : archivez les logs et nettoyez après N jours.
  • Mise à jour GeoIP : téléchargez la base fraîche chaque semaine et remplacez GeoLite2-Country.mmdb.
  • Observabilité : journalisez l’exécution (Start/End, volume traité, erreurs).

Tableau de bord SIEM (bonus)

Si vous utilisez un SIEM (ELK/Elastic, Splunk, etc.), ingérez les logs IIS et appliquez un processor GeoIP au fil de l’eau. Sur Elastic, un pipeline « geoip » alimente automatiquement les champs pays/ville/coordonnées ; sur Splunk, la commande iplocation produit des champs géographiques. Vous obtenez ainsi des tableaux et cartes en quasi temps réel, avec filtres par host, site, status, User‑Agent, etc.

Cas particuliers & bonnes pratiques

  • Reverse proxy / CDN : si c-ip est l’IP du proxy, exploitez X-Forwarded-For (ou True-Client-IP). Assurez-vous qu’IIS journalise cet en‑tête.
  • IPv6 : gardez la compatibilité (les bases GeoIP couvrent IPv4 et IPv6).
  • VPN, CGNAT, mobiles : attendez-vous à des approximations ; la géolocalisation d’IP n’est pas une science exacte.
  • Mises à jour de base : programmez une mise à jour hebdomadaire. Les allocations d’IP évoluent.
  • Qualité des données : filtrez les bots si nécessaire (p. ex. via User-Agent), dédupliquez les erreurs 4xx bruyantes, normalisez les pays (ISO‑3166‑1 alpha‑2).

RGPD et conformité

  • Minimisation : stockez idéalement des agrégats (codes pays, volumes) plutôt que des IP brutes.
  • Durée de conservation : définissez une rétention conforme aux exigences légales et métiers.
  • Pseudonymisation : si vous conservez des IP, hachez‑les (avec salage) ou tronquez (p. ex. /24, /48).
  • Traçabilité : journalisez l’accès aux données enrichies et qui y accède.

Tableau récapitulatif des choix

BesoinOutils/ressources possiblesAvantagesLimites/Points d’attention
AutomatisationPowerShell + GeoIP2, Planificateur de tâchesContrôle total, coûts faiblesNécessite maintenance de la base GeoIP
Temps quasi réelSIEM (Elastic/Splunk), WAF/CDN avec stats géoTableaux et alertes instantanésCoût/licences, intégration
Précision accrueBases GeoIP payantes (p. ex. City)Meilleure granularitéCoût récurrent, mise à jour indispensable
Conformité (RGPD)Stockage des codes pays, agrégationRéduction du risqueVérifier vos bases légales et politiques internes

Checklist de mise en œuvre

  • ✔️ IIS logge c-ip et l’en‑tête X-Forwarded-For si proxy.
  • ✔️ Les journaux tournent quotidiennement et sont conservés selon la politique.
  • ✔️ Le script PowerShell extrait les IP et les enrichit via GeoIP.
  • ✔️ Les agrégats (trafic par pays) sont exportés en CSV et visualisés.
  • ✔️ La base GeoIP est mise à jour automatiqument chaque semaine.
  • ✔️ Les considérations RGPD sont documentées (rétention, minimisation).

FAQ rapide

PerfMon peut‑il fournir le pays directement ?
Non. PerfMon n’expose aucun compteur de géolocalisation IP. Il est utile pour mesurer la santé du serveur, pas l’origine géographique du trafic.

Dois‑je utiliser IIS et le pare‑feu ?
Idéalement oui : IIS pour la granularité applicative (URI, statut), pare‑feu pour capter même quand l’application ne répond pas.

Que faire si c-ip n’est pas l’IP du client réel ?
Activez la journalisation de X-Forwarded-For (ou équivalent) côté IIS. Configurez aussi votre proxy/WAF pour transmettre l’IP client réel.

La précision de la base GeoIP est‑elle garantie ?
Non. Elle est généralement bonne à l’échelle du pays, mais des erreurs existent, surtout avec VPN, proxys, mobiles ou CGNAT.

Scripts complets (exemple prêt à l’emploi)

Ce script assemble extraction IIS, enrichissement GeoIP et export des agrégats. Adaptez les chemins et planifiez‑le quotidiennement.

# ===========================
# GeoIP HTTP/HTTPS - Windows Server 2019 (IIS)
# ===========================
param(
  [string]$IisLogRoot = "C:\inetpub\logs\LogFiles",
  [string]$GeoDir     = "C:\GeoIP",
  [string]$OutDir     = "C:\GeoIP\outputs"
)

$ErrorActionPreference = "Stop"
New-Item -ItemType Directory -Force -Path $GeoDir,$OutDir | Out-Null

$DbPath  = Join-Path $GeoDir "GeoLite2-Country.mmdb"
$DllPath = Join-Path $GeoDir "MaxMind.GeoIP2.dll"

# --- Fcts utilitaires ---
function Read-W3CLog {
    param([string]$Path)
    $header = $null
    foreach ($line in [System.IO.File]::ReadLines($Path)) {
        if ($line.StartsWith("#Fields:")) { $header = $line.Substring(8).Trim().Split(" "); continue }
        if ($line.StartsWith("#") -or [string]::IsNullOrWhiteSpace($line)) { continue }
        if (-not $header) { continue }
        $cols = $line.Split(" ")
        if ($cols.Count -ne $header.Count) { continue }
        $obj = [ordered]@{}
        for ($i=0; $i -lt $header.Count; $i++) { $obj[$header[$i]] = $cols[$i] }
        [pscustomobject]$obj
    }
}

function Get-ClientIP { param($r)
    $candidate = $null
    foreach ($h in @("x-forwarded-for","true-client-ip","cf-connecting-ip")) {
        $v = $r | Select-Object -ExpandProperty $h -ErrorAction SilentlyContinue
        if ($v -and $v -ne "-") { $candidate = $v; break }
    }
    if (-not $candidate) { $candidate = $r."c-ip" }
    if ($candidate -and $candidate.Contains(",")) { return $candidate.Split(",")[0].Trim() }
    return $candidate
}

# --- Chargement GeoIP ---
if (-not (Test-Path $DbPath))  { throw "Base GeoIP absente : $DbPath" }
if (-not (Test-Path $DllPath)) { throw "DLL GeoIP absente : $DllPath" }
[void][System.Reflection.Assembly]::LoadFrom($DllPath)
$Reader = [MaxMind.GeoIP2.DatabaseReader]::new($DbPath)

$Cache = New-Object 'System.Collections.Generic.Dictionary[string,string]'
function Resolve-CountryIso { param([string]$ip)
    if (-not $ip) { return "ZZ" }
    if ($Cache.ContainsKey($ip)) { return $Cache[$ip] }
    try { $iso = ($Reader.Country($ip)).Country.IsoCode } catch { $iso = "ZZ" }
    if (-not $iso) { $iso = "ZZ" }
    $Cache[$ip] = $iso
    return $iso
}

# --- Lecture logs & enrichissement ---
$DetailCsv = Join-Path $OutDir "detail_pays_http.csv"
$AggCsv    = Join-Path $OutDir "trafic_par_pays.csv"

$agg = @{}
$detail = New-Object System.Collections.Generic.List[object]

foreach ($siteDir in Get-ChildItem -Path $IisLogRoot -Directory) {
    foreach ($f in Get-ChildItem -Path $siteDir.FullName -Filter *.log) {
        foreach ($r in (Read-W3CLog -Path $f.FullName)) {
            $ip = Get-ClientIP $r
            if (-not $ip -or $ip -eq "-") { continue }
            $iso = Resolve-CountryIso $ip

            if (-not $agg.ContainsKey($iso)) { $agg[$iso] = 0 }
            $agg[$iso]++

            try { $dt = [datetime]::Parse($r.date + " " + $r.time) }
            catch { $dt = $null }

            $detail.Add([pscustomobject]@{
                DateTimeUTC = $dt
                CountryISO  = $iso
                IP          = $ip
                Method      = $r."cs-method"
                UriStem     = $r."cs-uri-stem"
                Status      = $r."sc-status"
                TimeTakenMs = $r."time-taken"
            })
        }
    }
}

# Exports
$detail | Export-Csv -NoTypeInformation -Encoding UTF8 $DetailCsv

$agg.GetEnumerator() |
    Sort-Object -Property Value -Descending |
    ForEach-Object { [pscustomobject]@{ CountryISO=$_.Key; Count=$_.Value } } |
    Export-Csv -NoTypeInformation -Encoding UTF8 $AggCsv

Write-Host "OK - Détails : $DetailCsv"
Write-Host "OK - Agrégats : $AggCsv"

Dépannage (troubleshooting)

  • Fichier vide ou peu de lignes : vérifiez l’activité du site et les permissions du dossier LogFiles.
  • Colonnes décalées : assurez-vous que la ligne #Fields: est correctement lue (pas de caractères BOM exotiques). Évitez d’éditer les logs à la main.
  • IP privée au lieu d’IP publique : vous êtes derrière un proxy ; activez la journalisation de X-Forwarded-For et configurez l’équipement amont.
  • Erreurs GeoIP : DLL manquante, base obsolète, ou IP non trouvée → renvoie ZZ. Mettez à jour la base.
  • Performances : utilisez un cache IP→pays (déjà inclus) et évitez le traitement ligne‑à‑ligne sur des dizaines de Go en une seule passe (traitez par jour).

Conclusion

La géolocalisation du trafic HTTP/HTTPS sur Windows Server 2019 ne passe pas par PerfMon mais par l’exploitation des journaux (IIS et/ou pare‑feu), enrichis par une base GeoIP. En quelques scripts PowerShell et une planification quotidienne, vous obtenez des tableaux clairs : volumétrie par pays, tendances horaires, détection d’anomalies régionales — tout en respectant les contraintes de conformité et de performance.

Sommaire