Microsoft.Xrm.OnlineManagementAPI.psm1

function Get-CrmLanguages
{
 <#
 .SYNOPSIS
 Get languages
 .DESCRIPTION
 The Get-CrmLanguages cmdlet retrieves all languages supported by Dynamics 365 Customer Enagagement (online) for the specified service release.
 Use Get-Help Get-CrmLanguages -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .PARAMETER ServiceReleaseId
 Release Id for a release of Microsoft Dynamics 365 (online). You can find the ServiceReleaseId by running the Get-CrmServiceVersions cmdlet.
 .EXAMPLE
 Get-CrmLanguages
 Get languages.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 30,

        [Parameter(Mandatory = $false)]
        [string]$ServiceReleaseId
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling Get-AdminPowerAppCdsDatabaseLanguages."
        $response = Get-AdminPowerAppCdsDatabaseLanguages -LocationName $location -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        $languages = @()
        foreach ($language in $response)
        {
            $languages += CreateLanguageObject -Language $language
        }

        return $languages
    }
}

function Get-CrmTemplates
{
 <#
 .SYNOPSIS
 Get templates
 .DESCRIPTION
 The Get-CrmTemplates cmdlet retrieves a list of application templates supported for provisioning a Dynamics 365 Customer Engagement (online) instance.
 Use Get-Help Get-CrmTemplates -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Get-CrmTemplates
 Get templates.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 30
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling Get-AdminPowerAppCdsDatabaseTemplates."
        $response = Get-AdminPowerAppCdsDatabaseTemplates -LocationName $location -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        $templates = @()
        foreach ($template in $response)
        {
            $templates += CreateTemplateObject -Template $template
        }

        return $templates
    }
}

function Get-CrmCurrencies
{
 <#
 .SYNOPSIS
 Get templates
 .DESCRIPTION
 The Get-CrmCurrencies cmdlet returns a list of currencies that are available for all instances within the same tenant.
 Use Get-Help Get-CrmCurrencies -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Get-CrmCurrencies
 Get templates.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 30
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling Get-AdminPowerAppCdsDatabaseCurrencies."
        $response = Get-AdminPowerAppCdsDatabaseCurrencies -LocationName $location -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        $currencies = @()
        foreach ($currency in $response)
        {
            $currencies += CreateCurrencyObject -Currency $currency
        }

        return $currencies
    }
}

function Get-CrmServiceVersions
{
 <#
 .SYNOPSIS
 Get service version
 .DESCRIPTION
 The Get-CrmServiceVersions cmdlet retrieves information about all currently supported releases for Dynamics 365 Customer Engagement (online).
 Use Get-Help Get-CrmServiceVersions -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .EXAMPLE
 Get-CrmServiceVersions
 Get service version
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential
    )
    process
    {
        $versions = @()
        $versions += CreateServiceVersionObject

        return $versions
    }
}

function Get-CrmInstances
{
 <#
 .SYNOPSIS
 Get Dynamics 365 Customer Engagement (online) instances
 .DESCRIPTION
 The Get-CrmInstances cmdlet retrieves details for Dynamics 365 Customer Engagement (online) instances that are in your tenant.
 Use Get-Help Get-CrmInstances -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
  .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .EXAMPLE
 Get-CrmInstances
 Get Dynamics 365 Customer Engagement (online) instances
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential = $null
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling Get-AdminPowerAppEnvironment."
        $response = Get-AdminPowerAppEnvironment -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        $instances = @()
        foreach ($environment in $response)
        {
            if ($environment.Internal.properties.linkedEnvironmentMetadata -ne $null)
            {
                $instances += CreateInstanceObject -Environment $environment
            }
        }

        return $instances
    }
}

function Get-CrmProtectedInstances
{
 <#
 .SYNOPSIS
 Get Dynamics 365 Customer Engagement (online) instances
 .DESCRIPTION
 The Get-CrmProtectedInstances cmdlet retrieves details for Dynamics 365 Customer Engagement (online) instances that are in your tenant.
 Use Get-Help Get-CrmProtectedInstances -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
  .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .EXAMPLE
 Get-CrmProtectedInstances
 Get Dynamics 365 Customer Engagement (online) instances
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling Get-AdminPowerAppEnvironment."
        $response = Get-AdminPowerAppEnvironment -GetProtectedEnvironment -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        $instances = @()
        foreach ($environment in $response)
        {
            if ($environment.Internal.properties.linkedEnvironmentMetadata -ne $null)
            {
                $instances += CreateInstanceObject -Environment $environment
            }
        }

        return $instances
    }
}

function Get-CrmInstance
{
 <#
 .SYNOPSIS
 Get Dynamics 365 Customer Engagement (online) instance
 .DESCRIPTION
 The Get-CrmInstance cmdlet retrieves a Dynamics 365 Customer Engagement (online) instance in your Office 365 tenant.
 Use Get-Help Get-CrmInstance -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER Id
 Instance Id for the specific instance of Dynamics 365 Customer Engagement (online). Use the Get-CrmInstances cmdlet to find all instance Ids within the same tenant.
 .EXAMPLE
 Get-CrmInstance -Id $InstanceId
 Get Dynamics 365 Customer Engagement (online) instance on Id
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [string]$Id,

        [Parameter(Mandatory = $false)]
        [object]$Credential
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        
        $environment = Get-AdminPowerAppEnvironment -InstanceId $Id -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        return CreateInstanceObject -Environment $environment
    }
}

function New-CrmInstanceInfo
{
 <#
 .SYNOPSIS
 Create CrmInstanceInfo object
 .DESCRIPTION
 The New-CrmInstanceInfo cmdlet creates the object that defines the parameters used to create a new instance of Dynamics 365 Customer Enagagement (online) by using the New-CrmInstance cmdlet.
 Use Get-Help Get-CrmInstances -Examples for more detail.
 .PARAMETER BaseLanguage
 Required parameter. The base language for the instance. For a list of available languages, run the Get-CrmLanguages cmdlet.
 .PARAMETER DomainName
 Required parameter. Used for part of the domain namespace and appears in the application url.
 .PARAMETER InitialUserEmail
 Required parameter. Email address to receive notifications about the instance.
 .PARAMETER ServiceVersionId
 Required parameter. Specifies the service release of the instance. For a list of available service releases, run the Get-CrmServiceVersions cmdlet.
 .PARAMETER InstanceType
 Required parameter. Sets the instance type, such as Sandbox or Production. For a full list, run the Get-CrmInstanceTypes cmdlet.
 .PARAMETER FriendlyName
 Required parameter. This is typically the name of your organization and is displayed in the Microsoft Dynamics 365 application.
 .PARAMETER TemplateList
 Optional parameter. Specifies the apps you want provisioned in the instance. For a list of available template names, run the Get-CrmTemplates cmdlet.
 .PARAMETER Purpose
 Optional parameter. A description used to associate the instance with a specific intent. Only tenant administrators will see this description.
 .PARAMETER SecurityGroupId
 Optional parameter. This value is used to determine the security group that includes the users who will have access to this instance of Microsoft Dynamics 365 (online).
 .PARAMETER CurrencyCode
 Optional parameter. The currency region code to use for the instance. You can find a list of currency region codes by running the Get-CrmCurrencies cmdlet.
 .PARAMETER CurrencyName
 Optional parameter. The currency name to use for the instance. You can find a list of currency names by running the Get-CrmCurrencies cmdlet.
 .PARAMETER CurrencyPrecision
 Optional parameter. Set the pricing decimal precision. Valid values are 0-4.
 .PARAMETER CurrencySymbol
 Optional parameter. Set the symbol that will represent the currency.
 .EXAMPLE
 New-CrmInstanceInfo -BaseLanguage 1033 -DomainName "Test" -InitialUserEmail admin@test.onmicrosoft.com -ServiceVersionId $ServiceVersions[0].Id -InstanceType Sandbox -FriendlyName "Test" -Purpose "TestPurpose" -CurrencyCode USD -CurrencySymbol "$" -CurrencyName "US Dollar"
 Create CrmInstanceInfo object
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [int]$BaseLanguage,

        [Parameter(Mandatory = $true)]
        [string]$DomainName,

        [Parameter(Mandatory = $true)]
        [string]$InitialUserEmail,

        [Parameter(Mandatory = $true)]
        [guid]$ServiceVersionId,

        [Parameter(Mandatory = $true)]
        [string]$InstanceType,

        [Parameter(Mandatory = $true)]
        [string]$FriendlyName,

        [Parameter(Mandatory = $false)]
        [string[]]$TemplateList,

        [Parameter(Mandatory = $false)]
        [string]$Purpose,

        [Parameter(Mandatory = $false)]
        [guid]$SecurityGroupId,

        [Parameter(Mandatory = $false)]
        [string]$CurrencyCode,

        [Parameter(Mandatory = $false)]
        [string]$CurrencyName,

        [Parameter(Mandatory = $false)]
        [int]$CurrencyPrecision,

        [Parameter(Mandatory = $false)]
        [string]$CurrencySymbol
    )

    $currency = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.Currency($CurrencyCode, $CurrencyPrecision, $CurrencySymbol, $CurrencyName)

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.NewInstance( `
        $ServiceVersionId, `
        $InstanceType, `
        $FriendlyName, `
        $DomainName, `
        $InitialUserEmail, `
        $currency, `
        $TemplateList, `
        $Purpose, `
        $BaseLanguage, `
        $SecurityGroupId)
}

