Om.Azure.Management.psm1

function ConvertFrom-OmResourceID {
    param (
        [Parameter(Mandatory = $true)]$ResourceID,
        [switch]$GetSubscriptionName
    )
    process {
        $ResourceIDSplit = [regex]::split($ResourceID, '\/|\\')
        $Subscription = $null
        $ManagementGroup = $null
        $ResourceGroup = $null
        $Resource = $null
        $returnObject = [PSCustomObject]@{
            ResourceID = $ResourceID
            ScopeLevel = $null
        }
        for ($i = 0; $i -lt $ResourceIDSplit.Count; $i++) {
            switch ($ResourceIDSplit[$i]) {
                "ManagementGroups" {
                    $ManagementGroup = $ResourceIDSplit[$i + 1]
                    $returnObject | Add-Member -MemberType NoteProperty -Name ManagementGroup -Value $ManagementGroup
                    $returnObject.scopelevel = "ManagementGroup"
                }
                "Subscriptions" {
                    $Subscription = $ResourceIDSplit[$i + 1]
                    $returnObject | Add-Member -MemberType NoteProperty -Name Subscription -Value $Subscription
                    if ($GetSubscriptionName -and ($null -ne $Subscription)) {
                        $SubscriptionName = (Get-AzSubscription -SubscriptionId $Subscription).Name
                        $returnObject | Add-Member -MemberType NoteProperty -Name SubscriptionName -Value $SubscriptionName
                    }
                    $returnObject.scopelevel = "Subscription"
                }   
                "ResourceGroups" {
                    $ResourceGroup = $ResourceIDSplit[$i + 1]
                    $returnObject | Add-Member -MemberType NoteProperty -Name ResourceGroup -Value $ResourceGroup
                    $returnObject.scopelevel = "ResourceGroup"
                    if ($null -ne $ResourceIDSplit[$i + 2]) {
                        $Resource = $ResourceIDSplit[-1]
                        $returnObject | Add-Member -MemberType NoteProperty -Name Resource -Value $Resource
                        $returnObject.scopelevel = "Resource"
                    }
                }    
            }
        }
        return $returnObject
    }
}

function ConvertTo-OmResourceID {
    param (
        [Parameter(ParameterSetName = 'ManagementGroup', Mandatory = $true)]
        [Alias('ManagementGroupName')]
        $ManagementGroup,
        [Parameter(ParameterSetName = 'Subscription', Mandatory = $true)]
        [Parameter(ParameterSetName = 'ResourceGroup', Mandatory = $true)]
        [Parameter(ParameterSetName = 'Resource', Mandatory = $true)]
        [Alias('Subscription')]
        $SubscriptionId,
        [Parameter(ParameterSetName = 'ResourceGroup', Mandatory = $true)]
        [Parameter(ParameterSetName = 'Resource', Mandatory = $true)]
        [Alias('ResourceGroup')]
        $ResourceGroupName,
        [Parameter(ParameterSetName = 'Resource', Mandatory = $true)]
        $Provider,
        [Parameter(ParameterSetName = 'Resource', Mandatory = $true)]
        [Alias('Resource')]
        $ResoureName

    )
    switch ($PSCmdlet.ParameterSetName) {
        "ManagementGroup" { return "/providers/Microsoft.Management/managementGroups/$managementGroup" }
        "Subscription" { return "/subscriptions/$subscriptionId" }
        "ResourceGroup" { return "/subscriptions/$subscriptionId/resourcegroups/$resourcegroupname" }
        "Resource" { return"/subscriptions/$subscriptionId/resourcegroups/$resourcegroupname/providers/$provider/$resourceName" }
    }
}

function Get-OmAADObject {
    param(
        [parameter(Mandatory = $true)]$ObjectID
    )
    # First check if the id refers to an user
    $AADObject = Get-AzADUser -ObjectId $ObjectID -ErrorAction Stop
    if ($null -eq $AADObject) {
        # If not: check if the id refers to a Service Principal
        $AADObject = Get-AzADServicePrincipal -ObjectId $ObjectID -ErrorAction Stop
        if ($null -eq $AADObject) {
            # If not: check if the id refers to a Group
            $AADObject = Get-AzADGroup -ObjectId $ObjectID -ErrorAction Stop
        }
    }
    return $AADObject
}

