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
            }
        }
    }
}

# SIG # Begin signature block
# MIIfjQYJKoZIhvcNAQcCoIIffjCCH3oCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAuhKQ7wx6R3STR
# 5mf/3i6uJ8J35tNDI37O3cZWN39MHKCCDRIwggZyMIIEWqADAgECAghkM1HTxzif
# CDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
# EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G
# A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe
# Fw0xNjA2MjQyMDQ0MzBaFw0zMTA2MjQyMDQ0MzBaMHgxCzAJBgNVBAYTAlVTMQ4w
# DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENv
# cnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBD
# QSBSU0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfgxNzqrDG
# bSHL24t6h3TQcdyOl3Ka5LuINLTdgAPGL0WkdJq/Hg9Q6p5tePOf+lEmqT2d0bKU
# Vz77OYkbkStW72fL5gvjDjmMxjX0jD3dJekBrBdCfVgWQNz51ShEHZVkMGE6ZPKX
# 13NMfXsjAm3zdetVPW+qLcSvvnSsXf5qtvzqXHnpD0OctVIFD+8+sbGP0EmtpuNC
# GVQ/8y8Ooct8/hP5IznaJRy4PgBKOm8yMDdkHseudQfYVdIYyQ6KvKNc8HwKp4WB
# wg6vj5lc02AlvINaaRwlE81y9eucgJvcLGfE3ckJmNVz68Qho+Uyjj4vUpjGYDdk
# jLJvSlRyGMwnh/rNdaJjIUy1PWT9K6abVa8mTGC0uVz+q0O9rdATZlAfC9KJpv/X
# gAbxwxECMzNhF/dWH44vO2jnFfF3VkopngPawismYTJboFblSSmNNqf1x1KiVgMg
# Lzh4gL32Bq5BNMuURb2bx4kYHwu6/6muakCZE93vUN8BuvIE1tAx3zQ4XldbyDge
# VtSsSKbt//m4wTvtwiS+RGCnd83VPZhZtEPqqmB9zcLlL/Hr9dQg1Zc0bl0EawUR
# 0tOSjAknRO1PNTFGfnQZBWLsiePqI3CY5NEv1IoTGEaTZeVYc9NMPSd6Ij/D+KNV
# t/nmh4LsRR7Fbjp8sU65q2j3m2PVkUG8qQIDAQABo4H7MIH4MA8GA1UdEwEB/wQF
# MAMBAf8wHwYDVR0jBBgwFoAU3QQJB6L1en1SUxKSle44gCUNplkwMAYIKwYBBQUH
# AQEEJDAiMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAE
# CjAIMAYGBFUdIAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6g
# LIYqaHR0cDovL2NybHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0G
# A1UdDgQWBBRUwv4QlQCTzWr158DX2bJLuI8M4zAOBgNVHQ8BAf8EBAMCAYYwDQYJ
# KoZIhvcNAQELBQADggIBAPUPJodwr5miyvXWyfCNZj05gtOII9iCv49UhCe204MH
# 154niU2EjlTRIO5gQ9tXQjzHsJX2vszqoz2OTwbGK1mGf+tzG8rlQCbgPW/M9r1x
# xs19DiBAOdYF0q+UCL9/wlG3K7V7gyHwY9rlnOFpLnUdTsthHvWlM98CnRXZ7WmT
# V7pGRS6AvGW+5xI+3kf/kJwQrfZWsqTU+tb8LryXIbN2g9KR+gZQ0bGAKID+260P
# Z+34fdzZcFt6umi1s0pmF4/n8OdX3Wn+vF7h1YyfE7uVmhX7eSuF1W0+Z0duGwdc
# +1RFDxYRLhHDsLy1bhwzV5Qe/kI0Ro4xUE7bM1eV+jjk5hLbq1guRbfZIsr0WkdJ
# LCjoT4xCPGRo6eZDrBmRqccTgl/8cQo3t51Qezxd96JSgjXktefTCm9r/o35pNfV
# HUvnfWII+NnXrJlJ27WEQRQu9i5gl1NLmv7xiHp0up516eDap8nMLDt7TAp4z5T3
# NmC2gzyKVMtODWgqlBF1JhTqIDfM63kXdlV4cW3iSTgzN9vkbFnHI2LmvM4uVEv9
# XgMqyN0eS3FE0HU+MWJliymm7STheh2ENH+kF3y0rH0/NVjLw78a3Z9UVm1F5VPz
# iIorMaPKPlDRADTsJwjDZ8Zc6Gi/zy4WZbg8Zv87spWrmo2dzJTw7XhQf+xkR6Od
# MIIGmDCCBICgAwIBAgIQIc+kRkn+AQupTThE+j58IjANBgkqhkiG9w0BAQsFADB4
# MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24x
# ETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmlu
# ZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMB4XDTI1MDgwODIwMTMzNFoXDTI2MDgw
# ODIwMTMzNFowfzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMRAwDgYD
# VQQHDAdKZXJpY2hvMR4wHAYDVQQKDBVOZWN0YXIgU2VydmljZXMgQ29ycC4xCzAJ
# BgNVBAsMAklUMR4wHAYDVQQDDBVOZWN0YXIgU2VydmljZXMgQ29ycC4wggGiMA0G
# CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCJXN3SkHk8zvqqnHkfyImA6vDVtGrO
# zTVO6nlzNe85eCoGRBk5ToZ+/uUwcFyjcxSV+jbR29y4O6azNBmwvEw96ukUVKUh
# J+0NIQoH2DJqtkp4v3EppsevtxxwarqC1fvMXMhz2NVBZS6moStIiFyeGTBgyR7P
# gdU+JRqiG1muq5QiSZWjCyBLN6DTDimz5YdX1nhc/64V/oT0g79tYmZm7UEw1rN8
# HZgk46Gezt3IIQGX22ng1nh/3vy1Q46T/8mCT3UANrd3l/jS0XUmYgW8Z91nYlrl
# iNH3nHbONGNFFN8WLPAakt3ITeGmqhZkHyyXmlxKlkLHiR8XewRHn7PpD/DT03x7
# ngKS5Ie0fUM1ZAdXDoghvQ6uNuQ5Q3TAjL2ukJs9u5VmvWyFL1l9ujuKCiNGfy1D
# cS7u1WlcCIXdrX4Hpe2lt/M7fZFkSMeS1TD2gM2+a/7xK5MWwmbV6qK27nKlRpbG
# Q6Yj0VmqJmcgekSrCKPFudNAsDyD6rUYxlUCAwEAAaOCAZUwggGRMAwGA1UdEwEB
# /wQCMAAwHwYDVR0jBBgwFoAUVML+EJUAk81q9efA19myS7iPDOMwegYIKwYBBQUH
# AQEEbjBsMEgGCCsGAQUFBzAChjxodHRwOi8vY2VydC5zc2wuY29tL1NTTGNvbS1T
# dWJDQS1Db2RlU2lnbmluZy1SU0EtNDA5Ni1SMS5jZXIwIAYIKwYBBQUHMAGGFGh0
# dHA6Ly9vY3Nwcy5zc2wuY29tMFEGA1UdIARKMEgwCAYGZ4EMAQQBMDwGDCsGAQQB
# gqkwAQMDATAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5zc2wuY29tL3JlcG9z
# aXRvcnkwEwYDVR0lBAwwCgYIKwYBBQUHAwMwTQYDVR0fBEYwRDBCoECgPoY8aHR0
# cDovL2NybHMuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmctUlNBLTQw
# OTYtUjEuY3JsMB0GA1UdDgQWBBQoc8kxtmxEx1bpEE+bhb8JNyGrHTAOBgNVHQ8B
# Af8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBAD1Wlyoz/oOVnzXFQKuv29SQ9J6G
# lqSNMvPzF6204eydcEDLwKh5IpZ7iGo6Km5AoVGNzYwJWyWaTwZpaxOoh+M+NK+E
# /QIOH+mItAaHhKogn96pHL7ZG9oD23s+/EP9jqNpenOZ0HSBWNHc4PO0e7Ys5zyQ
# 0a63PF83kU6hqkny0hca7Mr4gZoFyt6ynEJvv/TIkNCE5tqo/cBW94EpjQBfXTd4
# ycDoQ5Be1gbLboBoEjyrnPl0n+5dQgXSCg25hgf37iTfg+sCXzw3SKeiFauf4s8o
# h3wyXvNGzW4q0ZoE+ZNInk/puRVGnGmGZUr6iKuplqey1vneOOAv5we2hryR8PaA
# eqo6OqxyEw6a+nmRwf3lsp9k6Aml6ary1bdGjRY+ysScAxbeCvC9yS/EzwtaU3Z8
# R+2qLqh7cX8gztflHxrsV7Ql0nE3MrM0ry2bAWYzthbdwYtIuoeGHibng/qxuHOI
# uRLSTzddpXDdLSmw+G116kaxxHCBM+wf6K0nvehm08pXNyzAolNIJrVjsll9YHDT
# wws15kpojCglZsMIDYAlrFzF9MI0dRdiaj7/ttGSdblKZJoDOdfPrWMuBlSmjamk
# 5VxgKKHPQ9mHTP1Q5baEuotRYqJLgTWV1ZzCF+MYH2vmmuyjm4sSEjVfetLaYwIk
# RYAx7I+e9wcfhciSMYIR0TCCEc0CAQEwgYwweDELMAkGA1UEBhMCVVMxDjAMBgNV
# BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDE0
# MDIGA1UEAwwrU1NMLmNvbSBDb2RlIFNpZ25pbmcgSW50ZXJtZWRpYXRlIENBIFJT
# QSBSMQIQIc+kRkn+AQupTThE+j58IjANBglghkgBZQMEAgEFAKB8MBAGCisGAQQB
# gjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC
# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAMQLytDwr8kRccCkgE
# kaMOQ7DCPdjrM8U3+GXJpQpU2jANBgkqhkiG9w0BAQEFAASCAYCIcagMKoGS5xFg
# S4BgfXqdwCovEQsEVNXEMBi50RNMXq1UTt5OXkHy5g4LuIRdOXVdvj0YJnPmYLBi
# 3h/uDt+y4RMkJ6jIjlFI/sHVhhTMiRPpRayydz19CbDbbZ5KdEyRujNo0NApAYo5
# d52XDJk+7Z+l+2712QxGHbAgFRxTuTd3/igUIKILki44BwP2Hy0QRbdQLdkTOIHN
# EqLmNYpw9TtmA0RH34b6jD/EEmmcqcAmXxhK2LxSd9xEx+fG3nreTNgwsESIKw13
# AdHNhmP+8mYOLLsEtJEClNcq31OimidHxeaxljhiNjEdUVpHDPsM+YLJkGwKjQ74
# r+uZu7cbozqVfwBMPtXcHKHJo/3L4Z/05yRAXY92fWwpaCaJhdCLiG0zMRxTKwbg
# nuee2O5xc0G4TDuuCYEOFeSPJbVObxDYWhFi60Xvz1XHekYX2DMSe8DqK6EjOTDg
# XNNfW9t99GOA3FDe4cubNzyLkLdBeVMhuw87cC8llcxSmPazfiihgg8XMIIPEwYK
# KwYBBAGCNwMDATGCDwMwgg7/BgkqhkiG9w0BBwKggg7wMIIO7AIBAzENMAsGCWCG
# SAFlAwQCATB3BgsqhkiG9w0BCRABBKBoBGYwZAIBAQYMKwYBBAGCqTABAwYBMDEw
# DQYJYIZIAWUDBAIBBQAEIDObmC1oOwuPmy9nSvuBs3V3NQ3EDBBEHAgwryfYPp/o
# Aghv764pKMkTMRgPMjAyNjA0MDYxOTQzNTlaMAMCAQGgggwAMIIE/DCCAuSgAwIB
# AgIQH2sWYtIuG2xd8cDBoGAOODANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJV
# UzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNT
# TCBDb3JwMS8wLQYDVQQDDCZTU0wuY29tIFRpbWVzdGFtcGluZyBJc3N1aW5nIFJT
# QSBDQSBSMTAeFw0yNTAyMTgxNjMyMDJaFw0zNDExMTIxODUwMDVaMG4xCzAJBgNV
# BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UE
# CgwIU1NMIENvcnAxKjAoBgNVBAMMIVNTTC5jb20gVGltZXN0YW1waW5nIFVuaXQg
# MjAyNSBFMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBv7UVHHb+ZVluxPXlfE
# 3M6tg0Xnq8dic+O3vPOCRpalUM1vO9A+GRzSVVjyygHhYrBw62XLFh1kv7e+yRd/
# aTajggFaMIIBVjAfBgNVHSMEGDAWgBQMnRAljpqnG5mHQ88IfuG9gZD0zzBRBggr
# BgEFBQcBAQRFMEMwQQYIKwYBBQUHMAKGNWh0dHA6Ly9jZXJ0LnNzbC5jb20vU1NM
# LmNvbS10aW1lU3RhbXBpbmctSS1SU0EtUjEuY2VyMFEGA1UdIARKMEgwPAYMKwYB
# BAGCqTABAwYBMCwwKgYIKwYBBQUHAgEWHmh0dHBzOi8vd3d3LnNzbC5jb20vcmVw
# b3NpdG9yeTAIBgZngQwBBAIwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwRgYDVR0f
# BD8wPTA7oDmgN4Y1aHR0cDovL2NybHMuc3NsLmNvbS9TU0wuY29tLXRpbWVTdGFt
# cGluZy1JLVJTQS1SMS5jcmwwHQYDVR0OBBYEFM582cAEgMUkEGoJ6hyrJT0R/ajS
# MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAgHN1JIW9ZlNip07F
# fY9r1LJwLKTl1h/y9r1BjDgHMEz7U7J7mAy32JxA1KLyjCCJqNkuRfhDHGFEfZtj
# TOru7AQMDSDiUDvCCc3jaz5py9jGvwIe5npkLbXqXhA+SqCzG719USigwhHkYr7v
# 8NiXoaY0Xu03p0BiZZreZlpngmGB9+86N94FqIFx2I5Z5iuu7d4j7aAOZBNxfU9j
# FDIIGDtWfWeCpF0Q8rLE/3DHDlzJKs5Il3i/VoOl7rTU938oSrxMwww6GDBlKX8r
# GHxvkjgIb9MXkVglJSDHsNAZDeCS/inaxKHA+ChuB8K4OxIxa6k1e+eHBypakAY3
# wuN7w7PlPmhN6k5IdqZn3HZHs2VjoR841Z9wmVPFdGDdkBZ7XOmb5OZUaKufsvtE
# R0VbY2DkzaqAaItt1kc+I+FUz7PiVz2PUzpegkftRRxvrPyIL08blJTnbMQ0XrqD
# N0rEfuTLv4XhhnKyJaXhxCUdjO/cRumMzZ35QTUMItBWy1xGp0iyoFVnAya2pPsV
# MuAI+sy1zxlOS9l5iSxvKJ8gpnpOgjqYTa7u1eyZo+4JlcGoiiR17LthMTrF1q62
# tO5xOpI8txUZ6gtKGtkJV6wdj+vZLcwKZ2M93GqVpOo6UBlGjx1sKsVZ7AIUzJZl
# i4NJ0M2KYcrfNnlebkYKpDnSqWwwggb8MIIE5KADAgECAhBtUhhwh+gjTYVgANCA
# j5NWMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh
# czEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEw
# LwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB
# MB4XDTE5MTExMzE4NTAwNVoXDTM0MTExMjE4NTAwNVowczELMAkGA1UEBhMCVVMx
# DjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMREwDwYDVQQKDAhTU0wg
# Q29ycDEvMC0GA1UEAwwmU1NMLmNvbSBUaW1lc3RhbXBpbmcgSXNzdWluZyBSU0Eg
# Q0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCuURAT0vk8IKAg
# hd7JUBxkyeH9xek0/wp/MUjoclrFXqhh/fGH91Fc+7fm0MHCE7A+wmOiqBj9ODrJ
# AYGq3rm33jCnHSsCBNWAQYyoauLq8IjqsS1JlXL29qDNMMdwZ8UNzQS7vWZMDJ40
# JSGNphMGTIA2qn2bohGtgRc4p1395ESypUOaGvJ3t0FNL3BuKmb6YctMcQUF2sqo
# oMzd89h0E6ujdvBDo6ZwNnWoxj7YmfWjSXg33A5GuY9ym4QZM5OEVgo8ebz/B+gy
# hyCLNNhh4Mb/4xvCTCMVmNYrBviGgdPZYrym8Zb84TQCmSuX0JlLLa6WK1aO6qlw
# ISbb9bVGh866ekKblC/XRP20gAu1CjvcYciUgNTrGFg8f8AJgQPOCc1/CCdaJSYw
# hJpSdheKOnQgESgNmYZPhFOC6IKaMAUXk5U1tjTcFCgFvvArXtK4azAWUOO1Y3fd
# ldIBL6LjkzLUCYJNkFXqhsBVcPMuB0nUDWvLJfPimstjJ8lF4S6ECxWnlWi7OElV
# wTnt1GtRqeY9ydvvGLntU+FecK7DbqHDUd366UreMkSBtzevAc9aqoZPnjVMjvFq
# V1pYOjzmTiVHZtAc80bAfFe5LLfJzPI6DntNyqobpwTevQpHqPDN9qqNO83r3kaw
# 8A9j+HZiSw2AX5cGdQP0kG0vhzfgBwIDAQABo4IBgTCCAX0wEgYDVR0TAQH/BAgw
# BgEB/wIBADAfBgNVHSMEGDAWgBTdBAkHovV6fVJTEpKV7jiAJQ2mWTCBgwYIKwYB
# BQUHAQEEdzB1MFEGCCsGAQUFBzAChkVodHRwOi8vd3d3LnNzbC5jb20vcmVwb3Np
# dG9yeS9TU0xjb21Sb290Q2VydGlmaWNhdGlvbkF1dGhvcml0eVJTQS5jcnQwIAYI
# KwYBBQUHMAGGFGh0dHA6Ly9vY3Nwcy5zc2wuY29tMD8GA1UdIAQ4MDYwNAYEVR0g
# ADAsMCoGCCsGAQUFBwIBFh5odHRwczovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkw
# EwYDVR0lBAwwCgYIKwYBBQUHAwgwOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2Ny
# bHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0GA1UdDgQWBBQMnRAl
# jpqnG5mHQ88IfuG9gZD0zzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD
# ggIBAJIZdQ2mWkLPGQfZ8vyU+sCb8BXpRJZaL3Ez3VDlE3uZk3cPxPtybVfLuqac
# i0W6SB22JTMttCiQMnIVOsXWnIuAbD/aFTcUkTLBI3xys+wEajzXaXJYWACDS47B
# RjDtYlDW14gLJxf8W6DQoH3jHDGGy8kGJFOlDKG7/YrK7UGfHtBAEDVe6lyZ+FtC
# srk7dD/IiL/+Q3Q6SFASJLQ2XI89ihFugdYL77CiDNXrI2MFspQGswXEAGpHuaQD
# THUp/LdR3TyrIsLlnzoLskUGswF/KF8+kpWUiKJNC4rPWtNrxlbXYRGgdEdx8SMj
# UTDClldcrknlFxbqHsVmr9xkT2QtFmG+dEq1v5fsIK0vHaHrWjMMmaJ9i+4qGJSD
# 0stYfQ6v0PddT7EpGxGd867Ada6FZyHwbuQSadMb0K0P0OC2r7rwqBUe0BaMqTa6
# LWzWItgBjGcObXeMxmbQqlEz2YtAcErkZvh0WABDDE4U8GyV/32FdaAvJgTfe9Mi
# L2nSBioYe/g5mHUSWAay/Ip1RQmQCvmF9sNfqlhJwkjy/1U1ibUkTIUBX3HgymyQ
# vqQTZLLys6pL2tCdWcjI9YuLw30rgZm8+K387L7ycUvqrmQ3ZJlujHl3r1hgV76s
# 3WwMPgKk1bAEFMj+rRXimSC+Ev30hXZdqyMdl/il5Ksd0vhGMYICWTCCAlUCAQEw
# gYcwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3Vz
# dG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNvbSBUaW1lc3Rh
# bXBpbmcgSXNzdWluZyBSU0EgQ0EgUjECEB9rFmLSLhtsXfHAwaBgDjgwCwYJYIZI
# AWUDBAIBoIIBYTAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcN
# AQkFMQ8XDTI2MDQwNjE5NDM1OVowKAYJKoZIhvcNAQk0MRswGTALBglghkgBZQME
# AgGhCgYIKoZIzj0EAwIwLwYJKoZIhvcNAQkEMSIEICwXDMs3W6XQRCMO3+Pi3n5K
# FiR7xNnsTMPmZwHwi/WbMIHJBgsqhkiG9w0BCRACLzGBuTCBtjCBszCBsAQgVCr5
# oWqNci5mEUl4iumUwYqaruWmXLNEolSa+Wx5x4swgYswd6R1MHMxCzAJBgNVBAYT
# AlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwI
# U1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3Vpbmcg
# UlNBIENBIFIxAhAfaxZi0i4bbF3xwMGgYA44MAoGCCqGSM49BAMCBEgwRgIhAI6E
# X3EF/PaIX7URRajAUOMTG6bRdj4c60CoimZtvCcLAiEAl7IGs2L1yA/jcQbrAyDy
# GNwD4s93Yi9YCQ0dZQVjdag=
# SIG # End signature block