EntraExporter.psm1

#Requires -Version 5.1
#Requires -PSEdition Core,Desktop
#Requires -Module @{'ModuleVersion'='2.2.0';'ModuleName'='Microsoft.Graph.Authentication';'GUID'='883916f2-9184-46ee-b1f8-b6a2fb784cee'}

<#
.SYNOPSIS
    EntraExporter
.DESCRIPTION
    This module exports all the Entra objects and identity related settings in your tenant.
.NOTES
    ModuleVersion: 2.0.2
    GUID: d6c15273-d343-4556-a30d-b333eca3c1ab
    Author: Microsoft Identity
    CompanyName: Microsoft Corporation
    Copyright: Microsoft Corporation. All rights reserved.
.FUNCTIONALITY
    Connect-EntraExporter, Export-Entra
.LINK
    https://github.com/microsoft/entraexporter
#>


#region NestedModules Script(s)

#region Invoke-Graph.ps1

<#
.SYNOPSIS
    Run a Microsoft Graph Command
#>

function Invoke-Graph{
    [CmdletBinding()]
    param(
        # Graph endpoint such as "users".
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string[]] $RelativeUri,
        # Specifies unique Id(s) for the URI endpoint. For example, users endpoint accepts Id or UPN.
        [Parameter(Mandatory = $false)]
        [string[]] $UniqueId,
        # Filters properties (columns).
        [Parameter(Mandatory = $false)]
        [string[]] $Select,
        # Filters results (rows). https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
        [Parameter(Mandatory = $false)]
        [string] $Filter,
        # Parameters such as "$top".
        [Parameter(Mandatory = $false)]
        [hashtable] $QueryParameters,
        # API Version.
        [Parameter(Mandatory = $false)]
        [ValidateSet('v1.0', 'beta')]
        [string] $ApiVersion = 'v1.0',
        # Specifies consistency level.
        [Parameter(Mandatory = $false)]
        [string] $ConsistencyLevel = 'eventual',
        # Only return first page of results.
        [Parameter(Mandatory = $false)]
        [switch] $DisablePaging,
        # Force individual requests to MS Graph.
        [Parameter(Mandatory = $false)]
        [switch] $DisableBatching,
        # Specify Batch size.
        [Parameter(Mandatory = $false)]
        [int] $BatchSize = 20,
        # Base URL for Microsoft Graph API.
        [Parameter(Mandatory = $false)]
        [uri] $GraphBaseUri = 'https://graph.microsoft.com/'
    )
    
    begin {
        $listRequests = New-Object 'System.Collections.Generic.List[psobject]'

        function Format-Result ($results, $RawOutput) {
            if (!$RawOutput -and $results -and (Get-ObjectProperty $results 'value')) {
                foreach ($result in $results.value) {
                    if ($result -is [hashtable]) {
                        $result.Add('@odata.context', ('{0}/$entity' -f $results.'@odata.context'))
                    }
                    else {
                        $result | Add-Member -MemberType NoteProperty -Name '@odata.context' -Value ('{0}/$entity' -f $results.'@odata.context')
                    }
                    Write-Output $result
                }
            }
            else { Write-Output $results }
        }

        function Complete-Result ($results, $DisablePaging) {
            if (!$DisablePaging -and $results) {
                while (Get-ObjectProperty $results '@odata.nextLink') {
                    $results = Invoke-MgGraphRequest -Method GET -Uri $results.'@odata.nextLink' -Headers @{ ConsistencyLevel = $ConsistencyLevel } -OutputType PSObject
                    Format-Result $results $DisablePaging
                }
            }
        }
    }

    process {
        ## Initialize
        $results = $null
        if (!$UniqueId) { [string[]] $UniqueId = '' }
        if ($DisableBatching -and ($RelativeUri.Count -gt 1 -or $UniqueId.Count -gt 1)) {
            Write-Warning ('This command is invoking {0} individual Graph requests. For better performance, remove the -DisableBatching parameter.' -f ($RelativeUri.Count * $UniqueId.Count))
        }

        ## Process Each RelativeUri
        foreach ($uri in $RelativeUri) {
            $uriQueryEndpoint = New-Object System.UriBuilder -ArgumentList ([IO.Path]::Combine($GraphBaseUri.AbsoluteUri, $ApiVersion, $uri))

            ## Combine query parameters from URI and cmdlet parameters
            if ($uriQueryEndpoint.Query) {
                [hashtable] $finalQueryParameters = ConvertFrom-QueryString $uriQueryEndpoint.Query -AsHashtable
                if ($QueryParameters) {
                    foreach ($ParameterName in $QueryParameters.Keys) {
                        $finalQueryParameters[$ParameterName] = $QueryParameters[$ParameterName]
                    }
                }
            }
            elseif ($QueryParameters) { [hashtable] $finalQueryParameters = $QueryParameters }
            else { [hashtable] $finalQueryParameters = @{ } }
            if ($Select) { $finalQueryParameters['$select'] = $Select -join ',' }
            if ($Filter) { $finalQueryParameters['$filter'] = $Filter }
            $uriQueryEndpoint.Query = ConvertTo-QueryString $finalQueryParameters

            ## Invoke graph requests individually or save for single batch request
            foreach ($id in $UniqueId) {
                $uriQueryEndpointFinal = New-Object System.UriBuilder -ArgumentList $uriQueryEndpoint.Uri
                $uriQueryEndpointFinal.Path = ([IO.Path]::Combine($uriQueryEndpointFinal.Path, $id))

                if (!$DisableBatching -and ($RelativeUri.Count -gt 1 -or $UniqueId.Count -gt 1)) {
                    ## Create batch request entry
                    $request = New-Object PSObject -Property @{
                        id      = $listRequests.Count #(New-Guid).ToString()
                        method  = 'GET'
                        url     = $uriQueryEndpointFinal.Uri.AbsoluteUri -replace ('{0}{1}/' -f $GraphBaseUri.AbsoluteUri, $ApiVersion)
                        headers = @{ ConsistencyLevel = $ConsistencyLevel }
                    }
                    $listRequests.Add($request)
                }
                else {
                        ## Get results
                        $results = Invoke-MgGraphRequest -Method GET -Uri $uriQueryEndpointFinal.Uri.AbsoluteUri -Headers @{ ConsistencyLevel = $ConsistencyLevel } -OutputType PSObject
                        Format-Result $results $DisablePaging
                        Complete-Result $results $DisablePaging
                }
            }
        }
    }

    end {
        if ($listRequests.Count -gt 0) {
            $uriQueryEndpoint = New-Object System.UriBuilder -ArgumentList ([IO.Path]::Combine($GraphBaseUri.AbsoluteUri, $ApiVersion, '$batch'))
            for ($iRequest = 0; $iRequest -lt $listRequests.Count; $iRequest += $BatchSize) {
                $indexEnd = [System.Math]::Min($iRequest + $BatchSize - 1, $listRequests.Count - 1)
                $jsonRequests = New-Object psobject -Property @{ requests = $listRequests[$iRequest..$indexEnd] } | ConvertTo-Json -Depth 5
                Write-Debug $jsonRequests
                
                $resultsBatch = Invoke-MgGraphRequest -Method POST -Uri $uriQueryEndpoint.Uri.AbsoluteUri -Body $jsonRequests -OutputType PSObject
                $resultsBatch = $resultsBatch.responses | Sort-Object -Property id

                foreach ($results in ($resultsBatch.body)) {
                    Format-Result $results $DisablePaging
                    Complete-Result $results $DisablePaging
                }
            }
        }
    }
}
#endregion

#region Get-ObjectProperty.ps1

<#
.SYNOPSIS
    Get object property value.
.EXAMPLE
    PS C:\>$object = New-Object psobject -Property @{ title = 'title value' }
    PS C:\>$object | Get-ObjectProperty -Property 'title'
    Get value of object property named title.
