Functions/Public/MSAzure.ps1

# Microsoft Azure and Graph API functions

Function Get-MSAzureUser {
    <#
        .SYNOPSIS
        Return a list of users from Azure AD.
         
        .DESCRIPTION
        Return a list of users from Azure AD.
         
        .PARAMETER Properties
        A comma-delimited list of properties to return in the results
        Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter
        Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties
         
        .PARAMETER Filter
        Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter
        Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/user?view=graph-rest-1.0#properties
         
        .PARAMETER UPN
        Return user details for the given UserPrincipalName
 
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
 
        .PARAMETER HideProgressBar
        Don't show the progress bar. Cleans up logs when running on Docker.
         
        .PARAMETER AuthToken
        The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken
         
        .PARAMETER TotalCount
        Only return a total count of objects
         
        .PARAMETER ResultSize
        The number of results to return. Defaults to 1000.
         
        .EXAMPLE
        Get-MSAzureUser
        Returns all MS Azure user accounts
         
        .EXAMPLE
        Get-MSAzureUser -Properties 'id,userPrincipalName' -AuthToken $AuthToken
        Returns a list of Azure users' ID and userPrincipalNames using a previously-obtained authtoken
         
        .EXAMPLE
        Get-MSAzureUser -Filter "startswith(displayName,'Meeting Room')"
        Returns a list of Azure users whose display names start with 'Meeting Room'
 
        .NOTES
        Version 1.3
    #>


    
    Param (
        [Parameter(Mandatory=$False)]
        [string]$Properties,
        [Parameter(Mandatory=$False)]
        [string]$Filter,
        [Parameter(Mandatory=$False)]
        [string]$UPN,        
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(Mandatory=$False)]
        [switch]$TotalCount,
        [Parameter(Mandatory=$False)]
        [switch]$HideProgressBar,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$AuthToken,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,500000)]
        [int]$ResultSize = 500000,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,99999)]
        [int]$ProgressUpdateFreq = 1    
    )

    Process {
        $Body = @{'$count' = 'true'}
        
        Try {
            # Use globally set tenant name, if one was set and not explicitly included in the command
            If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }    
            
            If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken }
        }
        Catch {
            Write-Error "Could not obtain authorization token. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }        
        }
        
        If ($AuthToken) {
            Try {
                $Headers = @{
                    Authorization         = "Bearer $AuthToken"
                    ConsistencyLevel     = 'eventual'
                }

                If ($Properties) { $Body.Add('$select',$Properties)    }
                If ($Filter) { $Body.Add('$filter',$Filter) }

                # If UPN is entered, just look for that user and exit.
                If ($UPN) {
                    $URI = "https://graph.microsoft.com/v1.0/users/$UPN"
                    Write-Verbose $URI
    
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers

                    Return $JSON
                }
                
                If ($TotalCount) {
                    $URI = 'https://graph.microsoft.com/v1.0/users/$count'
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    
                    $UserCount = New-Object PsObject
                    $UserCount | Add-Member -NotePropertyName 'UserCount' -NotePropertyValue $JSON
                
                    If ($TenantName) { $UserCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName }
                    Clear-Variable -Name AuthToken
                    Return $UserCount
                }
                Else {
                    $URI = "https://graph.microsoft.com/v1.0/users"
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    $TotalUsers = $JSON.'@odata.count'
                    $JSON.value
                    $PageSize = $JSON.value.count
                    $Message = "Getting $TotalUsers Azure AD users for tenant $TenantName." 
                    
                    If ($HideProgressBar) { Write-Host $Message }
                    
                    $UserCount = 0
                    $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                    
                    While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) {
                        $NextURI = $JSON.'@odata.nextLink'
                        $JSON = Invoke-RestMethod -Method GET -URI $NextURI -Headers $Headers
                        $JSON.value
                        $PageSize += $JSON.value.count
                        
                        If ($Stopwatch.Elapsed.TotalSeconds -ge $ProgressUpdateFreq) {
                            $Percentage = ($PageSize / $TotalUsers) * 100
                                                    
                            If ($HideProgressBar) {
                                $Percentage = [math]::Round($Percentage,1)
                                Write-Host "Retrieving $Percentage`% of $($TotalUsers) users on tenant $TenantName..."
                            }
                            Else { 
                                Write-Progress -Activity $Message -PercentComplete $Percentage -Status 'Retrieving...' 
                            }
                        
                            $Stopwatch.Reset()
                            $Stopwatch.Start()
                        }
                    }
                }
                Clear-Variable -Name AuthToken
            }
            Catch {
                Write-Error "Could not get user data. $($_.Exception.Message)"
                Clear-Variable -Name AuthToken
            }
        }
    }
}


