Engineering PowerShell Raw Sockets Performance SAP

Analyse Deep-Dive : Latence SAP &
Reconstruction bas-niveau de la stack HTTP

Conception d'un outil d'instrumentation bas niveau pour différencier latence réseau et latence serveur au sein d'une architecture critique multi-sites.

Périmètre

SAP

Problème

Latences

Outil

RawSocket + PowerBI

Impact

Milliers d'utilisateurs

01.Le Contexte

Des lenteurs importantes affectaient certaines transactions SAP critiques (recherche produits, validation de commandes), impactant plusieurs milliers d'utilisateurs quotidiens. Les premières analyses orientaient vers une cause réseau (Wi-Fi / transit inter-sites). Cependant, les métriques bas niveau révélaient des temps de transit inférieurs à 50 ms.

Objectif : Isoler la cause racine sans accès au backend SAP, et de produire une preuve objectivable permettant d'arbitrer entre équipes.

Solution : Développement d'un client HTTP/HTTPS instrumenté en PowerShell (TCPClient + SslStream), avec gestion manuelle des requêtes/réponses et parsing HTTP pour mesurer finement connect/TLS/TTFB/SapPerf/fin de transfert.

Choix technologique : Le développement a été réalisé en PowerShell afin de respecter les contraintes de l'environnement client

Ce choix a permis une intégration immédiate dans l'environnement existant tout en conservant un niveau d'instrumentation bas niveau suffisant.

02.Analyse & Preuve par l'Image

Dashboards Power BI construits à partir des logs du robot PowerShell. Chaque transaction est mesurée finement : connectTime, sslTime, firstByteTime, lastByteTime, SapPerf2 (temps serveur via header HTTP).

Dashboard global transactions SAP – Vue d'ensemble avec pic à 154s sur transaction 8

Slide 1 – Vue globale : pic clair à 154 s sur transaction 8 (premier octet serveur). Preuve que le réseau n'est pas en cause (transit stable < 50 ms).

Zoom sur transaction 'Rechercher des produits' – Lenteurs firstByteTime et SapPerf2

Slide 2 – Zoom Recherche Produits (index 4) : lenteurs applicatives (rectangles jaunes) vs réseau (rouges). Dichotomie par index unique pour isoler rapidement les pages fautives.

Transaction 'Retour commandes <A valider>' – SapPerf2 > 150s, moyenne > 2 min

Slide 3 – Retour commandes (index 8) : SapPerf2 extrême (> 150 s), preuve formelle d'un défaut backend SAP (SQL générée inefficace).

03. Ingénierie "Raw Socket"

Développement d'un robot PowerShell custom pour reconstruire la stack HTTP/HTTPS/TLS sans dépendre de bibliothèques haut-niveau.

Note : L'architecture repose sur deux fonctions distinctes : une pour générer la requête, et une seconde pour gérer son envoi, ce qui facilite l'extension à d'autres protocoles.

ntwrkperf.ps1 - Connect
function connectSyn
{
        [CmdletBinding()]
        Param(

            [Parameter(Mandatory=$true,
            HelpMessage="Hostname or IPAdresse")]
            [string]$serverName,

            [Parameter(Mandatory=$true,
            HelpMessage="port")]
            [int]$port,

            [Parameter(Mandatory=$false,
            HelpMessage="stateObject")]
            $stateObject
        )

    $sw = [Diagnostics.StopWatch]::StartNew()
    [System.Net.Sockets.TcpClient]$stateObject.socket = new-object System.Net.Sockets.TcpClient($serverName, $port)
    [int]$stateObject.connectTime = $sw.ElapsedMilliseconds

    $stateObject.socket.ReceiveTimeout = 30000
    $stateObject.socket.SendTimeout = 30000

    $sw.Stop()
    $sw = $null

    return $stateObject
}
ntwrkperf.ps1 - Fermeture du Socket
function closeSocket
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false,
        HelpMessage="closeSocket")]
        [bool]$close = $true,
        
        [Parameter(Mandatory=$true,
        HelpMessage="stateObject")]
        $stateObject
    )

    if($close)
    {
        if($stateObject.sslStream)
        {
            $stateObject.sslStream.Close()
            $stateObject.sslStream.Dispose()   
        }
        if($stateObject.socket)
        {
            $stateObject.socket.Close()
            $stateObject.socket.Dispose()
        }

    }
    return $stateObject
}
                
