PowerShell + REST API: Invoke-RestMethod gotcha
A recent support case saw the customer complaining that one of the PowerShell scripts making multiple REST API calls was failing as below error was encountered:
Invoke-RestMethod : The operation has timed out
We tried checking the server API logs and saw that the connections made from the script were not even reaching the server. We fired up Fiddler to see what was happening behind the scenes but noticed that running the Fiddler in background mitigated the issue but the calls were still failing from the script using the Invoke-RestMethod in PowerShell v4.
A web search found this is a known issue for PUT and DELETE methods in a REST API call.
The customer was facing this issue in the GET method calls to the REST API. Strange!
Found workaround in one of the off-topic forums as this was suspected to be a server side issue. Credits goes to a user named LanceC1. As per the workaround, this is a sort of brute force as this will close all the connections to a hostname.
We have to instantiate an object of ServicePoint Class (this object is used for managing HTTP connections).
Store the ServicePoint Object for the hostname you are connecting to like below:
$servicePoint = [System.Net.ServicePointManager]::FindServicePoint("https://www.microsoft.com") |
Now try exploring the object:
PS> $ServicePoint BindIPEndPointDelegate : ConnectionLeaseTimeout : -1 Address : https://www.microsoft.com/ MaxIdleTime : 100000 UseNagleAlgorithm : True ReceiveBufferSize : -1 Expect100Continue : True IdleSince : 2/8/2015 8:10:46 AM ProtocolVersion : 1.1 ConnectionName : https ConnectionLimit : 2 <--------------------------- Simultaneous connections limit CurrentConnections : 0 Certificate : ClientCertificate : SupportsPipelining : True |
Also notice that the connection limit property can be set:
PS> $ServicePoint | Get-Member TypeName: System.Net.ServicePoint Name MemberType Definition ---- ---------- ---------- CloseConnectionGroup Method bool CloseConnectionGroup(string connectionGroupName) Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() SetTcpKeepAlive Method void SetTcpKeepAlive(bool enabled, int keepAliveTime, int keepAliveInterval) ToString Method string ToString() Address Property uri Address {get;} BindIPEndPointDelegate Property System.Net.BindIPEndPoint BindIPEndPointDelegate {get;set;} Certificate Property System.Security.Cryptography.X509Certificates.X509Certificate Certificate {get;} ClientCertificate Property System.Security.Cryptography.X509Certificates.X509Certificate ClientCertificate {get;} ConnectionLeaseTimeout Property int ConnectionLeaseTimeout {get;set;} ConnectionLimit Property int ConnectionLimit {get;set;} <-------------- This property can be set ConnectionName Property string ConnectionName {get;} CurrentConnections Property int CurrentConnections {get;} Expect100Continue Property bool Expect100Continue {get;set;} IdleSince Property datetime IdleSince {get;} MaxIdleTime Property int MaxIdleTime {get;set;} ProtocolVersion Property version ProtocolVersion {get;} ReceiveBufferSize Property int ReceiveBufferSize {get;set;} SupportsPipelining Property bool SupportsPipelining {get;} UseNagleAlgorithm Property bool UseNagleAlgorithm {get;set;} |
Now changing the Connectionlimit is one of the workaround as already stated in the Connect Bug that Invoke-RestMethod does a poor job of cleaning up the connections it made. We assume that Fiddler increases this limit and also cleans up the stale connections, that's why running Fiddler in background to debug won't be useful.
$ServicePoint = [System.Net.ServicePointManager]::FindServicePoint('<REST API URL>') $ServicePoint.ConnectionLimit = 4 #Change this value...though not advised (against HTTP 1.1 specs) #Make the REST API Call using Invoke-RestMethod |
Another way is to close the Connection Group. This will close all the connections made to the host.
We went with this way as our REST API Server URL has a different name then the Web Server endpoint.
$ServicePoint = [System.Net.ServicePointManager]::FindServicePoint('<REST API URL>') #Make the REST API Call using Invoke-RestMethod $ServicePoint.CloseConnectionGroup("") |
Please do update this wiki if you find other gotchas /workarounds and insights into the problem.