.EXAMPLE
    PS C:\>$object = New-Object psobject -Property @{ lvl1 = (New-Object psobject -Property @{ nextLevel = 'lvl2 data' }) }
    PS C:\>Get-ObjectProperty $object -Property 'lvl1', 'nextLevel'
    Get value of nested object property named nextLevel.
.INPUTS
    System.Collections.Hashtable
    System.Management.Automation.PSObject
#>

function Get-ObjectProperty {
    [CmdletBinding()]
    [OutputType([object])]
    param (
        # Object containing property values
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [object] $InputObjects,
        # Name of property. Specify an array of property names to tranverse nested objects.
        [Parameter(Mandatory = $true, ValueFromRemainingArguments = $true)]
        [string[]] $Property
    )

    process {
        foreach ($InputObject in $InputObjects) {
            for ($iProperty = 0; $iProperty -lt $Property.Count; $iProperty++) {
                ## Get property value
                if ($InputObject -is [hashtable]) {
                    if ($InputObject.ContainsKey($Property[$iProperty])) {
                        $PropertyValue = $InputObject[$Property[$iProperty]]
                    }
                    else { $PropertyValue = $null}
                }
                else {
                    $PropertyValue = Select-Object -InputObject $InputObject -ExpandProperty $Property[$iProperty] -ErrorAction SilentlyContinue
                }
                ## Check for more nested properties
                if ($iProperty -lt $Property.Count - 1) {
                    $InputObject = $PropertyValue
                    if ($null -eq $InputObject) { break }
                }
                else {
                    Write-Output $PropertyValue
                }
            }
        }
    }
}

#endregion

#region ConvertTo-OrderedDictionary.ps1

function ConvertTo-OrderedDictionary
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $InputObject
    )

    process
    {
        if($InputObject){
            if($InputObject -is [array]){
                $outputArray = @()
                foreach($item in $InputObject){
                    $outputArray += ConvertTo-OrderedDictionary $item
                }
                return $outputArray
            }
            elseif($InputObject -is [hashtable]){ 
                $outputObject = [ordered]@{}
                foreach ($Item in ($InputObject.GetEnumerator() | Sort-Object -Property Key)) {
                    if($Item){
                        $value = Get-ObjectProperty $Item 'Value'
                        if($value -is [hashtable] -or $value -is [array]){ #if child is a hashtable or array, sort it too
                            $Item.Value = ConvertTo-OrderedDictionary $value
                        }
                    }
                    $outputObject[$Item.Key] = $Item.Value
                }
                return $outputObject
            }
        }
        else {
            return $InputObject
        }
    }
}

#endregion

#region ConvertFrom-QueryString.ps1

<#
.SYNOPSIS
    Convert Query String to object.
.EXAMPLE
    PS C:\>ConvertFrom-QueryString '?name=path/file.json&index=10'
    Convert query string to object.
.EXAMPLE
    PS C:\>'name=path/file.json&index=10' | ConvertFrom-QueryString -AsHashtable
    Convert query string to hashtable.
.INPUTS
    System.String
.LINK
    https://github.com/jasoth/Utility.PS
#>

function ConvertFrom-QueryString {
    [CmdletBinding()]
    [OutputType([psobject])]
    [OutputType([hashtable])]
    param (
        # Value to convert
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string[]] $InputStrings,
        # URL decode parameter names
        [Parameter(Mandatory = $false)]
        [switch] $DecodeParameterNames,
        # Converts to hash table object
        [Parameter(Mandatory = $false)]
        [switch] $AsHashtable
    )

    process {
        foreach ($InputString in $InputStrings) {
            if ($AsHashtable) { [hashtable] $OutputObject = @{ } }
            else { [psobject] $OutputObject = New-Object psobject }

            if ($InputString[0] -eq '?') { $InputString = $InputString.Substring(1) }
            [string[]] $QueryParameters = $InputString.Split('&')
            foreach ($QueryParameter in $QueryParameters) {
                [string[]] $QueryParameterPair = $QueryParameter.Split('=')
                if ($DecodeParameterNames) { $QueryParameterPair[0] = [System.Net.WebUtility]::UrlDecode($QueryParameterPair[0]) }
                if ($OutputObject -is [hashtable]) {
                    $OutputObject.Add($QueryParameterPair[0], [System.Net.WebUtility]::UrlDecode($QueryParameterPair[1]))
                }
                else {
                    $OutputObject | Add-Member $QueryParameterPair[0] -MemberType NoteProperty -Value ([System.Net.WebUtility]::UrlDecode($QueryParameterPair[1]))
                }
            }
            Write-Output $OutputObject
        }
    }

}

#endregion

#region ConvertTo-QueryString.ps1

<#
.SYNOPSIS
    Convert Hashtable to Query String.
.EXAMPLE
    PS C:\>ConvertTo-QueryString @{ name = 'path/file.json'; index = 10 }
    Convert hashtable to query string.
.EXAMPLE
    PS C:\>[ordered]@{ title = 'convert&prosper'; id = [guid]'352182e6-9ab0-4115-807b-c36c88029fa4' } | ConvertTo-QueryString
    Convert ordered dictionary to query string.
.INPUTS
    System.Collections.Hashtable
.LINK
    https://github.com/jasoth/Utility.PS
#>

function ConvertTo-QueryString {
    [CmdletBinding()]
    [OutputType([string])]
    param (
        # Value to convert
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [object] $InputObjects,
        # URL encode parameter names
        [Parameter(Mandatory = $false)]
        [switch] $EncodeParameterNames
    )

    process {
        foreach ($InputObject in $InputObjects) {
            $QueryString = New-Object System.Text.StringBuilder
            if ($InputObject -is [hashtable] -or $InputObject -is [System.Collections.Specialized.OrderedDictionary] -or $InputObject.GetType().FullName.StartsWith('System.Collections.Generic.Dictionary')) {
                foreach ($Item in $InputObject.GetEnumerator()) {
                    if ($QueryString.Length -gt 0) { [void]$QueryString.Append('&') }
                    [string] $ParameterName = $Item.Key
                    if ($EncodeParameterNames) { $ParameterName = [System.Net.WebUtility]::UrlEncode($ParameterName) }
                    [void]$QueryString.AppendFormat('{0}={1}', $ParameterName, [System.Net.WebUtility]::UrlEncode($Item.Value))
                }
            }
            elseif ($InputObject -is [object] -and $InputObject -isnot [ValueType]) {
                foreach ($Item in ($InputObject | Get-Member -MemberType Property, NoteProperty)) {
                    if ($QueryString.Length -gt 0) { [void]$QueryString.Append('&') }
                    [string] $ParameterName = $Item.Name
                    if ($EncodeParameterNames) { $ParameterName = [System.Net.WebUtility]::UrlEncode($ParameterName) }
                    [void]$QueryString.AppendFormat('{0}={1}', $ParameterName, [System.Net.WebUtility]::UrlEncode($InputObject.($Item.Name)))
                }
            }
            else {
                ## Non-Terminating Error
                $Exception = New-Object ArgumentException -ArgumentList ('Cannot convert input of type {0} to query string.' -f $InputObject.GetType())
                Write-Error -Exception $Exception -Category ([System.Management.Automation.ErrorCategory]::ParserError) -CategoryActivity $MyInvocation.MyCommand -ErrorId 'ConvertQueryStringFailureTypeNotSupported' -TargetObject $InputObject
                continue
            }

            Write-Output $QueryString.ToString()
        }
    }
}

#endregion

#region Connect-EntraExporter.ps1

$global:TenantID = $null
<#
.SYNOPSIS
    Connect the Entra Exporter module to the Entra tenant.
.DESCRIPTION
    This command will connect Microsoft.Graph to your Entra tenant.
    You can also directly call Connect-MgGraph if you require other options to connect
.EXAMPLE
    PS C:\>Connect-EntraExporter
    Connect to home tenant of authenticated user.
.EXAMPLE
    PS C:\>Connect-EntraExporter -TenantId 3043-343434-343434
    Connect to a specific Tenant
#>