function New-CrmInstance
{
 <#
 .SYNOPSIS
 Provisions Dynamics 365 Customer Engagement (online) instance
 .DESCRIPTION
 The New-CrmInstance cmdlet provisions (creates) a Dynamics 365 Customer Engagement (online) instance in your Office 365 tenant.
 Use Get-Help New-CrmInstance -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER NewInstanceInfo
 Object created by using the New-CrmInstanceInfo cmdlet that contains the information used to provision the instance.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .PARAMETER ValidateOnly
 Indicates whether to validate that an instance can be provisioned with the provided parameters or create an instance. Set 1 to validate; set 0 to create.
 .EXAMPLE
 New-CrmInstance -ApiUrl $connecthost -Credential $cred -Verbose -NewInstanceInfo $instanceInfo
 Provisions Dynamics 365 Customer Engagement (online) instance
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [object]$NewInstanceInfo,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 10,

        [Parameter(Mandatory = $false)]
        [switch]$ValidateOnly
    )
    process
    {
        if ($ValidateOnly)
        {
            $context = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.OperationContext
            return CreateOperationStatusObject `
                    -OperationId "00000000-0000-0000-0000-000000000000" `
                    -Status "NotStarted" `
                    -Context $context
        }

        $waitUntilFinished = $true
        if ($MaxCrmConnectionTimeOutMinutes -eq 0)
        {
            $waitUntilFinished = $false
        }

        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl
        $geoType = UrlToGeoTypeMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling New-AdminPowerAppEnvironment."
        $response = New-AdminPowerAppEnvironment `
                    -DisplayName $NewInstanceInfo.FriendlyName `
                    -LocationName $location `
                    -EnvironmentSku $NewInstanceInfo.Type `
                    -ProvisionDatabase `
                    -CurrencyName $NewInstanceInfo.Currency.Name`
                    -LanguageName $NewInstanceInfo.BaseLanguage`
                    -Templates $NewInstanceInfo.Templates`
                    -SecurityGroupId $NewInstanceInfo.SecurityGroupId`
                    -DomainName $NewInstanceInfo.DomainName `
                    -WaitUntilFinished $waitUntilFinished `
                    -TimeoutInMinutes $MaxCrmConnectionTimeOutMinutes `
                    -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response -GeoType $geoType
    }
}

function Remove-CrmInstance
{
 <#
 .SYNOPSIS
 Removes Dynamics 365 Customer Engagement (online) instance
 .DESCRIPTION
 The Remove-CrmInstance cmdlet removes (deletes) the specified Dynamics 365 Customer Engagement (online) instance.
 Use Get-Help Remove-CrmInstance -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER Id
 Instance Id for the specific instance of Dynamics 365 Customer Engagement (online). Use the Get-CrmInstances cmdlet to find all instance Ids within the same tenant.
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Remove-CrmInstance -ApiUrl $connecthost -Credential $cred -Verbose -NewInstanceInfo $instanceInfo
 Removes Dynamics 365 Customer Engagement (online) instance
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$Id,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 30
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        $environment = Get-AdminPowerAppEnvironment -InstanceId $Id -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        Write-Verbose "Calling Remove-AdminPowerAppEnvironment."
        $response = Remove-AdminPowerAppEnvironment -EnvironmentName $environment.EnvironmentName -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response
    }
}

function Get-CrmInstanceTypes
{
 <#
 .SYNOPSIS
 Get information about all the instance types
 .DESCRIPTION
 The Get-CrmInstanceTypes cmdlet retrieves information about all the instance types available, such as production, sandbox, support, preview, and trial, in the Dynamics 365 Customer Engagement (online) tenant.
 Use Get-Help Get-CrmInstanceTypes -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .EXAMPLE
 Get-CrmInstanceTypes
 Get information about all the instance types
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling Get-AdminPowerAppTenantConsumedQuota."
        $response = Get-AdminPowerAppTenantConsumedQuota
        
        return CreateInstanceTypes -InstanceTypeInfo $response
    }
}

function Get-CrmInstanceType
{
 <#
 .SYNOPSIS
 Get information about all the instance types
 .DESCRIPTION
 The Get-CrmInstanceType cmdlet retrieves information about a Dynamics 365 Customer Engagement (online) instance type.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER InstanceType
 The instance type.The following are valid instance types: Production, Sandbox, Support, Preview, Trial
 .EXAMPLE
 Get-CrmInstanceType
 Get information about the instance type
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$InstanceType
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling Get-AdminPowerAppTenantConsumedQuota."
        $response = Get-AdminPowerAppTenantConsumedQuota
        
        $instanceTypes = CreateInstanceTypes -InstanceTypeInfo $response
        return ($instanceTypes | Where-Object { $_.Type -eq $InstanceType })[0]
    }
}

function Get-CrmOperationStatus
{
 <#
 .SYNOPSIS
 Get Dynamics 365 Customer Engagement (online) instances
 .DESCRIPTION
 The Get-CrmOperationStatus cmdlet retrieves the status for an operation from your Dynamics 365 Customer Engagement (online) instance.
 Use Get-Help Get-CrmOperationStatus -Examples for more detail.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER OperationLocation
 Operation location of a long-running process, such as a backup, restore, new instance, or delete operation. For example, when you run the Get-CrmInstanceBackup cmdlet, the operation Id is returned from the cmdlet.
 .EXAMPLE
 Get-CrmOperationStatus -ApiUrl $ApiUrl -Credential $Credential -OperationLocation $OperationLocation
 Get Dynamics 365 Customer Engagement (online) instances
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [string]$OperationLocation,

        [Parameter(Mandatory = $false)]
        [object]$Credential
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling Get-AdminPowerAppOperationStatus."
        $response = Get-AdminPowerAppOperationStatus -OperationStatusUrl $OperationLocation -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        $operationId = (($OperationLocation -split "operations/")[1]).Substring(0, 36)

        return CreateOperationStatusByResponse -Response $response -OperationId $operationId
    }
}

function Disable-CrmManagementApp
{
 <#
 .SYNOPSIS
 Disable the specified registered Azure Active Directory Web app/API application
 .DESCRIPTION
 The Disable-CrmManagementApp cmdlet disables the specified registered Azure Active Directory Web app/API application.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER Id
 The registration id for the app. You can find the registration ids for all registered Azure Active Directory Web app/API applications by running the Get-CrmManagementApps cmdlet.
 .EXAMPLE
 Disable-CrmManagementApp -ApiUrl $ApiUrl -Credential $Credential -Id $Id
 Disable the specified registered Azure Active Directory Web app/API application
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$Id
    )
    process
    {
        throw "Disable-CrmManagementApp is not supported."
    }
}

function Enable-CrmManagementApp
{
 <#
 .SYNOPSIS
 Enable the specified registered Azure Active Directory Web app/API application
 .DESCRIPTION
 The Enable-CrmManagementApp cmdlet enables the specified registered Azure Active Directory Web app/API application for use with the Microsoft.Xrm.OnlineManagementAPI PowerShell module.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER Id
 The registration id for the app. You can find the registration ids for all registered Azure Active Directory Web app/API applications by running the Get-CrmManagementApps cmdlet.
 .EXAMPLE
 Enable-CrmManagementApp -ApiUrl $ApiUrl -Credential $Credential -Id $Id
 Enable the specified registered Azure Active Directory Web app/API application
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$Id
    )
    process
    {
        throw "Enable-CrmManagementApp is not supported."
    }
}

function Get-CrmManagementApps
{
 <#
 .SYNOPSIS
 Get Azure Active Directory Web app/API application list
 .DESCRIPTION
 The Get-CrmManagementApps cmdlet returns a list of all registered Azure Active Directory Web app/API applications under the tenant.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .EXAMPLE
 Get-CrmManagementApps -ApiUrl $ApiUrl -Credential $Credential
 Get Azure Active Directory Web app/API application list
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling Get-PowerAppManagementApps."
        $response = Get-PowerAppManagementApps -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        if ($response.value.Count -gt 0)
        {
            $applications = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.TenantApplicationIdentity]'
            foreach ($app in $response.value)
            {
                 $appIdentity = CreateTenantApplicationIdentity -Application $app
                 $applications.Add($appIdentity)
            }

            return $applications
        }
    }
}

function Get-CrmManagementApp
{
 <#
 .SYNOPSIS
 Get Azure Active Directory Web app/API application
 .DESCRIPTION
 The Get-CrmManagementApp cmdlet returns the specified registered Azure Active Directory Web app/API application.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
  .PARAMETER Id
 The registration id for the app. You can find the registration ids for all registered Azure Active Directory Web app/API applications by running the Get-CrmManagementApps cmdlet.
 .EXAMPLE
 Get-CrmManagementApp -ApiUrl $ApiUrl -Credential $Credential
 Get Azure Active Directory Web app/API application
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$Id
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling Get-PowerAppManagementApp."
        $response = Get-PowerAppManagementApp -ApplicationId $Id -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        return CreateTenantApplicationIdentity -Application $response
    }
}

function New-CrmManagementApp
{
 <#
 .SYNOPSIS
 Registers an Azure Active Directory Web app/API
 .DESCRIPTION
 The New-CrmManagementApp cmdlet registers an Azure Active Directory Web app/API based on the app Id so it can be used with the Microsoft.Xrm.OnlineManagementAPI PowerShell module.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER AppId
 The application Id of the the Azure Active Directory Web app / API app. You can find the application Ids for Azure Active Directory Web app / API applications by running the Get-AzureADApplication Azure Active Directory PowerShell cmdlet or from the Azure Active Directory admin center.
 .PARAMETER Enable
 Enables the app for use with the Microsoft.Xrm.OnlineManagmentAPI PowerShell module.
 .EXAMPLE
 New-CrmManagementApp -AppId $Id -ApiUrl $connectionhost -Credential $cred
  Registers an Azure Active Directory Web app/API
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$AppId,

        [Parameter(Mandatory = $false)]
        [switch]$Enable
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling New-PowerAppManagementApp."
        $response = New-PowerAppManagementApp -ApplicationId $AppId -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        return CreateTenantApplicationIdentity -Application $response
    }
}


function Remove-CrmManagementApp
{
 <#
 .SYNOPSIS
 Removes the specified registered Azure Active Directory Web app/API application
 .DESCRIPTION
 The Remove-CrmManagementApp cmdlet removes the specified registered Azure Active Directory Web app/API application so it cannot be used with Microsoft.Xrm.OnlineManagementAPI PowerShell module.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER Id
 The application Id of the the Azure Active Directory Web app / API app. You can find the application Ids for Azure Active Directory Web app / API applications by running the Get-AzureADApplication Azure Active Directory PowerShell cmdlet or from the Azure Active Directory admin center.
 .EXAMPLE
 Remove-CrmManagementApp -ApiUrl $ApiUrl -Credential $Credential -Id $Id
  Removes the specified registered Azure Active Directory Web app/API application
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$Id
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        Write-Verbose "Calling Remove-PowerAppManagementApp."
        Remove-PowerAppManagementApp -ApplicationId $Id -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) > $null
    }
}

function Add-CrmFlightAudience
{
 <#
 .SYNOPSIS
 Adds the specified environment to a particular flight.
 .DESCRIPTION
 The Add-CrmFlightAudience cmdlet adds the specified environment to a particular flight.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER FlightId
 Uniquely identifies a particular flight. Use the Get-CrmFlights cmdlet to return all available flights.
 .PARAMETER InstanceId
 Id for the specific Common Data Service environment. Use the Get-CrmInstances cmdlet to find all Ids within the same tenant.
 .EXAMPLE
 Add-CrmFlightAudience -ApiUrl $ApiUrl -Credential $Credential -FlightId $FlightId -InstanceId $InstanceId
 Adds the specified environment to a particular flight.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$FlightId,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId 
    )
    process
    {
        throw "Add-CrmFlightAudience is not supported."
    }
}

function Get-CrmFlights
{
 <#
 .SYNOPSIS
 Lists all flights that are available for opt-in or opt-out.
 .DESCRIPTION
 The Get-CrmFlights cmdlet lists all flights that are available for opt-in or opt-out.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Get-CrmFlights -ApiUrl $ApiUrl -Credential $Credential
 Lists all flights that are available for opt-in or opt-out.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 30
    )
    process
    {
        throw "Get-CrmFlights is not supported."
    }
}

function Remove-CrmFlightAudience
{
 <#
 .SYNOPSIS
 Removes the environment as an audience to a particular flight.
 .DESCRIPTION
 The Remove-CrmFlightAudience cmdlet removes the environment as an audience to a particular flight.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER FlightId
 Uniquely identifies a particular flight. Use the Get-CrmFlights cmdlet to return all available flights.
 .PARAMETER InstanceId
 Id for the specific Common Data Service environment. Use the Get-CrmInstances cmdlet to find all Ids within the same tenant.
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Remove-CrmFlightAudience -ApiUrl $ApiUrl -Credential $Credential -FlightId $FlightId -InstanceId $InstanceId
 Removes the environment as an audience to a particular flight.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$FlightId,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 30
    )
    process
    {
        throw "Remove-CrmFlightAudience is not supported."
    }
}

function New-CrmAdminModeSetting
{
 <#
 .SYNOPSIS
 Create InstanceAdminModeSettings object
 .DESCRIPTION
 The New-CrmAdminModeSetting cmdlet defines the object that can be passed to the Enable-CrmAdminMode and Enable-CrmAdminMode cmdlets to enable or disable administration mode for an instance of Dynamics 365 Customer Engagement (online).
 .PARAMETER OverrideUserAADObjectId
 Azure Active Directory object Id that can be used as the authentication identity instead of the user's credentials.
 .PARAMETER NotificationText
 Optional parameter that diplays as a message when any user attempts to sign in to the instance.
 .PARAMETER AllowBackgroundOperations
 Optional parameter that determines whether the instance can run background operations. 1 or $true to allow background operations. 0 or $false to not allow background operations. Background operations are asynchronous operations, such as workflows and synchronization with Microsoft Exchange. When set to false, emails will not be sent and server-side synchronization for appointments, contacts, and tasks are disabled.
 .EXAMPLE
 New-CrmAdminModeSetting -OverrideUserAADObjectId $OverrideUserAADObjectId -NotificationText $NotificationText -AllowBackgroundOperations $true
 #>

    param
    (
        [Parameter(Mandatory = $false)]
        [string]$NotificationText,

        [Parameter(Mandatory = $false)]
        [string]$OverrideUserAADObjectId,

        [Parameter(Mandatory = $false)]
        [bool]$AllowBackgroundOperations
    )

    return New-Object -TypeName PSObject `
            | Add-Member -PassThru -MemberType NoteProperty -Name OverrideUserAADObjectId -Value $OverrideUserAADObjectId `
            | Add-Member -PassThru -MemberType NoteProperty -Name AdminMode -Value $null `
            | Add-Member -PassThru -MemberType NoteProperty -Name BackgroundOperationsEnabled -Value $AllowBackgroundOperations `
            | Add-Member -PassThru -MemberType NoteProperty -Name NotificationText -Value $NotificationText
}

function Enable-CrmAdminMode
{
 <#
 .SYNOPSIS
 Enable AdminMode
 .DESCRIPTION
 The Enable-CrmAdminMode cmdlet enables administration mode on a sandbox instance. When you place a Sandbox instance in administration mode only users with Dynamics 365 System Administrator or System Customizer security roles will be able to sign in to that instance. Administration mode is useful when you want to make operational changes and not have regular users affect your work, and not have your work affect regular users.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER InstanceId
 Id for the specific Common Data Service environment. Use the Get-CrmInstances cmdlet to find all Ids within the same tenant.
 .PARAMETER AdminModeSettings
 You create the object for this parameter by running the New-CrmAdminModeSetting cmdlet.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .PARAMETER ValidateOnly
 Indicates whether to validate that an instance can be provisioned with the provided parameters or create an instance. Set 1 to validate; set 0 to create.
 .EXAMPLE
 Enable-CrmAdminMode -ApiUrl $connecthost -Credential $cred -Verbose -AdminModeSettings $adminModeSettings
 Enable AdminMode
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId,

        [Parameter(Mandatory = $false)]
        [object]$AdminModeSettings,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 30,

        [Parameter(Mandatory = $false)]
        [switch]$ValidateOnly
    )
    process
    {
        if ($ValidateOnly)
        {
            return CreateOperationStatusObject `
                    -OperationId "00000000-0000-0000-0000-000000000000" `
                    -Status "NotStarted"
        }

        UpdateRuntimeState `
            -ApiUrl $ApiUrl `
            -InstanceId $InstanceId `
            -RuntimeState "AdminMode" `
            -Credential $Credential `
            -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    }
}