ntwrkperf.ps1 - SSL
function sslAuthenticate
{
    [CmdletBinding()]
    Param(

            [Parameter(Mandatory=$true,
            HelpMessage="Hostname or IPAdresse")]
            [string]$serverName,

            [Parameter(Mandatory=$false,
            HelpMessage="stateObject")]
            $stateObject
        )

    [System.Net.Sockets.NetworkStream]$stream = $stateObject.socket.GetStream()
    $stream.WriteTimeout = 30000
    $stream.ReadTimeout = 30000

    [System.Net.Security.SslStream]$stateObject.sslStream = New-Object System.Net.Security.SslStream($stream, $true, [System.Net.Security.RemoteCertificateValidationCallback]{$true})
    $stateObject.sslStream.ReadTimeout = 30000
    $stateObject.sslStream.WriteTimeout = 30000
    $ccCol = New-Object System.Security.Cryptography.X509Certificates.X509CertificateCollection

    $sw = [Diagnostics.StopWatch]::StartNew()
    $stateObject.sslStream.AuthenticateAsClient($serverName,$ccCol,[System.Security.Authentication.SslProtocols]::Tls12,$false)
    [int]$stateObject.sslTime = $sw.ElapsedMilliseconds

    $sw.Stop()
    $sw = $null
    return $stateObject
}
                
ntwrkperf.ps1 - HTTP
function HTTP
{
    [CmdletBinding()]
    Param(
            [Parameter(Mandatory=$true,
            HelpMessage="request")]
            [string]$request,

            [Parameter(Mandatory=$false,
            HelpMessage="stateObject")]
            $stateObject
        )

    $encodingUTF8 = New-Object System.Text.UTF8Encoding
    $encodingASCII = New-Object System.Text.ASCIIEncoding
    $buffer = New-Object System.Byte[] 1500
    [int]$i = -1
    [int]$read = -1
    [int]$decalage = -1
    [int]$pagesize = 0
    [int]$Length = 0

    $stream = $stateObject.socket.GetStream()
    $streamWriter = New-Object System.IO.StreamWriter($stream)
    $stream.WriteTimeout = 30000
    $stream.ReadTimeout = 30000

    $streamWriter.Write($request)
    $streamWriter.Flush()

    $sw = [Diagnostics.StopWatch]::StartNew()
    while($decalage -lt $Length -and $read -ne 0)
    {
        [int]$read = $stream.Read($buffer, 0, $buffer.Length)
        $pagesize += $read
        $responseFromServer += $encodingUTF8.GetString($buffer, 0, $read)
        if ($i -le 0)
        {
            if ($i -eq -1)
            {
                [int]$stateObject.firstByte = $sw.ElapsedMilliseconds; 
                $sw.Restart();
            }
            $isContent = isfindHeader $responseFromServer
            if($isContent -eq $false){$i = -1}
            else {$Length = findContentLength $responseFromServer; $decalage = 
                beginBuffer $responseFromServer;}
            $read = -1
        }
        $i++
        if($read -ge 0){$decalage += $read}
    }
    [int]$stateObject.lastByte = $sw.ElapsedMilliseconds
    [int]$stateObject.pagesize = $pagesize

    [int]$stateObject.contentLength = $Length
    [int]$stateObject.downloadLength = $decalage
    [int]$stateObject.responseCode = $responseFromServer.Substring(9, 3)
    $tmpTime = returnTimeConnect $stateObject.connectTime $stateObject.sslTime
    [int]$stateObject.total = $stateObject.firstByte + $stateObject.lastByte + $tmpTime
    $stateObject.responseFromServer = $responseFromServer

    if($stateObject.downloadLength -gt 0 -and $stateObject.lastByte -gt 0)
    { [int]$stateObject.debitMoyen = ($stateObject.downloadLength / $stateObject.lastByte * 1000) }
    else
    { [int]$stateObject.debitMoyen = 0 }
    
    $sw.Stop()
    $sw = $null

    return $stateObject
}
                