function Connect-EntraExporter {
    param(
        [Parameter(Mandatory = $false)]
            [string] $TenantId = 'common',
        [Parameter(Mandatory=$false)]
            [ArgumentCompleter( {
                param ( $CommandName, $ParameterName, $WordToComplete, $CommandAst, $FakeBoundParameters )
                (Get-MgEnvironment).Name
            } )]
            [string]$Environment = 'Global'
    )    
    Connect-MgGraph -TenantId $TenantId -Environment $Environment -Scopes 'Directory.Read.All', 
        'Policy.Read.All', 
        'IdentityProvider.Read.All', 
        'Organization.Read.All',
        'User.Read.All',
        'EntitlementManagement.Read.All',
        'UserAuthenticationMethod.Read.All',
        'IdentityUserFlow.Read.All',
        'APIConnectors.Read.All',
        'AccessReview.Read.All',
        'Agreement.Read.All',
        'Policy.Read.PermissionGrant',
        'PrivilegedAccess.Read.AzureResources',
        'PrivilegedAccess.Read.AzureAD',
        'Application.Read.All'
    Get-MgContext
    $global:TenantID = (Get-MgContext).TenantId
}

#endregion

#region Export-Entra.ps1

<#
 .Synopsis
  Exports Entra's configuration and settings for a tenant
 .Description
  This cmdlet reads the configuration information from the target Entra tenant and produces the output files
  in a target directory

 .PARAMETER OutputDirectory
    Specifies the directory path where the output files will be generated.

.PARAMETER Type
    Specifies the type of objects to export. Default to Config which exports the key configuration settings of the tenant.

.PARAMETER All
    If specified performs a full export of all objects and configuration in the tenant.

.EXAMPLE
   .\Export-Entra -Path 'c:\temp\contoso'

   Runs a default export and includes the key tenant configuration settings. Does not include large data collections such as Users, Groups, Applications, Service Principals, etc.
.EXAMPLE
   .\Export-Entra -Path 'c:\temp\contoso' -All
   
   Runs a full export of all objects and configuration settings.

.EXAMPLE
   .\Export-Entra -Path 'c:\temp\contoso' -Type ConditionalAccess, AppProxy

   Runs an export that includes just the Conditional Access and Application Proxy settings.

.EXAMPLE
   .\Export-Entra -Path 'c:\temp\contoso' -Type B2C

   Runs an export of all B2C settings.
#>


Function Export-Entra {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [String]$Path,        
        [Parameter(Mandatory = $false)]
        [ValidateSet('All', 'Config', 'AccessReviews', 'ConditionalAccess', 'Users', 'Groups', 'Applications', 'ServicePrincipals','B2C','B2B','PIM','PIMAzure','PIMAAD', 'AppProxy', 'Organization', 'Domains', 'EntitlementManagement', 'Policies', 'AdministrativeUnits', 'SKUs', 'Identity', 'Roles','Governance')]
        [String[]]$Type = 'Config',
        [Parameter(Mandatory = $false)]
        [object]$ExportSchema,
        [Parameter(Mandatory = $false)]
        [string[]]$Parents,
        [switch]
        $All,
        [switch]
        $CloudUsersAndGroupsOnly,
        [switch]
        $AllGroups
    )

    if ($null -eq (Get-MgContext)) {
        Write-Error "No active connection. Run Connect-EntraExporter to sign in and then retry."
        exit
    }
    if($All) {$Type = @('All')}
    $global:TenantID = (Get-MgContext).TenantId
    $global:Type = $Type #Used in places like Groups where Config flag will limit the resultset to just dynamic groups.

    if (!$ExportSchema) {
        $ExportSchema = Get-EEDefaultSchema
    }
    

    # aditional filters
    foreach ($entry in $ExportSchema) {
        $graphUri = Get-ObjectProperty $entry "GraphUri"
        # filter out synced users or groups
        if ($CloudUsersAndGroupsOnly -and ($graphUri -in "users","groups")) {
            $entry.Filter = "onPremisesSyncEnabled ne true"
        }
        # get all groups
        if (($All -or $AllGroups) -and ($graphUri -eq "groups")) {
            $entry.Filter = $null
        }
        # get all PIM elements
        if ($All -and ($graphUri -in "privilegedAccess/aadroles/resources","privilegedAccess/azureResources/resources")) {
            $entry.Filter = $null
        }
    }

    foreach ($item in $ExportSchema) {
        $typeMatch = Compare-Object $item.Tag $Type -ExcludeDifferent -IncludeEqual
        $hasParents = $Parents -and $Parents.Count -gt 0
        if( ($typeMatch)) {
            $outputFileName = Join-Path -Path $Path -ChildPath $item.Path

            $spacer = ''
            if($hasParents) { $spacer = ''.PadRight($Parents.Count + 3, ' ') + $Parents[$Parents.Count-1] }
            
            Write-Host "$spacer $($item.Path)"

            $command = Get-ObjectProperty $item 'Command'
            $graphUri = Get-ObjectProperty $item 'GraphUri'
            $apiVersion = Get-ObjectProperty $item 'ApiVersion'
            $ignoreError = Get-ObjectProperty $item 'IgnoreError'
            if (!$apiVersion) { $apiVersion = 'v1.0' }
            $resultItems = $null
            if($command) {
                if ($hasParents){ $command += " -Parents $Parents" }
                $resultItems = Invoke-Expression -Command $command
            }
            else {
                if ($hasParents){ $graphUri = $graphUri -replace '{id}', $Parents[$Parents.Count-1] }
                try {
                    $resultItems = Invoke-Graph $graphUri -Filter (Get-ObjectProperty $item 'Filter') -Select (Get-ObjectProperty $item 'Select') -QueryParameters (Get-ObjectProperty $item 'QueryParameters') -ApiVersion $apiVersion
                }
                catch {
                    $e = ""
                    if($_.ErrorDetails -and $_.ErrorDetails.Message) {
                        $e = $_.ErrorDetails.Message
                    }
                    
                    if($e.Contains($ignoreError) -or $e.Contains('Encountered an internal server error')){
                        Write-Debug $_
                    }
                    else {
                        Write-Error $_
                    }
                }
            }

            if ($outputFileName -match "\.json$") {
                if($resultItems){
                    $resultItems | ConvertTo-Json -depth 100 | Out-File (New-Item -Path $outputFileName -Force)
                }
            } else {
                foreach($resultItem in $resultItems) {
                    if (!$resultItem.PSObject.Properties['id']) {
                        continue
                    }
                    $itemOutputFileName = Join-Path -Path $outputFileName -ChildPath $resultItem.id
                    $parentOutputFileName = Join-Path $itemOutputFileName -ChildPath $resultItem.id
                    $resultItem | ConvertTo-Json -depth 100 | Out-File (New-Item -Path "$($parentOutputFileName).json" -Force)
                    if ($item.ContainsKey('Children')) {
                        $itemParents = $Parents
                        $itemParents += $resultItem.Id
                        Export-Entra -Path $itemOutputFileName -Type $Type -ExportSchema $item.Children -Parents $itemParents
                    }
                }
            }
        }
    }
}

#endregion

#region Get-EEDefaultSchema.ps1

<#
 .Synopsis
  Gets the default export schema definition

 .Description
  Gets the default export schema definition. Defining the order in which elements are exported.

 .Example
  Get-EEDefaultSchema
#>