function Disable-CrmAdminMode
{
 <#
 .SYNOPSIS
 Disable AdminMode
 .DESCRIPTION
 The Disable-CrmAdminMode cmdlet enables administration mode on a sandbox instance. When you place a Sandbox instance in administration mode only users with Dynamics 365 System Administrator or System Customizer security roles will be able to sign in to that instance. Administration mode is useful when you want to make operational changes and not have regular users affect your work, and not have your work affect regular users.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER InstanceId
 Id for the specific Common Data Service environment. Use the Get-CrmInstances cmdlet to find all Ids within the same tenant.
 .PARAMETER AdminModeSettings
 You create the object for this parameter by running the New-CrmAdminModeSetting cmdlet.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .PARAMETER ValidateOnly
 Indicates whether to validate that an instance can be provisioned with the provided parameters or create an instance. Set 1 to validate; set 0 to create.
 .EXAMPLE
 Disable-CrmAdminMode -ApiUrl $connecthost -Credential $cred -Verbose -AdminModeSettings $adminModeSettings
 Disable AdminMode
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId,

        [Parameter(Mandatory = $false)]
        [object]$AdminModeSettings,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 30,

        [Parameter(Mandatory = $false)]
        [switch]$ValidateOnly
    )
    process
    {
        if ($ValidateOnly)
        {
            return CreateOperationStatusObject `
                    -OperationId "00000000-0000-0000-0000-000000000000" `
                    -Status "NotStarted"
        }

        UpdateRuntimeState `
            -ApiUrl $ApiUrl `
            -InstanceId $InstanceId `
            -RuntimeState "Enabled" `
            -Credential $Credential `
            -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    }
}

function New-CrmInstanceCopyRequestInfo
{
 <#
 .SYNOPSIS
 Create CopyRequestInfo object
 .DESCRIPTION
 The New-CrmInstanceCopyRequestInfo cmdlet creates the object that defines the parameters used to copy a instance of Dynamics 365 Customer Enagagement (online) by using the Copy-CrmInstance cmdlet.
 .PARAMETER FriendlyName
 Required parameter. This is typically the name of your organization and is displayed in the Microsoft Dynamics 365 application.
 .PARAMETER TargetInstanceId
 Required parameter. This value is used to determine the target instance for the copy operation.
 .PARAMETER CopyType
 Required parameter. This value is used to determine the target instance for the copy operation.
 .PARAMETER SecurityGroupId
 Optional parameter. This value is used to determine the security group that includes the users who will have access to this instance of Microsoft Dynamics 365 (online).
 .EXAMPLE
 New-CrmInstanceCopyRequestInfo -OverrideUserAADObjectId $OverrideUserAADObjectId -NotificationText $NotificationText -AllowBackgroundOperations $true
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$FriendlyName,

        [Parameter(Mandatory = $true)]
        [guid]$TargetInstanceId,

        [Parameter(Mandatory = $true)]
        [Microsoft.Xrm.Services.Admin.Client.Models.CopyType]$CopyType,

        [Parameter(Mandatory = $false)]
        [guid]$SecurityGroupId
    )

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.CopyRequest( `
            $FriendlyName, `
            $SecurityGroupId, `
            $TargetInstanceId, `
            $CopyType)
}

function Copy-CrmInstance
{
 <#
 .SYNOPSIS
 Disable AdminMode
 .DESCRIPTION
 The Copy-CrmInstance cmdlet performs a copy operation on a Dynamics 365 Customer Engagement (online) instance to another Sandbox instance in your Office 365 tenant.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER SourceInstanceIdToCopy
 Id of the source instance to copy.
 .PARAMETER CopyInstanceRequestDetails
 Object created by using the New-CrmInstanceCopyRequestInfo cmdlet that contains the information used to copy the instance.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Copy-CrmInstance -ApiUrl $connectionhost -CopyInstanceRequestDetails $CopyInstanceRequestDetails -Credential $cred -SourceInstanceIdToCopy <Guid>
 Disable AdminMode
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [string]$SourceInstanceIdToCopy,

        [Parameter(Mandatory = $true)]
        [object]$CopyInstanceRequestDetails,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        $sourceEnvironment = Get-AdminPowerAppEnvironment -InstanceId $SourceInstanceIdToCopy -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        $targetEnvironment = Get-AdminPowerAppEnvironment -InstanceId $CopyInstanceRequestDetails.TargetInstanceId -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        $copyToRequest = [pscustomobject]@{
            SourceEnvironmentId = $sourceEnvironment.EnvironmentName
            TargetEnvironmentName = $CopyInstanceRequestDetails.FriendlyName
            TargetSecurityGroupId = $CopyInstanceRequestDetails.SecurityGroupId
            CopyType = $CopyInstanceRequestDetails.CopyType.ToString()
        }
        
        $waitUntilFinished = $true
        if ($MaxCrmConnectionTimeOutMinutes -eq 0)
        {
            $waitUntilFinished = $false
        }

        Write-Verbose "Calling Copy-PowerAppEnvironment."
        $response = Copy-PowerAppEnvironment `
            -EnvironmentName $targetEnvironment.EnvironmentName `
            -CopyToRequestDefinition $copyToRequest `
            -WaitUntilFinished $waitUntilFinished `
            -TimeoutInMinutes $MaxCrmConnectionTimeOutMinutes `
            -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response
    }
}