ntwrkperf.ps1 - HTTPS
function HTTPS
{
    [CmdletBinding()]
    Param(
            [Parameter(Mandatory=$true,
            HelpMessage="request")]
            [string]$request,

            [Parameter(Mandatory=$false,
            HelpMessage="stateObject")]
            $stateObject
        )

    $encodingUTF8 = New-Object System.Text.UTF8Encoding
    $encodingASCII = New-Object System.Text.ASCIIEncoding
    $buffer = New-Object System.Byte[] 1500
    [int]$i = -1
    [int]$read = -1
    [int]$decalage = -1
    [int]$pagesize = 0
    [int]$Length = 0

    $encodeBytes = $encodingASCII.GetBytes($request)

    $stateObject.sslStream.Write($encodeBytes)
    $stateObject.sslStream.Flush()

    $sw = [Diagnostics.StopWatch]::StartNew()
    while($decalage -lt $Length -and $read -ne 0)
    {
        [int]$read = $stateObject.sslStream.Read($buffer, 0, $buffer.Length)
        $pagesize += $read
        $responseFromServer += $encodingUTF8.GetString($buffer, 0, $read)
        if ($i -le 0)
        {
            if ($i -eq -1)
            {
                [int]$stateObject.firstByte = $sw.ElapsedMilliseconds; 
                $sw.Restart();
            }
            $isContent = isfindHeader $responseFromServer
            if($isContent -eq $false){$i = -1}
            else {$Length = findContentLength $responseFromServer; $decalage = 
                beginBuffer $responseFromServer;}
            $read = -1
        }
        $i++
        if($read -ge 0){$decalage += $read}
    }
    [int]$stateObject.lastByte = $sw.ElapsedMilliseconds
    [int]$stateObject.pagesize = $pagesize

    [int]$stateObject.contentLength = $Length
    [int]$stateObject.downloadLength = $decalage
    [int]$stateObject.responseCode = $responseFromServer.Substring(9, 3)
    $tmpTime = returnTimeConnect $stateObject.connectTime $stateObject.sslTime
    [int]$stateObject.total = $stateObject.firstByte + $stateObject.lastByte + $tmpTime
    $stateObject.responseFromServer = $responseFromServer
    $stateObject.sapperf = sapPerf $responseFromServer

    if($stateObject.downloadLength -gt 0 -and $stateObject.lastByte -gt 0)
    { [int]$stateObject.debitMoyen = ($stateObject.downloadLength / $stateObject.lastByte * 1000) }
    else
    { [int]$stateObject.debitMoyen = 0 }
    
    $sw.Stop()
    $sw = $null

    return $stateObject
}
                
ntwrkperf.ps1 - Fonctions communes
function isfindHeader($responseFromServer)
{
    if ($responseFromServer.Contains("`r`n`r`n"))
    {
        return $true
    }
    return $false
}

function beginBuffer($responseFromServer)
{
    if ($responseFromServer.Contains("`r`n`r`n"))
    {
        [int]$indexOf = $responseFromServer.IndexOf("`r`n`r`n")
        [int]$decalage = $responseFromServer.Length - $indexOf - 4
    }
    return $decalage
}

function findContentLength($responseFromServer)
{
    [int]$Length = 0
    $tmp = $responseFromServer -split "`r`n"
    foreach($header in $tmp)
    {
        if($header.Contains("Content-Length"))
        {
            $Length = $header -replace "Content-Length: ", ""
        }
        if($header.Contains("content-length"))
        {
            $Length = $header -replace "content-length: ", ""
        }
        if($header.Contains("Transfer-Encoding: chunked"))
        {
           $Length = 70000
        }
    }
    return $Length
}
                