function Get-OmAADMGraphGroup {
    param(
        [Parameter(ParameterSetName = 'object', Mandatory = $true)]
        [Alias('object')]
        $objectID,
        [Parameter(ParameterSetName = 'name', Mandatory = $true)]
        [Alias('name')]
        $displayName,
        [Parameter(ParameterSetName = 'nameStartsWith', Mandatory = $true)]
        [Alias('nameStartsWith')]
        $displayNameStartsWith,
        [Parameter(Mandatory = $false)]$mgmtGroupsApiVersion = 'v1.0',
        [Parameter(Mandatory = $false)]$headers = (Get-OmAzureMGraphAuthHeader)
    )

    switch ($PSCmdlet.ParameterSetName) {
        "object" {
            $graphUri = "https://graph.microsoft.com/$mgmtGroupsApiVersion/groups/$ObjectID"
            try {
                $response = Invoke-WebRequest -Headers $headers -Uri $graphUri -UseBasicParsing -Method "get" -ContentType "application/json"
            }
            catch {
                $response = Invoke-WebRequest -Headers $headers -Uri $graphUri -UseBasicParsing -Method "get" -ContentType "application/json"
            }
            $AADGroup = $response.Content | convertfrom-json        
            return $AADGroup

        }
        "name" {
            $filter = "`$filter=displayName eq '$DisplayName'"
        }
        "nameStartsWith" {
            $filter = "`$filter=startswith(displayName,'$displayNameStartsWith')"
        }
    }
    $graphUri = "https://graph.microsoft.com/$mgmtGroupsApiVersion/groups?$($filter)"
    $AADObjectResults = [System.Collections.ArrayList]@()
    while ($graphUri) {
        $objectList = $null
        try {
            $objectList = Invoke-RestMethod -Headers $headers -Uri $graphUri -UseBasicParsing -Method "get" -ContentType "application/json"
        }
        catch {
            $objectList = Invoke-RestMethod -Headers $headers -Uri $graphUri -UseBasicParsing -Method "get" -ContentType "application/json"            
        }
        $graphUri = $($objectList.'@odata.nextLink')
        if($null -ne $objectList.value -and $($objectList.value).count -gt 1){
            $AADObjectResults.AddRange($objectList.value)
        } elseif($null -ne $objectList.value) {
            [void]$AADObjectResults.Add($objectList.value) 
        }
        
    }
    return $AADObjectResults
}

function Expand-OmAADGroup {
    param (
        [parameter(Mandatory = $true)]$ObjectID,
        [switch]$recurse
    )
    $AADObjectList = New-Object System.Collections.Generic.List[System.Object]
    $groupName = (Get-azADGroup -objectid $objectID).DisplayName
    $group = Get-AzADGroupmember -objectid $ObjectID
    foreach ($member in $group) {
        $AADObject = get-OmAADObject -ObjectID $member.Id
        $AADObject | Add-Member -NotePropertyName "MemberOf" -NotePropertyValue $groupName
        [void]$AADObjectList.add($AADObject)
        if ($recurse -and $member.objecttype -eq "group") {
            $recurseList = expand-OmAADGroup -ObjectId $member.id -recurse
            foreach ($RecurseObject in $recurseList) {
                [void]$AADObjectList.add($RecurseObject)
            }
        }
    }
    return $AADObjectList
}

function Get-OmParentManagementGroup {
    param(
        [parameter(Mandatory = $true)]
        [Alias('SubscriptionID', 'Subscription', 'ManagementGroup', 'ManagementGroupID', 'ManagementGroupName')]$ID,
        [switch]$onlyName
    )
    $managementgroups = Get-AzManagementGroup
    foreach ($managementgroup in $managementgroups) {
        $managementgroupExpanded = Get-AzManagementGroup -Expand -Groupid $managementgroup.name
        foreach ($child in $managementgroupExpanded.Children) {
            if ($child.name -eq $ID) {
                if ($onlyName) {
                    return $managementgroup.name
                }
                return $managementgroupExpanded
            }
        }
    }
    throw "Could not find parent of $ID "
}