function Get-EEDefaultSchema  {
    $global:TenantID = (Get-MgContext).TenantId
    return  @(
        # Organization
        @{
            GraphUri = 'organization' 
            Path = 'Organization/Organization.json'
            Tag = @('All', 'Config', 'Organization')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
        },
        @{
            GraphUri = 'organization/{0}/settings' -f $TenantID
            Path = 'Organization/Settings.json'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'Organization')
            DelegatedPermission = 'User.Read.All'
        },
        @{
            GraphUri = 'organization/{0}/branding/localizations' -f $TenantID
            Path = 'Organization/Branding/Localizations.json'
            Tag = @('All', 'Config', 'Organization')
            DelegatedPermission = 'User.Read.All'
        },
        @{
            GraphUri = 'organization/{0}/certificateBasedAuthConfiguration' -f $TenantID
            Path = 'Organization/CertificateBasedAuthConfiguration.json'
            Tag = @('All', 'Config', 'Organization')
            DelegatedPermission = 'Organization.Read.All'
            ApplicationPermission = 'Organization.Read.All'
        },

        @{
            GraphUri = 'domains'
            Path = 'Domains'
            Tag = @('All', 'Config','Domains')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
        },
        @{
            GraphUri = 'identity/apiConnectors'
            Path = 'Identity/APIConnectors'
            ApiVersion = 'beta'
            IgnoreError = 'The feature self service sign up is not enabled for the tenant'
            Tag = @('All', 'Config', 'Identity')
            DelegatedPermission = 'APIConnectors.ReadWrite.All'
            ApplicationPermission = 'APIConnectors.ReadWrite.All'
        },
        @{
            GraphUri = 'identityProviders'
            Path = 'IdentityProviders'
            Tag = @('All', 'Config', 'Identity')
            DelegatedPermission = 'IdentityProvider.Read.All'
        },
        @{
            GraphUri = 'subscribedSkus'
            Path = 'SubscribedSkus'
            Tag = @('All', 'Config', 'SKUs')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
        },
        @{
            GraphUri = 'directoryRoles'
            Path = 'DirectoryRoles'
            Tag = @('All', 'Config', 'Roles')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
            Children = @(
                @{
                    GraphUri = 'directoryRoles/{id}/members'
                    Select = 'id, userPrincipalName, displayName'
                    Path = 'Members'
                    Tag = @('All', 'Config', 'Roles')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                }
                @{
                    GraphUri = 'directoryroles/{id}/scopedMembers'
                    Path = 'ScopedMembers'
                    Tag = @('All', 'Config', 'Roles')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                }
            )
        },

        # B2C
        @{
            GraphUri = 'identity/userFlows'
            Path = 'Identity/UserFlows'
            Tag = @('B2C')
            DelegatedPermission = 'IdentityUserFlow.Read.All'
            ApplicationPermission = 'IdentityUserFlow.Read.All'
        },
        @{
            GraphUri = 'identity/b2cUserFlows'
            Path = 'Identity/B2CUserFlows'
            Tag = @('B2C')
            DelegatedPermission = 'IdentityUserFlow.Read.All'
            ApplicationPermission = 'IdentityUserFlow.Read.All'
            Children = @(
                @{
                    GraphUri = 'identity/b2cUserFlows/{id}/identityProviders'
                    Path = 'IdentityProviders'
                    Tag = @('B2C')
                    DelegatedPermission = 'IdentityUserFlow.Read.All'
                    ApplicationPermission = 'IdentityUserFlow.Read.All'
                },
                @{
                    GraphUri = 'identity/b2cUserFlows/{id}/userAttributeAssignments'
                    QueryParameters = @{ expand = 'userAttribute' }
                    Path = 'UserAttributeAssignments'
                    Tag = @('B2C')
                    DelegatedPermission = 'IdentityUserFlow.Read.All'
                    ApplicationPermission = 'IdentityUserFlow.Read.All'
                },
                @{
                    GraphUri = 'identity/b2cUserFlows/{id}/apiConnectorConfiguration'
                    QueryParameters = @{ expand = 'postFederationSignup,postAttributeCollection' }
                    Path = 'ApiConnectorConfiguration'
                    Tag = @('B2C')
                    DelegatedPermission = 'IdentityUserFlow.Read.All'
                    ApplicationPermission = 'IdentityUserFlow.Read.All'
                },
                @{
                    GraphUri = 'identity/b2cUserFlows/{id}/languages'
                    Path = 'Languages'
                    Tag = @('B2C')
                    DelegatedPermission = 'IdentityUserFlow.Read.All'
                    ApplicationPermission = 'IdentityUserFlow.Read.All'
                }
            )
        },

        # B2B
        @{
            GraphUri = 'identity/userFlowAttributes'
            Path = 'Identity/UserFlowAttributes'
            ApiVersion = 'beta'
            Tag = @('Config', 'B2B', 'B2C')
            DelegatedPermission = 'IdentityUserFlow.Read.All'
            ApplicationPermission = 'IdentityUserFlow.Read.All'
            IgnoreError = 'The feature self service sign up is not enabled for the tenant'
        },
        @{
            GraphUri = 'identity/b2xUserFlows'
            Path = 'Identity/B2XUserFlows'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'B2B')
            DelegatedPermission = 'IdentityUserFlow.Read.All'
            ApplicationPermission = 'IdentityUserFlow.Read.All'
            Children = @(
                @{
                    GraphUri = 'identity/b2xUserFlows/{id}/identityProviders'
                    Path = 'IdentityProviders'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'B2B')
                    DelegatedPermission = 'IdentityUserFlow.Read.All'
                    ApplicationPermission = 'IdentityUserFlow.Read.All'
                },
                @{
                    GraphUri = 'identity/b2xUserFlows/{id}/userAttributeAssignments'
                    QueryParameters = @{ expand = 'userAttribute' }
                    Path = 'AttributeAssignments'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'B2B')
                    DelegatedPermission = 'IdentityUserFlow.Read.All'
                    ApplicationPermission = 'IdentityUserFlow.Read.All'
                },
                @{
                    GraphUri = 'identity/b2xUserFlows/{id}/apiConnectorConfiguration'
                    QueryParameters = @{ expand = 'postFederationSignup,postAttributeCollection' }
                    Path = 'APIConnectors'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'B2B')
                    DelegatedPermission = 'IdentityUserFlow.Read.All'
                    ApplicationPermission = 'IdentityUserFlow.Read.All'
                },
                @{
                    GraphUri = 'identity/b2xUserFlows/{id}/languages'
                    Path = 'Languages'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'B2B')
                    DelegatedPermission = 'IdentityUserFlow.Read.All'
                    ApplicationPermission = 'IdentityUserFlow.Read.All'
                }
            )
        },

        # Policies
        @{
            GraphUri = 'policies/identitySecurityDefaultsEnforcementPolicy'
            Path =  'Policies/IdentitySecurityDefaultsEnforcementPolicy'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authorizationPolicy'
            Path = 'Policies/AuthorizationPolicy'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/featureRolloutPolicies'
            Path = 'Policies/FeatureRolloutPolicies'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Directory.ReadWrite.All'
        },
        @{
            GraphUri = 'policies/activityBasedTimeoutPolicies'
            Path = 'Policies/ActivityBasedTimeoutPolicy'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/homeRealmDiscoveryPolicies'
            Path = 'Policies/HomeRealmDiscoveryPolicy'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/claimsMappingPolicies'
            Path = 'Policies/ClaimsMappingPolicy'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/tokenIssuancePolicies'
            Path = 'Policies/TokenIssuancePolicy'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/tokenLifetimePolicies'
            Path = 'Policies/TokenLifetimePolicy'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authenticationMethodsPolicy/authenticationMethodConfigurations/email'
            Path = 'Policies/AuthenticationMethodsPolicy/AuthenticationMethodConfigurations/Email.json'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authenticationMethodsPolicy/authenticationMethodConfigurations/fido2'
            Path = 'Policies/AuthenticationMethodsPolicy/AuthenticationMethodConfigurations/FIDO2.json'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authenticationMethodsPolicy/authenticationMethodConfigurations/microsoftAuthenticator'
            Path = 'Policies/AuthenticationMethodsPolicy/AuthenticationMethodConfigurations/MicrosoftAuthenticator.json'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authenticationMethodsPolicy/authenticationMethodConfigurations/sms'
            Path = 'Policies/AuthenticationMethodsPolicy/AuthenticationMethodConfigurations/SMS.json'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authenticationMethodsPolicy/authenticationMethodConfigurations/temporaryAccessPass'
            Path = 'Policies/AuthenticationMethodsPolicy/AuthenticationMethodConfigurations/TemporaryAccessPass.json'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authenticationMethodsPolicy/authenticationMethodConfigurations/softwareOath'
            Path = 'Policies/AuthenticationMethodsPolicy/AuthenticationMethodConfigurations/SoftwareOath.json'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authenticationMethodsPolicy/authenticationMethodConfigurations/voice'
            Path = 'Policies/AuthenticationMethodsPolicy/AuthenticationMethodConfigurations/Voice.json'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/authenticationMethodsPolicy/authenticationMethodConfigurations/x509Certificate'
            Path = 'Policies/AuthenticationMethodsPolicy/AuthenticationMethodConfigurations/X509Certificate.json'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/adminConsentRequestPolicy'
            Path = 'Policies/AdminConsentRequestPolicy'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/permissionGrantPolicies'
            Path = 'Policies/PermissionGrantPolicies'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.PermissionGrant'
            ApplicationPermission = 'Policy.Read.PermissionGrant'
        },
        @{
            GraphUri = 'policies/externalIdentitiesPolicy'
            Path = 'Policies/ExternalIdentitiesPolicy'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/crossTenantAccessPolicy'
            Path = 'Policies/CrossTenantAccessPolicy'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/crossTenantAccessPolicy/default'
            Path = 'Policies/CrossTenantAccessPolicy/Default'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'policies/crossTenantAccessPolicy/partners'
            Path = 'Policies/CrossTenantAccessPolicy/Partners'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'Policies')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'identity/customAuthenticationExtensions'
            Path = 'Identity/CustomAuthenticationExtensions'
            ApiVersion = 'beta'
            Tag = @('All', 'Config')
            DelegatedPermission = 'Application.Read.All'
            ApplicationPermission = 'Application.Read.All'
        },
        # Conditional Access
        @{
            GraphUri = 'identity/conditionalAccess/policies'
            Path =  'Identity/Conditional/AccessPolicies'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'ConditionalAccess')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },
        @{
            GraphUri = 'identity/conditionalAccess/namedLocations'
            Path =  'Identity/Conditional/NamedLocations'
            Tag = @('All', 'Config', 'ConditionalAccess')
            DelegatedPermission = 'Policy.Read.All'
            ApplicationPermission = 'Policy.Read.All'
        },

        # Identity Governance,
        @{
            GraphUri = 'identityGovernance/entitlementManagement/accessPackages'
            Path = 'IdentityGovernance\EntitlementManagement\AccessPackages'
            ApiVersion = 'beta'
            Tag = @('All', 'Governance', 'EntitlementManagement')
            DelegatedPermission = 'EntitlementManagement.Read.All'
            ApplicationPermission = 'EntitlementManagement.Read.All'
            Children = @(
                @{
                    Command = 'Get-EEAccessPackageAssignmentPolicies'
                    Path = 'AssignmentPolicies'
                    Tag = @('All', 'Governance', 'EntitlementManagement')
                    DelegatedPermission = 'EntitlementManagement.Read.All'
                    ApplicationPermission = 'EntitlementManagement.Read.All'
                },
                @{
                    Command = 'Get-EEAccessPackageAssignments'
                    Path = 'Assignments'
                    Tag = @('All', 'Governance', 'EntitlementManagement')
                    DelegatedPermission = 'EntitlementManagement.Read.All'
                    ApplicationPermission = 'EntitlementManagement.Read.All'
                },
                @{
                    Command = 'Get-EEAccessPackageResourceScopes'
                    Path = 'ResourceScopes'
                    Tag = @('All', 'Governance', 'EntitlementManagement')
                    DelegatedPermission = 'EntitlementManagement.Read.All'
                    ApplicationPermission = 'EntitlementManagement.Read.All'
                }
            )
        },
        @{
            GraphUri = 'identityGovernance/accessReviews/definitions'
            Path = 'IdentityGovernance/AccessReviews'
            ApiVersion = 'beta'
            Tag = @('All', 'AccessReviews', 'Governance')
            DelegatedPermission = 'AccessReview.Read.All'
            ApplicationPermission = 'AccessReview.Read.All'
            Children = @(
                @{
                    GraphUri = 'identityGovernance/accessReviews/definitions/{id}/instances'
                    Path = ''
                    Tag = @('All', 'AccessReviews', 'Governance')
                    DelegatedPermission = 'AccessReview.Read.All'
                    ApplicationPermission = 'AccessReview.Read.All'
                    Children = @(
                        @{
                            GraphUri = 'identityGovernance/accessReviews/definitions/{id}/instances/{id}/contactedReviewers'
                            Path = 'Reviewers'
                            ApiVersion = 'beta'
                            Tag = @('All', 'AccessReviews', 'Governance')
                            DelegatedPermission = 'AccessReview.Read.All'
                            ApplicationPermission = 'AccessReview.Read.All'
                        }
                    )
                }
            )
        },
        @{
            GraphUri = 'identityGovernance/termsOfUse/agreements'
            Path = 'IdentityGovernance/TermsOfUse/Agreements'
            Tag = @('All', 'Config', 'Governance')
            DelegatedPermission = 'Agreement.Read.All'
        },
        @{
            GraphUri = 'identityGovernance/entitlementManagement/connectedOrganizations'
            Path = 'IdentityGovernance/EntitlementManagement/ConnectedOrganizations'
            ApiVersion = 'beta'
            Tag = @('All', 'Config')
            DelegatedPermission = 'EntitlementManagement.Read.All'
            ApplicationPermission = 'EntitlementManagement.Read.All'
            Children = @(
                @{
                    GraphUri = 'identityGovernance/entitlementManagement/connectedOrganizations/{id}/externalSponsors'
                    Path = 'ExternalSponsors'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'Governance')
                    DelegatedPermission = 'EntitlementManagement.Read.All'
                    ApplicationPermission = 'EntitlementManagement.Read.All'
                },
                @{
                    GraphUri = 'identityGovernance/entitlementManagement/connectedOrganizations/{id}/internalSponsors'
                    Path = 'InternalSponsors'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'Governance')
                    DelegatedPermission = 'EntitlementManagement.Read.All'
                    ApplicationPermission = 'EntitlementManagement.Read.All'
                }
            )    
        },            
        @{
            GraphUri = 'identityGovernance/entitlementManagement/settings'
            Path = 'IdentityGovernance/EntitlementManagement/Settings'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'Governance')
            DelegatedPermission = 'EntitlementManagement.Read.All'
            ApplicationPermission = 'EntitlementManagement.Read.All'
        },
        @{
            GraphUri = 'AdministrativeUnits'
            Path = 'AdministrativeUnits'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'AdministrativeUnits')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
            Children = @(
                @{
                    GraphUri = 'administrativeUnits/{id}/members'
                    Select = 'Id'
                    Path = 'Members'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'AdministrativeUnits')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'administrativeUnits/{id}/scopedRoleMembers'
                    Path = 'ScopedRoleMembers'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'AdministrativeUnits')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'administrativeUnits/{id}/extensions'
                    Path = 'Extensions'
                    ApiVersion = 'beta'
                    Tag = @('All', 'Config', 'AdministrativeUnits')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                }
            )
        },

        # PIM
        @{
            GraphUri = 'privilegedAccess/aadroles/resources'
            Path = 'PrivilegedAccess/AADRoles/Resources'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'PIM', 'PIMAAD')
            DelegatedPermission = 'PrivilegedAccess.ReadWrite.AzureAD'
            ApplicationPermission = 'PrivilegedAccess.Read.AzureAD'
            Children = @(
                @{
                    GraphUri = 'privilegedAccess/aadroles/resources/{id}/roleDefinitions'
                    Path = 'RoleDefinitions'
                    ApiVersion = 'beta'
                    Filter = "Type ne 'BuiltInRole'"
                    Tag = @('All', 'Config', 'PIM', 'PIMAAD')
                    DelegatedPermission = 'PrivilegedAccess.ReadWrite.AzureAD'
                    ApplicationPermission = 'PrivilegedAccess.Read.AzureAD'
                },
                @{
                    GraphUri = 'privilegedAccess/aadroles/resources/{id}/roleSettings'
                    Path = 'RoleSettings'
                    ApiVersion = 'beta'
                    Filter = 'isDefault eq false'
                    Tag = @('All', 'Config', 'PIM', 'PIMAAD')
                    DelegatedPermission = 'PrivilegedAccess.ReadWrite.AzureAD'
                    ApplicationPermission = 'PrivilegedAccess.Read.AzureAD'
                },
                @{
                    GraphUri = 'privilegedAccess/aadroles/resources/{id}/roleAssignments'
                    Path = 'RoleAssignments'
                    ApiVersion = 'beta'
                    Filter = 'endDateTime eq null'
                    Tag = @('All', 'Config', 'PIM', 'PIMAAD')
                    DelegatedPermission = 'PrivilegedAccess.ReadWrite.AzureAD'
                    ApplicationPermission = 'PrivilegedAccess.Read.AzureAD'
                }
            )
        },
        @{
            GraphUri = 'privilegedAccess/azureResources/resources'
            Path = 'PrivilegedAccess/AzureResources/Resources'
            ApiVersion = 'beta'
            IgnoreError = 'The tenant has not onboarded to PIM.'
            Tag = @('All', 'Config', 'PIM', 'PIMAzure')
            DelegatedPermission = 'PrivilegedAccess.ReadWrite.AzureResources'
            ApplicationPermission = 'PrivilegedAccess.Read.AzureResources'
            Children = @(
                @{
                    GraphUri = 'privilegedAccess/azureResources/resources/{id}/roleDefinitions'
                    Path = 'RoleDefinitions'
                    ApiVersion = 'beta'
                    Filter = "Type ne 'BuiltInRole'"
                    Tag = @('All', 'Config', 'PIM', 'PIMAzure')
                    DelegatedPermission = 'PrivilegedAccess.ReadWrite.AzureResources'
                    ApplicationPermission = 'PrivilegedAccess.Read.AzureResources'
                },
                @{
                    GraphUri = 'privilegedAccess/azureResources/resources/{id}/roleSettings'
                    Path = 'RoleSettings'
                    ApiVersion = 'beta'
                    Filter = 'isDefault eq false'
                    Tag = @('All', 'Config', 'PIM', 'PIMAzure')
                    DelegatedPermission = 'PrivilegedAccess.ReadWrite.AzureResources'
                    ApplicationPermission = 'PrivilegedAccess.Read.AzureResources'
                },
                @{
                    GraphUri = 'privilegedAccess/azureResources/resources/{id}/roleAssignments'
                    Path = 'RoleAssignments'
                    ApiVersion = 'beta'
                    Filter = 'endDateTime eq null'
                    Tag = @('All', 'Config', 'PIM', 'PIMAzure')
                    DelegatedPermission = 'PrivilegedAccess.ReadWrite.AzureResources'
                    ApplicationPermission = 'PrivilegedAccess.Read.AzureResources'
                }
            )
        },

        #Application Proxy
        @{
            GraphUri = 'onPremisesPublishingProfiles/provisioning'
            QueryParameters = @{ expand = 'publishedResources,agents,agentGroups' }
            Path = 'OnPremisesPublishingProfiles/Provisioning.json'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'AppProxy')
            DelegatedPermission = 'OnPremisesPublishingProfiles.ReadWrite.All'
        },
        @{
            GraphUri = 'onPremisesPublishingProfiles/provisioning/publishedResources'
            QueryParameters = @{ expand = 'agentGroups' }
            Path = 'OnPremisesPublishingProfiles/Provisioning/PublishedResources'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'AppProxy')
            DelegatedPermission = 'OnPremisesPublishingProfiles.ReadWrite.All'
        },
        @{
            GraphUri = 'onPremisesPublishingProfiles/provisioning/agentGroups'
            QueryParameters = @{ expand = 'agents,publishedResources' }
            Path = 'OnPremisesPublishingProfiles/Provisioning/AgentGroups'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'AppProxy')
            DelegatedPermission = 'OnPremisesPublishingProfiles.ReadWrite.All'
        },
        @{
            GraphUri = 'onPremisesPublishingProfiles/provisioning/agents'
            QueryParameters = @{ expand = 'agentGroups' }
            Path = 'OnPremisesPublishingProfiles/Provisioning/Agents'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'AppProxy')
            DelegatedPermission = 'OnPremisesPublishingProfiles.ReadWrite.All'
        },
        @{
            GraphUri = 'onPremisesPublishingProfiles/applicationProxy/connectors'
            Path = 'OnPremisesPublishingProfiles/ApplicationProxy/Connectors'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'AppProxy')
            DelegatedPermission = 'Directory.ReadWrite.All'
        },
        @{
            GraphUri = 'onPremisesPublishingProfiles/applicationProxy/connectorGroups'
            Path = 'OnPremisesPublishingProfiles/ApplicationProxy/ConnectorGroups'
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'AppProxy')
            DelegatedPermission = 'Directory.ReadWrite.All'
            Children = @(
                @{
                    GraphUri = 'onPremisesPublishingProfiles/applicationProxy/connectorGroups/{id}/applications'
                    Path = 'Applications'
                    ApiVersion = 'beta'
                    IgnoreError = 'ApplicationsForGroup_NotFound'
                    Tag = @('All', 'Config', 'AppProxy')
                    DelegatedPermission = 'Directory.ReadWrite.All'
                },
                @{
                    GraphUri = 'onPremisesPublishingProfiles/applicationProxy/connectorGroups/{id}/members'
                    Path = 'Members'
                    ApiVersion = 'beta'
                    IgnoreError = 'ConnectorGroup_NotFound'
                    Tag = @('All', 'Config', 'AppProxy')
                    DelegatedPermission = 'Directory.ReadWrite.All'
                }
            )
        },

        # Groups
        # need to looks at app roles assignements
        # expanding app roles assignements breaks 'ne' filtering (needs eventual consistency and count)
        @{
            GraphUri = 'groups'
            Filter = "groupTypes/any(c:c eq 'DynamicMembership')" 
            Path = 'Groups'
            QueryParameters = @{ '$count' = 'true'; expand = 'extensions' }
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'Groups')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
            Children = @(
                @{
                    GraphUri =  'groups/{id}/owners'
                    Select = 'id, userPrincipalName, displayName'
                    Path = 'Owners'
                    Tag = @('All', 'Config', 'Groups')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                }
            )
        },
        @{
            GraphUri = 'groups'
            Filter = "NOT(groupTypes/any(c:c eq 'DynamicMembership'))" 
            Path = 'Groups'
            QueryParameters = @{ '$count' = 'true'; expand = 'extensions' }
            ApiVersion = 'beta'
            Tag = @('All', 'Config', 'Groups')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
            Children = @(
                @{
                    GraphUri =  'groups/{id}/owners'
                    Select = 'id, userPrincipalName, displayName'
                    Path = 'Owners'
                    Tag = @('All', 'Config', 'Groups')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'groups/{id}/members' 
                    Select = 'id, userPrincipalName, displayName'
                    Path = 'Members'
                    Tag = @('All', 'Groups')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                }
            )
        },        
        @{
            GraphUri = 'groupSettings'
            Path = 'GroupSettings'
            Tag = @('All', 'Config', 'Groups')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
        },

        # Applications
        @{
            GraphUri = 'applications'
            Path = 'Applications'
            Tag = @('All', 'Applications')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
            Children = @(
                @{
                    GraphUri = 'applications/{id}/extensionProperties'
                    Path = 'ExtensionProperties'
                    Tag = @('All', 'Applications')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'applications/{id}/owners'
                    Select = 'id, userPrincipalName, displayName'
                    Path = 'Owners'
                    Tag = @('All', 'Applications')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'applications/{id}/tokenIssuancePolicies'
                    Path = 'TokenIssuancePolicies'
                    Tag = @('All', 'Applications')
                    DelegatedPermission = 'Policy.Read.All'
                    ApplicationPermission = 'Policy.Read.All','Application.ReadWrite.All'
                },
                @{
                    GraphUri = 'applications/{id}/tokenLifetimePolicies'
                    Path = 'TokenLifetimePolicies'
                    Tag = @('All', 'Applications')
                    DelegatedPermission = 'Policy.Read.All'
                    ApplicationPermission = 'Policy.Read.All','Application.ReadWrite.All'
                }
            )
        },

        # Service Principals
        @{
            GraphUri = 'servicePrincipals'
            Path = 'ServicePrincipals'
            Tag = @('All', 'ServicePrincipals')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
            Children = @(
                @{
                    GraphUri = 'servicePrincipals/{id}/appRoleAssignments'
                    Path = 'AppRoleAssignments'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'servicePrincipals/{id}/appRoleAssignedTo'
                    Path = 'AppRoleAssignedTo'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'servicePrincipals/{id}/oauth2PermissionGrants'
                    Path = 'Oauth2PermissionGrants'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'servicePrincipals/{id}/delegatedPermissionClassifications'
                    Path = 'DelegatedPermissionClassifications'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'servicePrincipals/{id}/owners'
                    Select = 'id, userPrincipalName, displayName'
                    Path = 'Owners'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Directory.Read.All'
                    ApplicationPermission = 'Directory.Read.All'
                },
                @{
                    GraphUri = 'servicePrincipals/{id}/claimsMappingPolicies'
                    Path = 'claimsMappingPolicies'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Policy.Read.All'
                    ApplicationPermission = 'Policy.Read.All','Application.ReadWrite.All'
                },
                @{
                    GraphUri = 'servicePrincipals/{id}/homeRealmDiscoveryPolicies'
                    Path = 'homeRealmDiscoveryPolicies'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Policy.Read.All'
                    ApplicationPermission = 'Policy.Read.All','Application.ReadWrite.All'
                },
                @{
                    GraphUri = 'servicePrincipals/{id}/tokenIssuancePolicies'
                    Path = 'tokenIssuancePolicies'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Policy.Read.All'
                    ApplicationPermission = 'Policy.Read.All','Application.ReadWrite.All'
                },
                @{
                    GraphUri = 'servicePrincipals/{id}/tokenLifetimePolicies'
                    Path = 'tokenLifetimePolicies'
                    Tag = @('All', 'ServicePrincipals')
                    DelegatedPermission = 'Policy.Read.All'
                    ApplicationPermission = 'Policy.Read.All','Application.ReadWrite.All'
                }
            )
        },
        
        # Users
        # Todo look at app roles assignments
        @{
            GraphUri = 'users'
            Path = 'Users'
            Filter = $null
            QueryParameters = @{ '$count' = 'true'; expand = "extensions" }
            ApiVersion = 'beta'
            Tag = @('All', 'Users')
            DelegatedPermission = 'Directory.Read.All'
            ApplicationPermission = 'Directory.Read.All'
        }
    )
}
#endregion