function Get-CrmInstanceBackups
{
 <#
 .SYNOPSIS
 Get instance backups.
 .DESCRIPTION
 The Get-CrmInstanceBackups cmdlet retrieves all backups for a Dynamics 365 Customer Engagement (online) instance.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
  .PARAMETER InstanceId
 Instance Id for the specific instance of Dynamics 365 Customer Engagement (online). Use the Get-CrmInstances cmdlet to find all instance Ids within the same tenant.
 .EXAMPLE
 Get-CrmInstanceBackups -ApiUrl $ApiUrl -Credential $Credential -InstanceId $InstanceId
 Get instance backups.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        $environment = Get-AdminPowerAppEnvironment -InstanceId $InstanceId -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        Write-Verbose "Calling Get-PowerAppEnvironmentBackups."
        $response = Get-PowerAppEnvironmentBackups -EnvironmentName $environment.EnvironmentName -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        $manualBackups = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ManualBackup]'
        foreach ($backup in $response.value)
        {
            $manualBackup = CreateManualBackupObject -Backup $backup -InstanceId $InstanceId
            $manualBackups.Add($manualBackup)
        }

        $systemBackups = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.SystemBackup]'
        $datetime = (Get-Date).ToUniversalTime()
        $datetime -= New-TimeSpan -Minutes 15

        $systemBackup = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.SystemBackup
        $systemBackup.InstanceId = $environment.Internal.Properties.LinkedEnvironmentMetadata.ResourceId
        $systemBackup.BackupStartTimeUtc = $environment.Internal.Properties.RetentionDetails.BackupsAvailableFromDateTime
        $systemBackup.BackupEndTimeUtc = $datetime
        $systemBackups.Add($systemBackup)

        $instanceBackup = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.InstanceBackups
        $instanceBackup.ManualBackups = $manualBackups
        $instanceBackup.SystemBackups = $systemBackups

        return $instanceBackup
    }
}

function Backup-CrmInstance
{
 <#
 .SYNOPSIS
 Backup instance.
 .DESCRIPTION
 The Backup-CrmInstance cmdlet initiates a back up of a Dynamics 365 Customer Engagement (online) instance.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
  .PARAMETER InstanceId
 Instance Id for the specific instance of Dynamics 365 Customer Engagement (online). Use the Get-CrmInstances cmdlet to find all instance Ids within the same tenant.
  .PARAMETER Label
 Add a label to use to reference the backup.
  .PARAMETER Notes
 Add comments about the backup.
 .EXAMPLE
 Backup-CrmInstance -ApiUrl $ApiUrl -Credential $Credential -InstanceId $InstanceId -Label $Label -Notes $Notes
 Backup instance.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId,

        [Parameter(Mandatory = $true)]
        [string]$Label,

        [Parameter(Mandatory = $true)]
        [string]$Notes
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        $environment = Get-AdminPowerAppEnvironment -InstanceId $InstanceId -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        $backupRequest = [pscustomobject]@{
            Label = $Label
            Notes = $Notes
        }

        Write-Verbose "Calling Backup-PowerAppEnvironment."
        $response = Backup-PowerAppEnvironment -EnvironmentName $environment.EnvironmentName -BackupRequestDefinition $backupRequest -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        if ($response -ne $null -and $response.label -eq $Label -and $response.notes -eq $Notes)
        {
            return CreateOperationStatusObject `
                    -OperationId "00000000-0000-0000-0000-000000000000" `
                    -Status "Created"
        }

        $errorMessage = $response.Internal.Error.message
        $errorCode = $response.Internal.Error.code

        $errors = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription]'
        $errorObject = CreateItemDescription -Code $errorCode -Description $errorMessage
        $errors.Add($errorObject)

        return CreateOperationStatusObject `
                -OperationId "00000000-0000-0000-0000-000000000000" `
                -Status "Failed" `
                -Errors $errors
    }
}

function Restore-CrmInstance
{
 <#
 .SYNOPSIS
 Restore instance.
 .DESCRIPTION
 The Restore-CrmInstance cmdlet restores a Dynamics 365 Customer Engagement (online) instance from the specified backup and instance restore point ids.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
  .PARAMETER SourceInstanceId
 Instance where Backup is picked up from.
  .PARAMETER RestoreTimeUtc
 Backup TimeStamp to restore to.
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .PARAMETER FriendlyName
 Allows you to change the friendly name on restore.
 .PARAMETER SecurityGroupId
 Allows you to change the friendly name on restore.
 .PARAMETER ValidateOnly
 Indicates whether to validate that an instance can be provisioned with the provided parameters or create an instance. Set 1 to validate; set 0 to create.
 .EXAMPLE
 Restore-CrmInstance -ApiUrl $ApiUrl -Credential $Credential -SourceInstanceId $SourceInstanceId
 Restore instance.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$SourceInstanceId,

        [Parameter(Mandatory = $true)]
        [string]$TargetInstanceId,

        [Parameter(Mandatory = $true)]
        [datetime]$RestoreTimeUtc,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0,

        [Parameter(Mandatory = $false)]
        [string]$FriendlyName,

        [Parameter(Mandatory = $false)]
        [string]$SecurityGroupId,

        [Parameter(Mandatory = $false)]
        [switch]$ValidateOnly
    )
    process
    {
        if ($ValidateOnly)
        {
            return CreateOperationStatusObject `
                -OperationId "00000000-0000-0000-0000-000000000000" `
                -Status "NotStarted"
        }

        AuthenticationAndLoadModule -Credential $Credential

        $sourceEnvironment = Get-AdminPowerAppEnvironment -InstanceId $SourceInstanceId -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        if ([string]::IsNullOrEmpty($FriendlyName))
        {
            $FriendlyName = $sourceEnvironment.DisplayName
        }

        $restoreRequest = [pscustomobject]@{
            SourceEnvironmentId = $sourceEnvironment.EnvironmentName
            TargetEnvironmentName = $FriendlyName
            TargetSecurityGroupId = $SecurityGroupId
            RestorePointDateTime = $RestoreTimeUtc.ToString("yyyy-MM-dd HH:mm:ss")
        }

        $waitUntilFinished = $true
        if ($MaxCrmConnectionTimeOutMinutes -eq 0)
        {
            $waitUntilFinished = $false
        }

        Write-Verbose "Calling Restore-PowerAppEnvironment."
        $response = Restore-PowerAppEnvironment `
            -EnvironmentName $sourceEnvironment.EnvironmentName `
            -RestoreToRequestDefinition $restoreRequest `
            -WaitUntilFinished $waitUntilFinished `
            -TimeoutInMinutes $MaxCrmConnectionTimeOutMinutes `
            -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response
    }
}