function Get-OmParentResourceID {
    param(
        [parameter(Mandatory = $true)]$ResourceID
    )
    $scopeObject = ConvertFrom-OmResourceID $ResourceID

    switch ($scopeObject.scopelevel) {
        "ManagementGroup" { return (get-OmParentManagementGroup -ManagementGroupName $scopeObject.managementGroup).id }
        "Subscription" { return (get-OmParentManagementGroup -SubscriptionID $scopeObject.subscription).id } 
        "ResourceGroup" { return ConvertTo-OmResourceID -SubscriptionID $scopeObject.subscription }
        "Resource" { return ConvertTo-OmResourceID -SubscriptionID $scopeObject.subscription -ResourceGroupName $scopeObject.resourcegroup }
        Default { Throw "Didn't find scopelevel for scope: $scope" }
    }
}

function Get-OmSubscriptionsFromMG {
    param(
        [parameter(Mandatory = $true)][Alias("Parents", "ManagementGroup", "ManagementGroups")]$Parent
    )
    $mgs = [System.Collections.ArrayList]@()
    $subscriptions = [System.Collections.ArrayList]@()
    $parents = [System.Collections.ArrayList]@()
    if ($parent.count -eq 1) {
        $parents.Add($parent) | Out-Null
    }
    else {
        $parents.AddRange($parent) | Out-Null
    }
    try {
        $mgNames = (get-azManagementgroup -ErrorAction stop).Name
    }
    catch {
        write-host "Retrying get-azManagementgroup"
        $mgNames = (get-azManagementgroup -ErrorAction stop).Name
    }
    foreach ($mgName in $mgNames) {
        try {
            $null = $mgs.add($(get-azManagementGroup -GroupId $mgName -Expand -ErrorAction stop))
        }
        catch {
            write-host "Retrying get-azManagementgroup for $mgName"
            $null = $mgs.add($(get-azManagementGroup $mgName -Expand -ErrorAction stop))
        }
    }
    for ($i = 0; $i -lt $parents.Count; $i++) {
        $parent = $parents[$i]
        write-host $parent
        $parentMg = $mgs | Where-Object name -eq $parent
        [Array]$mgsubs = $ParentMg.Children | where-object Type -like "*subscription*"
        foreach ($sub in $mgSubs) {
            $sub | Add-Member -NotePropertyName "ManagementGroup" -NotePropertyValue $parent
            $null = $subscriptions.add($sub)
        }
        foreach ($mg in $mgs | where-object ParentName -eq $parent) {
            $parents.add($mg.name) | Out-Null
        }
    }
    return $subscriptions
}

function Get-OmRoleDefinitions {
    param (
        [Parameter(Mandatory = $true)]$scope,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader)
    )

    $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleDefinitions?api-version=2015-07-01"

    $roleDefinitionResults = [System.Collections.ArrayList]@()
    do {
        try {
            $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"
        }
        catch {
            Write-Host "Retrying getting roleDefinitions"
            Start-Sleep 30
            $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"
        }
        $roleDefinitionResultsChunk = ($response.Content | convertfrom-json).value
        if($null -ne $roleDefinitionResultsChunk -and $roleDefinitionResultsChunk.count -gt 1){
            $roleDefinitionResults.AddRange($roleDefinitionResultsChunk)
        } elseif($null -ne $roleDefinitionResultsChunk) {
            [void]$roleDefinitionResults.Add($roleDefinitionResultsChunk) 
        }
        $restUri = ($response.Content | convertfrom-json).nextLink
    } while ($restUri)
    return $roleDefinitionResults
}

function Get-OmMgDecendants {
    param(
        [Parameter(Mandatory = $true)]$managementGroup,
        [Parameter(Mandatory = $false)]$mgmtGroupsApiVersion = '2018-03-01-preview',
        [Parameter(Mandatory = $false)]$headers = (Get-OmAzureRMAuthHeader)
    )
    $uri = "https://management.azure.com/providers/Microsoft.Management/managementGroups/$managementGroup/descendants?api-version=$mgmtGroupsApiVersion"
    $method = 'GET'
    $mgmtGroupChildren =[System.Collections.ArrayList]@()
    do{
        try {
            $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method $method
        }
        catch {
            $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method $method
        }
        if($null -ne $response.value -and $response.value.count -gt 1){
            $mgmtGroupChildren.AddRange($response.value)
        } elseif($null -ne $response.value) {
            [void]$mgmtGroupChildren.Add($response.value)
        }
        $uri = $response.nextLink
    } while ($uri)
    return $mgmtGroupChildren 
}