#region Get-EERequiredScopes.ps1

<#
 .Synopsis
  Gets the required scopes for schema

 .Description
  Gets the require scopes for schema

 .Example
  Get-EERequiredScopes
#>


function Get-EERequiredScopes {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)] 
        [ValidateSet('Delegated','Application')]
        [string]$PermissionType,
        [Parameter(Mandatory = $false)]
        [object]$ExportSchema
    )

    if (!$ExportSchema) {
        $ExportSchema = Get-EEDefaultSchema
    }

    $scopeProperty = "DelegatedPermission"
    if ($PermissionType -eq "Application") {
        $scopeProperty = "ApplicationPermission"
    }

    $scopes = @()
    foreach($entry in $ExportSchema) {
        $entryScopes = Get-ObjectProperty $entry $scopeProperty
        $command = Get-ObjectProperty $entry 'Command'
        $graphUri = Get-ObjectProperty $entry 'GraphUri'
        $entryType = "graphuri"
        $tocall = $graphUri
        if ($command) {
            $entryType = "command"
            $tocall = $command
        }

        if (!$entryScopes) {
            write-warning "call to $entryType '$tocall' doesn't provide $PermissionType permissions"
        }
        
        foreach ($entryScope in $entryScopes) {
            if ($entryScope -notin $scopes) {
                $scopes += $entryScope
            }
        }
        if ($entry.ContainsKey('Children')) {
            $childScopes = Get-EERequiredScopes -PermissionType $PermissionType -ExportSchema $entry.Children
            foreach ($entryScope in $childScopes) {
                if ($entryScope -notin $scopes) {
                    $scopes += $entryScope
                }
            }
        }
    }

    $scopes | sort-object
}
#endregion

