Public/iis/Get-IISCurrentRequest.ps1
|
#Requires -Version 5.1 function Get-IISCurrentRequest { <# .SYNOPSIS Lists HTTP requests currently executing in IIS (typed equivalent of `appcmd list requests`). .DESCRIPTION Enumerates every request actively being processed by IIS worker processes on one or more target servers, joining each entry with the owning application pool, the served site, the absolute URL, HTTP verb, client IP, elapsed time and pipeline state. Provides real-time visibility on stuck or long-running requests -- diagnostic data that the IISAdministration module does not expose. Implementation parses `appcmd.exe list requests /xml` inside a remoting-aware scriptblock dispatched through Invoke-RemoteOrLocal. .PARAMETER ComputerName One or more computer names to query. Defaults to the local machine. Accepts pipeline input by value and by property name. .PARAMETER Credential Optional PSCredential for authenticating to remote computers. Not used for local queries. .PARAMETER AppPoolName Restrict the result set to requests served by one or more named application pools. Wildcards accepted via -like. .PARAMETER SiteName Restrict the result set to requests targeting one or more named IIS sites. Wildcards accepted via -like. .PARAMETER MinElapsedMs Return only requests whose TimeElapsedMs is greater than or equal to this threshold. Handy to surface stuck or long-running requests. .EXAMPLE Get-IISCurrentRequest Returns all in-flight HTTP requests on the local IIS instance. .EXAMPLE Get-IISCurrentRequest -ComputerName 'WEB01' Returns in-flight requests from a single remote server. .EXAMPLE 'WEB01','WEB02' | Get-IISCurrentRequest -Credential (Get-Credential) Queries multiple remote servers via pipeline with alternate credentials. .EXAMPLE Get-IISCurrentRequest -MinElapsedMs 5000 | Sort-Object TimeElapsedMs -Descending Surfaces stuck requests running for more than 5 seconds, sorted by elapsed time. .EXAMPLE Get-IISCurrentRequest -SiteName 'www.contoso.com' -AppPoolName 'API*' Filters in-flight requests to a specific site and application pool pattern. .OUTPUTS PSCustomObject (PSTypeName='PSWinOps.IISCurrentRequest') .NOTES Author: Franck SALLET Version: 1.0.0 Last Modified: 2026-05-15 Requires: PowerShell 5.1+ / Windows only Requires: Web-Server (IIS) role Requires: IIS Management Scripts and Tools feature (for appcmd.exe) .LINK https://github.com/k9fr4n/PSWinOps .LINK https://learn.microsoft.com/en-us/iis/get-started/getting-started-with-iis/getting-started-with-appcmdexe#list-the-currently-executing-requests #> [CmdletBinding()] [OutputType('PSWinOps.IISCurrentRequest')] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('CN', 'Name', 'DNSHostName')] [string[]]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter(Mandatory = $false)] [string[]]$AppPoolName, [Parameter(Mandatory = $false)] [string[]]$SiteName, [Parameter(Mandatory = $false)] [int]$MinElapsedMs ) begin { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting" $scriptBlock = { param( [string[]]$FilterAppPool, [string[]]$FilterSite, [int]$FilterMinElapsedMs ) $results = [System.Collections.Generic.List[hashtable]]::new() $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' # -- 1. Verify IIS (W3SVC) presence try { $null = Get-Service -Name 'W3SVC' -ErrorAction Stop } catch { $results.Add(@{ ProcessId = $null AppPoolName = $null SiteName = $null Url = $null Verb = $null ClientIPAddress = $null TimeElapsed = $null TimeElapsedMs = $null PipelineState = $null Status = 'IISNotInstalled' ErrorMessage = "W3SVC service not found: $($_.Exception.Message)" Timestamp = $ts }) return $results } # -- 2. Verify appcmd.exe presence $appcmdExe = Join-Path -Path $env:windir -ChildPath 'system32\inetsrv\appcmd.exe' if (-not (Test-Path -LiteralPath $appcmdExe -PathType Leaf)) { $results.Add(@{ ProcessId = $null AppPoolName = $null SiteName = $null Url = $null Verb = $null ClientIPAddress = $null TimeElapsed = $null TimeElapsedMs = $null PipelineState = $null Status = 'AppcmdMissing' ErrorMessage = "appcmd.exe not found at '$appcmdExe'. Install the IIS Management Scripts and Tools feature." Timestamp = $ts }) return $results } # -- 3. Query in-flight requests via appcmd /xml $rawXml = $null try { $rawXml = & $appcmdExe list requests /xml 2>$null } catch { $results.Add(@{ ProcessId = $null AppPoolName = $null SiteName = $null Url = $null Verb = $null ClientIPAddress = $null TimeElapsed = $null TimeElapsedMs = $null PipelineState = $null Status = 'Failed' ErrorMessage = "appcmd.exe execution failed: $($_.Exception.Message)" Timestamp = $ts }) return $results } if ([string]::IsNullOrWhiteSpace($rawXml)) { $results.Add(@{ ProcessId = $null AppPoolName = $null SiteName = $null Url = $null Verb = $null ClientIPAddress = $null TimeElapsed = $null TimeElapsedMs = $null PipelineState = $null Status = 'NoRequests' ErrorMessage = $null Timestamp = $ts }) return $results } # -- 4. Parse XML try { [xml]$doc = $rawXml } catch { $results.Add(@{ ProcessId = $null AppPoolName = $null SiteName = $null Url = $null Verb = $null ClientIPAddress = $null TimeElapsed = $null TimeElapsedMs = $null PipelineState = $null Status = 'Failed' ErrorMessage = "Failed to parse appcmd XML output: $($_.Exception.Message)" Timestamp = $ts }) return $results } $requestNodes = @($doc.appcmd.REQUEST) if ($requestNodes.Count -eq 0 -or ($requestNodes.Count -eq 1 -and $null -eq $requestNodes[0])) { $results.Add(@{ ProcessId = $null AppPoolName = $null SiteName = $null Url = $null Verb = $null ClientIPAddress = $null TimeElapsed = $null TimeElapsedMs = $null PipelineState = $null Status = 'NoRequests' ErrorMessage = $null Timestamp = $ts }) return $results } # -- 5. Valid pipeline state values $validStates = [System.Collections.Generic.HashSet[string]] @( 'BeginRequest', 'AuthenticateRequest', 'AuthorizeRequest', 'ResolveRequestCache', 'MapRequestHandler', 'AcquireRequestState', 'PreExecuteRequestHandler', 'ExecuteRequestHandler', 'ReleaseRequestState', 'UpdateRequestCache', 'LogRequest', 'EndRequest', 'SendResponse' ) # -- 6. Build result rows $matchedAny = $false foreach ($req in $requestNodes) { if ($null -eq $req) { continue } $poolName = $req.'APPPOOL.NAME' $site = $req.'SITE.NAME' $elapsedMs = [long]$req.'TIME.ELAPSED' $pipeRaw = $req.'PIPELINE_STATE' $pipelineState = if ($validStates.Contains($pipeRaw)) { $pipeRaw } else { 'Unknown' } # Caller-side filtering if ($FilterAppPool -and $FilterAppPool.Count -gt 0) { $poolMatch = $false foreach ($pat in $FilterAppPool) { if ($poolName -like $pat) { $poolMatch = $true; break } } if (-not $poolMatch) { continue } } if ($FilterSite -and $FilterSite.Count -gt 0) { $siteMatch = $false foreach ($pat in $FilterSite) { if ($site -like $pat) { $siteMatch = $true; break } } if (-not $siteMatch) { continue } } if ($FilterMinElapsedMs -gt 0 -and $elapsedMs -lt $FilterMinElapsedMs) { continue } $matchedAny = $true $results.Add(@{ ProcessId = if ($req.'WORKER_PROCESS.PID') { [int]$req.'WORKER_PROCESS.PID' } else { $null } AppPoolName = $poolName SiteName = $site Url = $req.URL Verb = $req.VERB ClientIPAddress = $req.CLIENTIP TimeElapsed = [System.TimeSpan]::FromMilliseconds($elapsedMs) TimeElapsedMs = $elapsedMs PipelineState = $pipelineState Status = 'InFlight' ErrorMessage = $null Timestamp = $ts }) } if (-not $matchedAny) { $results.Add(@{ ProcessId = $null AppPoolName = $null SiteName = $null Url = $null Verb = $null ClientIPAddress = $null TimeElapsed = $null TimeElapsedMs = $null PipelineState = $null Status = 'NoRequests' ErrorMessage = $null Timestamp = $ts }) } return $results } } process { foreach ($cn in $ComputerName) { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying '$cn'" try { $invokeParams = @{ ComputerName = $cn ScriptBlock = $scriptBlock ArgumentList = @($AppPoolName, $SiteName, $MinElapsedMs) } if ($PSBoundParameters.ContainsKey('Credential')) { $invokeParams['Credential'] = $Credential } $rawResults = Invoke-RemoteOrLocal @invokeParams } catch { [PSCustomObject]@{ PSTypeName = 'PSWinOps.IISCurrentRequest' ComputerName = $cn ProcessId = $null AppPoolName = $null SiteName = $null Url = $null Verb = $null ClientIPAddress = $null TimeElapsed = $null TimeElapsedMs = $null PipelineState = $null Status = 'Failed' ErrorMessage = $_.Exception.Message Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' } continue } foreach ($row in $rawResults) { [PSCustomObject]@{ PSTypeName = 'PSWinOps.IISCurrentRequest' ComputerName = $cn ProcessId = $row.ProcessId AppPoolName = $row.AppPoolName SiteName = $row.SiteName Url = $row.Url Verb = $row.Verb ClientIPAddress = $row.ClientIPAddress TimeElapsed = $row.TimeElapsed TimeElapsedMs = $row.TimeElapsedMs PipelineState = $row.PipelineState Status = $row.Status ErrorMessage = $row.ErrorMessage Timestamp = $row.Timestamp } } } } end { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Done" } } |