function New-CrmInstanceResetRequestInfo
{
 <#
 .SYNOPSIS
 Creates the object that defines the parameters used to reset a instance of Dynamics 365 Customer Enagagement (online) by using the Reset-CrmInstance cmdlet.
 .DESCRIPTION
 The New-CrmInstanceResetRequestInfo cmdlet creates the object that defines the parameters used to reset a instance of Dynamics 365 Customer Enagagement (online) by using the Reset-CrmInstance cmdlet.
 .PARAMETER BaseLanguage
 Required parameter. The base language for the instance. For a list of available languages, run the Get-CrmLanguages cmdlet.
 .PARAMETER CurrencyCode
 Optional parameter. The currency region code to use for the instance. You can find a list of currency region codes by running the Get-CrmCurrencies cmdlet.
 .PARAMETER CurrencyName
 Optional parameter. The currency name to use for the instance. You can find a list of currency names by running the Get-CrmCurrencies cmdlet.
 .PARAMETER CurrencyPrecision
 Optional parameter. Set the pricing decimal precision. Valid values are 0-4.
 .PARAMETER CurrencySymbol
 Optional parameter. Set the symbol that will represent the currency.
 .PARAMETER DomainName
 Required parameter. Used for part of the domain namespace and appears in the application url. For example, if you use contoso as the DomainName, the url will appear similar to https://contoso.crm.dynamics.com.
 .PARAMETER FriendlyName
 Required parameter. This is typically the name of your organization and is displayed in the Microsoft Dynamics 365 application.
 .PARAMETER Purpose
 Optional parameter. A description used to associate the instance with a specific intent. Only tenant administrators will see this description.
 .PARAMETER SecurityGroupId
 Optional parameter. This value is used to determine the security group that includes the users who will have access to this instance of Microsoft Dynamics 365 (online).
 .PARAMETER TemplateList
 Optional parameter. Specifies the apps you want provisioned in the instance. For a list of available template names, run the Get-CrmTemplates cmdlet.
 .PARAMETER TargetReleaseName
 Required parameter. The name of the target release.
 .PARAMETER PreferredCulture
 Optional parameter. Indicates if the customer prefers a certain culture for formatting for the new instance.
 .EXAMPLE
 New-CrmInstanceResetRequestInfo -OverrideUserAADObjectId $OverrideUserAADObjectId -NotificationText $NotificationText -AllowBackgroundOperations $true
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [int]$BaseLanguage,

        [Parameter(Mandatory = $false)]
        [string]$CurrencyCode,

        [Parameter(Mandatory = $false)]
        [string]$CurrencyName,

        [Parameter(Mandatory = $false)]
        [int]$CurrencyPrecision,

        [Parameter(Mandatory = $false)]
        [string]$CurrencySymbol,

        [Parameter(Mandatory = $true)]
        [string]$DomainName,

        [Parameter(Mandatory = $true)]
        [string]$FriendlyName,

        [Parameter(Mandatory = $false)]
        [string]$Purpose,

        [Parameter(Mandatory = $false)]
        [string]$SecurityGroupId,

        [Parameter(Mandatory = $false)]
        [string[]]$TemplateList,

        [Parameter(Mandatory = $true)]
        [string]$TargetReleaseName,

        [Parameter(Mandatory = $false)]
        [int]$PreferredCulture
    )

    $currency = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.Currency($CurrencyCode, $CurrencyPrecision, $CurrencySymbol, $CurrencyName)

    <# Blocked by BAP error: Could not find member 'TargetRelease' on object of type 'ResetRequestDefinition'.
    $resetRequest = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.ResetRequest
    $resetRequest.FriendlyName = $FriendlyName
    $resetRequest.DomainName = $DomainName
    $resetRequest.Purpose = $Purpose
    $resetRequest.SecurityGroupId = $SecurityGroupId
    $resetRequest.TargetRelease = $TargetReleaseName
    $resetRequest.BaseLanguageCode = $BaseLanguage
    $resetRequest.Currency = $currency
    $resetRequest.ApplicationNames = $TemplateList
 
    return $resetRequest
    #>


    return New-Object -TypeName PSObject `
            | Add-Member -PassThru -MemberType NoteProperty -Name FriendlyName -Value $FriendlyName `
            | Add-Member -PassThru -MemberType NoteProperty -Name DomainName -Value $DomainName `
            | Add-Member -PassThru -MemberType NoteProperty -Name SecurityGroupId -Value $SecurityGroupId `
            | Add-Member -PassThru -MemberType NoteProperty -Name Purpose -Value $Purpose `
            | Add-Member -PassThru -MemberType NoteProperty -Name BaseLanguageCode -Value $BaseLanguage `
            | Add-Member -PassThru -MemberType NoteProperty -Name Currency -Value $currency `
            | Add-Member -PassThru -MemberType NoteProperty -Name Templates -Value $TemplateList
}

function Reset-CrmInstance
{
 <#
 .SYNOPSIS
 Reset instance.
 .DESCRIPTION
 The Reset-CrmInstance cmdlet performs a reset operation on a Dynamics 365 Customer Engagement (online) Sandbox instance in your Office 365 tenant.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .PARAMETER ResetInstanceRequestDetails
 Object created by using the New-CrmInstanceResetRequestInfo cmdlet that contains the information used to reset the instance.
 .PARAMETER TargetInstanceIdToReset
 Id of the instance to reset
 .EXAMPLE
 Reset-CrmInstance -ApiUrl $connectionhost -ResetInstanceRequestDetails ResetInstanceRequestDetails -Credential $cred -TargetInstanceIdToReset $InstanceId
 Reset instance.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$TargetInstanceIdToReset,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0,

        [Parameter(Mandatory = $true)]
        [object]$ResetInstanceRequestDetails
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        $environment = Get-AdminPowerAppEnvironment -InstanceId $TargetInstanceIdToReset -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        $waitUntilFinished = $true
        if ($MaxCrmConnectionTimeOutMinutes -eq 0)
        {
            $waitUntilFinished = $false
        }

        Write-Verbose "Calling Reset-PowerAppEnvironment"
        $response = Reset-PowerAppEnvironment `
            -EnvironmentName $environment.EnvironmentName `
            -ResetRequestDefinition $ResetInstanceRequestDetails `
            -WaitUntilFinished $waitUntilFinished `
            -TimeoutInMinutes $MaxCrmConnectionTimeOutMinutes `
            -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response
    }
}

function Get-CrmGenerateProtectionKey
{
 <#
 .SYNOPSIS
 Get a new protection key.
 .DESCRIPTION
 The Get-CrmGenerateProtectionKey cmdlet returns a new protection key.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .PARAMETER KeyPassword
 Key password for the protection key.
 .PARAMETER SubjectName
 Subject name for the protection key
 .EXAMPLE
 Get-CrmGenerateProtectionKey -ApiUrl $connectionhost -Credential $cred
 Get a new protection key.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$SubjectName,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0,

        [Parameter(Mandatory = $true)]
        [string]$KeyPassword
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling Get-PowerAppGenerateProtectionKey"
        $response = Get-PowerAppGenerateProtectionKey -LocationName $location -KeyName $SubjectName -KeyPassword $KeyPassword -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        
        if (-not [string]::IsNullOrEmpty($response.keyBytes))
        {
            $keyBytes = [System.Convert]::FromBase64String($response.keyBytes)

            return $keyBytes
        }

        return CreateOperationStatusByResponse -Response $response
    }
}

function Get-CrmRetrieveTenantProtectionKey
{
 <#
 .SYNOPSIS
 Get the current tenant protection key.
 .DESCRIPTION
 The Get-CrmRetrieveTenantProtectionKey cmdlet returns the current tenant protection key.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Get-CrmRetrieveTenantProtectionKey -ApiUrl $connectionhost -Credential $cred
 Get the current tenant protection key.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling Get-PowerAppRetrieveTenantProtectionKey"
        $response = Get-PowerAppRetrieveTenantProtectionKey -LocationName $location -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        if (-not [string]::IsNullOrEmpty($response.KeyName))
        {
            return CreateProtectionKeyObject -Key $response
        }

        return CreateOperationStatusByResponse -Response $response
    }
}

function Get-CrmRetrieveAvailableTenantProtectionKeys
{
 <#
 .SYNOPSIS
 Get the available protection keys for current tenant.
 .DESCRIPTION
 The Get-CrmRetrieveAvailableTenantProtectionKeys cmdlet returns the available tenant protection keys.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Get-CrmRetrieveAvailableTenantProtectionKeys -ApiUrl $connectionhost -Credential $cred
 Get the available protection keys for current tenant.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling Get-PowerAppRetrieveAvailableTenantProtectionKeys"
        $response = Get-PowerAppRetrieveAvailableTenantProtectionKeys -LocationName $location -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        if ($response.value -ne $null)
        {
            $result = @()
            foreach ($key in $response.value)
            {
                $result += CreateProtectionKeyObject -Key $key
            }

            return $result        
        }

        return CreateOperationStatusByResponse -Response $response
    }
}

function New-CrmImportProtectionKey
{
 <#
 .SYNOPSIS
 Import a new protection key.
 .DESCRIPTION
 The New-CrmImportProtectionKey cmdlet add a new protection key.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER KeyName
 Name for the protection key
 .PARAMETER KeyType
 Type of the protection key
 .PARAMETER Key
 Content of the protection key
 .PARAMETER KeyPassword
 Key password for the protection key.
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 New-CrmImportProtectionKey -ApiUrl $connectionhost -Credential $cred -KeyName $keyName -Key $keyContent -KeyType 0 -KeyPassword $keyPassword
 Import a new protection key.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$KeyName,

        [Parameter(Mandatory = $true)]
        [int]$KeyType,

        [Parameter(Mandatory = $true)]
        [byte[]]$Key,

        [Parameter(Mandatory = $true)]
        [string]$KeyPassword,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling New-PowerAppImportProtectionKey"
        $response = New-PowerAppImportProtectionKey -LocationName $location -KeyName $KeyName -KeyType $KeyType -KeyPassword $KeyPassword -Key $key -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response
    }
}

function Set-CrmLockTenantProtectedInstances
{
 <#
 .SYNOPSIS
 Lock all the instances associated with the tenant.
 .DESCRIPTION
 Set-CrmLockTenantProtectedInstances locks all the instances associated with the tenant.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER KeyName
 Name for the protection key
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Set-CrmLockTenantProtectedInstances -ApiUrl $connectionhost -Credential $cred -KeyName $keyName
 Lock all the instances associated with the tenant.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$KeyName,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        Write-Verbose "Calling Set-PowerAppLockAllEnvironments"
        $response =  Set-PowerAppLockAllEnvironments -LocationName $location -KeyName $KeyName -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response
    }
}

function Set-CrmUnlockTenantProtectedInstance
{
 <#
 .SYNOPSIS
 Unlock the instance associated with the tenant.
 .DESCRIPTION
 Set-CrmUnlockTenantProtectedInstance unlocks the instance associated with the tenant.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER InstanceId
 Instance Id
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Set-CrmUnlockTenantProtectedInstance -ApiUrl $connectionhost -Credential $cred -InstanceId $instanceId
 Unlock the instance associated with the tenant.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        $environment = Get-AdminPowerAppEnvironment -InstanceId  $TargetInstanceIdToReset -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        Write-Verbose "Calling Set-PowerAppUnlockEnvironment"
        $response =  Set-PowerAppUnlockEnvironment -EnvironmentName $environment.EnvironmentName

        return CreateOperationStatusByResponse -Response $response
    }
}