#region Get-EEAccessPackageAssignmentPolicies.ps1

<#
 .Synopsis
  Gets the list of accessPackageAssignmentPolicies

 .Description
  GET /identityGovernance/entitlementManagement/accessPackageAssignmentPolicies
  https://docs.microsoft.com/en-us/graph/api/accesspackageassignmentpolicy-list?view=graph-rest-beta&tabs=http

 .Example
  EEAccessPackagesAssignmentPolicies
#>


Function Get-EEAccessPackageAssignmentPolicies  {
  [CmdletBinding()]
  param
  (
      [Parameter(Mandatory = $true)]
      [string[]]$Parents
  )
  Invoke-Graph 'identityGovernance/entitlementManagement/accessPackageAssignmentPolicies' -Filter "(accessPackage/id eq '$($Parents[0])')"  -ApiVersion 'beta'
}
#endregion

#region Get-EEAccessPackageAssignments.ps1

<#
 .Synopsis
  Gets the list of accessPackageAssignments

 .Description
  GET /identityGovernance/entitlementManagement/accessPackageAssignments?$filter=accessPackage/id eq
  https://docs.microsoft.com/en-us/graph/api/accesspackageassignment-list?view=graph-rest-beta&tabs=http

 .Example
  Get-EEAccessPackagesAssignments
