function Invoke-Endpoint { <# .SYNOPSIS Invokes a website and returns the response of the URL .DESCRIPTION Wraps Invoke-WebRequest with retry logic .PARAMETER Uri URI of the website to invoke .PARAMETER Method HTTP method, Get, Post, etc. .PARAMETER Baseuri Optional parameter taking the base Uri and concatinating with Uri. "$Baseuri$Uri". Helper to avoid having to concatinate before invoking site. .PARAMETER retryStatusCodes [System.Net.HttpStatusCode][] objects that are eligible for retry logic. [System.Net.HttpStatusCode]::ServiceUnavailable is default. .PARAMETER RetryDelay Seconds to wait between retries. Defaults to 5. Is exponential backoff. .PARAMETER RetryCount Total number of retry attempts before failing. .PARAMETER Body Used for POST Method .PARAMETER Headers String of Http headers to pass to the request. .PARAMETER WebSession Shared session to use for subsequent requests .PARAMETER UseNewSession Bool to determine if a new blank session will be used and returned instead of the passed-in WebSession. .PARAMETER TimeoutSeconds Int in seconds for the web timeout. .EXAMPLE $r = Invoke-Endpoint -Uri http://google.com #> [cmdletbinding()] param( [Parameter(Mandatory, ValueFromPipeline)] $Uri, [Parameter(Mandatory = $false)] $Method = 'GET', [Parameter(Mandatory = $false)] $Baseuri = '', [Parameter(Mandatory=$false)] [System.Net.HttpStatusCode[]] $retryStatusCodes, [Parameter(Mandatory=$false)] $RetryDelay = 5, [Parameter(Mandatory=$false)] $RetryCount = 3, [Parameter(Mandatory=$false)] $Body = $null, [Parameter(Mandatory=$false)] $Headers = $null, [Parameter(Mandatory=$false)] $WebSession = $null, [Parameter(Mandatory=$false)] [bool]$UseNewSession = $false, [Parameter(Mandatory=$false)] [int]$TimeoutSeconds = 120 ) begin { $auldProgressPreference = $ProgressPreference $ProgressPreference = 'silentlycontinue' } process { $logLead = (Get-LogLeadName) $logFilePath = (Get-WebTestLogPath -BankUrl "$Baseuri$Uri") $retryCounter = 0 $totalRuntime = 0 $errorsArray = @() $runtimeArray = @() $result = $null if($null -eq $retryStatusCodes) { $retryStatusCodes = @( [System.Net.HttpStatusCode]::BadGateway, [System.Net.HttpStatusCode]::BadRequest, [System.Net.HttpStatusCode]::GatewayTimeout, [System.Net.HttpStatusCode]::InternalServerError, [System.Net.HttpStatusCode]::ServiceUnavailable ) } do { $StopWatch = [System.Diagnostics.StopWatch]::StartNew() try { "$logLead : Performing web request to $($baseuri)$($uri)" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose if($UseNewSession) { $result = Invoke-WebRequest "$Baseuri$Uri" -Method $Method -Body $Body -Headers $Headers -SessionVariable NewWebSession -UseBasicParsing -TimeoutSec $TimeoutSeconds -DisableKeepAlive -UserAgent "SRE Web Test" $WebSession = $NewWebSession } else { $result = Invoke-WebRequest "$Baseuri$Uri" -Method $Method -Body $Body -Headers $Headers -WebSession $WebSession -UseBasicParsing -TimeoutSec $TimeoutSeconds -DisableKeepAlive -UserAgent "SRE Web Test" } # On success, stop executing break } catch [System.Net.WebException] { "$logLead : Exception: $_" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose $errorsArray += $_.Exception.Message if ($null -eq $_.Exception.Response -and $_.Exception.Message -eq "The operation has timed out.") { "$logLead : No response object found. Request timed out." | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose $retryCounter += 1 $hasRetriedEnough = $retryCounter -gt ($retryCount) if ($hasRetriedEnough) { "$logLead : Returning response object" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose break } else { $sleepTime = $retryDelay*$retryCounter "$logLead : Sleeping for $sleepTime seconds" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose Start-Sleep -Seconds $sleepTime $resp = $null } } else { $resp = $_.Exception.Response "$logLead : Response code: $($resp.StatusCode)" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose $retryCounter += 1 $isARetryCode = $retryStatusCodes -contains $resp.StatusCode "$logLead : Is retry code: $isARetryCode" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose $hasRetriedEnough = $retryCounter -gt ($retryCount) "$logLead : Has retried enough: $hasRetriedEnough" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose if ($hasRetriedEnough -or(-not($isARetryCode))) { "$logLead : Returning response object" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose break } else { $sleepTime = $retryDelay*$retryCounter "$logLead : Sleeping for $sleepTime seconds" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose Start-Sleep -Seconds $sleepTime } } } catch { $errorsArray += $_.Exception.Message "$logLead : Got a generic exception. Quitting immediately" | Tee-OutFile -Append -FilePath $logFilePath | Write-Verbose break } finally { $StopWatch.Stop() $totalRuntime += $StopWatch.ElapsedMilliseconds $runtimeArray += $StopWatch.Elapsed.ToString("ss\.fffffff") $isSuccess = $true if($result.StatusCode -ne 200){ if($null -eq $resp){ # If we got a timeout, send a null result Write-Verbose "$logLead : Request timed out." $result = $null } else { # If we got a failure response, map the most recent exception's data onto the result and give that back. Write-Verbose "$logLead : Got a $($resp.StatusCode)" $result = $resp } $isSuccess = $false } $responseObject = @{ ErrorResults = $errorsArray; LastError = $errorsArray[-1]; HasErrors = $errorsArray.Count -gt 0; Result = $result; Success = $isSuccess; StatusCode = $result.StatusCode; WebSession = $WebSession; Runtimes = $runtimeArray; LastRuntime = $runtimeArray[-1]; TotalRuntime = [System.TimeSpan]::FromMilliseconds($totalRuntime).ToString() } $result = $null } } while ($true) return $responseObject } end { $ProgressPreference = $auldProgressPreference } }