function Get-OmMgSubscriptionDecendants {
    param(
        [Parameter(Mandatory = $true)]$managementGroup,
        [Parameter(Mandatory = $false)]$mgmtGroupsApiVersion = '2018-03-01-preview',
        [Parameter(Mandatory = $false)]$headers = (Get-OmAzureRMAuthHeader),
        [switch]$onlyEnabled
        
    )
    $subscriptionDecendants = Get-OmMgDecendants -managementGroup $managementGroup -mgmtGroupsApiVersion $mgmtGroupsApiVersion -headers $headers | where-object  {$_.type -eq "/subscriptions"}
    if (($onlyEnabled) -and ($subscriptionDecendants.count -gt 0)) {
        try {
            $azureSubscriptions = Get-AzSubscription -ErrorAction Stop
        }
        catch {
            $azureSubscriptions = Get-AzSubscription -ErrorAction Stop
        }
        $azureSubscriptions = $azureSubscriptions | Where-Object {$_.State -eq 'Enabled'}
        $subscriptionDecendants = $subscriptionDecendants | Where-Object {$_.name -in $azureSubscriptions.id }
    }
    return $subscriptionDecendants
}

function Get-OmMgManagementGroupDecendants {
    param(
        [Parameter(Mandatory = $true)]$managementGroup,
        [Parameter(Mandatory = $false)]$mgmtGroupsApiVersion = '2018-03-01-preview',
        [Parameter(Mandatory = $false)]$headers = (Get-OmAzureRMAuthHeader)
    )
    return Get-OmMgDecendants -managementGroup $managementGroup -mgmtGroupsApiVersion $mgmtGroupsApiVersion -headers $headers | where-object  {$_.type -eq "/providers/Microsoft.Management/managementGroups"}
}

function Get-OmAzureRMAuthHeader {
    $azContext = Get-AzContext
    $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
    $profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($azProfile)
    $token = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId)
    $authHeader = @{
        'Content-Type'  = 'application/json'
        'Authorization' = 'Bearer ' + $token.AccessToken
    }
    return $authHeader 
}

function Get-OmAzureMGraphAuthHeader {
    param(
        [Parameter(ParameterSetName = 'context', Mandatory = $false)]
        [Alias('context')]
        $azContext = (Get-AzContext -ErrorAction Stop),
        [Parameter(ParameterSetName = 'client', Mandatory = $true)]$clientId,
        [Parameter(ParameterSetName = 'client', Mandatory = $true)]$clientSecret,
        [Parameter(ParameterSetName = 'client', Mandatory = $true)]$tenantId

    )
    switch ($PSCmdlet.ParameterSetName) {
        "client" {
            $Scope = "https://graph.microsoft.com/.default"
            $UrlToken = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
            $bodyToken = "grant_type=client_credentials&" +
            "client_id=" + $clientId + "&" +
            "scope=$Scope" + "&" +
            "client_secret=" + $clientSecret + "&"
            try {
                $response = Invoke-RestMethod -Body $bodyToken -Uri $urlToken -Method 'Post' -ContentType 'application/x-www-form-urlencoded'
            }
            catch {
                $response = Invoke-RestMethod -Body $bodyToken -Uri $urlToken -Method 'Post' -ContentType 'application/x-www-form-urlencoded'
            }
            $access_token = $response.access_token
            $headers = @{
                'Content-Type'  = 'application/json'
                'Authorization' = 'Bearer ' + $access_token
            }
            return $headers
        }
        "context" {
            $context = $azContext
            $graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.microsoft.com").AccessToken
            $headers = @{
                'Content-Type'  = 'application/json'
                'Authorization' = 'Bearer ' + $graphToken
            }
            return $headers
        }
        default {
            Throw "Failed. ParameterSetName not found"
        }
    }
}

### Privileged Identity Management - Graph API