#>


Function Get-EEAccessPackageAssignments {
  [CmdletBinding()]
  param
  (
      [Parameter(Mandatory = $true)]
      [string[]]$Parents
  )
    Invoke-Graph 'identityGovernance/entitlementManagement/accessPackageAssignments' -Filter "(accessPackage/id eq '$($Parents[0])')"  -ApiVersion 'beta'
}
#endregion

#region Get-EEAccessPackageResourceScopes.ps1

<#
 .Synopsis
  Gets the list of businessflowtemplatesRetrieve a list of accessPackage objects.

 .Description
  GET /identityGovernance/entitlementManagement/accessPackages/{id}?$expand=accessPackageResourceRoleScopes($expand=accessPackageResourceRole,accessPackageResourceScope)
  https://docs.microsoft.com/en-us/graph/api/accesspackage-list-accesspackageresourcerolescopes?view=graph-rest-beta&tabs=http

 .Example
  Get-EEAccessPackageResourceScopes
#>


Function Get-EEAccessPackageResourceScopes {
  [CmdletBinding()]
  param
  (
      [Parameter(Mandatory = $true)]
      [string[]]$Parents
  )
    Invoke-Graph "identityGovernance/entitlementManagement/accessPackages/$($Parents[0])" -QueryParameters @{expand='accessPackageResourceRoleScopes(expand=accessPackageResourceRole,accessPackageResourceScope)'} -ApiVersion 'beta'
}

#endregion

#endregion

<#
.DISCLAIMER
    THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
    ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
    THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    PARTICULAR PURPOSE.

    Copyright (c) Microsoft Corporation. All rights reserved.
#>


## Set Strict Mode for Module. https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/set-strictmode
Set-StrictMode -Version 3.0

## Initialize Module Configuration

## Initialize Module Variables
Export-ModuleMember -Function @('Connect-EntraExporter','Export-Entra') -Cmdlet @() -Variable @() -Alias @()