function Set-CrmProtectWithMicrosoftKey
{
 <#
 .SYNOPSIS
 Protects an instance with default Microsoft key.
 .DESCRIPTION
 The Set-CrmProtectWithMicrosoftKey cmdlet Protects an instance with default microsoft key.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER InstanceId
 Instance Id
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Set-CrmProtectWithMicrosoftKey -ApiUrl $connectionhost -Credential $cred -InstanceId $InstanceId
 Protects an instance with default Microsoft key.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        $environment = Get-AdminPowerAppEnvironment -InstanceId $InstanceId -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        $waitUntilFinished = $true
        if ($MaxCrmConnectionTimeOutMinutes -eq 0)
        {
            $waitUntilFinished = $false
        }

        Write-Verbose "Calling Set-PowerAppProtectionStatus"
        $response = Set-PowerAppProtectionStatus `
            -EnvironmentName $environment.EnvironmentName `
            -ProtectionKeyManagedBy "Microsoft" `
            -WaitUntilFinished $waitUntilFinished `
            -TimeoutInMinutes $MaxCrmConnectionTimeOutMinutes `
            -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response
    }
}

function Set-CrmProtectWithTenantKey
{
 <#
 .SYNOPSIS
 Protects and Instance with the Tenant Key.
 .DESCRIPTION
 The Set-CrmProtectWithTenantKey cmdlet protects a given instance with the Tenant Key and returns operation status.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER InstanceId
 Instance Id
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Set-CrmProtectWithTenantKey -ApiUrl $connectionhost -Credential $cred -InstanceId $InstanceId
 Protects and Instance with the Tenant Key.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl

        $environment = Get-AdminPowerAppEnvironment -InstanceId $InstanceId -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        $waitUntilFinished = $true
        if ($MaxCrmConnectionTimeOutMinutes -eq 0)
        {
            $waitUntilFinished = $false
        }

        Write-Verbose "Calling Set-PowerAppProtectionStatus"
        $response = Set-PowerAppProtectionStatus `
            -EnvironmentName $environment.EnvironmentName `
            -ProtectionKeyManagedBy "Customer" `
            -WaitUntilFinished $waitUntilFinished `
            -TimeoutInMinutes $MaxCrmConnectionTimeOutMinutes `
            -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        return CreateOperationStatusByResponse -Response $response
    }
}

function Set-CrmTenantProtectionKey
{
 <#
 .SYNOPSIS
 Sets an existing tenant protection key.
 .DESCRIPTION
 The Set-CrmTenantProtectionKey cmdlet sets an existing tenant protection key and returns the operation status.
 .PARAMETER ApiUrl
 The URL of the Dynamics 365 Customer Engagement (online) root service endpoint.
 .PARAMETER Credential
 User credential for signing in to Microsoft Dynamics 365 Customer Engagement (online).
 .PARAMETER KeyName
 Key name
 .PARAMETER MaxCrmConnectionTimeOutMinutes
 Maximum number in minutes to wait before quitting the operation.
 .EXAMPLE
 Set-CrmTenantProtectionKey -ApiUrl $connectionhost -Credential $cred
 Sets an existing tenant protection key.
 #>

    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $false)]
        [object]$Credential,

        [Parameter(Mandatory = $true)]
        [string]$KeyName,

        [Parameter(Mandatory = $false)]
        [int]$MaxCrmConnectionTimeOutMinutes = 0
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential
        $location = UrlToLocationMapping -ApiUrl $ApiUrl
        $waitUntilFinished = $true
        if ($MaxCrmConnectionTimeOutMinutes -eq 0)
        {
            $waitUntilFinished = $false
        }

        Write-Verbose "Calling Set-PowerAppTenantProtectionKey"
        $response = Set-PowerAppTenantProtectionKey `
            -LocationName $location `
            -KeyName $KeyName `
            -WaitUntilFinished $waitUntilFinished `
            -TimeoutInMinutes $MaxCrmConnectionTimeOutMinutes `
            -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        if ($response.geo -ne $null)
        {
            return ConvertCdsOperationToOperationStatus -CdsOperation $response
        }
        else
        {
            return CreateOperationStatusByResponse -Response $response
        }
    }
}


#internal, helper function
function UrlToLocationMapping
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl
    )

    $hostName = ($ApiUrl -split "/")[2]
    $apiUrlMapping = @{
        "admin.services.crm.dynamics.com" = "unitedstates";
        "admin.services.crm2.dynamics.com" = "southamerica";
        "admin.services.crm3.dynamics.com" = "canada";
        "admin.services.crm4.dynamics.com" = "europe";
        "admin.services.crm5.dynamics.com" = "asia";
        "admin.services.crm6.dynamics.com" = "australia";
        "admin.services.crm7.dynamics.com" = "japan";
        "admin.services.crm8.dynamics.com" = "india";
        "admin.services.crm10.dynamics.com" = "unitedstates";
        "admin.services.crm11.dynamics.com" = "unitedkingdom";
        "admin.services.crm16.dynamics.com" = "germany";
        "admin.services.crm12.dynamics.com" = "france";
        "admin.services.crm14.dynamics.com" = "southafrica";
        "admin.services.crm15.dynamics.com" = "unitedarabemirates";
        "admin.services.crm9.dynamics.com" = "usgov";
        "admin.services.crm.microsoftdynamics.us" = "usgovhigh";
        "admin.services.crm.appsplatform.us" = "usgovdod";
        "admin.services.crm.dynamics.cn" = "china";
    }

    return $apiUrlMapping[$hostName];
}

function UrlToGeoTypeMapping
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl
    )

    $hostName = ($ApiUrl -split "/")[2]
    $apiUrlMapping = @{
        "admin.services.crm.dynamics.com" = "Na";
        "admin.services.crm2.dynamics.com" = "Sam";
        "admin.services.crm3.dynamics.com" = "Can";
        "admin.services.crm4.dynamics.com" = "Emea";
        "admin.services.crm5.dynamics.com" = "Apac";
        "admin.services.crm6.dynamics.com" = "Oce";
        "admin.services.crm7.dynamics.com" = "Jpn";
        "admin.services.crm8.dynamics.com" = "Ind";
        "admin.services.crm10.dynamics.com" = "Na";
        "admin.services.crm11.dynamics.com" = "Gbr";
        "admin.services.crm16.dynamics.com" = "Ger";
        "admin.services.crm12.dynamics.com" = "Fra";
        "admin.services.crm14.dynamics.com" = "Zaf";
        "admin.services.crm15.dynamics.com" = "Uae";
        "admin.services.crm9.dynamics.com" = "Usg";
        "admin.services.crm.microsoftdynamics.us" = "Usg";
        "admin.services.crm.appsplatform.us" = "Dod";
        "admin.services.crm.dynamics.cn" = "Chn";
    }

    return $apiUrlMapping[$hostName];
}

function AuthenticationAndLoadModule
{
    param
    (
        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [object]$Credential = $null
    )

    LoadModule -ModuleName "Microsoft.PowerApps.Administration.PowerShell" -MinimumVersion 2.0.72

    Write-Verbose "Calling Add-PowerAppsAccount."

    if ($Credential -eq $null)
    {
        Add-PowerAppsAccount -Audience "https://service.apps.appsplatform.us/"
    }
    else
    {
        $endpoint = "prod"
        if ($Credential.Endpoint -ne $null)
        {
            $endpoint = $Credential.Endpoint
        }

        if ([string]::IsNullOrEmpty($Credential.TenantID))
        {
            Add-PowerAppsAccount `
                -Endpoint $endpoint `
                -Password $Credential.Password `
                -UserName $Credential.UserName          
        }
        else
        {
            Add-PowerAppsAccount `
                -Endpoint $endpoint `
                -TenantID $Credential.TenantID `
                -SecureClientSecret $Credential.Password `
                -ApplicationId $Credential.UserName          
        }
    }    
}

function LoadModule
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$ModuleName,

        [Parameter(Mandatory = $true)]
        [string]$MinimumVersion
    )

    # If the correct version of the module is not installed, but is in online gallery then install and import
    if ((Get-InstalledModule -Name $ModuleName -MinimumVersion $MinimumVersion -ErrorAction SilentlyContinue) -eq $null)
    {
        Install-Module -Name $ModuleName -Force
        Import-Module $ModuleName
    }
    else
    {
        # Check if module is imported
        if (-not (Get-Module | Where-Object {$_.Name -eq $ModuleName}))
        {
            # If module is not imported, but available on disk then import
            if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $ModuleName})
            {
                Import-Module $ModuleName
            }
        }
    }
}

function CreateLanguageObject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$Language
    )

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.Language($Language.LanguageName, $Language.LanguageDisplayName, $Language.LanguageLocalizedDisplayName)
}

function CreateTemplateObject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$Template
    )

    $supportedRelease = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.SupportedReleases($null, "Dynamics 365, version 9.0")
    $supportedReleases = New-Object 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.SupportedReleases]'
    $supportedReleases.Add($supportedRelease)

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.Template($Template.TemplateName, $Template.TemplateDisplayName, "", $supportedReleases)
}

function CreateCurrencyObject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$Currency
    )

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.Currency($Currency.CurrencyCode, 0, $Currency.CurrencySymbol, $Currency.CurrencyName)
}

function CreateInstanceObject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$Environment
    )
    
    $stateIsSupportedForDelete = $true
    if ($Environment.Internal.properties.linkedEnvironmentMetadata.InstanceState -ne "Ready")
    {
        $stateIsSupportedForDelete = $false
    }

    $additionalProperties = New-Object 'system.collections.generic.dictionary[[string],[string]]'
    $additionalProperties.Add("CreatedOnUTC", ([System.DateTimeOffset]$Environment.Internal.properties.linkedEnvironmentMetadata.createdTime).ToString())

    $adminMode = $false
    if ($Environment.Internal.properties.states.runtime.id.ToString() -eq "AdminMode")
    {
        $adminMode = $true
    }

    $protectionStatus = "MicrosoftEncrypted"
    if ($Environment.Internal.properties.protectionStatus.keyManagedBy -eq "Customer")
    {
        $protectionStatus = "TenantEncrypted"
    }

    $instance = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.Instance
    $instance.Id = $Environment.Internal.properties.linkedEnvironmentMetadata.ResourceId
    $instance.UniqueName = $Environment.Internal.properties.linkedEnvironmentMetadata.UniqueName
    $instance.Version = $Environment.Internal.properties.linkedEnvironmentMetadata.Version
    $instance.ApplicationUrl = $Environment.Internal.properties.linkedEnvironmentMetadata.instanceUrl
    $instance.ApiUrl = $Environment.Internal.properties.linkedEnvironmentMetadata.InstanceApiUrl
    $instance.State = $Environment.Internal.properties.linkedEnvironmentMetadata.InstanceState
    $instance.Type = $Environment.EnvironmentType
    $instance.FriendlyName = $Environment.Internal.properties.linkedEnvironmentMetadata.FriendlyName
    $instance.InitialUserPrincipalName = $Environment.CreatedBy.userPrincipalName
    $instance.StateIsSupportedForDelete = $stateIsSupportedForDelete
    $instance.AdminMode = $adminMode
    $instance.DomainName = $Environment.Internal.properties.linkedEnvironmentMetadata.domainName
    $instance.BaseLanguage = $Environment.Internal.properties.linkedEnvironmentMetadata.baseLanguage
    $instance.IsLocked = $Environment.Internal.properties.linkedEnvironmentMetadata.isLocked
    $instance.ProtectionStatus = $protectionStatus
    $instance.AdditionalProperties = $additionalProperties
    $instance.SecurityGroupId = $Environment.Internal.properties.linkedEnvironmentMetadata.securityGroupId

    return $instance
}