Function Get-MSAzureUserGroupMembership {
    <#
        .SYNOPSIS
        Return the groups that a user is a member of from Azure AD.
         
        .DESCRIPTION
        Return the groups that a user is a member of from Azure AD.
         
        .PARAMETER ID
        The Azure AD GUID of the user
         
        .PARAMETER Transitive
        Show groups where the user is a member of a group that is a member of another group
         
        .PARAMETER Properties
        A comma-delimited list of properties to return in the results
        Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter
        Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties
         
        .PARAMETER Filter
        Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter
        Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
 
        .PARAMETER AuthToken
        The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken
         
        .PARAMETER TotalCount
        Only return a total count of objects
         
        .PARAMETER ResultSize
        The number of results to return. Defaults to 10000.
         
        .EXAMPLE
        Get-MSAzureUserGroupMembership -ID abcdefab-1234-1234-1234-abcdabcdabcd
        Returns the groups that the selected user is a member of
         
        .EXAMPLE
        Get-MSAzureUser -Properties 'id,userPrincipalName' -AuthToken $AuthToken
        Returns a list of Azure users' ID and userPrincipalNames using a previously-obtained authtoken
 
        .NOTES
        Version 1.0
    #>


    
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$True)]
        [string]$ID,    
        [Parameter(Mandatory=$False)]
        [switch]$Transitive,
        [string]$Properties,
        [Parameter(Mandatory=$False)]
        [string]$Filter,        
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(Mandatory=$False)]
        [switch]$TotalCount,
        [Parameter(Mandatory=$False)]
        [switch]$HideProgressBar,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$AuthToken,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,500000)]
        [int]$ResultSize = 10000,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,99999)]
        [int]$ProgressUpdateFreq = 1    
    )
    
    Begin {
        If ($Transitive) {
            $MemberOfScope = 'transitiveMemberOf'
        }
        Else {
            $MemberOfScope = 'MemberOf'
        }
    }
    Process {
        $Body = @{}
        $Params = @{}
        
        Try {
            # Use globally set tenant name, if one was set and not explicitly included in the command
            If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }    
            
            If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken }
        }
        Catch {
            Write-Error "Could not obtain authorization token. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }        
        }
        
        If ($AuthToken) {
            Try {
                $Headers = @{
                    Authorization = "Bearer $AuthToken"
                }

                If ($Properties) { 
                    $Body.Add('$select',$Properties)
                    $Params.Add('Properties',$Properties)
                }

                If ($Filter) { 
                    $Body.Add('$filter',$Filter)
                    $Params.Add('Filter',$Filter)
                }

                If ($TotalCount) {
                    $Body.Add('ConsistencyLevel','eventual')
                    
                    $URI = "https://graph.microsoft.com/v1.0/users/$ID/$MemberOfScope/`$count"
                    Write-Verbose $URI
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    
                    $GroupCount = New-Object PsObject
                    $GroupCount | Add-Member -NotePropertyName 'UserCount' -NotePropertyValue $JSON
                
                    If ($TenantName) { $GroupCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName }
                    Clear-Variable -Name AuthToken
                    Return $GroupCount
                }
                Else {
                    $URI = "https://graph.microsoft.com/v1.0/users/$ID/$MemberOfScope"                    
                    Write-Verbose $URI
                    
                    $Params = @{}
                    If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) }
                    If ($TenantName) { $Params.Add('Tenant',$TenantName) }
                    
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    $JSON.value
                    
                    $PageSize = $JSON.value.count
                    
                    While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) {
                        $NextURI = $JSON.'@odata.nextLink'
                        $JSON = Invoke-RestMethod -Method GET -URI $NextURI -Headers $Headers
                        $JSON.value
                        $PageSize += $JSON.value.count
                    }
                }
                Clear-Variable -Name AuthToken
            }
            Catch {
                Write-Error "Could not get group membership data. $($_.Exception.Message)"
                Clear-Variable -Name AuthToken
            }
        }
    }
}