# SIG # Begin signature block
# MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCNcYy0PjuC1XtQ
# 6y8a33Mievf0pBi7q3B6CV7z5Wy8J6CCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGaIwghmeAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIDHVcivvxd6WEUy8ImIf3od4
# jXJZ3ZoBiqkcNPXBoblRMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEADwPQnx2Kz23Ti4Qho8LukCoX08ncH3TArd6xh5m4uBZoxY5k4lgEwlHQ
# oiP+Ed/Bkx1HvUgsnTH9CLdL5KYm47MYqp0eMbdWIVGVCyNhTgKMXSUwUThRfBVJ
# VUtNR+ZdNQ+/mRWeRiHydiJuKr605cGFxQUuPq4fU1vavy0ttCRSkgNHEu0xy02m
# vgTGcULAe7FdT/ja/t5Y778sXawHIFCK+/YdIyR/0MBFdW1iCrgYzY9TNnH+/Rg2
# dqRMqD8/RmS1XdlkFg9dFgXoHy4w0DG1szz9KnPUiXiEH0lgoWTW5Io9SziZgdIv
# kt2g74hQ6QLAxQBDNTmhCElJwFZxPqGCFywwghcoBgorBgEEAYI3AwMBMYIXGDCC
# FxQGCSqGSIb3DQEHAqCCFwUwghcBAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCXC81k2G9i4IuhZ1csfpaolirhLYKBNog1DKtx7vCb5AIGZN5Rf3kr
# GBMyMDIzMDgxODEzMDQxNS4zNTdaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRezCCBycwggUPoAMCAQICEzMAAAGybkADf26plJIAAQAAAbIwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjAxWhcNMjMxMjE0MjAyMjAxWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqiZTIde/lQ4rC+Bml5f/Wu
# q/xKTxrfbG23HofmQ+qZAN4GyO73PF3y9OAfpt7Qf2jcldWOGUB+HzBuwllYyP3f
# x4MY8zvuAuB37FvoytnNC2DKnVrVlHOVcGUL9CnmhDNMA2/nskjIf2IoiG9J0qLY
# r8duvHdQJ9Li2Pq9guySb9mvUL60ogslCO9gkh6FiEDwMrwUr8Wja6jFpUTny8tg
# 0N0cnCN2w4fKkp5qZcbUYFYicLSb/6A7pHCtX6xnjqwhmJoib3vkKJyVxbuFLRhV
# XxH95b0LHeNhifn3jvo2j+/4QV10jEpXVW+iC9BsTtR69xvTjU51ZgP7BR4YDEWq
# 7JsylSOv5B5THTDXRf184URzFhTyb8OZQKY7mqMh7c8J8w1sEM4XDUF2UZNy829N
# VCzG2tfdEXZaHxF8RmxpQYBxyhZwY1rotuIS+gfN2eq+hkAT3ipGn8/KmDwDtzAb
# nfuXjApgeZqwgcYJ8pDJ+y/xU6ouzJz1Bve5TTihkiA7wQsQe6R60Zk9dPdNzw0M
# K5niRzuQZAt4GI96FhjhlUWcUZOCkv/JXM/OGu/rgSplYwdmPLzzfDtXyuy/GCU5
# I4l08g6iifXypMgoYkkceOAAz4vx1x0BOnZWfI3fSwqNUvoN7ncTT+MB4Vpvf1QB
# ppjBAQUuvui6eCG0MCVNAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUmfIngFzZEZlP
# kjDOVluBSDDaanEwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBANxHtu3FzIabaDbW
# qswdKBlAhKXRCN+5CSMiv2TYa4i2QuWIm+99piwAhDhADfbqor1zyLi95Y6GQnvI
# WUgdeC7oL1ZtZye92zYK+EIfwYZmhS+CH4infAzUvscHZF3wlrJUfPUIDGVP0lCY
# Vse9mguvG0dqkY4ayQPEHOvJubgZZaOdg/N8dInd6fGeOc+0DoGzB+LieObJ2Q0A
# tEt3XN3iX8Cp6+dZTX8xwE/LvhRwPpb/+nKshO7TVuvenwdTwqB/LT6CNPaElwFe
# KxKrqRTPMbHeg+i+KnBLfwmhEXsMg2s1QX7JIxfvT96md0eiMjiMEO22LbOzmLMN
# d3LINowAnRBAJtX+3/e390B9sMGMHp+a1V+hgs62AopBl0p/00li30DN5wEQ5If3
# 5Zk7b/T6pEx6rJUDYCti7zCbikjKTanBnOc99zGMlej5X+fC/k5ExUCrOs3/VzGR
# CZt5LvVQSdWqq/QMzTEmim4sbzASK9imEkjNtZZyvC1CsUcD1voFktld4mKMjE+u
# DEV3IddD+DrRk94nVzNPSuZXewfVOnXHSeqG7xM3V7fl2aL4v1OhL2+JwO1Tx3B0
# irO1O9qbNdJk355bntd1RSVKgM22KFBHnoL7Js7pRhBiaKmVTQGoOb+j1Qa7q+ci
# xGo48Vh9k35BDsJS/DLoXFSPDl4mMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtcwggJAAgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow
# ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAjhJ+EeySRfn2KCNsjn9cF9AUSTqggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOiJeK4wIhgPMjAyMzA4MTgxMjU3MThaGA8yMDIzMDgxOTEyNTcxOFowdzA9Bgor
# BgEEAYRZCgQBMS8wLTAKAgUA6Il4rgIBADAKAgEAAgIKBgIB/zAHAgEAAgIRVjAK
# AgUA6IrKLgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAFbs8I6wq+qc789M
# DRAu6MRl2g9hWp0+i9q6cWurDsiocUsOb0ixx9l6orOYQh6mFlB6mWk8vWRFCq3+
# biuAv5S110Btu4HXzJ1KhghfOyL5Ivkf2HAXSY9cMibDOE0S1t48vIK83DpPDNfR
# xpjCnmIwZ4sqO1ZMYmHsab06aiyfMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAGybkADf26plJIAAQAAAbIwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQg9Cp67G7xIVW5LLjbjX86oxSViVlOMVQBUof4Wo5GzhAwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCBTeM485+E+t4PEVieUoFKX7PVyLo/nzu+htJPC
# G04+NTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# sm5AA39uqZSSAAEAAAGyMCIEIJz6iLPWjOOqNZegNSjofr7W2jSUfA80FXvMwLWu
# 6eoIMA0GCSqGSIb3DQEBCwUABIICAHuEXIX6VokpKXJvUqggnQC2gj3vLMIsGNdS
# xRjzt/W3UHOIJnIJdq+gIdjn1SrckxdVu0tZZOkCDfE9Q2CdExP7tAKFehXy1/KH
# MAmyoY6D4pPkaBw0WbJzGpQShCM+UWdVoQQ+vGKvB1h8as5FRA0c3POcHNoaH4aw
# 26fKUobF1LQkyPiF2pos6JhOB/QeEVk4gJCDe4qhtzP+bAB6zdsuXJTKBN5CuW0V
# Pd9KEl86p8XiMEhg/YYg65whLfqm0goL5ZG/DK9K+B5CUZhj+DSyfXQCgvZ1qNmA
# h+KO/+POYT9neNfaFu4i6bb+DTmVzNklu65RO8WjxcF4LNP1RLy1FsJH+XNrYwKh
# BDTmcpMRTmApeQx98z8OgNshnFLN+PnFK7kJulo43ZDnQpfItjhto0uPIUWaYtJC
# WvMowO7FiLmPpvdBzPPT9IoXMusRfTu0GyV/sHdgCks75h+RbG/q1llF28Th7ZPj
# 6I82zRNc1YHYhcgIOvFo/ALbVcj6ndGY79+XVEgoF1pphAKeJLQ9eP80eUaHyTC9
# m5AZdWhArwkYpbXtXuiuEoESs352cvueWK0bCJG1QvDWhWW7RGQH4WET3LtsQb8H
# xpQ6iCZlxArCY6aDvMBZEvYSBBTYXVRzE2KgoFa943ZRrVLLflomG7khawkFXPLn
# yIa8qpSz
# SIG # End signature block