D365SalesIAM.psm1

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

#====================================================================
# if (-not(get-module -ListAvailable -Name JWTDetails)) {
# Install-Module -Name JWTDetails -AllowClobber -Force
# }

# if (-not(get-module -ListAvailable -Name MSAL.PS)) {
# Install-Module -Name MSAL.PS -AllowClobber -Force
# }

# JWT Token Module
# Import-Module JWTDetails
# Set-ExecutionPolicy RemoteSigned
Import-Module -Name $PSScriptRoot\Modules\JWTDetails\1.0.3\JWTDetails.psm1 

# MSAL.PS Token Module
# Import-Module MSAL.PS
# Import-Module -Name $PSScriptRoot\Modules\MSAL.PS\4.37.0.0\MSAL.PS.psm1

function Set-D365SalesGlobals {
    <#
.SYNOPSIS
Set Global variables used in D365 oData API functions

.DESCRIPTION
Set Global variables used in D365 oData API functions

.PARAMETER D365SalesOrgURI
D365 Org Id.
e.g. https://YOUROrg.api.crm6.dynamics.com/

.PARAMETER EntraIDTenantID
Entra ID TenantId
e.g. https://yourtenant.onmicrosoft.com or d227d874-8033-4640-8a16-a86e1d3e9eee

.PARAMETER D365SalesCreds
PS Credential object with EntraID Application Registration Client (Application) ID as UserName and EntraID Application Registration Client Secret as the Password.
e.g. d55a2c66-727a-460d-ba91-56bd167ccdad & System.Security.SecureString

.INPUTS
Token from Pipeline

.OUTPUTS
PowerShell Object

SYNTAX
Set-D365SalesGlobals

.EXAMPLE
PS> Set-D365SalesGlobals -D365SalesOrgURI "https://yourtenant.api.crm6.dynamics.com" -EntraIDTenantID "https://yourtenant.onmicrosoft.com" -D365SalesCreds $myD365SalesCreds

.LINK
https://blog.darrenjrobinson.com

#>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$D365SalesOrgURI,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$EntraIDTenantID,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [pscredential]$D365SalesCreds
    )

    if (-not $D365SalesOrgURI.EndsWith("/")) {
        $D365SalesOrgURI = "$($D365SalesOrgURI)/"
    }

    $Global:D365SalesOrgURI = $D365SalesOrgURI
    $Global:EntraIDTenantID = $EntraIDTenantID
    $Global:D365SalesAPIURI = "$($D365SalesOrgURI)api/data/v9.2"
    $Global:d365SalesCreds = $D365SalesCreds

    if ($null -ne $D365SalesCreds.UserName -and $null -ne $D365SalesCreds.Password) {
        Try {
            $Global:D365SalesToken = Get-D365SalesToken 
            if (get-module -name JWTDetails) {
                Get-JWTDetails -token ($Global:D365SalesToken)
            }
        }
        catch {
            Write-Error $_ -ErrorAction Continue
        }
    }
}

