Results missing - queries always not complete

I have the problem that when I query Sophos Central Partner API (Powershell) across all customer tenants and firewalls, I don't get all the results returned.

It is not always the same tenants that are missing from the results, but different ones. However, the queries are not correct every time.

What am I doing wrong?
  • <#
    	.SYNOPSIS
    	    This Script is gets all registered Firewalls from Sophos Central via the API
    
    	.PARAMETER ClientID
    	    The Client-ID from Sophos-Central API-Management
    
    	.PARAMETER ClientSecret
    	    The Secret for the ClientID
    #>
    param (
    	[Parameter(Mandatory)]
    	[string] $ClientID,
    
    	[Parameter(Mandatory)]
    	[string] $ClientSecret
    )
    
    function Get-SophosAccessToken() {
    	param (
    		[Parameter(Mandatory)]
    		[string] $ClientID,
    
    		[Parameter(Mandatory)]
    		[string] $ClientSecret
    	)
    
    	$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    	$headers.Add("Content-Type", "application/x-www-form-urlencoded")
    	$body = -join("grant_type=client_credentials&scope=token&client_id=", $ClientID, "&client_secret=", $ClientSecret)
    	$response = Invoke-RestMethod 'https://id.sophos.com/api/v2/oauth2/token' -Method 'POST' -Headers $headers -Body $body
    
    	$response.access_token
    }
    
    function Get-SophosPartnerId() {
    	param (
    		[Parameter(Mandatory)]
    		[string] $AccessToken
    	)
    
    	$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    	$headers.Add("Authorization", (-join("Bearer ", $AccessToken)))
    	$response = Invoke-RestMethod 'https://api.central.sophos.com/whoami/v1' -Method 'GET' -Headers $headers
    
    	$response.id
    }
    
    function List-SophosTenants() {
    	param (
    		[Parameter(Mandatory)]
    		[string] $PartnerID,
    
    		[Parameter(Mandatory)]
    		[string] $AccessToken,
    
    		[int] $PageNumber = 1
    	)
    
    	$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    	$headers.Add("X-Partner-ID", $PartnerID)
    	$headers.Add("Authorization", (-join("Bearer ", $AccessToken)))
    	$headers.Add("Accept", "application/json")
    	$response = Invoke-RestMethod (-join("https://api.central.sophos.com/partner/v1/tenants?page=", $PageNumber, "&pageSize=100&pageTotal=true")) -Method 'GET' -Headers $headers
    
    	# Save all tenant IDs in a list/map with the name of the tenant
    	$tenants = @{}
    	foreach ($tenant in $response.items) {
    		$tenants.add( $tenant.id, [pscustomobject]@{
    			Id = $tenant.id
    			Name = $tenant.name
    			APIDataRegion = $tenant.dataRegion
    		})
    	}
    
    	$page = $response.pages.current
    	if ($page -lt $response.pages.total) {
    		$nextPage = $page + 1
    		$results = List-SophosTenants -PartnerID $PartnerID -AccessToken $AccessToken -PageNumber $nextPage
    		foreach ($key in $results.Keys) {
    			$tenants.add($key, $results[$key])
    		}
    	}
    
    	$tenants
    }
    
    function Get-SophosFirewalls() {
    	param (
    		[Parameter(Mandatory)]
    		[string] $TenantID,
    		[string] $TenantName,
    
    		[string] $DataRegion = "eu2",
    
    		[Parameter(Mandatory)]
    		[string] $AccessToken
    	)
    
    	$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    	$headers.Add("X-Tenant-ID", $TenantID)
    	$headers.Add("Authorization", (-join("Bearer ", $AccessToken)))
    	$headers.Add("Accept", "application/json")
    	try {
    		$response = Invoke-RestMethod (-join("https://api-", $DataRegion, ".central.sophos.com/firewall/v1/firewalls")) -Method 'GET' -Headers $headers
    	} catch {
    		Write-Host (-join("**** Error on Tenant: ", $TenantName, " (", $TenantID, ") in Data-Region ", $DataRegion, " ****`n"))
    		return
    	}
    
    	$firewalls = @{}
    	foreach ($firewall in $response.items) {
    		$firewalls[$firewall.id] = @([pscustomobject]@{
    			Id = $firewall.id
    			Name = $firewall.name
    			HostName = $firewall.hostname
    			Version = $firewall.firmwareVersion
    			Model = $firewall.model
    			PublicIpv4 = $firewall.externalIpv4Addresses[0]
    		})
    	}
    
    	$firewalls
    }
    
    $AccessToken = Get-SophosAccessToken -ClientID $ClientID -ClientSecret $ClientSecret
    $PartnerID = Get-SophosPartnerId -AccessToken $AccessToken
    $Tenants = List-SophosTenants -PartnerID $PartnerID -AccessToken $AccessToken
    
    foreach ($Tenant in $Tenants.Values) {
        $Firewalls = Get-SophosFirewalls -TenantID $Tenant.Id -AccessToken $AccessToken -DataRegion $Tenant.APIDataRegion -TenantName $Tenant.Name
    
        if ($Firewalls.Values.count -gt 0) {
            $date = Get-Date -Format "dd.MM.yyyy"
            $fileName = "FirewallInformationen_$date.txt"
    
            $output = "Tenant: $($Tenant.Name)`r`n"
            $output += "Id: $($Tenant.Id)`r`n"
            foreach ($Firewall in $Firewalls.Values) {
                $output += "`tId: $($Firewall.Id)`r`n"
                $output += "`tName: $($Firewall.Name)`r`n"
                $output += "`tHostname: $($Firewall.HostName)`r`n"
                $output += "`tVersion: $($Firewall.Version)`r`n"
                $output += "`tModel: $($Firewall.Model)`r`n"
                $output += "`tIpv4: $($Firewall.PublicIpv4)`r`n"
                $output += "`r`n"
            }
            $output | Out-File -FilePath $fileName -Append
        }
    
    }
    pause



    cose
    [edited by: Yannik Lehmann at 1:53 PM (GMT -7) on 12 Sep 2023]