ntwrkperf.ps1 - Fonctions Récupération SAPPerf
function  sapPerf($responseFromServer)
{
    if ($responseFromServer -match 'sap-perf-fesrec: (\d+)') 
    {
        return $Matches[1]
    }
    return -1
}
ntwrkperf.ps1 - Réimplementation protocol HTTP
function Invoke-RequestWeb
{
        [CmdletBinding()]
        Param(

            [Parameter(Mandatory=$true,
            HelpMessage="Hostname or IPAdresse")]
            [string]$serverName,

            [Parameter(Mandatory=$true,
            HelpMessage="uri")]
            [string]$uri,

            [Parameter(Mandatory=$false,
            HelpMessage="fulluri")]
            [string]$fulluri,

            [Parameter(Mandatory=$false,
            HelpMessage="method")]
            [string]$method = "GET",

            [Parameter(Mandatory=$false,
            HelpMessage="cookie")]
            [string]$cookie = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="keep-alive")]
            [bool]$keepalive = $false,

            [Parameter(Mandatory=$false,
            HelpMessage="referer")]
            [string]$referer,

            [Parameter(Mandatory=$false,
            HelpMessage="bodyPost")]
            [string]$bodyPost = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="bodyPost")]
            [int]$contentlength = 0,

            [Parameter(Mandatory=$false,
            HelpMessage="accept")]
            [string]$accept = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="accept")]
            [string]$useragent = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="contenttype")]
            [string]$contenttype = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="secfetch")]
            [string]$secfetch = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="maxage")]
            [string]$maxage = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="upgradesecure")]
            [string]$upgradesecure = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="authobasic")]
            [string]$authobasic = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="xmlRequest")]
            [bool]$xmlRequest=$false,

            [Parameter(Mandatory=$false,
            HelpMessage="proxy")]
            [bool]$proxy,

            [Parameter(Mandatory=$false,
            HelpMessage="user")]
            [string]$user,

            [Parameter(Mandatory=$false,
            HelpMessage="password")]
            [string]$password,

            [Parameter(Mandatory=$false,
            HelpMessage="domaine")]
            [string]$domaine,

            [Parameter(Mandatory=$false,
            HelpMessage="authNTLM")]
            [bool]$authNTLM = $false,

            [Parameter(Mandatory=$false,
            HelpMessage="authNTLMResponse")]
            [string]$authNTLMResponse = $null
        )

    $text = ("$method $uri HTTP/1.1`r`n" +
        "Host: $serverName`r`n")
    if ($keepalive) {$text += "Connection: keep-alive`r`n"}
    else {$text += "Connection: close`r`n"}
    if ($accept.Length -gt 0) {$text += "Accept: $accept`r`n"}
    else {$text += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8`r`n"}
    if ($useragent.Length -gt 0){$text += "User-Agent: $useragent`r`n"}
    else {$text += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0`r`n"}
    if ($bodyPost.Length -gt 0){$text += "Content-Length: $($bodyPost.Length)`r`n"}
    if($contentlength -gt 0) {$text += "Content-Length: $contentlength`r`n"}
    if ($xmlRequest){$text += "X-Requested-With: XMLHttpRequest`r`n"}
    if($contenttype) {$text += "Content-Type: application/x-www-form-urlencoded`r`n"}

    if($authobasic.Length -gt 0){$text += "Authorization: Basic $authobasic`r`n"}
    $text += "Accept-Encoding: br`r`n"
    $text += "Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3`r`n"

    if ($cookie.Length -gt 0) {$text += "$cookie`r`n"}
    
    if($maxage){$text += "Cache-Control: max-age=0`r`n"}
    if($secfetch){$text += "Sec-Fetch-Dest: document`r`nSec-Fetch-Mode: navigate`r`nSec-Fetch-Site: none`r`n"}
    if($upgradesecure){$text += "Upgrade-Insecure-Requests: 1`r`n"}

    if($authoNTLM)
    {
        $obj = [System.Net.Ntlm]::new($user, $password, $domaine)
        if(!$authNTLMexecute)
        {
            $data = $obj.CreateNegotiateMessage()
            $text += "Authorization: $data`r`n"
        }
        else
        {
            $data = $obj.ProcessChallenge($authNTLMResponse)
            $text += "Authorization: $data`r`n"
        }
    }
    $text += "`r`n"

    if ($bodyPost.Length -gt 0) {$text += $bodyPost}
    return $text
}
                
ntwrkperf.ps1 - Orchestrateur + Retry
function Invoke-NetworkWeb
{
        [CmdletBinding()]
        Param(

            [Parameter(Mandatory=$true,
            HelpMessage="Hostname or IPAdresse")]
            [string]$serverName,

            [Parameter(Mandatory=$true,
            HelpMessage="port")]
            [int]$port,

            [Parameter(Mandatory=$false,
            HelpMessage="proxy")]
            [string]$proxy = $null,

            [Parameter(Mandatory=$true,
            HelpMessage="text")]
            [string]$request,

            [Parameter(Mandatory=$false,
            HelpMessage="ssl")]
            [bool]$ssl = $false,

            [Parameter(Mandatory=$false,
            HelpMessage="stateObject")]
            [Hashtable]$stateObject,

            [Parameter(Mandatory=$false,
            HelpMessage="retry")]
            [int]$retry = 0
        )


    [uri]$checkProxy = if($proxy.Length -gt 0) {decodeProxy $proxy}

    [bool]$close = $false
    if($request.Contains("Connection: close"))
    {
        $close = $true
    }

    $stateObject.serverName = $serverName
    $stateObject.port = $port
    $stateObject.method = findMethod $request
    $stateObject.uri = findUri $request
    $stateObject.urivariable = findUriVariable $request
    $stateObject.variable = findvariable $request
    $stateObject.retry = $retry

    $stateObject.startDateHours = get-date -Format "dd/MM/yyyy HH:00:00"
    $stateObject.startDateFiveMinutes = dateFiveMinute
    $stateObject.startDateMinutes = get-date -Format "dd/MM/yyyy HH:mm:00"
    $stateObject.startDateSeconds = get-date -Format "dd/MM/yyyy HH:mm:ss"
    $stateObject.startDate = get-date -Format "dd/MM/yyyy HH:mm:ss.fff"

    try
    {
        if (!$stateObject.socket.Connected)
        {
            if($checkProxy.IsAbsoluteUri)
            {
                $stateObject = connectSyn $checkProxy.Authority $checkProxy.Port $stateObject
            }
            else
            {
                $stateObject = connectSyn $serverName $port $stateObject
                if($stateObject.port -eq "443" -or $ssl)
                {
                    $stateObject = sslAuthenticate $serverName $stateObject
                }
            }
        }
        if($stateObject.socket.Connected)
        {
            $stateObject = if($stateObject.port -eq "443" -or $ssl){HTTPS $request $stateObject}
                else{HTTP $request $stateObject}

            $stateObject = closeSocket $close $stateObject   
        }
    }
    catch
    {
        New-Log($_)
        $stateObject.error = 1
        $stateObject.endDate=get-date -Format "dd/MM/yyyy HH:mm:ss.fff"
        if($retry -eq 3)
        {
            return $stateObject
        }
        else
        {
            Start-Sleep -Seconds 3
            $retry = $retry + 1
            $stateObject = closeSocket $close $stateObject
            Write-Host "[Retry]" -NoNewline -ForegroundColor Gray ; Write-Host " Nombre de retry : $($retry)"
            $stateObject = Invoke-NetworkWeb -serverName $serverName -port $port -request $request -stateObject $stateObject -retry $retry
        }
        return $stateObject
    }
    $stateObject.endDate=get-date -Format "dd/MM/yyyy HH:mm:ss.fff"
    return $stateObject
}
                
ntwrkperf.ps1 - Réimplementation protocol HTTP
function Invoke-RequestWeb
{
        [CmdletBinding()]
        Param(

            [Parameter(Mandatory=$true,
            HelpMessage="Hostname or IPAdresse")]
            [string]$serverName,

            [Parameter(Mandatory=$true,
            HelpMessage="uri")]
            [string]$uri,

            [Parameter(Mandatory=$false,
            HelpMessage="fulluri")]
            [string]$fulluri,

            [Parameter(Mandatory=$false,
            HelpMessage="method")]
            [string]$method = "GET",

            [Parameter(Mandatory=$false,
            HelpMessage="cookie")]
            [string]$cookie = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="keep-alive")]
            [bool]$keepalive = $false,

            [Parameter(Mandatory=$false,
            HelpMessage="referer")]
            [string]$referer,

            [Parameter(Mandatory=$false,
            HelpMessage="bodyPost")]
            [string]$bodyPost = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="bodyPost")]
            [int]$contentlength = 0,

            [Parameter(Mandatory=$false,
            HelpMessage="accept")]
            [string]$accept = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="accept")]
            [string]$useragent = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="contenttype")]
            [string]$contenttype = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="secfetch")]
            [string]$secfetch = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="maxage")]
            [string]$maxage = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="upgradesecure")]
            [string]$upgradesecure = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="authobasic")]
            [string]$authobasic = $null,

            [Parameter(Mandatory=$false,
            HelpMessage="xmlRequest")]
            [bool]$xmlRequest=$false,

            [Parameter(Mandatory=$false,
            HelpMessage="proxy")]
            [bool]$proxy,

            [Parameter(Mandatory=$false,
            HelpMessage="user")]
            [string]$user,

            [Parameter(Mandatory=$false,
            HelpMessage="password")]
            [string]$password,

            [Parameter(Mandatory=$false,
            HelpMessage="domaine")]
            [string]$domaine,

            [Parameter(Mandatory=$false,
            HelpMessage="authNTLM")]
            [bool]$authNTLM = $false,

            [Parameter(Mandatory=$false,
            HelpMessage="authNTLMResponse")]
            [string]$authNTLMResponse = $null
        )

    $text = ("$method $uri HTTP/1.1`r`n" +
        "Host: $serverName`r`n")
    if ($keepalive) {$text += "Connection: keep-alive`r`n"}
    else {$text += "Connection: close`r`n"}
    if ($accept.Length -gt 0) {$text += "Accept: $accept`r`n"}
    else {$text += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8`r`n"}
    if ($useragent.Length -gt 0){$text += "User-Agent: $useragent`r`n"}
    else {$text += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0`r`n"}
    if ($bodyPost.Length -gt 0){$text += "Content-Length: $($bodyPost.Length)`r`n"}
    if($contentlength -gt 0) {$text += "Content-Length: $contentlength`r`n"}
    if ($xmlRequest){$text += "X-Requested-With: XMLHttpRequest`r`n"}
    if($contenttype) {$text += "Content-Type: application/x-www-form-urlencoded`r`n"}

    if($authobasic.Length -gt 0){$text += "Authorization: Basic $authobasic`r`n"}
    $text += "Accept-Encoding: br`r`n"
    $text += "Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3`r`n"

    if ($cookie.Length -gt 0) {$text += "$cookie`r`n"}
    
    if($maxage){$text += "Cache-Control: max-age=0`r`n"}
    if($secfetch){$text += "Sec-Fetch-Dest: document`r`nSec-Fetch-Mode: navigate`r`nSec-Fetch-Site: none`r`n"}
    if($upgradesecure){$text += "Upgrade-Insecure-Requests: 1`r`n"}

    if($authoNTLM)
    {
        $obj = [System.Net.Ntlm]::new($user, $password, $domaine)
        if(!$authNTLMexecute)
        {
            $data = $obj.CreateNegotiateMessage()
            $text += "Authorization: $data`r`n"
        }
        else
        {
            $data = $obj.ProcessChallenge($authNTLMResponse)
            $text += "Authorization: $data`r`n"
        }
    }
    $text += "`r`n"

    if ($bodyPost.Length -gt 0) {$text += $bodyPost}
    return $text
}
                
ntwrkperf.ps1 - Cookie
function replaceCookie($serverName, $cookieContainer, $updcookie)
{
    foreach($cookie in $cookieContainer)
    {
        if ($cookie.name -eq $updcookie.name -and $serverName -eq $cookie.servername)
        {
            $cookie.value = $updcookie.value
            break
        }
    }
    return $cookieContainer
}

function existeCookie($serverName, $cookieContainer, $udpcookie)
{
    foreach($cookie in $cookieContainer)
    {
        if ($cookie.name -eq $udpcookie.name -and $serverName -eq $cookie.servername)
        {
            return 1
        }
    }    
    return 0
}

function saveCookie($serverName, $responseFromServer, [array]$cookieContainer)
{
    $tmp = $responseFromServer -split "`r`n`r`n"
    $headers = $tmp[0] -split "`r`n"

    foreach($header in $headers)
    {
        if ($header -match "Set-Cookie")
        {
            $cookieString = $header -replace "Set-Cookie: ", ""
            $cookieTmp = $cookieString -split ';'

            $cookie = @{}
            $cookie.servername = $serverName
            $cookie.name = $cookieTmp[0].Substring(0, $cookieTmp[0].IndexOf('=')).Trim()
            $cookie.value = $cookieTmp[0].Substring($cookieTmp[0].IndexOf('=') + 1, ($cookieTmp[0].Length - $cookieTmp[0].IndexOf('=') -1)).Trim()
            if ($cookie.value -eq "") {$cookie.value = $null}
            if ($cookieContainer.Count -eq 0)
            {
                $cookieContainer += $cookie
            }
            else
            {
                $check = existeCookie $serverName $cookieContainer $cookie
                if (!$check) {$cookieContainer += $cookie}
                else {$cookieContainer = replaceCookie $serverName $cookieContainer $cookie}
            }
        }
    }
    return $cookieContainer
}

function createCookie($serverName, $cookieContainer, $liste)
{
    $newCookieString = "Cookie: "
    foreach($object in $liste)
    {
        foreach($cookie in $cookieContainer)
        {
            if ($cookie.name -match $object -and $serverName -eq $cookie.servername)
            {
                $newCookieString = "{0}{1}={2}; " -f $newCookieString, $cookie.name, $cookie.value
            }
        }
    }
    $newCookieString = $newCookieString.Substring(0, $newCookieString.Length -2)
    return $newCookieString
}
                
ntwrkperf.ps1 - Initialisation objet $stateObject
function initStateObject()
{

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false,
        HelpMessage="stateObject")]
        [Hashtable]$oldStateObject
    )

    [Hashtable]$stateObject = @{}

    if ($oldStateObject.Count -gt 0)
    {
        $stateObject.socket = $oldStateObject.socket
        $stateObject.sslStream = $oldStateObject.sslStream
    }
    else
    {
        [System.Net.Sockets.TcpClient]$stateObject.socket = $null
        [System.Net.Security.SslStream]$stateObject.sslStream = $null
    }

    [int]$stateObject.index = -1
    [int]$stateObject.port = $null
    [int]$stateObject.connectTime = $null
    [int]$stateObject.sslTime = $null
    [int]$stateObject.firstByte = $null
    [int]$stateObject.lastByte = $null
    [int]$stateObject.pagesize = $null
    [int]$stateObject.contentLength = 0
    [int]$stateObject.downloadLength = 0
    [int]$stateObject.debitMoyen = $null
    [int]$stateObject.responseCode = $null
    [int]$stateObject.total = $null
    [int]$stateObject.check = 0
    [int]$stateObject.error = 0
    [int]$stateObject.sapperf = 0
    [int]$stateObject.retry = 0

    $stateObject.startDate = "null"
    $stateObject.startDateHours = "null"
    $stateObject.startDateFiveMinutes = "null"
    $stateObject.startDateMinutes = "null"
    $stateObject.startDateSeconds = "null"
    $stateObject.endDate = "null"
    $stateObject.message = "null"
    $stateObject.method = "null"
    $stateObject.responseFromServer = "null"
    $stateObject.serverName = "null"
    $stateObject.name = $global:name
    $stateObject.uri = "null"
    $stateObject.urivariable = "null"
    $stateObject.variable = "null"
    $stateObject.ntlmChallenge = "null"
    $stateObject.log = "null"

    return [Hashtable]$stateObject
}
                