function Get-OmPrivilegedAzureResource {
    param (
        [Parameter(Mandatory = $true)]$externalId,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureMGraphAuthHeader)
    )
    $uri = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/resources?`$filter=externalId+eq+'$externalId'"
    try {
        $pimResource = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"   
    }
    catch {
        $pimResource = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"  
    }
    if($pimResource.value.count -gt 1) {
        return "Error, retrieved more than 1 resource:$($Results.value.Length)"
    }    
    return $pimResource.value[0]
}

function Get-OmPrivilegedAzureResourcesRoleDefinitions {
    param (
        [Parameter(Mandatory = $true)]$resourceId,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureMGraphAuthHeader)
    )
    $uri = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/resources/$($resourceId)/roleDefinitions"

    $roleDefResults = [System.Collections.ArrayList]@()
    while ($uri) {
        $roleDefChunk = $null
        try {
            $roleDefChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"
        }
        catch {
            $roleDefChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"       
        }
        $uri = $($roleDefChunk.'@odata.nextLink')
        if($null -ne $roleDefChunk.value -and $($roleDefChunk.value).count -gt 1){
            $roleDefResults.AddRange($roleDefChunk.value)
        } elseif($null -ne $roleDefChunk.value) {
            [void]$roleDefResults.Add($roleDefChunk.value) 
        }
    }
    return $roleDefResults
}

function Get-OmPrivilegedAzureResourcesroleAssignments {
    param (
        [Parameter(Mandatory = $true)]$resourceId,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureMGraphAuthHeader)
    )
    $uri = "https://graph.microsoft.com/beta/privilegedAccess/azureResources/resources/$($resourceId)/roleAssignments"

    $roleAssignResults = [System.Collections.ArrayList]@()
    while ($uri) {
        $roleAssignChunk = $null
        try {
            $roleAssignChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"
        }
        catch {
            $roleAssignChunk = Invoke-RestMethod -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"   
        }
        $uri = $($roleAssignChunk.'@odata.nextLink')
        if($null -ne $roleAssignChunk.value -and $($roleAssignChunk.value).count -gt 1){
            $roleAssignResults.AddRange($roleAssignChunk.value)
        }elseif($null -ne $roleAssignChunk.value) {
            [void]$roleAssignResults.Add($roleAssignChunk.value) 
        }
    }
    return $roleAssignResults
}


### Privileged Identity Management - Management API

function Get-OmPrivilegedRoleAssignments {
    param (
        [Parameter(Mandatory = $true)]$scope,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader),
        [switch]$atScope
    )
    #$uriFilter = "principalId eq '$principalId'"
    if ($atScope) {
        $uriFilter = "atScope()" 
    }
    $uri = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?`$filter=$($uriFilter)&api-version=2020-10-01-preview"

    $roleAssignResults = [System.Collections.ArrayList]@()
    do {
        try {
            $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"
        }
        catch {
            $response = Invoke-WebRequest -Headers $headers -Uri $uri -UseBasicParsing -Method "get" -ContentType "application/json"
        }
        $roleAssignResultsChunk = ($response.Content | convertfrom-json).value
        if($null -ne $roleAssignResultsChunk -and $roleAssignResultsChunk.count -gt 1){
            $roleAssignResults.AddRange($roleAssignResultsChunk)
        } elseif($null -ne $roleAssignResultsChunk) {
            [void]$roleAssignResults.Add($roleAssignResultsChunk) 
        }
        $restUri = ($response.Content | convertfrom-json).nextLink
    } while ($restUri)

    return $roleAssignResults
}

function New-OmPrivilegedRoleAssignment {
    param (
        [Parameter(Mandatory = $true)]$scope,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader),
        [Parameter(Mandatory = $true)]$startDateTime,
        [Parameter(Mandatory = $true)]$requestType,
        [Parameter(Mandatory = $true)]$principalId,
        [Parameter(Mandatory = $true)]$endDateTime,
        [Parameter(Mandatory = $true)]$roleDefinitionId,
        [Parameter(Mandatory = $false)]$justification
    )

    $startDateTime = "{0:yyyy-MM-ddTHH:mm:ss.ffZ}" -f ($startDateTime).ToUniversalTime()
    $endDateTime = "{0:yyyy-MM-ddTHH:mm:ss.ffZ}" -f ($endDateTime).ToUniversalTime()
    $body   = @"
    {
        "properties": {
          "justification": "$justification",
          "principalId": "$principalId",
          "roleDefinitionId": "$roleDefinitionId",
          "requestType": "$requestType",
          "scheduleInfo": {
            "startDateTime": "$startDateTime",
            "expiration": {
              "type": "AfterDateTime",
              "endDateTime": "$endDateTime"
            }
          },
          "condition": "",
          "conditionVersion": ""
        }
      }
