HttpDiag.ps1


<#PSScriptInfo
 
.VERSION 1.0
 
.GUID f13a3f6d-1994-4d5a-bb1e-54093542dbe2
 
.AUTHOR Will Aftring
 
.COMPANYNAME
 
.COPYRIGHT 2022
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI https://github.com/WAftring/HttpDiag
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
.PRIVATEDATA
 
#>


<#
 
    .SYNOPSIS
    Display http.sys relevent data or start http.sys specific ETW tracing. Run with no parameters displays configuration information.
    .DESCRIPTION
    A diagnostic script for the http.sys Windows component
    .PARAMETER StartTrace
    Starts a http.sys ETW tracing session
    .PARAMETER StopTrace
    Stops a http.sys ETW tracing session
    .PARAMETER InteractiveTrace
    Starts an interactive http.sys ETW tracing session
    .PARAMETER LogDir
    Output path for any data collection
 
#>

#Requires -runasadministrator
[CmdletBinding()]
param(
  [Parameter(ParameterSetName = "Start")]
  [switch]$StartTrace,
  [Parameter(ParameterSetName = "Stop")]
  [switch]$StopTrace,
  [Parameter(ParameterSetName = "Interactive")]
  [switch]$InteractiveTrace,
  [string]$LogDir = "C:\HttpDiag"
)

#region GLOBALS

$Script:LogPath = $LogDir
$Script:Version = "1.0"
$Script:EventLogs = @(
  "Microsoft-Windows-HttpService/Log!HttpService.evtx"
  "Microsoft-Windows-HttpService/Trace!HttpService-Trace.evtx"
  "Microsoft-Windows-CAPI2/Operational!CAPI2.evtx"
)

#endregion


# This is just a wrapper function
function Get-HttpConfig {
  # Component information
  Write-Host "Service Information:"
  Get-Service Http | Format-List | Out-String | Write-Host

  Write-Host "Component Versions:"
    (Get-Item "C:\Windows\System32\httpapi.dll", "C:\Windows\System32\drivers\http.sys").VersionInfo | Select-Object -Property FileName, ProductVersion, FileVersion | Out-String | Write-Host

  Get-HttpRequestQueues
  Get-HttpReservedUrls

  Write-Host "Registry Information:"
  Get-Item HKLM:\SYSTEM\CurrentControlSet\Services\HTTP, HKLM:\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\ | Out-String | Write-Host

}
function Get-HttpRequestQueues {
  Write-Debug "Enter Get-HttpConfig"
  $HttpConfigList = [System.Collections.ArrayList]::New()

  $RawRequestQueues = Invoke-Expression -Command 'netsh http show servicestate view="req"'
  $RequestQueueLines = $RawRequestQueues | Select-String "^Request queue name"

  $RequestQueueLines | ForEach-Object {
    $offset = 2
    $HttpObj = [PSCustomObject]@{
      Processes      = $null
      Active         = $false
      NumProcesses   = 0
      NumUrls        = 0
      RegisteredUrls = $null
    }

    $LineNumber = $_.LineNumber - 1
    $HttpObj.Active = $RawRequestQueues[$LineNumber + $offset].Trim() -eq "State: Active"
    $offset += 3
    $HttpObj.NumProcesses = $RawRequestQueues[$LineNumber + $offset].Trim().Split(" ")[-1]
    $offset += 2
    $Processes = ""
    for ($i = 0; $i -lt $HttpObj.NumProcesses; $i++) {
      $ID = $RawRequestQueues[$LineNumber + $offset + $i].Trim().Split(" ")[1].Replace(",", "")
      $Proc = Get-Process -Id $ID
      $Processes += ("$($Proc.Name)($($Proc.Id)) ")
    }

    $HttpObj.Processes = $Processes
    $offset += $i + 9
    $HttpObj.NumUrls = $RawRequestQueues[$LineNumber + $offset].Trim().Split(" ")[-1]
    $offset += 2
    $HttpObj.RegisteredUrls = [System.Collections.ArrayList]::New()
    for ($j = 0; $j -lt $HttpObj.NumUrls; $j++) {
      # Silencing the add result
      [void]$HttpObj.RegisteredUrls.Add($RawRequestQueues[$LineNumber + $offset + $j].Trim())
    }

    # Silencing the Add result
    [void]$HttpConfigList.Add($HttpObj)
  }

  Write-Host "Configured Request Queues:"
  $HttpConfigList | Sort-Object -Property NumUrls, NumProcesses -Descending | Format-Table -AutoSize -Wrap | Out-String | Write-Host
  Write-Debug "Exit Get-HttpConfig"
}