ntwrkperf.ps1 - Exemple suivi scénario
function Network()
{
    $mail = htmlencode $SAP.mail
    $password = convertToSecureString $SAP.password
    $responseTime = @()
    $cookieContainer = @()
    $tokenRand = $null
    $serverName = "le_serveur_sap.com"
    [int]$requestCount = 1

    $XSRF = $null
    #### Première requête, /
    $stateObject = initStateObject
    $requestConn = Invoke-RequestWeb -serverName $serverName -uri "/qs1_120" -method "GET"
    $stateObject = Invoke-NetworkWeb -serverName $serverName -port 443 -request $requestConn -stateObject $stateObject
	$stateObject.index = $requestCount++
	if ($stateObject.responseCode -eq 200)
	{
	    $cookieContainer = saveCookie $serverName $stateObject.responseFromServer $cookieContainer
        $XSRF = findXSRF $stateObject.responseFromServer
        $stateObject.check = 1
	}
	$stateObject.responseFromServer = encodeBase64 $stateObject.responseFromServer
	$responseTime += $stateObject


    $location = $null
    #### deuxième requête ### /Authentification
    $dataPost = "sap-system-login-oninputprocessing=onLogin&sap-urlscheme=&sap-system-login=onLogin&sap-system-login-basic_auth=&sap-client=120&sap-accessibility=&sap-login-XSRF=$XSRF&sap-system-login-cookie_disabled&sap-hash=&sap-user=$($mail)&sap-password=$($password)&sap-language=FR"
    $stateObject = initStateObject
    $liste = @("sap-")
	$cookieString = createCookie $serverName $cookieContainer $liste
    $requestAuth = Invoke-RequestWeb -contenttype $true -serverName $serverName -uri "/qs1_120" -method "POST" -cookie $cookieString -bodyPost $dataPost
    $stateObject = Invoke-NetworkWeb -serverName $serverName -port 443 -request $requestAuth -stateObject $stateObject
    $stateObject.index = $requestCount++
    if($stateObject.responseCode -eq 302)
    {
        $cookieContainer = saveCookie $serverName $stateObject.responseFromServer $cookieContainer
        $location = findLocation $stateObject.responseFromServer
        $stateObject.check = 1
    }
	$stateObject.responseFromServer = encodeBase64 $stateObject.responseFromServer
	$responseTime += $stateObject
}
                
