Private/Invoke-RestMethodWithProgress.ps1
# Start with a guess at how long this API call will take $script:DefaultResponseTimeSeconds = 10 $script:EndpointResponseTimeSeconds = @{} $script:SupportedHosts = @("ConsoleHost") function Reset-APIEstimatedResponseTimes { $script:DefaultResponseTimeSeconds = 10 $script:EndpointResponseTimeSeconds = @{} } function Get-APIEstimatedResponseTime { param ( [string] $Method, [string] $Uri ) $endpointResponseTimeKey = $Method + $Uri $estimatedResponseTime = $script:EndpointResponseTimeSeconds[$endpointResponseTimeKey] if($null -eq $estimatedResponseTime -or $estimatedResponseTime -lt $script:DefaultResponseTimeSeconds) { $estimatedResponseTime = $script:DefaultResponseTimeSeconds } return $estimatedResponseTime } function Set-APIResponseTime { param ( [string] $Method, [string] $Uri, [int] $ResponseTimeSeconds ) $endpointResponseTimeKey = $Method + $Uri $script:EndpointResponseTimeSeconds[$endpointResponseTimeKey] = $ResponseTimeSeconds } function Test-HostSupportsRestMethodWithProgress { # Check if the current host meets all the requirements to be able to send the restmethod to the background if($script:SupportedHosts -notcontains (Get-Host).Name) { return $false } $currentLocation = Get-Location if($currentLocation.Provider.Name -ne "FileSystem") { return $false } if($null -ne [System.Net.WebRequest]::DefaultWebProxy.Address -or $null -ne $env:HTTP_PROXY) { return $false } return $true } function Invoke-RestMethodWithProgress { param ( [hashtable] $Params, $ProgressActivity = "Thinking..." ) # Some hosts can't support background jobs. It's best to opt-in to this feature by using a list of supported hosts if(-not (Test-HostSupportsRestMethodWithProgress)) { return Invoke-RestMethod @Params } $estimatedResponseTime = Get-APIEstimatedResponseTime -Method $Params["Method"] -Uri $Params["Uri"] try { try { [Console]::CursorVisible = $false } catch [System.IO.IOException] { <# unit tests don't have a console #> } Push-Location -StackName "RestMethodWithProgress" $currentLocation = Get-Location if($currentLocation.Path -ne $currentLocation.ProviderPath) { Set-Location $currentLocation.ProviderPath } $job = Start-Job { $restParameters = $using:Params $response = Invoke-RestMethod @restParameters return @{ Response = $response } } $start = Get-Date while($job.State -eq "Running") { $percent = ((Get-Date) - $start).TotalSeconds / $estimatedResponseTime * 100 # Slow the progress towards the end of the progress bar because the api is a bit all over the show for response times, this makes sure the bar doesn't fill up linearly $logPercent = [int][math]::Min([math]::Max(1, $percent * [math]::Log(1.5)), 100) $status = "$logPercent% Completed" if($logPercent -eq 100) { $status = "API is taking longer than expected" } Write-Progress -Id 1 -Activity $ProgressActivity -Status $status -PercentComplete $logPercent Start-Sleep -Milliseconds 50 } Write-Progress -Id 1 -Activity $ProgressActivity -Completed # If Invoke-RestMethod failed in the job rethrow this up to the caller so it's like a normal web error if($job.State -eq "Failed") { throw $job.ChildJobs[0].JobStateInfo.Reason } Set-APIResponseTime -Method $Params["Method"] -Uri $Params["Uri"] -ResponseTimeSeconds ((Get-Date) - $start).TotalSeconds return (Receive-Job $job).Response } catch { throw $_ } finally { Pop-Location -StackName "RestMethodWithProgress" -ErrorAction "SilentlyContinue" if($null -ne $job) { Stop-Job $job -ErrorAction "SilentlyContinue" Remove-Job $job -Force -ErrorAction "SilentlyContinue" } try { [Console]::CursorVisible = $true } catch [System.IO.IOException] { <# unit tests don't have a console #> } } } |