"@

    $roleEligibilityScheduleRequestName = $((New-Guid).guid)
    $uri = "https://management.azure.com/$($scope)/providers/Microsoft.Authorization/roleEligibilityScheduleRequests/$($roleEligibilityScheduleRequestName)?api-version=2020-10-01-preview"
    try {
        $response = Invoke-WebRequest -Method PUT -Uri $uri -header $headers -Body $body
    }
    catch {
        Write-Host "Retrying creating role assignment"
        Start-Sleep 30
        $response = Invoke-WebRequest -Method PUT -Uri $uri -header $headers -Body $body
    }
    if ($response.StatusCode -eq 201) {
        Write-Output "Code 201: Roleassignment Created"
    } else {
        Write-Output "Code $($response.StatusCode): Roleassignment was not created"
    }
}

function Search-OmResourceGraph {
    param (
        [Parameter(Mandatory = $true)][Alias('query')]$queries,
        [Parameter(Mandatory = $true)][Alias('Subscription')]$Subscriptions,
        [Parameter(Mandatory = $false)]$throttleLimit = 100,
        [Parameter(Mandatory = $false)][Alias("header")]$headers = (Get-OmAzureRMAuthHeader)
    )

    $subscriptionGroups = [System.Collections.ArrayList]@()
    if ($Subscriptions.count -gt 1) {
        $numOfGroups = [int][Math]::Ceiling($Subscriptions.count / $throttleLimit)
        for ($i = 0; $i -lt $numOfGroups; $i++) {
            $start = $i * $throttleLimit
            $end = [Math]::Clamp(($i + 1) * $throttleLimit - 1, 0, $Subscriptions.count - 1)
            $subscriptionGroups.add($Subscriptions[$start..$end]) | Out-Null
        }
    }
    else {
        $subscriptionGroups.Add(@($Subscriptions)) | Out-Null
    }
    $queries | ForEach-Object {
        $query = $_
        foreach ($subscriptionGroup in $subscriptionGroups) {
            $response = $null
            $Subscription = $SubscriptionGroup -join ""","""
            $body = @"
    {
        "subscriptions": [ "$Subscription"],
        "query": '$query',
        "options": {
            "ResultFormat": "objectArray"
        }
    }
"@

            $restUri = "https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2019-04-01"
            do {
                $response = $null
                try {
                    $response = Invoke-webrequest -Uri $restUri -Method Post -Body $body -Headers $headers
                }
                catch {
                    $response = Invoke-webrequest -Uri $restUri -Method Post -Body $body -Headers $headers
                }
            
                $responseData = $null
                $responseData = $response.Content | convertfrom-json -Depth 100
                $responseData.data
                if (($responseData.resultTruncated -eq $true)) {
                    Write-Warning "Query result for subscription $Subscription is truncated."
                }
                $body = @"
    {
        "subscriptions": [ "$Subscription"],
        "query": '$query',
        "options": {
            "`$skipToken": "$($responseData.'$skipToken')",
            "ResultFormat": "objectArray"
        }
    }
"@

                if ($null -ne $response.Headers) {
                    # Need to wait if query quota is 0
                    $remainingQuota = $response.Headers["x-ms-user-quota-remaining"]
                    if ($remainingQuota -eq 0) {
                        $resetAfter = ([System.TimeSpan]::Parse($response.Headers["x-ms-user-quota-resets-after"])).TotalSeconds
                        $resetAfter++
                        Write-Host "Hitting Resource Graph quota limit. Waiting $resetAfter seconds"
                        Start-Sleep $resetAfter
                    }
                }
            } While ($responseData.'$skipToken')
            Start-Sleep 3
        } 
    }
}