ntwrkperf.ps1 - Main
function auto($polling, $exectime)
{
    $sleep = getStartTime
    $endExecution = getEndTime $execTime
    if($execTime -ne 0)
    {
        Write-Host "[INFO]" -NoNewline -ForegroundColor Yellow; Write-Host " Fin d'exécution du script à $endExecution"
    }

    Write-Host "[Sync]" -NoNewline -ForegroundColor Green; Write-Host " En cours de synchronisation..."
    Write-Host "            Temps restant $sleep secondes."
    Start-Sleep -Seconds $sleep
    while($true)
    {
        Write-Host ""
        $dateSystem = Get-Date
        if(HeuresOuvres $dateSystem)
        {
            Write-Host "[Depart]" -NoNewline -ForegroundColor Green; Write-Host " Date : $dateSystem"
            $responseTime = network
            $responseTime
        }
        $scnSleep = getStartTime
        Write-Host "[Info]" -NoNewline -ForegroundColor Yellow; Write-Host " SLEEP = $scnSleep secondes"
        Start-sleep -seconds $scnSleep
    }
}
                

04.Méthodologie & Impact

  • Récupération traces réseau + mesure bas-niveau via robot custom.
  • Définition d'un scénario de reproduction, reverse engineering des échanges HTTP, prise en compte de l'authentification et des requêtes multiples.
  • Visualisation Power BI : corrélation entre les temps réseau et les temps de traitement serveur (SapPerf2).
  • Mise en évidence factuelle : transit réseau < 50 ms vs traitement serveur > 150 s.
  • Reconnaissance par l'éditeur SAP d'un problème de performance lié aux requêtes SQL.
  • Impact : Réduction drastique du temps d'analyse d'incidents (jours → heures), justification pour correctif éditeur.
  • Outil : Script actuellement exploité sur d'autres projets.