Function Get-MSAzureGroup {
    <#
        .SYNOPSIS
        Return a list of groups from Azure AD.
         
        .DESCRIPTION
        Return a list of groups from Azure AD.
         
        .PARAMETER Properties
        A comma-delimited list of properties to return in the results
        Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter
        Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties
         
        .PARAMETER Filter
        Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter
        Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
 
        .PARAMETER HideProgressBar
        Don't show the progress bar. Cleans up logs when running on Docker.
         
        .PARAMETER AuthToken
        The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken
         
        .PARAMETER TotalCount
        Only return a total count of objects
         
        .PARAMETER ResultSize
        The number of results to return. Defaults to 1000.
         
        .EXAMPLE
        Get-MSAzureGroup
        Returns all MS Azure groups
         
        .EXAMPLE
        Get-MSAzureGroup -Filter "startsWith(displayName,'Global-')"
        Returns all MS Azure groups whose display names start with Global-
         
        .EXAMPLE
        Get-MSAzureGroup -Properties 'id,DisplayName' -AuthToken $AuthToken
        Returns a list of Azure group ID and display names using a previously-obtained authtoken
 
        .NOTES
        Version 1.0
    #>


    
    Param (
        [Parameter(Mandatory=$False)]
        [string]$Properties,
        [Parameter(Mandatory=$False)]
        [string]$Filter,        
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(Mandatory=$False)]
        [switch]$TotalCount,
        [Parameter(Mandatory=$False)]
        [switch]$HideProgressBar,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$AuthToken,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,500000)]
        [int]$ResultSize,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,99999)]
        [int]$ProgressUpdateFreq = 1    
    )

    Process {
        $Body = @{}
        $Params = @{}
        
        Try {
            # Use globally set tenant name, if one was set and not explicitly included in the command
            If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }    
            
            If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken }
        }
        Catch {
            Write-Error "Could not obtain authorization token. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }        
        }
        
        If ($AuthToken) {
            Try {
                $Headers = @{
                    Authorization = "Bearer $AuthToken"
                }

                If ($Properties) { 
                    $Body.Add('$select',$Properties)
                    $Params.Add('Properties',$Properties)
                }

                If ($Filter) { 
                    $Body.Add('$filter',$Filter)
                    $Params.Add('Filter',$Filter)
                }

                If ($TotalCount) {
                    $Body.Add('ConsistencyLevel','eventual')
                    
                    $URI = 'https://graph.microsoft.com/v1.0/groups/$count'
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    
                    $GroupCount = New-Object PsObject
                    $GroupCount | Add-Member -NotePropertyName 'GroupCount' -NotePropertyValue $JSON
                
                    If ($TenantName) { $GroupCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName }
                    Clear-Variable -Name AuthToken
                    Return $GroupCount
                }
                Else {
                    $URI = "https://graph.microsoft.com/v1.0/groups"
                    
                    Write-Verbose $URI
                    
                    $Params = @{}
                    If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) }
                    If ($TenantName) { $Params.Add('Tenant',$TenantName) }
                    
                    $TotalGroups = Get-MSAzureGroup @Params -TotalCount
                    
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    $JSON.value
                    
                    $PageSize = $JSON.value.count
                    
                    $Message = "Getting Azure AD groups for tenant $TenantName." 
                    
                    If ($HideProgressBar) { Write-Host $Message }
                    
                    $GroupCount = 0
                    $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                    
                    While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) {
                        $NextURI = $JSON.'@odata.nextLink'
                        $JSON = Invoke-RestMethod -Method GET -URI $NextURI -Headers $Headers
                        $JSON.value
                        $PageSize += $JSON.value.count
                        
                        If ($Stopwatch.Elapsed.TotalSeconds -ge $ProgressUpdateFreq) {
                            $Percentage = ($PageSize / $TotalGroups.GroupCount) * 100
                                                    
                            If ($HideProgressBar) {
                                $Percentage = [math]::Round($Percentage,1)
                                Write-Host "Retrieving $Percentage`% of $($TotalGroups.GroupCount) users on tenant $TenantName..."
                            }
                            Else { 
                                Write-Progress -Activity $Message -PercentComplete $Percentage -Status 'Retrieving...' 
                            }
                        
                            $Stopwatch.Reset()
                            $Stopwatch.Start()
                        }
                    }
                }
                Clear-Variable -Name AuthToken
            }
            Catch {
                Write-Error "Could not get group data. $($_.Exception.Message)"
                Clear-Variable -Name AuthToken
            }
        }
    }
}