function CreateServiceVersionObject
{
    param
    (
    )
    
    $serviceVersion = New-Object Microsoft.Xrm.Services.Admin.Client.Models.ServiceVersion
    $serviceVersion.LocalizedName = "Dynamics 365, version 9.0"
    $serviceVersion.LCID = 1033
    $serviceVersion.Version = "9.0"
    $serviceVersion.Id = "bce9abbf-90fd-42e7-b0e5-1ced6df22fa1"
    $serviceVersion.Name = "Dynamics 365, version 9.0"

    return $serviceVersion
}

function CreateOperationStatusObject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [guid]$OperationId,

        [Parameter(Mandatory = $true)]
        [string]$Status,

        [Parameter(Mandatory = $false)]
        [system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription]]$Errors,

        [Parameter(Mandatory = $false)]
        [system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription]]$Information,

        [Parameter(Mandatory = $false)]
        [string]$OperationLocation = $null,

        [Parameter(Mandatory = $false)]
        [string]$ResourceLocation = $null,

        [Parameter(Mandatory = $false)]
        [Microsoft.Xrm.Services.Admin.Client.Models.OperationContext]$Context
    )

    if ($Errors -eq $null)
    {
        $Errors = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription]'
    }

    if ($Information -eq $null)
    {
        $Information = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription]'
    }

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.OperationStatus( `
            $OperationId, `
            $Status, `
            $Errors, `
            $Information, `
            $OperationLocation, `
            $ResourceLocation, `
            $context)
}

function CreateInstanceTypes
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$InstanceTypeInfo
    )

    $instanceTypeInfos = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.InstanceTypeInfo]'
    if ($InstanceTypeInfo.value.production -ne $null)
    {
        $instanceType = CreateInstanceType -Type "Production" -Total $InstanceTypeInfo.value.production -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "Production" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)
    
    if ($InstanceTypeInfo.value.sandbox -ne $null)
    {
        $instanceType = CreateInstanceType -Type "Sandbox" -Total $InstanceTypeInfo.value.sandbox -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "Sandbox" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)

    if ($InstanceTypeInfo.value.preview -ne $null)
    {
        $instanceType = CreateInstanceType -Type "Preview" -Total $InstanceTypeInfo.value.preview -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "Preview" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)

    if ($InstanceTypeInfo.value.support -ne $null)
    {
        $instanceType = CreateInstanceType -Type "Support" -Total $InstanceTypeInfo.value.support -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "Support" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)

    if ($InstanceTypeInfo.value.trial -ne $null)
    {
        $instanceType = CreateInstanceType -Type "Trial" -Total $InstanceTypeInfo.value.trial -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "Trial" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)

    if ($InstanceTypeInfo.value.default -ne $null)
    {
        $instanceType = CreateInstanceType -Type "Default" -Total $InstanceTypeInfo.value.default -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "Default" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)

    if ($InstanceTypeInfo.value.developer -ne $null)
    {
        $instanceType = CreateInstanceType -Type "Developer" -Total $InstanceTypeInfo.value.developer -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "Developer" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)

    if ($InstanceTypeInfo.value.subscriptionBasedTrial -ne $null)
    {
        $instanceType = CreateInstanceType -Type "SubscriptionBasedTrial" -Total $InstanceTypeInfo.value.subscriptionBasedTrial -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "SubscriptionBasedTrial" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)

    if ($InstanceTypeInfo.value.teams -ne $null)
    {
        $instanceType = CreateInstanceType -Type "Teams" -Total $InstanceTypeInfo.value.teams -Consumed 0
    }
    else
    {
        $instanceType = CreateInstanceType -Type "Teams" -Total 0 -Consumed 0
    }

    $instanceTypeInfos.Add($instanceType)

    return $instanceTypeInfos
}

function CreateInstanceType
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$Type,

        [Parameter(Mandatory = $true)]
        [int]$Total,

        [Parameter(Mandatory = $true)]
        [int]$Consumed
    )

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.InstanceTypeInfo($Type, $Total, $Consumed)
}

function CreateTenantApplicationIdentity
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$application
    )

    $tenantApplicationIdentity = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.TenantApplicationIdentity
    $tenantApplicationIdentity.Id = $application.applicationId
    $tenantApplicationIdentity.AadApplicationId = $application.applicationId
    $tenantApplicationIdentity.TenantId = $global:currentSession.tenantId
    $tenantApplicationIdentity.Enabled = $true
    $tenantApplicationIdentity.CreatedOn = (Get-Date -Format "dddd MM/dd/yyyy HH:mm K")

    return $tenantApplicationIdentity
}

function CreateProtectionKeyObject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$Key
    )

    $protectionKey = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.Key
    $protectionKey.KeyName = $Key.keyName
    $protectionKey.TenantId = $Key.tenantId
    $protectionKey.KeyState = $Key.keyState
    $protectionKey.CreatedOn = $Key.createdDateTime
    $protectionKey.LockedOn = $Key.lockedDateTime

    return $protectionKey
}

function UpdateRuntimeState
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$ApiUrl,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId,

        [Parameter(Mandatory = $true)]
        [string]$RuntimeState,

        [Parameter(Mandatory = $false)]
        [object]$Credential
    )
    process
    {
        AuthenticationAndLoadModule -Credential $Credential

        $environment = Get-AdminPowerAppEnvironment -InstanceId $InstanceId -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
        $response = Set-AdminPowerAppEnvironmentRuntimeState `
                    -EnvironmentName $environment.EnvironmentName `
                    -RuntimeState $RuntimeState `
                    -Verbose:($script:MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)

        if ($response.Code -eq 204 -or $response.Code -eq 200)
        {
            return CreateOperationStatusObject `
                    -OperationId "00000000-0000-0000-0000-000000000000" `
                    -Status "Succeeded"     
        }

        return CreateOperationStatusObject `
                -OperationId "00000000-0000-0000-0000-000000000000" `
                -Status "Failed" `
                -Errors $response.Errors
    }
}

function CreateManualBackupObject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$Backup,

        [Parameter(Mandatory = $true)]
        [string]$InstanceId
    )

    $manualBackup = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.ManualBackup
    $manualBackup.Notes = $Backup.Notes
    $manualBackup.Label = $Backup.Label
    $manualBackup.CreatedByUserPrincipalName = $Backup.CreatedBy.UserPrincipalName
    $manualBackup.InstanceId = $InstanceId
    $manualBackup.Id = $Backup.Id
    $manualBackup.TimestampUtc = $Backup.BackupPointDateTime
    $manualBackup.ExpiryTimeUtc = $Backup.BackupExpiryDateTime

    return $manualBackup
}

function CreateItemDescription
{
    param
    (
        [Parameter(Mandatory = $false)]
        [string]$Subject,

        [Parameter(Mandatory = $false)]
        [string]$Description,

        [Parameter(Mandatory = $false)]
        [string]$Code
    )

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription($Subject, $Description, $Code)
}

function CreateOperationStatusByResponse
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$Response,

        [Parameter(Mandatory = $false)]
        [string]$GeoType,

        [Parameter(Mandatory = $false)]
        [string]$OperationId = "00000000-0000-0000-0000-000000000000"
    )

    $errors = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription]'
    $context = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.OperationContext
    if ($Response -ne $null)
    {
        if ($Response.Code -eq 200)
        {
            return CreateOperationStatusObject `
                    -OperationId $OperationId `
                    -Status "Succeeded" `
                    -Context $context
        }
        elseif ($Response.Code -eq 202)
        {
            if ($Response.Headers['Operation-Location'] -ne $null)
            {
                $operationLocation = $Response.Headers['Operation-Location']
            }
            else
            {
                $operationLocation = $Response.Headers['Location']
            }
        
            $operationId = (($operationLocation -split "operations/")[1]).Substring(0, 36)
            return CreateOperationStatusObject `
                    -OperationId $operationId `
                    -Status "Running" `
                    -OperationLocation $operationLocation `
                    -Context $context
        }
        elseif ($Response.Code -eq 204)
        {
            return CreateOperationStatusObject `
                    -OperationId "00000000-0000-0000-0000-000000000000" `
                    -Status "Succeeded" `
                    -Context $context
        }
        elseif ($Response.CommonDataServiceDatabaseProvisioningState -ne $null -and $Response.CommonDataServiceDatabaseProvisioningState -eq "Succeeded")
        {
            # New-CrmInstance returns environment
            $items = New-Object 'system.collections.generic.dictionary[[string],[object]]'
            $items.Add("admin.InstanceId", $response.Internal.properties.linkedEnvironmentMetadata.ResourceId)
            $items.Add("InstanceState", "Ready")

            $resourceLocation = "https://admin.powerplatform.microsoft.com/environments/instance/{instanceId}/hub?geo={geoType}" `
                | ReplaceMacro -Macro "{instanceId}" -Value $response.Internal.properties.linkedEnvironmentMetadata.ResourceId `
                | ReplaceMacro -Macro "{geoType}" -Value $GeoType

            $context = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.OperationContext($items)

            return CreateOperationStatusObject `
                    -OperationId "00000000-0000-0000-0000-000000000000" `
                    -Status "Succeeded" `
                    -ResourceLocation $resourceLocation `
                    -Context $context
        }

        if ($response.Errors -ne $null)
        {
            foreach ($error in $response.Errors)
            {
                $errorObject = CreateErrorObject -Error $error
                $errors.Add($errorObject)
            }
        }
        else
        {
            $errorObject = CreateErrorObject -Error $response.Error
            $errors.Add($errorObject)
        }
    }
    else
    {
        $errorObject = CreateItemDescription -Code "NotSpecified" -Description "Response is null"
        $errors.Add($errorObject)
    }

    return CreateOperationStatusObject `
        -OperationId "00000000-0000-0000-0000-000000000000" `
        -Status "Failed" `
        -Errors $errors
}

