Public/Invoke-EasyGraphRequest.ps1

function Invoke-EasyGraphRequest {
<#
.SYNOPSIS
    Invokes a query to Microsoft Graph
 
.DESCRIPTION
    Invokes a query to Microsoft Graph using the Access Token previously retrieved with Connect-EasyGraph.
    Returns the response from Microsoft Graph, with automatic paging and throttling handling.
 
.EXAMPLE
    # Lists first page of all user resources
    Invoke-EasyGraphRequest -Resource '/users'
 
.EXAMPLE
    # Lists all user resources
    Invoke-EasyGraphRequest -Resource '/users' -All
 
.EXAMPLE
    # Lists all user resources from the 'beta' endpoint
    Invoke-EasyGraphRequest -Resource '/users' -APIVersion beta -All
 
.EXAMPLE
    # Create a user
    $body = @{
        accountEnabled= $true
        displayName= 'displayName-value'
        mailNickname = 'mailNickname-value'
        userPrincipalName = 'upn-value@tenant-value.onmicrosoft.com'
        passwordProfile = @{
            forceChangePasswordNextSignIn = $true
            password = 'password-value'
        }
    }
    Invoke-EasyGraphRequest -Resource '/users' -Method Post -Body $body
 
.PARAMETER All
    Overrides the page size settings, and returns all matching results.
 
    This parameter is only valid for output to the pipeline, and cannot be used when the OutFile parameter is also used in the command. Consider increasing the page size with the $top parameter in the Request to return all results instead.
 
.PARAMETER APIVersion
    Specified the version of the Microsoft Graph API you are using. Default is 'v1.0'.
 
.PARAMETER Body
    Specifies the Body that will be sent in your request. Only required for Post, Patch and Put Methods.
 
.PARAMETER ContentType
    Specifies the content type of the request.
 
    If this parameter is omitted, "application/json" is used.
 
.PARAMETER Headers
    Specifies the headers of the web request.
 
.PARAMETER InFile
    Specifies a file with content that will be sent in the request body.
 
.PARAMETER Method
    Specifies the HTTP Method for the request. If no method is specified, a Get request will be sent
 
.PARAMETER OutFile
    Saves the response in the specified file. By default, Invoke-EasyGraphRequest returns the results to the pipeline. To send the results to a file and to the pipeline, use the Passthru parameter.
 
.PARAMETER PassThru
    Returns the results, in addition to writing them to a file. This parameter is valid only when the OutFile parameter is also used in the command.
 
.PARAMETER Resource
    Specifies the resource you want to access
 
.INPUTS
    None
 
.OUTPUTS
    The data that you requested or the result of the request. The response message can be empty for some requests.
#>

    Param
        (
            [Parameter(Mandatory=$true)]
            [string]$Resource,

            [ValidateSet('Get','Post','Patch','Put','Delete')]
            [string]$Method = 'Get',

            [ValidateSet('v1.0','beta')]
            [string]$APIVersion = 'v1.0',

            [object]$Body,
            [hashtable]$Headers,

            [string]$InFile,
            [string]$OutFile,
            [switch]$PassThru,
            [switch]$All,
            [string]$ContentType = 'application/json; charset=utf-8'
        )
    begin {

        if (($GraphConnection.Expires - [DateTime]::UtcNow).TotalSeconds -lt 300 -or -not $GraphConnection.AccessToken) {
            if ($GraphConnection.AccessToken) {
                Write-Verbose 'Renewing Access Token'
            }
            Get-EasyGraphAuthToken
        }

        if ($ContentType -like 'application/json*' -and $Body) {
            $Body = $Body | ConvertTo-Json
            $Body = [System.Text.Encoding]::UTF8.GetBytes($Body)
        }

        $Request = @{
            Headers = @{
                Authorization = "Bearer $($GraphConnection.AccessToken)"
            }
            Uri = "https://graph.microsoft.com/$APIVersion$Resource"
            ContentType = $ContentType
            Method = $Method
            Body = $Body
        }

        if ($Headers) {
            $Request.Headers += $Headers
        }

        if ($InFile) {
            $Request.InFile = $InFile
        }

        if ($OutFile -and -not $All) {
            $Request.OutFile = $OutFile
            $Request.PassThru = $PassThru
        }

    } process {

        do {
            try {
                $Response = Invoke-WebRequest @Request -UseBasicParsing -ErrorAction Stop
                $ResponseStatus = $Response.StatusCode

                switch -Regex ($Response.Headers.'Content-Type') {
                    'application/json' {
                        $ResponseContent = $Response.Content | ConvertFrom-Json
                    }
                    default {
                        $ResponseContent = $Response.Content
                    }
                }

                if ($ResponseContent.'@odata.context' -and $ResponseContent.value -is [array]) {
                    Write-Output $ResponseContent.value
                } else {
                    Write-Output $ResponseContent
                }

                if ($All -and $ResponseContent.'@odata.nextLink') {
                    $Request.Uri = $ResponseContent.'@odata.nextLink'
                }

            } catch {

                $ResponseStatus = $_.Exception.Response.StatusCode.value__

                if ($ResponseStatus -eq 429) {

                    if( $_.Exception.Response.Headers -and $_.Exception.Response.Headers.Contains('Retry-After') ) {
                        $RetryInternval = $_.Exception.Response.Headers.GetValues('Retry-After')
                        if($RetryInternval -is [string[]]) {
                            $RetryInternval = [int]$RetryInternval[0]
                        }
                    } else {
                        $RetryInternval = 15
                    }
                    Write-Warning "Requests are throttled, waiting $RetryInternval seconds"
                    Start-Sleep -Seconds $RetryInternval

                } else {
                    throw $_
                }
            }

        } while ($ResponseStatus -eq 429 -or ($All -and $ResponseContent.'@odata.nextLink'))

        if ($ResponseContent.'@odata.nextLink' -and -not $All) {
            Write-Warning 'More results available, use -All to see all results'
        }
    }
}