Engineering PowerShell Raw Sockets Performance SAP

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

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 récurrentes sur des transactions SAP (recherche produits, validation commandes) impactaient des milliers d'utilisateurs quotidiens. Les équipes suspectaient le réseau/Wi-Fi, mais les métriques bas-niveau montraient des temps de transit < 50 ms.

Objectif : isoler la vraie cause racine sans accès direct au backend SAP.

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.