Function Get-MSAzureGroupMembers {
    <#
        .SYNOPSIS
        Return a list of group members from Azure AD.
         
        .DESCRIPTION
        Return a list of group members from Azure AD.
         
        .PARAMETER ID
        The GUID of the group to return membership info
     
        .PARAMETER Transitive
        Show members where the members are members of a group that is a member of another group
         
        .PARAMETER Properties
        A comma-delimited list of properties to return in the results
        Format as per https://docs.microsoft.com/en-us/graph/query-parameters?view=graph-rest-1.0#select-parameter
        Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties
         
        .PARAMETER Filter
        Add filter parameters as per https://docs.microsoft.com/en-us/graph/query-parameters?context=graph%2Fapi%2F1.0&view=graph-rest-1.0#filter-parameter
        Available properties can be found at https://docs.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties
         
        .PARAMETER TenantName
        The name of the Nectar DXP tenant. Used in multi-tenant configurations.
 
        .PARAMETER AuthToken
        The authorization token used for this request. Normally obtained via Get-MSGraphAccessToken
         
        .PARAMETER ResultSize
        The number of results to return. Defaults to 10000.
         
        .EXAMPLE
        Get-MSAzureGroup -Filter "startsWith(displayName,'Global-Sales')" | Get-MSAzureGroupMembers -TotalOnly
        Returns all MS Azure groups whose display names start with Global-Sales
         
        .NOTES
        Version 1.0
    #>


    
    Param (
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$ID,
        [Parameter(Mandatory=$False)]
        [switch]$Transitive,
        [Parameter(Mandatory=$False)]
        [string]$Properties,
        [Parameter(Mandatory=$False)]
        [string]$Filter,    
        [Parameter(Mandatory=$False)]
        [switch]$TotalCount,        
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$TenantName,
        [Parameter(ValueFromPipelineByPropertyName, Mandatory=$False)]
        [string]$AuthToken,
        [Parameter(Mandatory=$False)]
        [ValidateRange(1,500000)]
        [int]$ResultSize = 10000
    )

    Begin {
        If ($Transitive) {
            $MemberScope = 'transitiveMembers'
        }
        Else {
            $MemberScope = 'members'
        }
        
        Try {
            # Use globally set tenant name, if one was set and not explicitly included in the command
            If ($Global:NectarTenantName -And !$PSBoundParameters.ContainsKey('TenantName')) { 
                $TenantName = $Global:NectarTenantName 
            } ElseIf ($TenantName) {
                If ($TenantName -NotIn $Global:NectarTenantList) {
                    $TList = $Global:NectarTenantList -join ', '
                    Throw "Could not find a tenant with the name $TenantName on https://$Global:NectarCloud. Select one of $TList. $($_.Exception.Message)"
                }
            }    
            
            If (!$AuthToken) { $AuthToken = Get-NectarMSTeamsConfig -TenantName $TenantName | Get-MSGraphAccessToken }
        }
        Catch {
            Write-Error "Could not obtain authorization token. $($_.Exception.Message)"
            If ($PSCmdlet.MyInvocation.BoundParameters["ErrorAction"] -ne "SilentlyContinue") { Get-JSONErrorStream -JSONResponse $_ }        
        }
    }
    Process {
        $Body = @{}
        $Params = @{}
        
        If ($AuthToken) {
            Try {
                $Headers = @{
                    Authorization = "Bearer $AuthToken"
                }

                If ($Properties) { 
                    $Body.Add('$select',$Properties)
                    $Params.Add('Properties',$Properties)
                }

                If ($Filter) { 
                    $Body.Add('$filter',$Filter)
                    $Params.Add('Filter',$Filter)
                }

                If ($TotalCount) {
                    $Body.Add('ConsistencyLevel','eventual')
                    
                    $URI = "https://graph.microsoft.com/v1.0/groups/$ID/$MemberScope/`$count"
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    
                    $MemberCount = New-Object PsObject
                    $MemberCount | Add-Member -NotePropertyName 'MemberCount' -NotePropertyValue $JSON
                
                    If ($TenantName) { $MemberCount | Add-Member -NotePropertyName 'TenantName' -NotePropertyValue $TenantName }
                    Clear-Variable -Name AuthToken
                    Return $MemberCount
                }
                Else {
                    $URI = "https://graph.microsoft.com/v1.0/groups/$ID/$MemberScope"
                    
                    $Params = @{}
                    If ($AuthToken) { $Params.Add('AuthToken',$AuthToken) }
                    If ($TenantName) { $Params.Add('Tenant',$TenantName) }
                    
                    $JSON = Invoke-RestMethod -Method GET -URI $URI -Headers $Headers -Body $Body
                    $JSON.value
                    
                    $PageSize = $JSON.value.count
                    
                    While (($JSON.'@odata.nextLink') -And ($PageSize -lt $ResultSize)) {
                        $NextURI = $JSON.'@odata.nextLink'
                        $JSON = Invoke-RestMethod -Method GET -URI $NextURI -Headers $Headers
                        $JSON.value
                        $PageSize += $JSON.value.count
                    }
                }
                Clear-Variable -Name AuthToken
            }
            Catch {
                Write-Error "Could not get group member data. $($_.Exception.Message)"
                Clear-Variable -Name AuthToken
            }
        }
    }
}