function Get-HttpReservedUrls {
  Write-Debug "Enter Get-HttpReservedUrls"
  $ReservedUrlsList = [System.Collections.ArrayList]::New()
  $RawReservedUrls = Invoke-Expression -Command "netsh http show urlacl"
  $ReservedUrls = $RawReservedUrls | Select-String "Reserved URL"
  $ReservedUrls | ForEach-Object {
    $UrlObj = [PSCustomObject]@{
      User = "Unknown"
      Url  = ""
    }
    $LineNumber = $_.LineNumber - 1

    $UrlObj.Url = $RawReservedUrls[$LineNumber].Trim().Split(" ")[-1]
    if ($RawReservedUrls[$LineNumber + 1] -like "*User*") {
      $UrlObj.User = $RawReservedUrls[$LineNumber + 1].Trim().Split(" ")[-1]
    }
    [void]$ReservedUrlsList.Add($UrlObj)
  }
  Write-Host "Reserved Urls"
  $ReservedUrlsList | Format-Table -AutoSize -Wrap | Out-String | Write-Host
  Write-Debug "Exit Get-HttpReservedUrls"
}

function Invoke-HttpTrace {
  param(
    [switch]$Start,
    [switch]$Stop
  )
  Write-Debug "Enter Invoke-HttpTrace Start: $Start Stop: $Stop"
  $Pktmon = Get-Command pktmon -ErrorAction SilentlyContinue
  if ($Start) {
    Invoke-Expression -Command "netsh trace start overwrite=yes capture=yes scenario=InternetServer_dbg maxsize=4096 persistent=yes report=disable tracefile=$Script:LogPath\HttpTrace.etl"
    if ($Pktmon -ne $null) {
      Invoke-Expression -Command "pktmon start --capture -f $Script:LogPath\Pktmon.etl -s 4096"
    }
    Enable-HttpLogs
  }
  elseif ($Stop) {
    Invoke-Expression -Command "netsh trace stop"
    if ($Pktmon -ne $null) {
      Invoke-Expression -Command "pktmon stop"
      Invoke-Expression -Command "pktmon list -a > $Script:LogPath\Pktmon-list.txt"
      Get-HttpTraceData
    }
  }
  Write-Debug "Exit Invoke-HttpTrace"
}

function Enable-HttpLogs {
  Write-Debug "Enter Enable-HttpLogs"
  foreach ($EventLog in $Script:EventLogs) {
    $Params = $EventLog.Split("!")
    $LogName = $Params[0]
    Invoke-Expression -Command "wevtutil sl `"$LogName`" /enabled:true /quiet:true"
  }
  Write-Debug "Exit Enable-HttpLogs"
}
function Get-HttpTraceData {
  Write-Debug "Enter Get-HttpTraceData"
  Invoke-Expression -Command "ipconfig /all > $Script:LogPath\ipconfig.txt"
  Invoke-Expression -Command "systeminfo > $Script:LogPath\systeminfo.txt"
  foreach ($EventLog in $Script:EventLogs) {
    $Params = $EventLog.Split("!")
    $LogName = $Params[0]
    $OutFile = $Params[1]
    Invoke-Expression -Command "wevtutil epl `"$LogName`" `"$Script:LogPath\$OutFile`" /overwrite:true"
  }
  Write-Debug "Exit Get-HttpTraceData"
}

#region main

if (-not (Test-Path $Script:LogPath)) {
  New-Item $Script:LogPath -ItemType Directory | Out-Null
}

if ($InteractiveTrace -or $StopTrace) { Start-Transcript -Path "$Script:LogPath\Transcript.log" | Out-Null }

Write-Host "HttpDiag vers: $Script:Version`n"

if ($StartTrace -or $InteractiveTrace) { Invoke-HttpTrace -Start }
if ($InteractiveTrace) {
  Read-Host -Prompt "Press enter to stop the capture"
}
if ($StopTrace -or $InteractiveTrace) { Invoke-HttpTrace -Stop }

if (-not $StartTrace) { Get-HttpConfig }

if ($InteractiveTrace -or $Stop) { Stop-Transcript | Out-Null }

#endregion