Powershell Rate Limiting


When working with different API’s via PowerShell, you may encounter the need to have a rate limit and as such I’ve built two functions to do this.

Code

Function New-RateLimitWaitTime
{
	[cmdletbinding()]
	param (
		[System.TimeSpan]$MinimumDelay = [System.TimeSpan]::FromSeconds(1),
		[System.TimeSpan]$TimeFrame = [System.TimeSpan]::FromHours(1),
		[int]$RatePerTimeFrame = 1000
	)
	
	BEGIN { }
	
	PROCESS
	{
		$props = @{
			'MinimumDelay'  = $MinimumDelay;
			'TimeFrame'	    = $TimeFrame;
			'AvailableTime' = $TimeFrame.TotalMilliseconds;
			'DefaultCost'   = $TimeFrame.TotalMilliseconds / $RatePerTimeFrame;
			'Wait'		    = $MinimumDelay;
			'FirstUse'	    = [DateTime]::Now;
			'LastUse'	    = [DateTime]::Now;
			'RatePerTimeFrame' = $RatePerTimeFrame
		}
		
		$obj = New-Object -TypeName PSObject -Property $props
		$obj.PSObject.TypeNames.Insert(0, 'Custom.RateLimit')
		
	}
	END
	{
		Write-Output $obj
	}
}

Function Get-RateLimitWaitTime
{
	[cmdletbinding()]
	param (
		[parameter(mandatory = $true)]
		[PSObject]$RateLimit
	)
	
	BEGIN
	{
		
	}
	
	PROCESS
	{
		
		$TimeCost = $RateLimit.DefaultCost
		$RateLimit.Wait = $RateLimit.MinimumDelay
		
		$Compare = [System.TimeSpan]::FromTicks($RateLimit.FirstUse.AddMilliseconds($RateLimit.TimeFrame.TotalMilliseconds).Ticks - (get-date).Ticks)
		
		Write-Verbose "`$Compare = $($Compare)"
		
		if ($Compare.TotalMilliseconds -lt 0)
		{
			Write-Verbose "Time elasped setting AvailableTime to $($RateLimit.TimeFrame.TotalMilliseconds)"
			$RateLimit.AvailableTime = $RateLimit.TimeFrame.TotalMilliseconds
			$RateLimit.FirstUse = [DateTime]::Now
		}
		
		if (($RateLimit.AvailableTime - $TimeCost) -lt 0)
		{
			$RateLimit.Wait = [System.TimeSpan]::FromTicks($RateLimit.FirstUse.AddMilliseconds($RateLimit.TimeFrame.TotalMilliseconds).Ticks - (get-date).Ticks)
			$TimeCost = 0
		}
		
		$RateLimit.LastUse = [DateTime]::Now
		$RateLimit.AvailableTime -= $TimeCost
		
	}
	
	END
	{
		
		
		Write-Verbose "WaitTime = $($RateLimit.Wait), Using MS = $($TimeCost), MsToUseInQueue = $($RateLimit.AvailableTime), TimeFrame = $($RateLimit.TimeFrame), MaxRatePerTimeFrame = $($RateLimit.RatePerTimeFrame), TimeFrame LastUsed = $($RateLimit.LastUse)"
		Write-Output $RateLimit
		
	}
}

Usage

#creating a new rate limit object, this instance we are creating will allow 100 requests per minute with a limit of 1500 per hour as such the minimum delay is 600 ms
$RateLimit = New-RateLimitWaitTime -RatePerTimeFrame 1500 -TimeFrame ([System.TimeSpan]::FromMinutes(60)) -MinimumDelay ([System.TimeSpan]::FromMilliseconds(600))

for ($i = 1; $i -le 10; $i++) {
    $RateLimit = Get-RateLimitWaitTime($RateLimit)
    Start-Sleep -Milliseconds $RateLimit.Wait.TotalMilliseconds
    Write-Host "$($i)"
}