function CreateErrorObject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$Error
    )

    if ($Error -ne $null -and $Error.code)
    {
        $errorCode = $Error.code
    }
        
    if ($Error -ne $null -and $Error.message -ne $null)
    {
        $errorMessage = $Error.message
    }

    return CreateItemDescription -Code $errorCode -Description $errorMessage
}

function ConvertCdsOperationToOperationStatus
{
    param
    (
        [Parameter(Mandatory = $true)]
        [object]$CdsOperation
    )

    $operationLocation = "https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/locations/{location}/protectionKeyOperations/{operationId}?api-version=2020-05-01" `
        | ReplaceMacro -Macro "{location}" -Value $CdsOperation.Geo `
        | ReplaceMacro -Macro "{operationId}" -Value $CdsOperation.Id;

    $errors = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription]'
    if ($CdsOperation.Errors -ne $null)
    {
        foreach($error in $CdsOperation.Errors)
        {
            $errorObject = CreateErrorObject -Error $error
            $errors.Add($errorObject)
        }
    }

    $Information = New-Object -TypeName 'system.collections.generic.list[Microsoft.Xrm.Services.Admin.Client.Models.ItemDescription]'
    $context = New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.OperationContext

    return New-Object -TypeName Microsoft.Xrm.Services.Admin.Client.Models.OperationStatus( `
            $CdsOperation.Id, `
            $CdsOperation.State, `
            $errors, `
            $Information, `
            $operationLocation, `
            $null, `
            $context)
}
# SIG # Begin signature block
# MIIjgwYJKoZIhvcNAQcCoIIjdDCCI3ACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBwmbQ35xEkrdQI
# xkISLBTwwsv057qXLbhbhq08QWK4YKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX
# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB
# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH
# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d
# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ
# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV
# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy
# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K
# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV
# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr
# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx
# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe
# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g
# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf
# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI
# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5
# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea
# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVWDCCFVQCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN
# BglghkgBZQMEAgEFAKCBoDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgUctVG0S8
# ThjNP68I9jfRNc8ODcEOKDwFLZS7qN6HXsAwNAYKKwYBBAGCNwIBDDEmMCSgEoAQ
# AFQAZQBzAHQAUwBpAGcAbqEOgAxodHRwOi8vdGVzdCAwDQYJKoZIhvcNAQEBBQAE
# ggEAoTxCqP1CNrO9e81mNGpTpuX75rCKfRTDsK3+31EYzxQEmYQ61lsIKKlypsNL
# ze3hUXDOplYZy9Fo6sMmpcCOTvsdxNgXnNB0Fs/X49E1Lgyxq6no73ZiYn+s7Wxh
# Yc6APZxqTRw2F57TWfIdrgLx5gk6Vp0GsRQkc89iFDh8jpqKk7AtzgqHAJkyuY6n
# MeROcdt0+ljHRkI718oWr6syXEOyvQopJoutMLuafZfOIqgwu9/UYy9WFl2yyA+o
# taVONq1e5DgBkTinZFssDUXv6vVLvPw2Rkq9fAVxfmbAl5NrerPRqebqddu+llRX
# 5KwdCEWTpc/O1sT7r04aHAOUKqGCEvAwghLsBgorBgEEAYI3AwMBMYIS3DCCEtgG
# CSqGSIb3DQEHAqCCEskwghLFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFUBgsqhkiG
# 9w0BCRABBKCCAUMEggE/MIIBOwIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQC
# AQUABCANkH+Qi/JGNTNDDKz7g+51kFltHfwtWPBJuIsgip9WugIGX3SUej1TGBIy
# MDIwMTAwNTE3MjMxNi41MlowBIACAfSggdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRp
# b25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJELUUz
# RDUtM0IxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCC
# DkQwggT1MIID3aADAgECAhMzAAABLqjSGQeT9GvoAAAAAAEuMA0GCSqGSIb3DQEB
# CwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTE5MTIxOTAxMTUw
# NVoXDTIxMDMxNzAxMTUwNVowgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBS
# aWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJELUUzRDUtM0IxRDElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBAK7TTKJRU196LFIjMQ9q/UjpPhz43m5RnHgHAVp2
# YGni74+ltsYoO1nZ58rTbJhCQ8GYHy8B4devgbqqYPQNU3i+drpEtEcNLbsMr4ME
# q3SM+vO3a6QMFd1lDRy7IQLPJNLKvcM69Nt7ku1YyM5NnPNDcRJsnUb/8Yx/zcW5
# cWjnoj8s9fQ93BPf/J74qM1ql2CdzQV74PBisMP/tppAnSuNwo8I7+uWr6vfpByn
# SWDvJeMDrcsa62Xsm7DbB1NnSsPGAGt3RzlBV9KVicize4U3fo4chdoB2+QLu17P
# aEmj07qq700CG5XJkpEYOjedNFiByApF7YRvQrOZQ07QYiMCAwEAAaOCARswggEX
# MB0GA1UdDgQWBBSGmokmTguJN7uqSTQ1UhLwt1RObDAfBgNVHSMEGDAWgBTVYzpc
# ijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p
# Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAxMC0w
# Ny0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAx
# LmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3
# DQEBCwUAA4IBAQCN4ARqpzCuutNqY2nWJDDXj35iaidlgtJ/bspYsAX8atJl19If
# UKIzTuuSVU3caXZ6/YvMMYMcbsNa/4J28us23K6PWZAljIj0G8QtwDMlQHjrKnrc
# r4FBAz6ZqvB6SrN3/Wbb0QSK/OlxsU0mfD7z87R2JM4gwKJvH6EILuAEtjwUGSB1
# NKm3Twrm51fCD0jxvWxzaUS2etvMPrh8DNrrHLJBR3UHvg/NXS2IzdQn20xjjsW0
# BUAiTf+NCRpxUvu/j80Nb1++vnejibfpQJ2IlXiJdIi+Hb+OL3XOr8MaDDSYOaRF
# AIfcoq3VPi4BkvSC8QGrvhjAZafkE7R6L5FJMIIGcTCCBFmgAwIBAgIKYQmBKgAA
# AAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUg
# QXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcNMjUwNzAxMjE0NjU1WjB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIwDQYJKoZIhvcNAQEBBQAD
# ggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0VBDVpQoAgoX77XxoSyxf
# xcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEwRA/xYIiEVEMM1024OAiz
# Qt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQedGFnkV+BVLHPk0ySwcSm
# XdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKxXf13Hz3wV3WsvYpCTUBR
# 0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4GkbaICDXoeByw6ZnNPOcv
# RLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEAAaOCAeYwggHiMBAGCSsG
# AQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7fEYbxTNoWoVtVTAZBgkr
# BgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
# AwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBN
# MEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0
# cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoG
# CCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p
# Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0gAQH/BIGVMIGSMIGPBgkr
# BgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABl
# AGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJ
# KoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOhIW+z66bM9TG+zwXiqf76
# V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS+7lTjMz0YBKKdsxAQEGb
# 3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlKkVIArzgPF/UveYFl2am1
# a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon/VWvL/625Y4zu2JfmttX
# QOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOiPPp/fZZqkHimbdLhnPkd
# /DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/fmNZJQ96LjlXdqJxqgaK
# D4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCIIYdqwUB5vvfHhAN/nMQek
# kzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0cs0d9LiFAR6A+xuJKlQ5
# slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7aKLixqduWsqdCosnPGUFN
# 4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQcdeh0sVV42neV8HR3jDA
# /czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+NR4Iuto229Nfj950iEkS
# oYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0
# byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjozMkJELUUzRDUtM0IxRDEl
# MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsO
# AwIaAxUA+1/CN6BILeU1yDGo+b6WkpLoQpuggYMwgYCkfjB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOMlqlMwIhgPMjAyMDEw
# MDUxODIxMDdaGA8yMDIwMTAwNjE4MjEwN1owdzA9BgorBgEEAYRZCgQBMS8wLTAK
# AgUA4yWqUwIBADAKAgEAAgIoagIB/zAHAgEAAgIR+jAKAgUA4yb70wIBADA2Bgor
# BgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAID
# AYagMA0GCSqGSIb3DQEBBQUAA4GBAHDwXAHBW1gWoIgpBGLXl5Z9jh53ubLzeQ3c
# PyjqFVRS+w/XoCQW/nogGDt4sLz8+LWQHIQ1wId9Sy5w1pyuYlYt8hmW5zsPqAIO
# 3Hhvqy3lwakFGY2AFEkP+9oPySWtKQk+MFmUajdKEMGYDRBsdHODEFOW+E5uIvJ+
# CkuPrBzCMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAEuqNIZB5P0a+gAAAAAAS4wDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqG
# SIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgYYrqNRRxNs95
# lhrUDcRqtyoKFCa94NK6z1Z5jSGW9BswgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHk
# MIG9BCDa/s3O8YhWiqpVN0kTeK+x2m0RAh17JpR6DiFoTILJKTCBmDCBgKR+MHwx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABLqjSGQeT9GvoAAAAAAEu
# MCIEIIfthjHoMwXzQ/9MRUrr6tiOJEs9RhOOpx2n05jQEBUIMA0GCSqGSIb3DQEB
# CwUABIIBABU0jGCIJx8NUkU8R5j2dxCPzlDx8n817BGJI835Ja8pY36BbzrNhZmt
# di3Lycv3W5YDQPd6EO4OjcRrPyC6kY2iH9FDvufjq+6AaT9un7nIv7kskLDD9yxK
# 8HuH2/eA4yq+VduavgP0W5CMVRHeL8v8dTxV7jUL3l+fxLVNy71oCMtH6c/AvQat
# wUc0AbfG576fEf/08ipxWO1H+2qlTdZUi/TH+pxPNad1oh8lKWAlrIc3PyEonVuT
# eL5NfjxDl5dkAZncfvvvR4XFfIrEjybleCt3UXqOUxz6lIVpB3D1IBwgJ2qZ16/U
# MDVAiEmEsiBIyn8vUgUn3al539gzWG4=
# SIG # End signature block