Function Get-D365SalesToken {
    <#
.SYNOPSIS
Acquires an access token for Dynamics 365 Sales using OAuth2 client credentials authentication.

.DESCRIPTION
The Get-D365SalesToken function retrieves an access token for Dynamics 365 Sales using the OAuth2 client credentials grant flow. It requires the global variables $Global:d365SalesCreds, $Global:D365SalesOrgURI, and $Global:EntraIDTenantID to be set beforehand, which contain the Dynamics 365 Sales client ID, client secret, organization URI, and EntraID tenant ID, respectively. The function returns the acquired access token.

.PARAMETER
None.

.INPUTS
None.

.OUTPUTS
The acquired access token as a string.

.EXAMPLE
# Assuming $Global:d365SalesCreds, $Global:D365SalesOrgURI, and $Global:EntraIDTenantID are set using Set-D365SalesGlobals
$d365SalesToken = Get-D365SalesToken

#>


    [cmdletbinding()]
    param()

    $tokenBody = @{
        "grant_type"    = "client_credentials"
        "client_id"     = $Global:d365SalesCreds.UserName
        "client_secret" = $Global:d365SalesCreds.GetNetworkCredential().Password #($Global:d365SalesCreds.Password | ConvertFrom-SecureString)
        "resource"      = $Global:D365SalesOrgURI 
    }
    
    try {
        $tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$Global:EntraIDTenantID/oauth2/token" -Method POST -Body $tokenBody
        return $tokenResponse.access_token
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Get-D365SalesSystemUsers {
    <#
    
    .SYNOPSIS
Retrieves Dynamics 365 Sales system users.

.DESCRIPTION
The Get-D365SalesSystemUsers function retrieves Dynamics 365 Sales system users using the REST API. It optionally takes a systemuser ID as a parameter to retrieve a specific user. If no parameter is specified, all system users are retrieved. The function returns the user data as a PowerShell object.

.PARAMETER systemuserid
(Optional) The ID of the system user to retrieve.

.INPUTS
None.

.OUTPUTS
value: A PowerShell object containing the user data.

.EXAMPLE
Retrieving all system users
Get-D365SalesSystemUsers

.EXAMPLE
Retrieving a specific system user:
Get-D365SalesSystemUsers -systemuserid "00000000-0000-0000-0000-000000000000"
Get-D365SalesSystemUsers -filter "internalemailaddress eq 'some.email@address'"

    #>
    
    
    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$systemuserid,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$filter,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$select
    )

    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {
        if ($systemuserid) {
            $uri = "$($Global:D365SalesAPIURI)/systemusers($($systemuserid))"

            if ($select) {
                $uri = $uri + "?`$select=$($select)"
            }

            $response = Invoke-RestMethod -Method Get -Uri $uri -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" }
            return $response
        }
        elseif ($filter) {

            $uri = "$($Global:D365SalesAPIURI)/systemusers?`$filter=$($filter)"

            if ($select) {
                $uri = $uri + "&`$select=$($select)"
            }

            $response = Invoke-RestMethod -Method Get `
                -Uri $uri `
                -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" }
            return $response.value[0]                                            
        }
        else {

            $uri = "$($Global:D365SalesAPIURI)/systemusers"

            if ($select) {
                $uri = $uri + "?`$select=$($select)"
            }

            $response = Invoke-RestMethod -Method Get -Uri $uri -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" }
            return $response.value
        }
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Get-D365SalesSystemRoles {
    <#
        .SYNOPSIS
Retrieves Dynamics 365 Sales system roles.

.DESCRIPTION
The Get-D365SalesSystemRoles function retrieves Dynamics 365 Sales system roles using the REST API. It optionally takes a role ID as a parameter to retrieve a specific role. If no parameter is specified, all system roles are retrieved. The function returns the role data as a PowerShell object.

.PARAMETER roleid
(Optional) The ID of the system role to retrieve.

.INPUTS
None.

.OUTPUTS
A PowerShell object containing the role data.

.EXAMPLE
Retrieving all system roles
Get-D365SalesSystemRoles

.EXAMPLE
Retrieving a specific system role
Get-D365SalesSystemRoles -roleid "00000000-0000-0000-0000-000000000000"

        #>
    
    
    [cmdletbinding()]
    param( 
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$roleid,
        
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$select
    )

    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {
        if ($roleid) {
            $uri = "$($Global:D365SalesAPIURI)/roles($($roleid))"

            if ($select) {
                $uri = $uri + "?`$select=$($select)"
            }

            $response = Invoke-RestMethod -Method Get -Uri $uri -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" }
            return $response
        }
        else {

            $uri = "$($Global:D365SalesAPIURI)/roles"

            if ($select) {
                $uri = $uri + "?`$select=$($select)"
            }

            $response = Invoke-RestMethod -Method Get -Uri $uri -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" }
            return $response.value
        }
    }
    catch {
        Write-Error $_

        throw GetErrorMessage $_
    }
}

Function Get-D365SalesSystemUserRoles {
    <#
    
    .SYNOPSIS
Retrieves the system user roles associated with a specified Dynamics 365 Sales system user.

.DESCRIPTION
The Get-D365SalesSystemUserRoles function retrieves the system user roles associated with a specified Dynamics 365 Sales system user using the REST API. It requires the system user ID as a mandatory parameter. The function returns the system user role data as a PowerShell object.

.PARAMETER systemuserid
(Mandatory) The ID of the system user for which to retrieve system user roles.

.INPUTS
None.

.OUTPUTS
value: A PowerShell object containing the system user role data.

.EXAMPLE
Get-D365SalesSystemUserRoles -systemuserid "00000000-0000-0000-0000-000000000000"

    #>
    
    
    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$systemuserid,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$select
    )

    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {

        $uri = "$($Global:D365SalesAPIURI)/systemusers($($systemuserid))/systemuserroles_association"

        if ($select) {
            $uri = $uri + "?`$select=$($select)"
        }

        $response = Invoke-RestMethod -Method Get -Uri $uri -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" }
        return $response.value  
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Get-D365SalesRolesSystemUserMembership {
    <#
    
    .SYNOPSIS
Retrieves the system users associated with a specified Dynamics 365 Sales system role.

.DESCRIPTION
The Get-D365SalesRolesSystemUserMembership function retrieves the system users associated with a specified Dynamics 365 Sales system role using the REST API. It requires the system role ID as a mandatory parameter. The function returns the system user membership data as a PowerShell object.

.PARAMETER roleid
(Mandatory) The ID of the system role for which to retrieve associated system users.

.INPUTS
None.

.OUTPUTS
value: A PowerShell object containing the system user membership data.

.EXAMPLE
Get-D365SalesRolesSystemUserMembership -roleid "00000000-0000-0000-0000-000000000000"

    #>
    
    
    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$roleid,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$select
    )

    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {

        $uri = "$($Global:D365SalesAPIURI)/roles($($roleid))/systemuserroles_association"

        if ($select) {
            $uri = $uri + "?`$select=$($select)"
        }

        $response = Invoke-RestMethod -Method Get -Uri $uri -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" }
        return $response.value  
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Add-D365SalesRoleToSystemUser {
    <#
.SYNOPSIS
Adds a Dynamics 365 Sales system role to a specified system user.

.DESCRIPTION
The Add-D365SalesRoleToSystemUser function adds a specified Dynamics 365 Sales system role to a specified system user using the REST API. It requires the system user ID and system role ID as mandatory parameters. The function returns the response from the REST API.

.PARAMETER systemuserid
(Mandatory) The ID of the system user to which to add the system role.

.PARAMETER roleid
(Mandatory) The ID of the system role to add to the system user.

.INPUTS
None.

.Outputs
The response from the REST API as a PowerShell object.

.EXAMPLE
Add-D365SalesRoleToSystemUser -systemuserid "00000000-0000-0000-0000-000000000000" -roleid "00000000-0000-0000-0000-000000000000"

    #>

    
    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$systemuserid,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$roleid
    )

    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {
        $body = @{"@odata.id" = "$($Global:D365SalesAPIURI)/roles($($roleid))" } 
        $response = Invoke-RestMethod -Method Post -Uri "$($Global:D365SalesAPIURI)/systemusers($($systemuserid))/systemuserroles_association/`$ref" -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" } -Body ($body | ConvertTo-Json)
        return $response
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Remove-D365SalesRoleFromSystemUser {

    <#
    .SYNOPSIS
Removes a Dynamics 365 Sales system role from a specified system user.

.DESCRIPTION
The Remove-D365SalesRoleFromSystemUser function removes a specified Dynamics 365 Sales system role from a specified system user using the REST API. It requires the system user ID and system role ID as mandatory parameters. The function returns the response from the REST API.

.PARAMETER systemuserid
(Mandatory) The ID of the system user from which to remove the system role.

.PARAMETER roleid
(Mandatory) The ID of the system role to remove from the system user.

.INPUTS
None.

.OUTPUTS
The response from the REST API as a PowerShell object.

.EXAMPLE
Remove-D365SalesRoleFromSystemUser -systemuserid "00000000-0000-0000-0000-000000000000" -roleid "00000000-0000-0000-0000-000000000000"

    #>


    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$systemuserid,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$roleid
    )

    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {
        $response = Invoke-RestMethod -Method Delete -Uri "$($Global:D365SalesAPIURI)/roles($($roleid))/systemuserroles_association($($systemuserid))/`$ref" -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" }
        return $response
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Get-D365SalesBusinessUnits {
    <#
    
.SYNOPSIS
Retrieves Dynamics 365 Sales business units.

.DESCRIPTION
The Get-D365SalesBusinessUnits function retrieves Dynamics 365 Sales business units using the REST API. It requires the Global:D365SalesToken variable to be set, which contains the access token for Dynamics 365 Sales. The function returns the business unit data as a PowerShell object.

.PARAMETER
None.

.INPUTS
None.

.OUTPUTS
value: A PowerShell object containing the business unit data.

.EXAMPLE
$Global:D365SalesToken = Get-D365SalesToken
$businessUnits = Get-D365SalesBusinessUnits
Write-Output $businessUnits

    #>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$filter,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$select
    )
    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {
        if ($filter) {
            $uri = "$($Global:D365SalesAPIURI)/businessunits?`$filter=$($filter)"
            if ($select) {
                $uri = $uri + "&`$select=$($select)"
            }            
        }
        else {
            $uri = "$($Global:D365SalesAPIURI)/businessunits"
            if ($select) {
                $uri = $uri + "?`$select=$($select)"
            }
        }

        $response = Invoke-RestMethod -method Get -URI $uri -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" } 
        return $response.value     
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Get-D365SalesOrganisations {

    <#

.SYNOPSIS
Retrieves Dynamics 365 Sales organizations.

.DESCRIPTION
The Get-D365SalesOrganisations function retrieves Dynamics 365 Sales organizations using the REST API. It requires the Global:D365SalesToken variable to be set, which contains the access token for Dynamics 365 Sales. The function returns the organization data as a PowerShell object.

.PARAMETER
None.

.INPUTS
None.

.OUTPUTS
value: A PowerShell object containing the organization data.

.EXAMPLE
$organizations = Get-D365SalesOrganisations
Write-Output $organizations

#>


    [cmdletbinding()]
    param()
    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {
        $response = Invoke-RestMethod -method Get `
            -URI "$($Global:D365SalesAPIURI)/organizations" `
            -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" } 
        return $response.value  
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function New-D365SalesSystemUser {
    
    <#
    .SYNOPSIS
Creates a new Dynamics 365 Sales system user.

.DESCRIPTION
The New-D365SalesSystemUser function creates a new Dynamics 365 Sales system user using the REST API. It requires a PSCustomObject containing the system user details as a mandatory parameter. The function returns the response from the REST API.

.Parameter userDetails
(Mandatory) A PSCustomObject containing the system user details. The PSCustomObject should have properties corresponding to the required system user fields, such as firstname, lastname, fullname, emailaddress1, and domainname.

.INPUTS
None.

.OUTPUTS
The response from the REST API as a PowerShell object.

.EXAMPLE
# Create a PSCustomObject with system user details
New-D365SalesSystemUser -userDetails $userDetails

    #>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [PSCustomObject]$userDetails
    )

    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {
        $response = Invoke-RestMethod -Method Post -Uri "$($Global:D365SalesAPIURI)/systemusers" -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" } -Body ($userDetails | ConvertTo-Json)
        return $response 
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Update-D365SalesSystemUser {

    <#
    .SYNOPSIS
Updates an existing Dynamics 365 Sales system user.

.DESCRIPTION
The Update-D365SalesSystemUser function updates an existing Dynamics 365 Sales system user using the REST API. It requires a PSCustomObject containing the updated system user details as a mandatory parameter, along with the system user ID to identify the user to be updated. The function returns the response from the REST API.

.PARAMETER userDetails
(Mandatory) A PSCustomObject containing the updated system user details. The PSCustomObject should have properties corresponding to the updated fields, such as firstname, lastname, fullname, emailaddress1, and domainname.

.PARAMETER systemuserid
(Mandatory) The ID of the system user to update.

.INPUTS
None.

.OUTPUTS
The response from the REST API as a PowerShell object.

.EXAMPLE
# Create a PSCustomObject with updated system user details
$userDetails = New-Object PSCustomObject -Property @{
    firstname = "Jane"
    lastname = "Doe"
    fullname = "Jane Doe"
}

# Update the system user with ID "00000000-0000-0000-0000-000000000000"
$systemuserid = "00000000-0000-0000-0000-000000000000"
$response = Update-D365SalesSystemUser -userDetails $userDetails -systemuserid $systemuserid

    #>


    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [PSCustomObject]$userDetails,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [PSCustomObject]$systemuserid
    )

    # Refresh Token
    $Global:D365SalesToken = Get-D365SalesToken

    Try {
        $response = Invoke-RestMethod -Method Patch `
            -Uri "$($Global:D365SalesAPIURI)/systemusers($($systemuserid))" `
            -Headers @{Authorization = "Bearer $($D365SalesToken)"; "Content-Type" = "application/json" } `
            -Body ($userDetails | ConvertTo-Json)
        return $response 
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Get-MicrosoftGraphToken {
    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [pscredential]$msgraphCredential,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$EntraIDTenantID,
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)]
        [string]$RedirectUri = "https://localhost"
    )

    <#
    .SYNOPSIS
Obtains an access token for the Microsoft Graph API using EntraID authentication.

.DESCRIPTION
The Get-MicrosoftGraphToken function acquires an access token for the Microsoft Graph API by utilizing EntraID authentication. It requires the EntraID credentials, EntraID tenant ID, and optionally a redirect URI as mandatory parameters. The function returns the acquired access token.

.Parameter msgraphCredential
(Mandatory) The EntraID credentials for accessing the Microsoft Graph API.

.Parameter EntraIDTenantID
(Mandatory) The EntraID tenant ID associated with the Microsoft Graph API tenant.

.Parameter RedirectUri
(Optional) The redirect URI to be used during the authentication process. Defaults to "https://localhost".

.INPUTS
None.

.OUTPUTS
The acquired access token as a string.

.EXAMPLE
# Assuming $msgraphCredential is a PSCredential object containing EntraID credentials
$EntraIDTenantID = "<EntraID_tenant_ID>"
$msgraphAccessToken = Get-MicrosoftGraphToken -msgraphCredential $msgraphCredential -EntraIDTenantID $EntraIDTenantID - RedirectUri = "http://localhost"

    
    #>

    # Import-Module MSAL.PS
    Set-ExecutionPolicy Bypass
    Import-Module -Name $PSScriptRoot\Modules\MSAL.PS\4.37.0.0\MSAL.PS.psm1 

    $entraIDToken = Get-MsalToken -ClientId $Global:d365SalesCreds.UserName -ClientSecret $Global:d365SalesCreds.Password -RedirectUri "https://localhost" -TenantId $Global:EntraIDTenantID -ForceRefresh
    return $entraIDToken.AccessToken
}

Function Invoke-MicrosoftGraphPostRequest {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$URI,    
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$msgraphAccessToken,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$postBody
    )

    <#
    .SYNOPSIS
Executes a POST request to the Microsoft Graph API using the provided access token and post body.

.DESCRIPTION
The Invoke-MicrosoftGraphPostRequest function simplifies the process of making POST requests to the Microsoft Graph API. It requires the target URI, access token, and post body as mandatory parameters. The function returns the response from the REST API.

.Parameter URI
(Mandatory) The URI of the Microsoft Graph API endpoint to which to send the POST request.

.Parameter msgraphAccessToken
(Mandatory) The access token for accessing the Microsoft Graph API.

.Parameter postBody
(Mandatory) The JSON-formatted post body to be included in the POST request.

.INPUTS
None.

.OUTPUTS
The response from the REST API as a PowerShell object.

.EXAMPLE
    
$URI = "https://graph.microsoft.com/v1.0/users"
$msgraphAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cm4iOiJ1cGVyOjM3ODQ1MTE1LTUwN2EtNDcwOS04YjEwLTU4ZTE2OGNmYjU1OSIsImlzcyI6IjE4YzE1ZDZhLWRmZGMtNDk4NC04YjY0LTU2MzI4OTk5YjE2MiIsInJvb2JpIjoidWNlcnJlZ2lvbi1zaGQifQ.4-hK-5jY2h7h9pK05h7r5_8y6-8-QzPq5-6Z5w9yM"
$postBody = @{"displayName" = "John Doe"; "userPrincipalName" = "johndoe@example.com"}
$response = Invoke-MicrosoftGraphPostRequest -URI $URI -msgraphAccessToken $msgraphAccessToken -postBody $postBody
Write-Output $response

    #>

    
    try {
        $response = Invoke-RestMethod -Method Post `
            -Uri $URI `
            -Headers @{"Authorization" = "Bearer $($msgraphAccessToken)"; "Content-Type" = "application/json" } `
            -Body $postBody
        
        return $response 
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

Function Invoke-MicrosoftGraphGetRequest {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$URI,    
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$msgraphAccessToken
    )

    <#
    $URI = "https://graph.microsoft.com/v1.0/users"
    $msgraphAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cm4iOiJ1bGVrOjM3ODQ1MTE1LTUwN2EtNEcwOS04YjEwLTU4ZTE2OGNmYjU1OSIsImlzcyI6IjE4YzE1ZDZhLWRmZGMtNDk4NC04YjY0LTU2MzI4OTk5YjE2MiIsInJvb2JpIjoidWNlcnJlZ2lvbi1zaGQifQ.4-hK-5jY2h7h9pK05h7r5_8y6-8-QzPq5-6Z5w9yM"
    $postBody = @{"displayName" = "John Doe"; "userPrincipalName" = "johndoe@example.com"}
    $response = Invoke-MicrosoftGraphPostRequest -URI $URI -msgraphAccessToken $msgraphAccessToken -postBody $postBody
    Write-Output $response

#>

    
    try {
        $response = Invoke-RestMethod -Method Get `
            -Uri $URI `
            -Headers @{"Authorization" = "Bearer $($msgraphAccessToken)"; "Content-Type" = "application/json" } 
        
        return $response 
    }
    catch {
        Write-Error $_
        throw GetErrorMessage $_
    }
}

function GetErrorMessage {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        $exception
    )

    $streamReader = [System.IO.StreamReader]::new($exception.Exception.Response.GetResponseStream())
    try {
        $streamReader.BaseStream.Position = 0
        $ErrResp = $streamReader.ReadToEnd() | ConvertFrom-Json
    }
    finally {
        $streamReader.Close()
    }
    return $ErrResp.error.message
}


# SIG # Begin signature block
# MIIoJQYJKoZIhvcNAQcCoIIoFjCCKBICAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCClGDHLYDeVZJnE
# WLksdftpGldsPgmsG8zD8YcCecPSj6CCISgwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqG
# SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMy
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcg
# Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXH
# JQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMf
# UBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w
# 1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRk
# tFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYb
# qMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUm
# cJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP6
# 5x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzK
# QtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo
# 80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjB
# Jgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXche
# MBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB
# /wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU
# 7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDig
# NqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v
# dEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZI
# hvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd
# 4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiC
# qBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl
# /Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeC
# RK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYT
# gAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/
# a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37
# xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmL
# NriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0
# YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJ
# RyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIG
# sDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQw
# HhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0
# ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zr
# PYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHM
# gQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8Irg
# nQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyC
# EUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0
# p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQa
# khCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0
# XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960I
# HnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2
# FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBH
# X8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q2
# 7IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYD
# VR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1k
# TN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcD
# AzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj
# ZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmww
# HAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIB
# ADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6j
# fCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmI
# moqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtf
# JqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrx
# oj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3
# LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx
# 4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9
# Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+I
# Cw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug
# 0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5
# Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQ
# C65mvFq6f5WHxvnpBOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0
# ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAw
# MDAwMFoXDTM1MTEyNTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp
# Z2lDZXJ0MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjE
# iDtqmeOlwf0KMCBDEr4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOc
# Re8+CEJp+3R2O8oo76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/
# GLoUb35SfWHh43rOH3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0Cha
# V76Nhnj37DEYTX9ReNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8U
# uKGn9966fR5X6kgXj3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHw
# SJ+QQRZ1fisD8UTVDSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4
# EfvFrpVNnes4c16Jidj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzI
# Xp4P0wXkgNs+CO/CacBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3Jyidx
# W48jwBqIJqImd93NRxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizch
# NULpUEoA6Vva7b1XCB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJ
# cv6dQ4aEKOX5AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/
# BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEE
# AjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8w
# HQYDVR0OBBYEFJ9XLAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuG
# SWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQw
# OTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKG
# TGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJT
# QTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIB
# AD2tHh92mVvjOIQSR9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq
# 3igpwrPvBmZdrlWBb0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcH
# zBMutB6HzeledbDCzFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTV
# OoJ4eTq7gj9UFAL1UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4H
# v5swO+aAXxWUm3WpByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgt
# d7/fvWTlCs30VAGEsshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaid
# RJXrI+UzB6vAlk/8a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhd
# mm4bhYsVA6G2WgNFYagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dH
# PoWrUhftNpFC5H7QEY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDi
# CLg4D+TPVgKx2EgEdeoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7z
# cEO1xwcdcqJsyz/JceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHbTCCBVWgAwIBAgIQ
# CcjsXDR9ByBZzKg16Kdv+DANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0
# ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIz
# MDMyOTAwMDAwMFoXDTI2MDYyMjIzNTk1OVowdTELMAkGA1UEBhMCQVUxGDAWBgNV
# BAgTD05ldyBTb3V0aCBXYWxlczEUMBIGA1UEBxMLQ2hlcnJ5YnJvb2sxGjAYBgNV
# BAoTEURhcnJlbiBKIFJvYmluc29uMRowGAYDVQQDExFEYXJyZW4gSiBSb2JpbnNv
# bjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMesp+e1UZ5doOnpL+ep
# m6Iq6GYiqK8ZNcz1XBe7M7eBXwVy4tYP5ByIa6NORYEselVWI9XmO1M+cPS6jRMr
# pZb9xtUH+NpKZO+eSthgTAtnEO1dWaAK6Y7AH/ZVjmgOTWZXBVibjAE/JQKIfZyx
# 4Hm5FOH6hq3bslA+RUQpo3NQxNv2AuzckKQwbW7AoXINudj0duYCiDYshn/9mHzz
# gL0VpNYRpmgEa7WWgc1JH17V+SYlaf6qMWpYoWuODwuDltSH2p57qAI2/4J6rUYE
# vns7QZ9sgIUdGlUr596fp0Y4juypyVGE7Rr0a8PtByLWUupyV7Z5kKPr/MRjerXA
# mBnf6AdhI3kY6Gjz356fZkPA49UuCIXFgyTZT84Ao6Klw+0RqJ70JDt449Uky7hd
# a+h8h2PiUdf7rXQamV57mY65+lHAmc4+UgTuWsnpwnTuNlkbZxRnCw2D+W3qto2a
# BhDebciKZzivfiAWlWfTcHtCpy96gM5L+OB45ezDpU6KAH1hwRSjORUlW5yoFTXU
# bPUBRflU3O2bZ0wdAJeyUYaHWAayNoyFfuKdrmCLtIx726O06dz9Kg+cJf+1ZdJ7
# KcUvZgR2d8F19FV5G1CVMnOzhMZR2dnIeJ5h0EgcOKNHl3hMKFdVRx4lhW8tcrQQ
# N4ZT2EgGfI9fBc0i3GXTFA0xAgMBAAGjggIDMIIB/zAfBgNVHSMEGDAWgBRoN+Dr
# tjv4XxGG+/5hewiIZfROQjAdBgNVHQ4EFgQUBTFWqXTuYnNp+d03es2KM9JdGUgw
# DgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0w
# gaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0
# ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1o
# dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2ln
# bmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDA+BgNVHSAENzA1MDMGBmeBDAEE
# ATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQG
# CCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
# dC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEu
# Y3J0MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQELBQADggIBAFhACWjPMrcafwDfZ5me
# /nUrkv4yYgIi535cddPAm/2swGDTuzSVBVHIMBp8LWLmzXPA1GbxBOmA4L8vvDgj
# EpQF9I9Ph5MNYgYhg0xSpAIp9/KAoc4OQnwlyRGPN+CjayY40xxTz4/hHohWg4rn
# JMIuVEjkMtKnMdTbpnqU85w78AQlfD79v/gWQ2dL1T3n18HOEjTt8VSurxkEhQ5I
# 3SH8Cr9YhUv94ObWIUbOKUt5SG7m/d+y2mfkKRSOmRluLSoYLPWbx35pArsYkaPp
# jf5Yl5jiJPY3GQzEU/SRVW0rrwDAbtKSN0gKWtZxijPDbs8aQUYCijFfje6OWGF4
# RnmPSQh0Ff8AyzPQcx9LjQ/8W7gUELsE6IFuXP5bj2i6geLy65LRe46QZlYDq/bM
# azUoZQTlje/hs6pkOL4f1Kv7tbJZmMENVVURJNmeDRejvNliHaaGEAv/iF0Zo7pq
# vj4wCCCGG3j/sNR5WSRYnxf5xQ4r9i9gZqk4yjwk/DJCW2rmKNCUoxNIZWh2EIlM
# SDzw3DMKk2ylZdiY/LAi5GmbCyGLt6sTz/IE1w1NYwrp/z6v4I91lDgdXg+fTkhh
# xt47hWmjMOD3ZYVSFzQmg8al1iQ/+6RYKgfsww64tIky8JOOZX/3ss/uhxKUjPJx
# YJkOwQwUyoAYzjcu/AE7By0rMYIGUzCCBk8CAQEwfTBpMQswCQYDVQQGEwJVUzEX
# MBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0
# ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAJyOxc
# NH0HIFnMqDXop2/4MA0GCWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAI
# oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB
# CzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFpG1WLo+BJkQYT2n0xJ
# Q3QF7sRX0B1xm9ZxgQiXf5KwMA0GCSqGSIb3DQEBAQUABIICAImuUzgXc5mbKGN8
# ARRQNk0/3h0GeUmnnVNdR7hWPN/ByL3BcZ/cgSh5MKvdSTLLPq9XmMc3q7TGYd1U
# Ui9ao45pbsndYnvCgblmPq2YY4Zdv+t9vkCHTbaQJw14UdAVl71Cr0aWX5Up95/C
# IkCcavTE+WyGMUuAGlejkMyTP/1Q09k7LOTT9KTOW2vvxhOFPni28n+bUnW2Gmdg
# AVuSWuiVKZZZ8oSMyUilDT2xVU867yqDzRMUtqRyY1yMuyl+3vV0FF3RvvNryOH6
# t7/NsPMOrH3QBzZapoTkn4IprjyVUe0n8Seqby221kmYNzBvCr51pI5DzMRUnZhl
# JccDQbMB+aDNxuAg+wCoaysnFGA6iRt3ExsyXHLde1SmvKbBBkeXmPcgRZbbrAU7
# /5rATHNmBPK62UfJz3xM2Mzkk4dXEbszNnuni0AIjicKUNnVCH7o7e450q/kwN6c
# OpRb+XK6mdb+Bd2/yfQ3NpkPotBt9Kcq3VzKdub01W0jie1ie5ViJft6s5Si8R7U
# P+tihqCCMXeGg3hz2QqwGfMFmMmlxuJUogpwzJtqJxhJchCJexv1GMwbbtCgDbYi
# Uvnsnx0bDiBMEv8hW2iPvxplpG3D/Nz31CP5FAHHQy3q2f0wSnyNMqeY8IVHyZUU
# Gp/JxfaCRJiNd6dqkIFkp+DcAIunoYIDIDCCAxwGCSqGSIb3DQEJBjGCAw0wggMJ
# AgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTsw
# OQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVT
# dGFtcGluZyBDQQIQC65mvFq6f5WHxvnpBOMzBDANBglghkgBZQMEAgEFAKBpMBgG
# CSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTI1MDMxOTAy
# MjMyNFowLwYJKoZIhvcNAQkEMSIEIAwWNijhcTCG4gfrsuMonEttIBu3DmzIcfRZ
# qoJfCOyOMA0GCSqGSIb3DQEBAQUABIICACBpCp1PVzUDLE0S8AOF/fwIJKs0Jaac
# VT6b4YE1rE85wpNBeVQiUhcnh2Cm0pmTxS91Fav+plbeRwGFXjEvO7IzzWdkd2yf
# epTsBjzbWEG3jkmNiVTocjn4DW6Qf4IkTQO8j8kSeVV1jSp683FiS/FxGQj9w/Om
# uSrPXSxhc4g/QFTQv3hMAFJYIwB4oDBsi7Gxpetsfy4PtMkOPCxECHO+So8BbPeM
# minyVXvsJuBsH+H+CtcyKHGnCbZe+LNsY8Um+jmKIhT2DdSaQUo0Gg1Rd+fO1/ac
# TiSnEJ6/6lXWHa8YbpOkbtVGYJ9SJCvpIsFJsle1gIqEk8joFoOHO2GrZ58eeFsK
# JL72D8iHn/G6tU3uc1pqiw/p3hS+4jHrvu+fVECsZyOE+Ug7dDzfo+ZSxvq7oU0P
# CdD027mQu5ZzHuoV4WrdqM/gtpvyQKdMFnZPTdtpBiaAnWmRTR2919jCK8pCfYvz
# bfmNNLBHkU4yj705m/bWb6LS0r5+aMmaTZTljdJd1aPF6JWgXV3f6Qngf8DgzaBV
# L67cFH52N4iQnTPztRFMLr4m5VHWN9+i8E4VlOqyL+slh4HUy2rEnDpg2f2gMUtY
# bMSy0pyM2cbE9JOg7xOh25v3VtZ7dfdoUXNuN5ZPMoCZiatGEKBNxt20HH/nO+x9
# gkCBINln/6SC
# SIG # End signature block