PureStorage.CBS.AVS.psm1

 . $PSScriptRoot/PureStorage.Logger.ps1
 . $PSScriptRoot/PureStorage.CommonUtil.ps1
 . $PSScriptRoot/PureStorage.RunCommandLauncher.ps1
 . $PSScriptRoot/PureStorage.CBS.AVS.VMFS.ps1
 . $PSScriptRoot/PureStorage.CBS.AVS.VVOLS.ps1
 . $PSScriptRoot/PureStorage.CBS.AVS.VVOLS.Replication.ps1
 . $PSScriptRoot/PureStorage.CBS.AVS.Configuration.ps1
 . $PSScriptRoot/PureStorage.CBS.AVS.Monitor.ps1

$VVOL_WARNIING = "vVols Cmdlets are experimental and not supported in this release. Please use with caution."
$DEFAULT_UTILIZATION_THRESHOLD = 80

function Build-PCBSCluster {
    <#
    .SYNOPSIS
     Build or update settings for a cluster of ESXi servers
    .DESCRIPTION
     Build or update settings for a cluster of ESXi servers. Creates a hostgroup in Pure Cloud Block Store if it does not exists and
     updates iSCSI settings. Can be used when creating a new cluster or when adding hosts to a cluster.
    .PARAMETER ClusterName
     Cluster name
    .PARAMETER PureCloudBlockStoreConnection
     Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet
    .PARAMETER AVSCloudName
     AVS cloud name
    .PARAMETER AVSResourceGroup
     AVS Resource group name
    .PARAMETER TimeoutInMinutes
     Optional. Timeout in minutes for RunCommand operations.
    .EXAMPLE
    Build-PCBSCluster -ClusterName "mycluster" -PureCloudBlockStoreConnection $CBSConnection `
      -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [String]$ClusterName,

        [Parameter(Mandatory=$false)]
        $PureCloudBlockStoreConnection,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,

        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int] $TimeoutInMinutes
    )

    Write-Progress -Activity "Building cluster" -Status "0% Complete:" -PercentComplete 1

    $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        $cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName
        if (-not $cluster) {
            throw "Could not find cluster '$ClusterName'..."
        }

        $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters

        $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName
        if ($ClusterStatus -ne "Succeeded") {
            throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'"
        }

        Test-MPIOProvisionStatus -Cluster $cluster

        Write-Progress -Activity "Configuring iSCSI" -Status "25% Complete:" -PercentComplete 25
        $updated_hosts = New-PfaHostGroupfromVcCluster -FlashArray $fa -Cluster $cluster `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes
        Write-Progress -Activity "Removing unused hosts" -Status "50% Complete:" -PercentComplete 50
        Remove-PfaUnusedHosts -FlashArray $fa -Cluster $cluster | Out-Null

        Write-Progress -Activity "Refreshing iSCSI targets" -Status "75% Complete:" -PercentComplete 75
        if ($updated_hosts) {
            $ethList = (Get-Pfa2NetworkInterface -Array $FlashArray | Where-Object {$_.services -eq "iscsi"} | Where-Object {$_.enabled -eq $true} | Where-Object {$null -ne $_.Eth.address}).Eth
            $ISCSIAddressList = @()
            foreach ($eth in $ethList) {
            $ISCSIAddressList += $eth.address
            }

            $params = @{
                ClusterName = $ClusterName
                # Need to join the list as string as RunCommand does not support array type
                ISCSIAddress = $ISCSIAddressList -join ","
            }

            Invoke-RunScript -RunCommandName  "Remove-VMHostStaticiSCSITargets" -RunCommandModule "Microsoft.AVS.VMFS" -Parameters $params `
                -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes
        }
        Write-Host "Cluster '$ClusterName' is successfully built"
        Write-Progress -Activity "Operation is done" -Status "100% Complete:" -PercentComplete 100
    }
    catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function New-PCBSVmfsDatastore {
    <#
    .SYNOPSIS
      Creates a new VMFS datastore and mounts to a VMware cluster
    .DESCRIPTION
      Creates a new VMFS datastore and mounts to a VMware cluster
    .PARAMETER ClusterName
      Cluster name
    .PARAMETER DatastoreName
      Datastore name
    .PARAMETER Size
      Datastore capacity size in bytes
    .PARAMETER PodName
     Optional. Pod name. If the parameter is specified, the backing volume for the datastore will be created in the specified Pod.
    .PARAMETER NoDefaultProtection
     Optional. Bypass default protection group. If the parameter is specified, the datastore will not be protected by default protection group
    .PARAMETER PureCloudBlockStoreConnection
     Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet
    .PARAMETER AVSCloudName
     AVS cloud name
    .PARAMETER AVSResourceGroup
     AVS Resource group name
    .PARAMETER TimeoutInMinutes
      Optional. Timeout in minutes for RunCommand operations.

    .EXAMPLE
      New-PCBSVmfsDatastore -ClusterName myClusterName -PureCloudBlockStoreConnection $CBSConnection -DatastoreName MyVMFSStore -Size 4GB `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

      Create a datastore "MyVMFS"
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [String]$ClusterName,

        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [String]$DatastoreName,

        [ValidateRange(1GB,64TB)] # 1 GB to 64 TB
        [Parameter(Mandatory=$true)]
        [UInt64]$Size,

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

        [Parameter(Mandatory=$false)]
        [switch] $NoDefaultProtection,

        [Parameter(Mandatory=$false)]
        $PureCloudBlockStoreConnection,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,

        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int] $TimeoutInMinutes
    )
    Write-Progress -Activity "Creating datastore" -Status "0% Complete:" -PercentComplete 1

    $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore
        if (-not $Cluster) {
            throw "Cluster $ClusterName does not exist."
        }

        $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName
        if ($ClusterStatus -ne "Succeeded") {
            throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'"
        }
        $TimeoutInMinutes = Get-Timeout -Cluster $Cluster -InputParams $PSBoundParameters
        New-PfaVmfs -Cluster $Cluster -Flasharray $fa -Name $DatastoreName -vCenterServer $vCenterServer -Size $Size -NoDefaultProtection:$NoDefaultProtection.IsPresent `
            -PodName $PodName -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

        Write-Host "Datastore '$DatastoreName' is successfully created"
        Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    } catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function Restore-PCBSVmfsDatastore {
    <#
    .SYNOPSIS
      Mounts a copy of a VMFS datastore to a VMware cluster from a Pure Cloud Block Store snapshot, volume, or pod.
    .DESCRIPTION
      Takes in a snapshot/volume/pod name, the corresponding Pure Cloud Block Store, and a cluster. The VMFS copy will be resignatured and mounted.
    .PARAMETER ClusterName
      Cluster name
    .PARAMETER VolumeSnapshotName
      Volume snapshot name. A volume will be created from the volume snapshot. A datastore will be created from the volume
    .PARAMETER VolumeName
      Volume name. A datastore will be created from the volume. No volume copy will be created, the volume specified will be directly used for the datastore
    .PARAMETER ProtectionGroupSnapshotName
      Protection group snapshot name. All of volume snapshots of the protection group snapshot will be used for restoring. The snapshot will be skipped if not supported.
    .PARAMETER PodName
      Pod name. All of volumes of the pod will be used for restoring. The volume will be skipped if not supported
    .PARAMETER PureCloudBlockStoreConnection
      Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet
    .PARAMETER DatastoreName
      Optional. Datastore name. If the parameter is not specified, a generated name will be used.
    .PARAMETER AVSCloudName
      AVS cloud name
    .PARAMETER AVSResourceGroup
      AVS Resource group name
    .EXAMPLE
      Restore-PCBSVmfsDatastore -ClusterName myClusterName -VolumeSnapshotName mySnapshotName -PureCloudBlockStoreConnection $CBSConnection `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

      Takes in a snapshot name, the corresponding Pure Cloud Block Store, and a cluster. The VMFS copy will be resignatured and mounted.
    .EXAMPLE
      Restore-PCBSVmfsDatastore -ClusterName myClusterName -VolumeName myVolumeName -PureCloudBlockStoreConnection $CBSConnection `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

      Takes in a volume name, the corresponding Pure Cloud Block Store, and a cluster. The VMFS copy will be resignatured and mounted.
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [String]$ClusterName,

        [Parameter(Mandatory = $true, ParameterSetName = 'VolumeSnapshot')]
        [String]$VolumeSnapshotName,

        [Parameter(Mandatory = $true, ParameterSetName = 'Volume')]
        [String]$VolumeName,

        [Parameter(Mandatory = $true, ParameterSetName = 'ProtectionGroupSnapshot')]
        [String]$ProtectionGroupSnapshotName,

        [Parameter(Mandatory = $true, ParameterSetName = 'Pod')]
        [String]$PodName,

        [Parameter(Mandatory = $false, ParameterSetName = 'Volume')]
        [Parameter(Mandatory = $false, ParameterSetName = 'VolumeSnapshot')]
        [String]$DatastoreName,

        [Parameter(Mandatory=$false)]
        $PureCloudBlockStoreConnection,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,


        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup
    )
    Write-Progress -Activity "Restoring datastore" -Status "0% Complete:" -PercentComplete 1

    $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore
        if (-not $Cluster) {
            throw "Cluster '$ClusterName' does not exist."
        }

        $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName
        if ($ClusterStatus -ne "Succeeded") {
            throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'"
        }

        if (-not [string]::IsNullOrEmpty($DatastoreName)) {
        $Datastore = Get-Datastore -Server $vCenterServer -Name $DatastoreName -ErrorAction Ignore
            if ($Datastore) {
                throw "Datastore '$Datastore' already exists."
            }
        }

        switch ($PSCmdlet.ParameterSetName) {
            'Volume' {
                Write-Host "Creating datastore from volume..."
            $NewDatastore = Restore-PfaVmfsFromVolume -FlashArray $fa -Cluster $Cluster -VolumeName $VolumeName -DatastoreName $DatastoreName `
                                    -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

                break
            }
            'VolumeSnapshot' {
                Write-Host "Creating datastore from volume snapshot..."
            $NewDatastore = Restore-PfaVmfsFromVolumeSnapshot -FlashArray $fa -Cluster $Cluster -VolumeSnapshotName $VolumeSnapshotName -DatastoreName $DatastoreName `
                                    -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

                break
            }
            'ProtectionGroupSnapshot' {
                Write-Host "Creating datastore from protection group snapshot..."
            $NewDatastore = Restore-PfaVmfsFromProtectionGroupSnapshot -FlashArray $fa -Cluster $Cluster -ProtectionGroupSnapshotName $ProtectionGroupSnapshotName `
                                    -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
                break
            }
            'Pod' {
                Write-Host "Creating datastore from pod..."
            $NewDatastore = Restore-PfaVmfsFromPod -FlashArray $fa -Cluster $Cluster -PodName $PodName `
                                    -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
                break
            }
        }
        Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed

        Write-Host "Datastore '$($NewDatastore.Name)' is successfully restored"
    } catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
    return $NewDatastore
}


function Remove-PCBSVmfsDatastore {
  <#
    .SYNOPSIS
     Detaches and unmount a VMFS datastore from a cluster. Remove the datastore from the host group
    .DESCRIPTION
     Detaches and unmount a VMFS datastore from a cluster. Remove the datastore from the host group. The connection configured for the volume and cluster host/host group will be removed from Pure Cloud Block Store. The volume will be destroyed if there is no other connection left.
    .PARAMETER ClusterName
     Cluster name
    .PARAMETER DatastoreName
     Datastore name
    .PARAMETER PureCloudBlockStoreConnection
     Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet
    .PARAMETER AVSCloudName
     AVS cloud name
    .PARAMETER AVSResourceGroup
     AVS Resource group name
    .PARAMETER TimeoutInMinutes
    Optional. Timeout in minutes for RunCommand operations.
    .EXAMPLE
     Remove-PCBSVmfsDatastore -ClusterName "mycluster" -DatastoreName "myDatastore" PureCloudBlockStoreConnection $CBSConnection `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

     Takes in a datastore name, datastore would be removed.
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [String]$ClusterName,

        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [String]$DatastoreName,

        [Parameter(Mandatory=$false)]
        $PureCloudBlockStoreConnection,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,

        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int] $TimeoutInMinutes
    )
    Write-Progress -Activity "Removing datastore" -Status "0% Complete:" -PercentComplete 1
    $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore
        if (-not $Cluster) {
            throw "Cluster '$ClusterName' does not exist."
        }

        $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters

        $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName
        if ($ClusterStatus -ne "Succeeded") {
            throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'"
        }

        $Datastore = Get-Datastore -Server $vCenterServer -Name $DatastoreName -ErrorAction Ignore
        if (-not $Datastore) {
            throw "Datastore '$DatastoreName' does not exist."
        }
        Remove-PfaVmfsDatastore -Cluster $Cluster -Datastore $DataStore -FlashArray $FlashArray `
            -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

        Write-Host "Datastore '$DatastoreName' is successfully removed"
        Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    } catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}


function Set-PCBSVmfsCapacity {
  <#
    .SYNOPSIS
     Increase the size of a Pure Cloud Block Store-based VMFS datastore.
    .DESCRIPTION
     Takes in a datastore, the corresponding Pure Cloud Block Store, and a new size. Both the volume and the VMFS will be grown.
    .PARAMETER ClusterName
     Cluster name
    .PARAMETER DatastoreName
     Datastore name
    .PARAMETER Size
     New datastore capacity size in bytes
    .PARAMETER PureCloudBlockStoreConnection
     Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet
    .PARAMETER AVSCloudName
     AVS cloud name
    .PARAMETER AVSResourceGroup
     AVS Resource group name
    .PARAMETER TimeoutInMinutes
      Optional. Timeout in minutes for RunCommand operations.
    .EXAMPLE
     Set-PCBSVmfsCapacity -ClusterName "mycluster" -DatastoreName myDatastore -Size 3GB -PureCloudBlockStoreConnection $CBSConnection `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

     Expand size of the datastore myDatastore to 3GB.
    #>

    [CmdletBinding()]
    Param (
          [Parameter(Mandatory=$true)]
          [String]$ClusterName,

          [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
          [string]$DatastoreName,

          [ValidateRange(1GB,64TB)] # 1 GB to 64 TB
          [Parameter(Mandatory=$true)]
          [UInt64]$Size,

          [Parameter(Mandatory=$false)]
          $PureCloudBlockStoreConnection,

          [Parameter(Mandatory=$true)]
          [String]$AVSCloudName,

          [Parameter(Mandatory=$true)]
          [String]$AVSResourceGroup,

          [Parameter(Mandatory=$false)]
          [ValidateRange(5, 60)]
          [int]$TimeoutInMinutes
    )
    Write-Progress -Activity "Resizing datastore" -Status "0% Complete:" -PercentComplete 1

    $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore
        if (-not $Cluster) {
            throw "Cluster '$ClusterName' does not exist."
        }

        $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters

        $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName
        if ($ClusterStatus -ne "Succeeded") {
            throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'"
        }

        $Datastore = Get-Datastore -Server $vCenterServer -Name $DatastoreName -ErrorAction SilentlyContinue
        if (-not $Datastore) {
            throw "Could not find datastore '$DatastoreName'! Please make sure to select an existing datastore."
        }
        Set-PfaVmfsCapacity -Cluster $Cluster -FlashArray $FlashArray -Datastore $Datastore -SizeInByte $Size `
            -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

        Write-Host "Datastore '$DatastoreName' is successfully resized"
        Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    } catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}


function New-PCBSVASAProvider {
    <#
    .SYNOPSIS
    Register Pure Cloud Block Store VASA providers with vCenter

    .DESCRIPTION
    Register Pure Cloud Block Store VASA providers for all array controllers with vCenter. If a VASA provider has a certificate problem, it will be created

    .PARAMETER PureCloudBlockStoreConnection
    Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet

    .PARAMETER PureCloudBlockStoreCredential
    Pure Cloud BlockStore Credential

    .PARAMETER AVSCloudName
    AVS cloud name

    .PARAMETER AVSResourceGroup
    AVS Resource group name

    .PARAMETER TimeoutInMinutes
    Optional. Timeout in minutes for RunCommand operations.

    .EXAMPLE
    New-PCBSVASAProvider -PureCloudBlockStoreConnection $CBSConnection -PureCloudBlockStoreCredential $CBSConnectionCredential `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false)]
        $PureCloudBlockStoreConnection,

        [Parameter(Mandatory=$true)]
        [PSCredential]$PureCloudBlockStoreCredential,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,

        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int]$TimeoutInMinutes = 10
    )

    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Register VASA provider" -Status "0% Complete:" -PercentComplete 1
    $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        Update-VASAProvider -vCenterServer $vCenterServer -FlashArray $FlashArray -FlashArrayCredential $PureCloudBlockStoreCredential `
                            -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes
    }catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    Write-Host "VASA provider is successfully registered."
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function Update-PCBSVASAProvider {
    <#
    .SYNOPSIS
    Refresh Pure Cloud Block Store VASA provider vCenter certificates

    .DESCRIPTION
    Refresh Pure Cloud Block Store VASA provider vCenter certificates

    .PARAMETER PureCloudBlockStoreConnection
    Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet

    .PARAMETER PureCloudBlockStoreCredential
    Pure Cloud BlockStore Credential

    .PARAMETER AVSCloudName
    AVS cloud name

    .PARAMETER AVSResourceGroup
    AVS Resource group name

    .PARAMETER TimeoutInMinutes
    Optional. Timeout in minutes for RunCommand operations.

    .EXAMPLE
    Update-PCBSVASAProvider -PureCloudBlockStoreConnection $CBSConnection -PureCloudBlockStoreCredential $CBSConnectionCredential `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false)]
        $PureCloudBlockStoreConnection,

        [Parameter(Mandatory=$true)]
        [PSCredential]$PureCloudBlockStoreCredential,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,


        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int]$TimeoutInMinutes = 10
    )

    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Update VASA provider" -Status "0% Complete:" -PercentComplete 1
    $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        Update-VASAProvider -vCenterServer $vCenterServer -FlashArray $FlashArray -FlashArrayCredential $PureCloudBlockStoreCredential -RefreshOnly `
          -AVSResourceGroup $AVSResourceGroup -AVSCloudName $AVSCloudName -TimeoutInMinutes $TimeoutInMinutes
    }catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    Write-Host "VASA provider is successfully updated."
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function New-PCBSVvolDataStore {
    <#
    .SYNOPSIS
    Mount a vVol datastore on all of the hosts in the cluster.

    .DESCRIPTION
    Mount a vVol datastore on all of the hosts in the cluster.

    .PARAMETER ClusterName
    Cluster name

    .PARAMETER DatastoreName
    Optional. Datastore name. If not provided, a datastore name driven from the Pure Cloud Clock Store Name will be used

    .PARAMETER UseDefaultStore
    Optional. Indicates whether to use default root container. If not provided, the default store will not be used and multi-storage Pod container will be used.
    Note that Non-default store can only be used with Pure Cloud Block Store version 6.4.1 or later.

    .PARAMETER PureCloudBlockStoreConnection
    Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet

    .PARAMETER AVSCloudName
    AVS cloud name

    .PARAMETER AVSResourceGroup
    AVS resource group name

    .PARAMETER TimeoutInMinutes
    Optional. Timeout in minutes for RunCommand operations.

    .EXAMPLE
    New-PCBSVVolDataStore -ClusterName "mycluster" -DatastoreName "myDatastore" -PureCloudBlockStoreConnection $CBSConnection `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [String]$ClusterName,

        [Parameter(Mandatory=$false)]
        [String]$DatastoreName,

        [Parameter(Mandatory=$false)]
        [bool]$UseDefaultStore,

        [Parameter(Mandatory=$false)]
        $PureCloudBlockStoreConnection,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,


        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int]$TimeoutInMinutes
    )

    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Add datastore" -Status "0% Complete:" -PercentComplete 1

    $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore
        if (-not $Cluster) {
            throw "Cluster $ClusterName does not exist."
        }
        $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName
        if ($ClusterStatus -ne "Succeeded") {
            throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'"
        }
        $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters
        $datastore = Mount-VvolDatastore -vCenterServer $vCenterServer -DatastoreName $DatastoreName -Cluster $Cluster -FlashArray $FlashArray -UseDefaultStore $UseDefaultStore `
                                        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes
        Wait-VvolDatastoreCreation -DataStoreName $datastore.Name
    } catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    Write-Host "Datastore '$($datastore.Name)' is successfully created"
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function Remove-PCBSVVolDataStore {
    <#
    .SYNOPSIS
    Remove a vVol datastore from all of the hosts in the cluster.

    .DESCRIPTION
    Remove a vVol datastore from all of the hosts in the cluster.

    .PARAMETER ClusterName
    Cluster name

    .PARAMETER DatastoreName
    Datastore name

    .PARAMETER PureCloudBlockStoreConnection
    Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet

    .PARAMETER AVSCloudName
    AVS Cloud Name

    .PARAMETER AVSResourceGroup
    AVS Resource Group

    .PARAMETER TimeoutInMinutes
    Optional. Timeout in minutes for RunCommand operations.

    .EXAMPLE
    Remove-PCBSVVolDataStore -ClusterName "mycluster" -DatastoreName "myDatastore" -PureCloudBlockStoreConnection $CBSConnection `
        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    #>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [String]$ClusterName,

        [Parameter(Mandatory=$true)]
        [String]$DatastoreName,

        [Parameter(Mandatory=$false)]
        $PureCloudBlockStoreConnection,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,


        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int]$TimeoutInMinutes
    )

    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Remove datastore" -Status "0% Complete:" -PercentComplete 1

    $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection
    $WorkflowID = New-WorkflowID
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:BeginWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand

    try {
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore
        if (-not $Cluster) {
            throw "Cluster $ClusterName does not exist."
        }
        $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName
        if ($ClusterStatus -ne "Succeeded") {
            throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'"
        }
        $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters
        $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName
        Dismount-VvolDatastore -vCenterServer $vCenterServer -DatastoreName $DatastoreName -Cluster $Cluster -FlashArray $FlashArray `
                                -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

        Write-Host "Datastore '$DatastoreName' is successfully removed"

    }
    catch {
        New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:ErrorWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_
        throw
    }
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
    New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event $global:CompleteWorkflow -ID $WorkflowID -Name $MyInvocation.MyCommand
}

function New-PCBSStoragePolicy {
    <#
    .SYNOPSIS
      Creates a new Pure Cloud Block Store vVol Storage Policy
    .DESCRIPTION
      Creates a new Pure Cloud Block Store vVol Storage Policy with specified capabilities
    .INPUTS
      Capabilities
    .OUTPUTS
      New storage policy
    .PARAMETER PolicyName
      Pure Cloud Block Store vVol Storage Policy name.
    .PARAMETER PolicyDescription
      Optional. Pure Cloud Block Store vVol Storage Policy description.
    .PARAMETER SourcePureCloudBlockStores
      Optional. Name of one or more Pure Cloud Block Store to use. Use comma as seperater if there are multiple names
    .PARAMETER ReplicationEnabled
      Optional. Replication capability rule. The input value could be yes, no or empty, If not provided replication will not be enabled.
    .PARAMETER ReplicationInterval
      Optional. Replication capability rule. Protection group remote snapshot creation interval in seconds.
    .PARAMETER ReplicationRetentionInterval
      Optional. Replication capability rule. Protection group remote snapshot retention interval in seconds.
    .PARAMETER ReplicationRuleLocalSnapshotEnabled
      Optional. Replication capability rule. The input value could be yes, no or empty.
    .PARAMETER ReplicationRuleLocalSnapshotInterval
      Optional. Replication capability rule. Protection group local snapshot creation interval in seconds.
    .PARAMETER ReplicationRuleLocalSnapshotRetentionInterval
      Optional. Replication capability rule. Protection group local snapshot retention interval in seconds.
    .PARAMETER ReplicationConcurrency
      Optional. Replication capability rule. The number of target Pure Cloud Block Store to replicate to at once.
    .PARAMETER ReplicationRansomwareProtection
      Optional. Replication capability rule. Protection group ransomware protection
    .PARAMETER TargetPureCloudBlockStores
      Optional. Replication capability rule. Names of specific Pure Cloud Block Store desired as replication targets. Use comma as seperater if there are multiple names
    .PARAMETER ConsistencyGroupName
      Optional. Replication capability rule. A Pure Cloud Block Store protection group name
    .PARAMETER PerVirtualDiskIOPSLimit
      Optional. QoS placement capability rule. IOPS limit get applied to per virtual disk
    .PARAMETER PerVirtualDiskIOPSLimitUnit
      Optional. QoS placement capability rule. IOPS limit unit (K, M, or -)
    .PARAMETER PerVirtualDiskBandwidthLimit
      Optional. QoS placement capability rule. Bandwidth limit get applied to per virtual disk
    .PARAMETER PerVirtualDiskBandwidthLimitUnit
      Optional. QoS placement capability rule. Bandwidth limit unit (KB/s, MB/s or GB/s)
    .PARAMETER VolumeTaggingKey
      Optional. Volume tagging placement capability. Volumes with this policy will be tagged with the key
    .PARAMETER VolumeTaggingValue
      Optional. Volume tagging placement capability. Volumes with this policy will be tagged with the value
    .PARAMETER VolumeTaggingCopyable
      Optional. Volume tagging placement capability. When set to yes, any volume copies of this volume will receive the tag
    .PARAMETER PlacementRuleLocalSnapshotInterval
      Optional. Local snapshot protection placement capability. Snapshots will be taken in the specified interval in seconds.
    .PARAMETER PlacementRuleLocalSnapshotRetentionInterval
      Optional. Local snapshot protection placement capability. Snapshots will be retained for the timespan in seconds.
    .PARAMETER PlacementRuleLocalSnapshotRetainAdditionalSnapshots
      Optional. Local snapshot protection placement capability. Optional additional retention: After the retention timespan specified above is up, a number of snapshots will be selected, evenly spaced out, from the last retention timespan. This parameter specifies the number of snapshots to select from the last retention timespan. These snapshots will be retained for a number of additional days (see parameter below). Leave this parameter as "0" if additional retention is not required.
    .PARAMETER PlacementRuleLocalSnapshotRetainAdditionalDays
      Optional. Local snapshot protection placement capability. Optional additional retention: This parameter specifies the number of days that additional snapshots should be retained. If a selection for "Retain additional snapshots" (see above) was made, this parameter must also be specified. Leave this parameter as "0" if additional retention is not required.
    .PARAMETER OffloadType
      Optional. Snapshot offload protection placement capability. Purity//FA Snap to Cloud is a policy-based solution to manage portable snapshots through the offload of volume snapshots to a target, such as an Azure Blob container, or an S3 bucket, for long-term retention. With different types of offload targets this rule allows policies to specify a specific offload target type or to have no requirement for a specific type of target.
    .PARAMETER OffloadTargetNames
      Optional. Snapshot offload protection placement capability. Upon the first assignment of this policy to a VM on a given datastore a consistency group will be created with the settings supplied. This rule specifies which targets are added to the consistency groups target list. If no requirement is selected then the storage provider will selected one of the available offload targets on the compatible datastore's array.
    .PARAMETER OffloadReplicationInterval
      Optional. Snapshot offload protection placement capability. Snapshots will be taken and replicated to the offload target in the specified interval.
    .PARAMETER OffloadOffloadReplicationTime
      Optional. Snapshot offload protection placement capability. If the replication interval is set to one or more days, optionally set the 'Daily/weekly replication time' to specify the preferred hour of each day when Purity//FA replicates the snapshot. For example, if the replication schedule is set to "Replicate every 4 days at 6pm," Purity//FA replicates the snapshots every four days at or around 6:00 p.m.
    .PARAMETER OffloadRetentionInterval
      Optional. Snapshot offload protection placement capability. Replicated snapshots will be retained for the specified timespan on the offload target.
    .PARAMETER OffloadRetainAdditionalSnapshots
      Optional. Snapshot offload protection placement capability. After the retention timespan specified is up, a number of snapshots will be selected, evenly spaced out, from the last retention timespan. This parameter specifies the number of snapshots to select from the last retention timespan. These snapshots will be retained for a number of additional days (see parameter below). Leave this parameter as "0" if additional retention is not required.
    .PARAMETER OffloadRetainAdditionalDays
      Optional. Snapshot offload protection placement capability. This parameter specifies the number of days that additional snapshots should be retained. If a selection for "Retain additional snapshots" (see above) was made, this parameter must also be specified. Leave this parameter as "0" if additional retention is not required.
    .PARAMETER OffloadReplicationBlackoutFrom
      Optional. Snapshot offload protection placement capability. Define a timespan for which replication is suspended or "blacked out". The asynchronous replication process stops during the blackout period. When the blackout period starts, replication processes that are still in progress will not be interrupted. Instead, Purity//FA will wait until the in-progress snapshot replication is complete before it observes the blackout period. This value dictates when the blackout period begins.
    .PARAMETER OffloadReplicationBlackoutTo
      Optional. Snapshot offload protection placement capability. Define a timespan for which replication is suspended or "blacked out". The asynchronous replication process stops during the blackout period. When the blackout period starts, replication processes that are still in progress will not be interrupted. Instead, Purity//FA will wait until the in-progress snapshot replication is complete before it observes the blackout period. This value dictates when the blackout period ends and replication jobs can be initiated again.
    .PARAMETER DefaultProtectionOptout
      Optional. Default protection opt-out placement capability. Whether should new volumes be placed in default protection groups. If not provided, new volumes will be placed in default protection groups.
    .PARAMETER AVSCloudName
      AVS Cloud Name
    .PARAMETER AVSResourceGroup
      AVS Resource Group
    .PARAMETER TimeoutInMinutes
      Optional. Timeout in minutes for RunCommand operations.
    .EXAMPLE
      New-PCBSStoragePolicy

      Creates the default SPBM policy that indicates a VM should be on a Pure Cloud Block Store using vVols. Default generated name and description.
    .EXAMPLE
      New-PCBSStoragePolicy -PolicyName myGreatPolicy `
      -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

      Creates a SPBM policy with the specified name that indicates a VM should be on a Pure Cloud Block Store using vVols. Default generated description.
    .EXAMPLE
      New-PCBSStoragePolicy -PolicyName myGreatReplicationPolicy -ReplicationInterval 7200 -ReplicationEnabled "yes" -ReplicationConcurrency 2 `
      -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

      Creates a replication-type SPBM policy with the specified name that indicates a VM should be on a Pure Cloud Block Store using vVols, replicated every 2 hours to at least two other Pure Cloud Block Stores. Default generated description.

    .EXAMPLE
      New-PCBSStoragePolicy -PolicyName myGreatReplicationPolicy -SourcePureCloudBlockStores "MyArrayOne,MyArrayTwo" `
      -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

      Creates a SPBM policy with the specified name that indicates a VM should be on the specific Pure Cloud Block Store using vVols.
      #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [String]$PolicyName,

        [Parameter(Mandatory=$false)]
        [String]$PolicyDescription = "Pure Storage Cloud Block Store vVol storage policy default description",

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

        [Parameter(Mandatory=$false)]
        [Nullable[boolean]]$ReplicationEnabled,

        [Parameter(Mandatory=$false)]
        [Timespan]$ReplicationInterval,

        [Parameter(Mandatory=$false)]
        [Timespan]$ReplicationRetentionInterval,

        [Parameter(Mandatory=$false)]
        [Nullable[boolean]]$ReplicationRuleLocalSnapshotEnabled,

        [Parameter(Mandatory=$false)]
        [Timespan]$ReplicationRuleLocalSnapshotInterval,

        [Parameter(Mandatory=$false)]
        [Timespan]$ReplicationRuleLocalSnapshotRetentionInterval,

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

        [Parameter(Mandatory=$false)]
        [Nullable[boolean]]$ReplicationRansomwareProtection,

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

        [Parameter(Mandatory=$false)]
        [String]$ConsistencyGroupName,

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

        [Parameter(Mandatory=$false)]
        [String]$PerVirtualDiskIOPSLimitUnit,

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

        [Parameter(Mandatory=$false)]
        [String]$PerVirtualDiskBandwidthLimitUnit,

        [Parameter(Mandatory=$false)]
        [String]$VolumeTaggingKey,

        [Parameter(Mandatory=$false)]
        [String]$VolumeTaggingValue,

        [Parameter(Mandatory=$false)]
        [Nullable[boolean]]$VolumeTaggingCopyable,

        [Parameter(Mandatory=$false)]
        [Timespan]$PlacementRuleLocalSnapshotInterval,

        [Parameter(Mandatory=$false)]
        [Timespan]$PlacementRuleLocalSnapshotRetentionInterval,

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

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

        [Parameter(Mandatory=$false)]
        [ValidateSet("s3", "nfs", "azure")]
        [string]$OffloadType,

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

        [Parameter(Mandatory=$false)]
        [Timespan]$OffloadReplicationInterval,

        [Parameter(Mandatory=$false)]
        [ValidateSet("-", "12am", "1am", "2am", "3am", "4am", "5am", "6am", "7am", "8am", "9am", "10am", "11am", "12pm", "1pm", "2pm", "3pm", "4pm", "5pm", "6pm", "7pm", "8pm", "9pm", "10pm", "11pm")]
        [string]$OffloadOffloadReplicationTime,

        [Parameter(Mandatory=$false)]
        [Timespan]$OffloadRetentionInterval,

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

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

        [Parameter(Mandatory=$false)]
        [ValidateSet("-", "12am", "1am", "2am", "3am", "4am", "5am", "6am", "7am", "8am", "9am", "10am", "11am", "12pm", "1pm", "2pm", "3pm", "4pm", "5pm", "6pm", "7pm", "8pm", "9pm", "10pm", "11pm")]
        [string]$OffloadReplicationBlackoutFrom,

        [Parameter(Mandatory=$false)]
        [ValidateSet("-", "12am", "1am", "2am", "3am", "4am", "5am", "6am", "7am", "8am", "9am", "10am", "11am", "12pm", "1pm", "2pm", "3pm", "4pm", "5pm", "6pm", "7pm", "8pm", "9pm", "10pm", "11pm")]
        [string]$OffloadReplicationBlackoutTo,

        [Parameter(Mandatory=$false)]
        [Nullable[boolean]]$DefaultProtectionOptout,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,


        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int]$TimeoutInMinutes = 10
    )
    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Create storage policy" -Status "0% Complete:" -PercentComplete 1

    $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName

    New-VvolStoragePolicy -PolicyName $PolicyName -PolicyDescription $PolicyDescription -SourcePureCloudBlockStores $SourcePureCloudBlockStores -ReplicationEnabled $ReplicationEnabled -ReplicationInterval $ReplicationInterval -ReplicationRetentionInterval $ReplicationRetentionInterval `
    -ReplicationRuleLocalSnapshotEnabled $ReplicationRuleLocalSnapshotEnabled -ReplicationRuleLocalSnapshotInterval $ReplicationRuleLocalSnapshotInterval -ReplicationRuleLocalSnapshotRetentionInterval $ReplicationRuleLocalSnapshotRetentionInterval -ReplicationConcurrency $ReplicationConcurrency -ReplicationRansomwareProtection $ReplicationRansomwareProtection `
    -TargetPureCloudBlockStores $TargetPureCloudBlockStores -ConsistencyGroupName $ConsistencyGroupName -PerVirtualDiskIOPSLimit $PerVirtualDiskIOPSLimit -PerVirtualDiskIOPSLimitUnit $PerVirtualDiskIOPSLimitUnit `
    -PerVirtualDiskBandwidthLimit $PerVirtualDiskBandwidthLimit -PerVirtualDiskBandwidthLimitUnit $PerVirtualDiskBandwidthLimitUnit -VolumeTaggingKey $VolumeTaggingKey -VolumeTaggingValue $VolumeTaggingValue -VolumeTaggingCopyable $VolumeTaggingCopyable `
    -PlacementRuleLocalSnapshotInterval $PlacementRuleLocalSnapshotInterval -PlacementRuleLocalSnapshotRetentionInterval $PlacementRuleLocalSnapshotRetentionInterval -PlacementRuleLocalSnapshotRetainAdditionalSnapshots $PlacementRuleLocalSnapshotRetainAdditionalSnapshots -PlacementRuleLocalSnapshotRetainAdditionalDays $PlacementRuleLocalSnapshotRetainAdditionalDays `
    -OffloadType $OffloadType -OffloadTargetNames $OffloadTargetNames -OffloadReplicationInterval $OffloadReplicationInterval -OffloadOffloadReplicationTime $OffloadOffloadReplicationTime -OffloadRetentionInterval $OffloadRetentionInterval -OffloadRetainAdditionalSnapshots $OffloadRetainAdditionalSnapshots -OffloadRetainAdditionalDays $OffloadRetainAdditionalDays -OffloadReplicationBlackoutFrom $OffloadReplicationBlackoutFrom -OffloadReplicationBlackoutTo $OffloadReplicationBlackoutTo -DefaultProtectionOptout $DefaultProtectionOptout `
    -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -vCenterServer $vCenterServer -TimeoutInMinutes $TimeoutInMinutes

    Write-Host "Storage policy '$PolicyName' is successfully created"
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function Remove-PCBSStoragePolicy {
<#
    .SYNOPSIS
     Remove a Pure Cloud Block Store vVol Storage Policy
    .DESCRIPTION
     Remove a Pure Cloud Block Store vVol Storage Policy using policy name. The policy must be unused and belong to Pure
    .INPUTS
     SPBM policy name
    .PARAMETER PolicyName
     Pure Cloud Block Store vVol Storage Policy name
    .PARAMETER AVSCloudName
     AVS cloud name
    .PARAMETER AVSResourceGroup
     AVS Resource group name
    .PARAMETER TimeoutInMinutes
      Optional. Timeout in minutes for RunCommand operations.
    .EXAMPLE
     Remove-PCBSStoragePolicy -PolicyName myPolicyName -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
 #>


    [CmdletBinding()]
    Param(
        [Parameter(mandatory = $true)]
        [string]$PolicyName,

        [Parameter(Mandatory = $true)]
        [String]$AVSCloudName,

        [Parameter(Mandatory = $true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int]$TimeoutInMinutes = 10
    )

    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Remove storage policy" -Status "0% Complete:" -PercentComplete 1

    $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName

    Remove-VvolStoragePolicy -PolicyName $PolicyName -vCenterServer $vCenterServer `
                            -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

    Write-Host "Storage policy '$PolicyName' is successfully removed"
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}


function Start-PCBSFailover {
  <#
  .SYNOPSIS
    Issue test failover or failover operation against "Target" replication group.
  .DESCRIPTION
    Issue test failover or failover operation against replication group. Must be run on a "Target" Replication Group.
  .PARAMETER TargetReplicationGroupID
    Target VMWare Replication Group Id to be used on failover. Must be a "Target" Replication Group.
  .PARAMETER PointInTimeReplicaName
    Optional. Point in time replica name to failover to. If not provided, the latest replica will be used.
  .PARAMETER ClusterName
    Cluster name where failover VMs will be created.
  .PARAMETER PowerOn
    Optional. Indicates whether to or not to power on created VMs. If not provided the VM(s) will be powered on.
  .PARAMETER TestFailover
    Optional. Indicates whether to actually perform a failover(false) or to only perform a test failover(true). If not provided will only perform failover test.
  .PARAMETER AVSCloudName
    AVS cloud name. The target AVS instance where the failover will be performed.
  .PARAMETER AVSResourceGroup
    AVS resource group name.
  .PARAMETER TimeoutInMinutes
    Optional. Timeout in minutes for RunCommand operations.
  .EXAMPLE
    Start-PCBSFailover -TargetReplicationGroupID myGroupId -ClusterName myclustername -TestFailover $true -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    Issues test failover on replication group specified by id "myGroupId", test VMs will be created under cluster "myclustername", and group replication state will be set to "InTest"
  .EXAMPLE
    Start-PCBSFailover -TargetReplicationGroupID myGroupId -ClusterName myclustername -PowerOn $true -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    Issues failover on replication group specified by id "myGroupId", test VMs will be created under cluster "myclustername", VMs created will be powered on, and group replication state will be set to "FailedOver"
  .EXAMPLE
    Start-PCBSFailover -TargetReplicationGroupID myGroupId -ClusterName myclustername -PowerOn $true -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    Issues Failover on replication group specified by id "myGroupId", new VMs will be created under cluster "myclustername", VMs created will be powered on, and group replication state will be set to "FailedOver"
    #>


    [CmdletBinding()]
    Param(
        [Parameter(mandatory = $true)]
        [String]$ClusterName,

        [Parameter(mandatory = $true)]
        [String]$TargetReplicationGroupID,

        [Parameter(mandatory = $false)]
        [String]$PointInTimeReplicaName,

        [Parameter(mandatory = $false)]
        [switch]$PowerOn,

        [Parameter(mandatory = $false)]
        [switch]$TestFailover,

        [Parameter(Mandatory = $true)]
        [String]$AVSCloudName,

        [Parameter(Mandatory = $true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [ValidateRange(5, 60)]
        [int]$TimeoutInMinutes = 10
    )
    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Start failover" -Status "0% Complete:" -PercentComplete 1
    $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName

    Start-VvolReplicationGroupFailover -ReplicationGroupID $TargetReplicationGroupID -PointInTimeReplicaName $PointInTimeReplicaName -ClusterName $ClusterName -PowerOn $PowerOn -TestFailover $TestFailover -vCenterServer $vCenterServer `
                                        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

    Write-Host "Replication group '$TargetReplicationGroupID' successfully failovered to cluster '$ClusterName'"
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function Sync-PCBSReplicationGroup {
  <#
  .SYNOPSIS
    Synchronize a replication group. Must be run on a "Target" Replication Group.
  .DESCRIPTION
    Synchronize a replication group. Triggers an on demand snapshot replication job. Must be run on a "Target" Replication Group.
  .PARAMETER TargetReplicationGroupID
    Replication Group Id that was used on the test failover
  .PARAMETER PointInTimeReplicaName
    Optional. Point in time replica name to sync to. If not provided, a generated name "Sync-*" will be used.
  .PARAMETER AVSCloudName
    AVS cloud name
  .PARAMETER AVSResourceGroup
    AVS resource group name
  .PARAMETER TimeoutInMinutes
    Optional. Timeout in minutes for RunCommand operations.
  .EXAMPLE
    Sync-PCBSReplicationGroup -TargetReplicationGroupID myGroupId -PointInTimeReplicaName myReplicaName -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    Synchronizes a replication group specified by id "myGroupId". An on demand snapshot replication job will be triggered on array
    #>


    [CmdletBinding()]
    Param(
      [Parameter(mandatory=$true)]
      [String]$TargetReplicationGroupID,

      [Parameter(mandatory = $false)]
      [String]$PointInTimeReplicaName,

      [Parameter(Mandatory = $true)]
      [String]$AVSCloudName,

      [Parameter(Mandatory = $true)]
      [String]$AVSResourceGroup,

      [Parameter(Mandatory=$false)]
      [ValidateRange(5, 60) ]
      [int]$TimeoutInMinutes = 10
    )
    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Start Replication Group sync" -Status "0% Complete:" -PercentComplete 1
    $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName

    if (-not $PointInTimeReplicaName) {
      $PointInTimeReplicaName = "Sync-$([guid]::newguid())"
    }
    Sync-VvolReplicationGroup -ReplicationGroupID $TargetReplicationGroupID -PointInTimeReplicaName $PointInTimeReplicaName -vCenterServer $vCenterServer `
                      -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

    Write-Host "Replication group '$TargetReplicationGroupID' is successfully synced to point in time replica '$PointInTimeReplicaName'"
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function Stop-PCBSFailoverTest {
  <#
  .SYNOPSIS
    Stops a test failover that was started on replication group
  .DESCRIPTION
    Stops a test failover that was started on replication group. Must be used on a "InTest" Replication Group
  .PARAMETER InTestTargetReplicationGroupID
    Replication Group Id that was used on the test failover
  .PARAMETER AVSCloudName
    AVS cloud name
  .PARAMETER AVSResourceGroup
    AVS resource group name
  .PARAMETER TimeoutInMinutes

  .EXAMPLE
    Stop-PCBSFailoverTest -InTestReplicationGroupID myGroupId -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    Stops a test failover that was started on replication group specified by id "myGroupId", test VMs will be stopped and deleted and group replication state will be set to "Target"
    #>


    [CmdletBinding()]
    Param(
      [Parameter(mandatory=$true)]
      [String]$InTestTargetReplicationGroupID,

      [Parameter(Mandatory = $true)]
      [String]$AVSCloudName,

      [Parameter(Mandatory = $true)]
      [String]$AVSResourceGroup,

      [Parameter(Mandatory=$false)]
      [ValidateRange(5, 60) ]
      [int]$TimeoutInMinutes = 10
    )
    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Stop failover test" -Status "0% Complete:" -PercentComplete 1
    $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName

    Stop-VvolReplicationGroupFailoverTest -ReplicationGroupID $InTestTargetReplicationGroupID -vCenterServer $vCenterServer `
                                              -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

    Write-Host "Failover test associated with Replication group '$InTestTargetReplicationGroupID' is successfully stopped"
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function Start-PCBSFailoverCleanup {
  <#
  .SYNOPSIS
    Cleans up the original source site after failing over to a new site.
  .DESCRIPTION
    Cleans up the original source site after failing over to a new site. Must be run on source site. Will stop and unregister VMs protected by replication group.
  .PARAMETER SourceReplicationGroupID
    Replication Group Id for source group used on the failover
  .PARAMETER RemoveFromDisk
    Optional. Indicates whether to remove VMs from disk.
  .PARAMETER AVSCloudName
    AVS cloud name
  .PARAMETER AVSResourceGroup
    AVS resource group name
  .EXAMPLE
    Start-PCBSFailoverCleanup -SourceReplicationGroupID myGroupId -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    Will stop and unregister VMs protected by replication group identified by "myGroupId".
    #>


    [CmdletBinding()]
    Param(
      [Parameter(Mandatory=$true)]
      [String]$SourceReplicationGroupID,

      [Parameter(Mandatory=$false)]
      [switch]$RemoveFromDisk,

      [Parameter(Mandatory = $true)]
      [String]$AVSCloudName,

      [Parameter(Mandatory = $true)]
      [String]$AVSResourceGroup

    )
    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Clean up original source site after failover" -Status "0% Complete:" -PercentComplete 1
    $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName

    Start-VvolCleanupSourceReplicationGroupForFailover -ReplicationGroupID $SourceReplicationGroupID -RemoveFromDisk $RemoveFromDisk -vCenterServer $vCenterServer `
                                                       -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

    Write-Host "The original source site '$SourceReplicationGroupID' is successfully cleaned up"
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

function Start-PCBSFailoverReverse {
  <#
  .SYNOPSIS
    Reverse replication on a FailedOver replication group and reprotect by assigning a storage policy to affected objects
  .DESCRIPTION
    Reverse replication on a FailedOver replication group and reprotect by assigning a storage policy to affected objects. Must be used on a FailedOver Replication Group. The Failedover Replication Group will become "Source"
  .PARAMETER FailedoverTargetReplicationGroupID
    Replication Group Id that was used on failover
  .PARAMETER PolicyName
    Optional. Policy Name to be used to reprotect objects.
  .PARAMETER AVSCloudName
    AVS cloud name
  .PARAMETER AVSResourceGroup
    AVS resource group name
  .PARAMETER TimeoutInMinutes
    Optional. Timeout in minutes for RunCommand operations.
  .EXAMPLE
    Start-PCBSFailoverReverse -FailedoverTargetReplicationGroupID myGroupId -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    Reprotect replication group specified by id "myGroupId", and sets objects storage policy to the default "VVol No Requirements Policy"
  .EXAMPLE
    Start-PCBSFailoverReverse -FailedoverTargetReplicationGroupID myGroupId -PolicyName PolicyName -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup
    Reprotect replication group specified by id "myGroupId", and sets objects storage policy to "PolicyName"
    #>


    [CmdletBinding()]
    Param(
      [Parameter(mandatory=$true)]
      [String]$FailedoverTargetReplicationGroupID,

      [Parameter(mandatory=$false)]
      [String]$PolicyName,

      [Parameter(Mandatory = $true)]
      [String]$AVSCloudName,

      [Parameter(Mandatory = $true)]
      [String]$AVSResourceGroup,

      [Parameter(Mandatory=$false)]
      [ValidateRange(5, 60) ]
      [int]$TimeoutInMinutes = 10
    )
    Write-Warning $VVOL_WARNIING
    Write-Progress -Activity "Reprotect replication group" -Status "0% Complete:" -PercentComplete 1
    $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName

    Start-VvolReprotectReplicationGroup -ReplicationGroupID $FailedoverTargetReplicationGroupID -PolicyName $PolicyName -vCenterServer $vCenterServer `
                                        -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes

    Write-Host "The FailedOver replication group '$FailedoverTargetReplicationGroupID' is successfully reprotected"
    Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue
}

<#
.SYNOPSIS
Fully remove a Pure Storage CBS AVS monitor deployment from Azure infrastructure

.DESCRIPTION
Fully remove a Pure Storage CBS AVS monitor deployment from Azure infrastructure. The resource group and its resources will be destroyed. The vNet subnet used by the monitor will also be remove from the vNet.

.PARAMETER MonitorResourceGroup
ResourceGroup to host monitor infrastructure components

 .PARAMETER RemoveSubnet
Optional. Indicates whether to remove the subnet used by the monitor from the vNet. If not provided, the subnet will not be removed.

.EXAMPLE
Remove-PCBSAVSMonitor -MonitorResourceGroup "myAVSMonitorResourceGroup"

.NOTES
Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet
#>

function Remove-PCBSAVSMonitor {
  [CmdletBinding()]
    Param (
      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroup,

      [Parameter(Mandatory=$false)]
      [Switch]$RemoveSubnet
    )

    $params = @{
      MonitorResourceGroup = $MonitorResourceGroup
      MonitorType = "host"
      RemoveSubnet = $RemoveSubnet
    }

    Remove-Monitor @params
}

<#
.SYNOPSIS
Deploy or update a Pure Storage CBS AVS monitor to Azure infrastructure

.DESCRIPTION
Deploy or update a Pure Storage CBS AVS monitor to Azure infrastructure. The monitor will scan periodically for AVS Cluster/Host changes and will make sure to change iSCSI configuration accordingly.

.PARAMETER MonitorResourceGroup
Resource group to host monitor infrastructure components. The resource group will be created if not exists

.PARAMETER MonitorResourceGroupRegion
Resource group region to host monitor infrastructure components.

.PARAMETER AVSCloudName
AVS cloud name

.PARAMETER AVSResourceGroup
AVS Resource group name

.PARAMETER PureCloudBlockStoreEndpoint
Pure Cloud Block Store endpoint address

.PARAMETER PureCloudBlockStoreCredential
Pure Cloud Block Store credentials

.PARAMETER VNetName
An existing VNet name. The VNey specified should have access to AVS as well as Pure Storage Cloud Block Store (CBS) array.

.PARAMETER VNetResourceGroup
VNet REsourceGroup

.PARAMETER VNetSubnetAddress
The VNet subnet address range in CIDR notation (e.g. 192.168.1.0/24). It must be contained by the address space of the virtual network.

.PARAMETER VNetSubnetName
If VNetSubnetAddress is specified, then VnetSubnetName can be optionaly used to specify a new subnet name otherwise it is existing VNet subnet name.

.PARAMETER MonitorIntervalInMinute
Optional. The default monitor interval is 10 minutes.

.EXAMPLE
Deploy-PCBSAVSMonitor -PureCloudBlockStoreEndpoint "192.168.2.100" -PureCloudBlockStoreCredential (Get-Credential) -AVSCloudName "my-avs" -AVSResourceGroup "avs-resourcegroup" `
              -MonitorResourceGroup "NewResourceGroup" -MonitorResourceGroupRegion "westus2" -VNetName "my-vnet" -VNetResourceGroup "vnet-resourcegroup" -VNetSubnetAddress "192.168.3.0/24"

.NOTES
Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet
#>

function Deploy-PCBSAVSMonitor {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [String]$MonitorResourceGroup,

        [Parameter(Mandatory=$true)]
        [String]$MonitorResourceGroupRegion,

        [Parameter(Mandatory=$true)]
        [String]$AVSCloudName,

        [Parameter(Mandatory=$true)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$true)]
        [String]$PureCloudBlockStoreEndpoint,

        [Parameter(Mandatory=$true)]
        [pscredential]$PureCloudBlockStoreCredential,

        [Parameter(Mandatory=$true)]
        [String]$VNetName,

        [Parameter(Mandatory=$true)]
        [String]$VNetResourceGroup,

        [Parameter(ParameterSetName='NewSubnet', Mandatory=$true)]
        [String]$VNetSubnetAddress,

        [Parameter(ParameterSetName='ExistingSubnet', Mandatory=$true)]
        [Parameter(ParameterSetName='NewSubnet', Mandatory=$false)]
        [String]$VNetSubnetName,

        [Parameter(Mandatory=$false)]
        [ValidateScript({ $_ -ge 10 }, ErrorMessage = "The minimum interval for the monitor is 10 minutes.")]
        [ValidateScript({ $_ -le 60 }, ErrorMessage = "The maximum interval for the monitor is 60 minutes.")]
        [int]$MonitorIntervalInMinute = 10
    )

    $params = @{
      MonitorResourceGroup = $MonitorResourceGroup
      MonitorResourceGroupRegion = $MonitorResourceGroupRegion
      AVSCloudName = $AVSCloudName
      AVSResourceGroup = $AVSResourceGroup
      VNetName = $VNetName
      VNetResourceGroup = $VNetResourceGroup
      MonitorIntervalInMinute = $MonitorIntervalInMinute
      MonitorType = "host"
  }
  $paramSetName = $PSCmdlet.ParameterSetName
  if ($paramSetName -eq "NewSubnet") {
      $params.Add("VNetSubnetAddress", $VNetSubnetAddress)
      if ($VNetSubnetName) {
          $params.Add("VNetSubnetName", $VNetSubnetName)
      }
  } else {
      $params.Add("VNetSubnetName", $VNetSubnetName)
  }
  Deploy-MonitoringResource @params
  Write-Host "Adding CBS AVS monitor array to the monitor ..."
  Add-PCBSAVSMonitorArray -MonitorResourceGroup $MonitorResourceGroup `
    -PureCloudBlockStoreEndpoint $PureCloudBlockStoreEndpoint -PureCloudBlockStoreCredential $PureCloudBlockStoreCredential -Force
}

<#
.SYNOPSIS
Deploy or update a Pure Storage CBS Capacity monitor

.DESCRIPTION
Deploy or update a Pure Storage CBS Capacity monitor to Azure infrastructure.
The monitor will scan periodically for CBS space utilization and will automatically request expanding the storage if the utilization reaches pre-defined threshold

.PARAMETER MonitorResourceGroup
Resource group to host monitor infrastructure components. The resource group will be created if not exists

.PARAMETER MonitorResourceGroupRegion
Resource group region to host monitor infrastructure components.

.PARAMETER AVSCloudName
AVS cloud name

.PARAMETER AVSResourceGroup
AVS Resource group name

.PARAMETER VNetName
An existing VNet name. The VNey specified should have access to AVS as well as Pure Storage Cloud Block Store (CBS) array.

.PARAMETER VNetResourceGroup
VNet REsourceGroup

.PARAMETER VNetSubnetAddress
The VNet subnet address range in CIDR notation (e.g. 192.168.1.0/24). It must be contained by the address space of the virtual network.

.PARAMETER VNetSubnetName
If VNetSubnetAddress is specified, then VnetSubnetName can be optionaly used to specify a new subnet name otherwise it is existing VNet subnet name.

.PARAMETER DefaultUtilizationThreshold
Optional. The default utilization threshold is 80%.

.PARAMETER MonitorIntervalInMinute
Optional. The default monitor interval is 10 minutes.

.EXAMPLE
Deploy-PCBSCapacityMonitor -AVSCloudName "my-avs" -AVSResourceGroup "avs-resourcegroup" `
              -MonitorResourceGroup "NewResourceGroup" -MonitorResourceGroupRegion "westus2" `
              -VNetName "my-vnet" -VNetResourceGroup "vnet-resourcegroup" -VNetSubnetAddress "192.168.3.0/24"

.NOTES
Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet
#>

function Deploy-PCBSCapacityMonitor {
  [CmdletBinding()]
  Param(
      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroup,

      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroupRegion,

      [Parameter(Mandatory=$true)]
      [String]$AVSCloudName,

      [Parameter(Mandatory=$true)]
      [String]$AVSResourceGroup,

      [Parameter(Mandatory=$true)]
      [String]$VNetName,

      [Parameter(Mandatory=$true)]
      [String]$VNetResourceGroup,

      [Parameter(ParameterSetName='NewSubnet', Mandatory=$true)]
      [String]$VNetSubnetAddress,

      [Parameter(ParameterSetName='ExistingSubnet', Mandatory=$true)]
      [Parameter(ParameterSetName='NewSubnet', Mandatory=$false)]
      [String]$VNetSubnetName,

      [Parameter(Mandatory=$false)]
      [ValidateRange(1, 100)]
      [int] $DefaultUtilizationThreshold = $DEFAULT_UTILIZATIOn_THRESHOLD,

      [Parameter(Mandatory=$false)]
      [ValidateScript({ $_ -ge 10 }, ErrorMessage = "The minimum interval for the monitor is 10 minutes.")]
      [ValidateScript({ $_ -le 60 }, ErrorMessage = "The maximum interval for the monitor is 60 minutes.")]
      [int]$MonitorIntervalInMinute = 30
  )

  $params = @{
      MonitorResourceGroup = $MonitorResourceGroup
      MonitorResourceGroupRegion = $MonitorResourceGroupRegion
      AVSCloudName = $AVSCloudName
      AVSResourceGroup = $AVSResourceGroup
      VNetName = $VNetName
      VNetResourceGroup = $VNetResourceGroup
      MonitorIntervalInMinute = $MonitorIntervalInMinute
      DefaultUtilizationThreshold = $DefaultUtilizationThreshold
      MonitorType = "capacity"
  }
  $paramSetName = $PSCmdlet.ParameterSetName
  if ($paramSetName -eq "NewSubnet") {
      $params.Add("VNetSubnetAddress", $VNetSubnetAddress)
      if ($VNetSubnetName) {
          $params.Add("VNetSubnetName", $VNetSubnetName)
      }
  } else {
      $params.Add("VNetSubnetName", $VNetSubnetName)
  }
  Deploy-MonitoringResource @params
}

<#
.SYNOPSIS
List the existing Pure Cloud Block Store in the Pure Storage CBS AVS monitor

.DESCRIPTION
List the existing Pure Cloud Block Store in the Pure Storage CBS AVS monitor

.PARAMETER MonitorResourceGroup
ResourceGroup to host monitor infrastructure components

.EXAMPLE
Get-PCBSAVSMonitorArray -MonitorResourceGroup myMonitor

.NOTES
Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet
#>


function Get-PCBSAVSMonitorArray {
    [CmdletBinding()]
    Param (
      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroup
    )
    $ResourceGroup = Get-AzResourceGroup -Name $MonitorResourceGroup

    if (-not $ResourceGroup) {
        throw "Resource group $MonitorResourceGroupdoes not exist"
    }

    if (-not $ResourceGroup.Tags["PureStorage.CBS.AVS"]) {
        throw "Resouce group $MonitorResourceGroup specified does not host Pure Storage CBS AVS monitor"
    }

    $UserPrincipalName = (Get-AzContext).Account.Id
    $KeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup
    Set-AzKeyVaultAccessPolicy -VaultName $KeyVault.VaultName  -UserPrincipalName $UserPrincipalName -PermissionsToSecrets set,delete,get,purge,list

    $Arrays = @()
    $Secrets = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName | where-object {$_.Name -like "*-$($KeyVault.VaultName)-username"}
    foreach ($Secret in $Secrets) {
        $ArrayName = ($Secret.name -Split "-$($KeyVault.VaultName)-username")[0]
        # If the string matches the format like "172-168-1-0", the array ip addressed was processed because secret name does not allow "."
        if ($ArrayName -match "^\d+-\d+-\d+-\d+$") {
            $ArrayName = $ArrayName.Replace("-", ".")
        }
        $Arrays += $ArrayName
    }

    return $Arrays
}

<#
.SYNOPSIS
Add a Pure Cloud Block Store to an existing Pure Storage CBS AVS monitor

.DESCRIPTION
Add a Pure Cloud Block Store to an existing Pure Storage CBS AVS monitor. If the Pure Block store already exists, it will be overwritten

.PARAMETER MonitorResourceGroup
ResourceGroup to host monitor infrastructure components

.PARAMETER PureCloudBlockStoreEndpoint
Pure Cloud Block Store endpoint address

.PARAMETER PureCloudBlockStoreCredential
Pure Cloud Block Store credential

.PARAMETER Force
Optional. If specified, the cmdlet will not throw error if the Pure Cloud Block Store cannot be verified

.EXAMPLE
Add-PCBSAVSMonitorArray -MonitorResourceGroup myMonitorGroup -PureCloudBlockStoreEndpoint myArray -PureCloudBlockStoreCredential (Get-Credential)

.NOTES
Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet
#>

function Add-PCBSAVSMonitorArray {
    [CmdletBinding()]
    Param (
      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroup,

      [Parameter(Mandatory=$true)]
      [String]$PureCloudBlockStoreEndpoint,

      [Parameter(Mandatory=$true)]
      [pscredential]$PureCloudBlockStoreCredential,

      [Parameter(Mandatory=$false)]
      [Switch]$Force
    )

    $params = @{
      MonitorResourceGroup = $MonitorResourceGroup
      PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint
      PureCloudBlockStoreCredential = $PureCloudBlockStoreCredential
      MonitorType = "host"
    }

    if ($Force) {
        $params.Add("Force", $Force)
    }

    Add-MonitorArray @params
}

<#
.SYNOPSIS
Remove an existing Pure Cloud Block Store from a Pure Storage CBS AVS monitor

.DESCRIPTION
Remove an existing Pure Cloud Block Store from a Pure Storage CBS AVS monitor. If the Pure Block store already exists, it will be overwritten

.PARAMETER MonitorResourceGroup
ResourceGroup to host monitor infrastructure components

.PARAMETER PureCloudBlockStoreEndpoint
Pure Cloud Block Store endpoint address

.EXAMPLE
Remove-PCBSAVSMonitorArray -MonitorResourceGroup myMonitorGroup -PureCloudBlockStoreEndpoint myArray

.NOTES
Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet
#>

function Remove-PCBSAVSMonitorArray {
    [CmdletBinding()]
    Param (
      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroup,

      [Parameter(Mandatory=$true)]
      [String]$PureCloudBlockStoreEndpoint
    )

    $params = @{
      MonitorResourceGroup = $MonitorResourceGroup
      MonitorType = "host"
      PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint
    }
    Remove-MonitorArray @params
}

function Get-DefaultUtilizationThreshold {
    [CmdletBinding()]
    Param (
      [Parameter(Mandatory=$true)]
      $ResourceGroupName
    )

    $AzFunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroupName
    if (-not $AzFunctionApp) {
        throw "Resource group $ResourceGroupName does not exist"
    }
    $AppSettings = Get-AzFunctionAppSetting -ResourceGroupName $ResourceGroupName -Name $AzFunctionApp.Name
    $DefaultUtilizationThreshold = $DEFAULT_UTILIZATION_THRESHOLD
    if ($AppSettings["DEFAULT_UTILIZATION_THRESHOLD"]) {
        $DefaultUtilizationThreshold = [int]$AppSettings["DEFAULT_UTILIZATION_THRESHOLD"]
    }
    return $DefaultUtilizationThreshold
}

<#
.SYNOPSIS
Fully remove a Pure Storage CBS Capacity monitor deployment from Azure infrastructure

.DESCRIPTION
Fully remove a Pure Storage CBS capacity monitor deployment from Azure infrastructure. The resource group and its resources will be destroyed. The vNet subnet used by the monitor will also be remove from the vNet.

.PARAMETER MonitorResourceGroup
ResourceGroup to host monitor infrastructure components

 .PARAMETER RemoveSubnet
Optional. Indicates whether to remove the subnet used by the monitor from the vNet. If not provided, the subnet will not be removed.

.EXAMPLE
Remove-PCBSCapacityMonitor -MonitorResourceGroup "myCapacityMonitorResourceGroup"

.NOTES
Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet
#>

function Remove-PCBSCapacityMonitor {
  [CmdletBinding()]
    Param (
      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroup,

      [Parameter(Mandatory=$false)]
      [Switch]$RemoveSubnet
    )

    $params = @{
      MonitorResourceGroup = $MonitorResourceGroup
      MonitorType = "capacity"
      RemoveSubnet = $RemoveSubnet
    }

    Remove-Monitor @params
}

<#
.SYNOPSIS
Add a Pure Cloud Block Store to an existing Pure Storage CBS capacity monitor

.DESCRIPTION
Add a Pure Cloud Block Store to an existing Pure Storage CBS capacity monitor. If the Pure Block store already exists, it will be overwritten

.PARAMETER MonitorResourceGroup
ResourceGroup to host monitor infrastructure components

.PARAMETER PureCloudBlockStoreEndpoint
Pure Cloud Block Store endpoint address

.PARAMETER PureCloudBlockStoreCredential
Pure Cloud Block Store credential

.PARAMETER UtilizationThreshold
Optional. The utilization threshold for the Pure Cloud Block Store. If not provided, the default threshold will be used.

.PARAMETER Force
Optional. If specified, the cmdlet will not throw error if the Pure Cloud Block Store cannot be verified

.EXAMPLE
Add-PCBSCapacityMonitorArray -MonitorResourceGroup myMonitorGroup -PureCloudBlockStoreEndpoint myArray -PureCloudBlockStoreCredential (Get-Credential) `
                             -UtilizationThreshold 80

.NOTES
Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet
#>

function Add-PCBSCapacityMonitorArray {
  [CmdletBinding()]
  Param (
    [Parameter(Mandatory=$true)]
    [String]$MonitorResourceGroup,

    [Parameter(Mandatory=$true)]
    [String]$PureCloudBlockStoreEndpoint,

    [Parameter(Mandatory=$true)]
    [pscredential]$PureCloudBlockStoreCredential,

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

    [Parameter(Mandatory=$false)]
    [Switch]$Force
  )

  $params = @{
    MonitorResourceGroup = $MonitorResourceGroup
    PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint
    PureCloudBlockStoreCredential = $PureCloudBlockStoreCredential
    MonitorType = "capacity"
  }

  if ($UtilizationThreshold) {
    $params.Add("UtilizationThreshold", $UtilizationThreshold)
  }

  if ($Force) {
    $params.Add("Force", $Force)
  }

  Add-MonitorArray @params
}

<#
.SYNOPSIS
Get metadata of a Pure Storage CBS capacity monitor

.DESCRIPTION
Get metadata of a Pure Storage CBS capacity monitor

.PARAMETER MonitorResourceGroup
ResourceGroup that hosts monitor infrastructure components
#>

function Get-PCBSCapacityMonitor {
  param (
    [Parameter(Mandatory=$true)]
    [String]$MonitorResourceGroup
  )

  $result = @{Arrays = @()}
  $ResourceGroup = Get-AzResourceGroup -Name $MonitorResourceGroup

  if (-not $ResourceGroup) {
      throw "Resource group $MonitorResourceGroup does not exist"
  }

  if (-not $ResourceGroup.Tags["PureStorage.CBS.AVS"]) {
      throw "Resouce group $MonitorResourceGroup specified does not host Pure Storage CBS AVS monitor"
  }

  # Get global utilization threshold
  $DefaultUtilizationThreshold = Get-DefaultUtilizationThreshold -ResourceGroupName $MonitorResourceGroup
  $result["DefaultUtilizationThreshold"] = $DefaultUtilizationThreshold

  $KeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup

  # List CBS arras and their corresponding utilization threshold
  $UserPrincipalName = (Get-AzContext).Account.Id
  $KeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup
  Set-AzKeyVaultAccessPolicy -VaultName $KeyVault.VaultName  -UserPrincipalName $UserPrincipalName -PermissionsToSecrets set,delete,get,purge,list

  $Secrets = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName | where-object {$_.Name -like "*-$($KeyVault.VaultName)-capacity-username"}
  foreach ($Secret in $Secrets) {
      $ArrayName = ($Secret.name -Split "-$($KeyVault.VaultName)-capacity-username")[0]
      $EncodedThreshold = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($ArrayName)-$($KeyVault.VaultName)-capacity-threshold"
      if ($EncodedThreshold) {
        $Threshold = [int] (ConvertFrom-SecureString -SecureString $EncodedThreshold.SecretValue -AsPlainText)
      }
      else {
        $Threshold = $null
      }
      # If the string matches the format like "172-168-1-0", the array ip addressed was processed because secret name does not allow "."
      if ($ArrayName -match "^\d+-\d+-\d+-\d+$") {
          $ArrayName = $ArrayName.Replace("-", ".")
      }

      $result["Arrays"] += @{ArrayName = $ArrayName; Threshold=$Threshold}
  }
  return $result
}

<#
.SYNOPSIS
Remove an existing Pure Cloud Block Store from a Pure Storage CBS capacity monitor

.DESCRIPTION
Remove an existing Pure Cloud Block Store from a Pure Storage CBS capacity monitor.

.PARAMETER MonitorResourceGroup
ResourceGroup that hosts the monitor infrastructure components

.PARAMETER PureCloudBlockStoreEndpoint
Pure Cloud Block Store endpoint address
#>


function Remove-PCBSCapacityMonitorArray {
  param (
    [Parameter(Mandatory=$true)]
    [String]$MonitorResourceGroup,

    [Parameter(Mandatory=$true)]
    [String]$PureCloudBlockStoreEndpoint
  )

  $params = @{
    MonitorResourceGroup = $MonitorResourceGroup
    MonitorType = "capacity"
    PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint
  }

  Remove-MonitorArray @params
}

Export-ModuleMember -Function Build-PCBSCluster
Export-ModuleMember -Function New-PCBSVmfsDatastore
Export-ModuleMember -Function Restore-PCBSVmfsDatastore
Export-ModuleMember -Function Remove-PCBSVmfsDatastore
Export-ModuleMember -Function Set-PCBSVmfsCapacity
Export-ModuleMember -Function Deploy-PCBSAVSMonitor
Export-ModuleMember -Function Remove-PCBSAVSMonitor
Export-ModuleMember -Function Add-PCBSAVSMonitorArray
Export-ModuleMember -Function Remove-PCBSAVSMonitorArray
Export-ModuleMember -Function Get-PCBSAVSMonitorArray
Export-ModuleMember -Function New-PCBSVvolDataStore
Export-ModuleMember -Function Remove-PCBSVvolDataStore
Export-ModuleMember -Function New-PCBSVASAProvider
Export-ModuleMember -Function Update-PCBSVASAProvider
Export-ModuleMember -Function New-PCBSStoragePolicy
Export-ModuleMember -Function Remove-PCBSStoragePolicy
Export-ModuleMember -Function Start-PCBSFailover
Export-ModuleMember -Function Stop-PCBSFailoverTest
Export-ModuleMember -Function Start-PCBSFailoverCleanup
Export-ModuleMember -Function Start-PCBSFailoverReverse
Export-ModuleMember -Function Sync-PCBSReplicationGroup
Export-ModuleMember -Function Deploy-PCBSCapacityMonitor
Export-ModuleMember -Function Remove-PCBSCapacityMonitor
Export-ModuleMember -Function Add-PCBSCapacityMonitorArray
Export-ModuleMember -Function Get-PCBSCapacityMonitor
Export-ModuleMember -Function Remove-PCBSCapacityMonitorArray

# SIG # Begin signature block
# MIIuiAYJKoZIhvcNAQcCoIIueTCCLnUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB+BhtuF3Ri7Ogf
# sX7uvoqeonAQAbuzvdmLl++ey9gpGaCCE2gwggVyMIIDWqADAgECAhB2U/6sdUZI
# k/Xl10pIOk74MA0GCSqGSIb3DQEBDAUAMFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQK
# ExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENvZGUgU2ln
# bmluZyBSb290IFI0NTAeFw0yMDAzMTgwMDAwMDBaFw00NTAzMTgwMDAwMDBaMFMx
# CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQD
# EyBHbG9iYWxTaWduIENvZGUgU2lnbmluZyBSb290IFI0NTCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBALYtxTDdeuirkD0DcrA6S5kWYbLl/6VnHTcc5X7s
# k4OqhPWjQ5uYRYq4Y1ddmwCIBCXp+GiSS4LYS8lKA/Oof2qPimEnvaFE0P31PyLC
# o0+RjbMFsiiCkV37WYgFC5cGwpj4LKczJO5QOkHM8KCwex1N0qhYOJbp3/kbkbuL
# ECzSx0Mdogl0oYCve+YzCgxZa4689Ktal3t/rlX7hPCA/oRM1+K6vcR1oW+9YRB0
# RLKYB+J0q/9o3GwmPukf5eAEh60w0wyNA3xVuBZwXCR4ICXrZ2eIq7pONJhrcBHe
# OMrUvqHAnOHfHgIB2DvhZ0OEts/8dLcvhKO/ugk3PWdssUVcGWGrQYP1rB3rdw1G
# R3POv72Vle2dK4gQ/vpY6KdX4bPPqFrpByWbEsSegHI9k9yMlN87ROYmgPzSwwPw
# jAzSRdYu54+YnuYE7kJuZ35CFnFi5wT5YMZkobacgSFOK8ZtaJSGxpl0c2cxepHy
# 1Ix5bnymu35Gb03FhRIrz5oiRAiohTfOB2FXBhcSJMDEMXOhmDVXR34QOkXZLaRR
# kJipoAc3xGUaqhxrFnf3p5fsPxkwmW8x++pAsufSxPrJ0PBQdnRZ+o1tFzK++Ol+
# A/Tnh3Wa1EqRLIUDEwIrQoDyiWo2z8hMoM6e+MuNrRan097VmxinxpI68YJj8S4O
# JGTfAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBQfAL9GgAr8eDm3pbRD2VZQu86WOzANBgkqhkiG9w0BAQwFAAOCAgEA
# Xiu6dJc0RF92SChAhJPuAW7pobPWgCXme+S8CZE9D/x2rdfUMCC7j2DQkdYc8pzv
# eBorlDICwSSWUlIC0PPR/PKbOW6Z4R+OQ0F9mh5byV2ahPwm5ofzdHImraQb2T07
# alKgPAkeLx57szO0Rcf3rLGvk2Ctdq64shV464Nq6//bRqsk5e4C+pAfWcAvXda3
# XaRcELdyU/hBTsz6eBolSsr+hWJDYcO0N6qB0vTWOg+9jVl+MEfeK2vnIVAzX9Rn
# m9S4Z588J5kD/4VDjnMSyiDN6GHVsWbcF9Y5bQ/bzyM3oYKJThxrP9agzaoHnT5C
# JqrXDO76R78aUn7RdYHTyYpiF21PiKAhoCY+r23ZYjAf6Zgorm6N1Y5McmaTgI0q
# 41XHYGeQQlZcIlEPs9xOOe5N3dkdeBBUO27Ql28DtR6yI3PGErKaZND8lYUkqP/f
# obDckUCu3wkzq7ndkrfxzJF0O2nrZ5cbkL/nx6BvcbtXv7ePWu16QGoWzYCELS/h
# AtQklEOzFfwMKxv9cW/8y7x1Fzpeg9LJsy8b1ZyNf1T+fn7kVqOHp53hWVKUQY9t
# W76GlZr/GnbdQNJRSnC0HzNjI3c/7CceWeQIh+00gkoPP/6gHcH1Z3NFhnj0qinp
# J4fGGdvGExTDOUmHTaCX4GUT9Z13Vunas1jHOvLAzYIwggbmMIIEzqADAgECAhB3
# vQ4DobcI+FSrBnIQ2QRHMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNVBAYTAkJFMRkw
# FwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENv
# ZGUgU2lnbmluZyBSb290IFI0NTAeFw0yMDA3MjgwMDAwMDBaFw0zMDA3MjgwMDAw
# MDBaMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8w
# LQYDVQQDEyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25pbmcgQ0EgMjAyMDCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANZCTfnjT8Yj9GwdgaYw90g9
# z9DljeUgIpYHRDVdBs8PHXBg5iZU+lMjYAKoXwIC947Jbj2peAW9jvVPGSSZfM8R
# Fpsfe2vSo3toZXer2LEsP9NyBjJcW6xQZywlTVYGNvzBYkx9fYYWlZpdVLpQ0LB/
# okQZ6dZubD4Twp8R1F80W1FoMWMK+FvQ3rpZXzGviWg4QD4I6FNnTmO2IY7v3Y2F
# QVWeHLw33JWgxHGnHxulSW4KIFl+iaNYFZcAJWnf3sJqUGVOU/troZ8YHooOX1Re
# veBbz/IMBNLeCKEQJvey83ouwo6WwT/Opdr0WSiMN2WhMZYLjqR2dxVJhGaCJedD
# CndSsZlRQv+hst2c0twY2cGGqUAdQZdihryo/6LHYxcG/WZ6NpQBIIl4H5D0e6lS
# TmpPVAYqgK+ex1BC+mUK4wH0sW6sDqjjgRmoOMieAyiGpHSnR5V+cloqexVqHMRp
# 5rC+QBmZy9J9VU4inBDgoVvDsy56i8Te8UsfjCh5MEV/bBO2PSz/LUqKKuwoDy3K
# 1JyYikptWjYsL9+6y+JBSgh3GIitNWGUEvOkcuvuNp6nUSeRPPeiGsz8h+WX4VGH
# aekizIPAtw9FbAfhQ0/UjErOz2OxtaQQevkNDCiwazT+IWgnb+z4+iaEW3VCzYkm
# eVmda6tjcWKQJQ0IIPH/AgMBAAGjggGuMIIBqjAOBgNVHQ8BAf8EBAMCAYYwEwYD
# VR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU
# 2rONwCSQo2t30wygWd0hZ2R2C3gwHwYDVR0jBBgwFoAUHwC/RoAK/Hg5t6W0Q9lW
# ULvOljswgZMGCCsGAQUFBwEBBIGGMIGDMDkGCCsGAQUFBzABhi1odHRwOi8vb2Nz
# cC5nbG9iYWxzaWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUwRgYIKwYBBQUHMAKG
# Omh0dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2NvZGVzaWduaW5n
# cm9vdHI0NS5jcnQwQQYDVR0fBDowODA2oDSgMoYwaHR0cDovL2NybC5nbG9iYWxz
# aWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUuY3JsMFYGA1UdIARPME0wQQYJKwYB
# BAGgMgEyMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29t
# L3JlcG9zaXRvcnkvMAgGBmeBDAEEATANBgkqhkiG9w0BAQsFAAOCAgEACIhyJsav
# +qxfBsCqjJDa0LLAopf/bhMyFlT9PvQwEZ+PmPmbUt3yohbu2XiVppp8YbgEtfjr
# y/RhETP2ZSW3EUKL2Glux/+VtIFDqX6uv4LWTcwRo4NxahBeGQWn52x/VvSoXMNO
# Ca1Za7j5fqUuuPzeDsKg+7AE1BMbxyepuaotMTvPRkyd60zsvC6c8YejfzhpX0FA
# Z/ZTfepB7449+6nUEThG3zzr9s0ivRPN8OHm5TOgvjzkeNUbzCDyMHOwIhz2hNab
# XAAC4ShSS/8SS0Dq7rAaBgaehObn8NuERvtz2StCtslXNMcWwKbrIbmqDvf+28rr
# vBfLuGfr4z5P26mUhmRVyQkKwNkEcUoRS1pkw7x4eK1MRyZlB5nVzTZgoTNTs/Z7
# KtWJQDxxpav4mVn945uSS90FvQsMeAYrz1PYvRKaWyeGhT+RvuB4gHNU36cdZytq
# tq5NiYAkCFJwUPMB/0SuL5rg4UkI4eFb1zjRngqKnZQnm8qjudviNmrjb7lYYuA2
# eDYB+sGniXomU6Ncu9Ky64rLYwgv/h7zViniNZvY/+mlvW1LWSyJLC9Su7UpkNpD
# R7xy3bzZv4DB3LCrtEsdWDY3ZOub4YUXmimi/eYI0pL/oPh84emn0TCOXyZQK8ei
# 4pd3iu/YTT4m65lAYPM8Zwy2CHIpNVOBNNwwggcEMIIE7KADAgECAgxcuW61kTkv
# +4t8zgQwDQYJKoZIhvcNAQELBQAwWTELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEds
# b2JhbFNpZ24gbnYtc2ExLzAtBgNVBAMTJkdsb2JhbFNpZ24gR0NDIFI0NSBDb2Rl
# U2lnbmluZyBDQSAyMDIwMB4XDTI0MDMxMTE0MDQxMloXDTI3MDMxMjE0MDQxMlow
# cjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC1Nh
# bnRhIENsYXJhMRswGQYDVQQKExJQdXJlIFN0b3JhZ2UsIEluYy4xGzAZBgNVBAMT
# ElB1cmUgU3RvcmFnZSwgSW5jLjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAMCQrioSn48IvHpTg5dofsUYj/pNTDidwjYUrcxVu78NoyhSweG8FhcxDi/S
# I40+8Fccl3D5ZoqpjkFnGhzSwmpxU3J4AP7+fdTZht9eWD1I5qKY07esYwdPDV4y
# g+csPfdGPqI2XjRfT5UC3YkXQeUrX8KQZldD4KqvgxzpYcuBwsgHbTb/eArpi68Y
# gFR2jgZGyZigfy8RuJMrL1thcBOe/VWjUyK21wVT8cuunBYFaStLHhsRBRMDcZBD
# uTSGC4evE6oaCqlQbdMl9YFJ64mDQsKlCxrr7rmLVtcVzKGwmjp4b2xRwE+RmTh6
# JtrUL9Wx/3a3UzgAnDNimfwp85zoL48kyLtHqQ3FI8tVKGm+aBOgBZfmURoy7fbp
# 4zKhGgqFbpOmILO16i4f999YsEEJQgIF3CtyH1R60/ZZWlDmoeeEgjAGrnd14muU
# 5Hk3Cksr43uPUAg+fV78Y0fDV85ibm42ZwwPuz6MI4HhYNUlGzRwIQ31vjaGuAMW
# HNqFKkcO0JuIeHQ/gFKPnYIxnGC9H9R4Kw/uMezqtnYJwGU2epB/ABl/w7U4NgU2
# ZOxWB5BFy4frZ3f+hNgbjFUjMaXnVFotOJxXntzjdSl4znw8DaKiC5ooChteZMIT
# G9p078p/TUsOJQbUtFADSY1hsfCfB7t+gJSNt5peS9GOZIMVAgMBAAGjggGxMIIB
# rTAOBgNVHQ8BAf8EBAMCB4AwgZsGCCsGAQUFBwEBBIGOMIGLMEoGCCsGAQUFBzAC
# hj5odHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2djY3I0NWNv
# ZGVzaWduY2EyMDIwLmNydDA9BggrBgEFBQcwAYYxaHR0cDovL29jc3AuZ2xvYmFs
# c2lnbi5jb20vZ3NnY2NyNDVjb2Rlc2lnbmNhMjAyMDBWBgNVHSAETzBNMEEGCSsG
# AQQBoDIBMjA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNv
# bS9yZXBvc2l0b3J5LzAIBgZngQwBBAEwCQYDVR0TBAIwADBFBgNVHR8EPjA8MDqg
# OKA2hjRodHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjQ1Y29kZXNpZ25j
# YTIwMjAuY3JsMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB8GA1UdIwQYMBaAFNqzjcAk
# kKNrd9MMoFndIWdkdgt4MB0GA1UdDgQWBBSzJ9KiDCa3UBiAajy+Iioj5kQjzDAN
# BgkqhkiG9w0BAQsFAAOCAgEAHsFQixeQEcoHurq9NWSUt4S39Q+UGP6crmVq3Wwy
# 9g23YbdWg+SgMxoLUqdoDfA4k4B6Dyoo0jEQzn2kxnsnT9lNHKrcZHH88dv0hjfi
# H2qAiQWazPjS3LhK2J6nhpyipJPpyRaSQG4x4aG0NB2D4WUfUz9CGAYsERJGww/w
# kTaaxMipttKDTaI1C49u1igDfRzIO+Q8vuyyBFLiYTno/df97xtjNC+KxxFhDhl/
# 4tawK6kwxaVzCMAfj48I67Wbo4DMH6pM1s19as7c3qp92i3MylGKsB6+u+o7UkbS
# dLNkS4ALI33CJOUc+GoK3Nt5IXXCFJTQFHBXkBdAur3gmlXEm8vlNG/1Sbxr0H7T
# 1e7ABGH/48o/+PeMLuCc72EeK5dJ4cX9NEQ3QnTsZHwGnYzjEOvOvP0s1c7yNsDb
# cUHoIqQvb5xS5aqMU5G+8sdPQ1nwpPf7gGaEEbAVW4w51Pam42qeN9HIPa+ZinXn
# sN02Kk1Qw0QwUqzaQy9W/gIquI0KOjw0LmoW9M/8S0lrjpEq2eEeUw9WQLhhUEIi
# rFxGPtjqiCLiiS9CZ+kf2vWLJKUspkYv+OHT3q805Zg1dJsBFAzEYUFLb1mhmigD
# EO9bsMorjECIL2ijE5zHtbGkalrrsPWu8tiDT/B7P9GSYzKfOOy4PoOIfWSK0Ixl
# S7Ixghp2MIIacgIBATBpMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxT
# aWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25p
# bmcgQ0EgMjAyMAIMXLlutZE5L/uLfM4EMA0GCWCGSAFlAwQCAQUAoIGcMBkGCSqG
# SIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3
# AgEVMC8GCSqGSIb3DQEJBDEiBCBftnF3RqIvSV1YNGYHqaPTPdXkV54mv6NsfzTc
# DYUVaTAwBgorBgEEAYI3AgEMMSIwIKACgAChGoAYaHR0cHM6Ly9wdXJlc3RvcmFn
# ZS5jb20gMA0GCSqGSIb3DQEBAQUABIICALevQ37MAdZZjvr5u0jzJ1H9N5gBEBEy
# o9wfZB0wC7DL7Cz5OEhzQs4ZI33l2e16gkOJ+3QsWWgiUPLfaXXLs7MLcENiIdqe
# +ZE0P9n9MzWbizRLrjSFKpo9JzSyQHp3MVzVxXtCyI5RfXc888VGIWNGL76bRJ/W
# RUuo3VZAsT2WzTh5JPUOL7ifgVWYuSoFqcXHxtPwrO2Fx+66pbhlE36ghjnbJUuj
# 9zh7GEkZrcJt+0oflCJICzoWd330Ujrbf4W/MuOm6qyW5wOKx/sC6D1MHhC/JaER
# 0etN1n4yW9Qjcaa7wxGv8mxOqHAtQ28L3ennebiPbuMAwHqxcxjob/Raa4FtVvMs
# yb69DdU/9VwCSZSKTwPjF/LfNl0UfoJd7noLZ0DTRKDwS1QAtSuP1s+vAF7vpRxq
# EhH4O6h26n3X14om/3Gll1Sn9tWMjlVLNQ5yp8Or/g6aF31C14TDt5Ac39hzNc2l
# rx4t6UIPMrSplMGutZ8VlLVmtMuiW8wutPK01BC5/XO2Gif6zxF9cCtVYeO7HcL+
# ibCjFTdvjhNQH0q766fdiPhnADf7JD76AoMHWfDuCku3mHIQWxBAVmgfwB8IW5nJ
# eBi2r7yAwIWI+iQV9R0NL5zznHEObXaxKu3r64EPDa5JHnIez5CVZ8asEZTPIGic
# V9bWkKy7OM0hoYIXPzCCFzsGCisGAQQBgjcDAwExghcrMIIXJwYJKoZIhvcNAQcC
# oIIXGDCCFxQCAQMxDzANBglghkgBZQMEAgEFADB3BgsqhkiG9w0BCRABBKBoBGYw
# ZAIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIJlfLHL03gKXPR3fk1aM
# 1ZjPPjxtSA+NrIe4DC0PPTFGAhBDfP3KMkoEOQ7Fl5Y2YAhIGA8yMDI0MDQwNjE5
# MDQ1OFqgghMJMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/X+VhFjANBgkqhkiG
# 9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x
# OzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGlt
# ZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAxMzIzNTk1OVowSDEL
# MAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSAwHgYDVQQDExdE
# aWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOXejsqnGfcYhVYwamT
# EafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbjaedp/lvD1isgHMGX
# lLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7ReoNYWyd/nFexAaa
# PPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PBuOZSlbVH13gpOWvg
# eFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu6QXuqvYk9R28mxyy
# t1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769SgLDSb495uZBkHNwGR
# Dxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUGFOpiCBPTaR58ZE2d
# D9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZcClhMAD6FaXXHg2TW
# dc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmhcbJsca8+uG+W1eEQ
# E/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U20clfCKRwo+wK8REu
# ZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qDy0B7AgMBAAGjggGL
# MIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAK
# BggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwHwYD
# VR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFKW27xPn783Q
# ZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBp
# bmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3Rh
# bXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6gqbWYF7xwjU+KPGic
# 2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s1+FgnCvt7T1Ijrhr
# unxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0qBXqEKZi2V3mP2yZW
# K7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp44pMadqJpddNQ5EQS
# viANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6wQSbd3lqXTzON1I13
# fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4ZiQPq1JE3701S88lg
# IcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wnLEHQmjNKqDbUuXKW
# fpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5AdzaROY63jg7B145WPR8
# czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy0tg6uLFGhmu6F/3E
# d2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDAdwrUAuBcYLso/zjl
# UlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2XlG3O2mflrLAZG70Ee
# 8PBf4NvZrZCARK+AEEGKMIIGrjCCBJagAwIBAgIQBzY3tyRUfNhHrP0oZipeWzAN
# BgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg
# SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2Vy
# dCBUcnVzdGVkIFJvb3QgRzQwHhcNMjIwMzIzMDAwMDAwWhcNMzcwMzIyMjM1OTU5
# WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNV
# BAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1w
# aW5nIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxoY1BkmzwT1y
# SVFVxyUDxPKRN6mXUaHW0oPRnkyibaCwzIP5WvYRoUQVQl+kiPNo+n3znIkLf50f
# ng8zH1ATCyZzlm34V6gCff1DtITaEfFzsbPuK4CEiiIY3+vaPcQXf6sZKz5C3GeO
# 6lE98NZW1OcoLevTsbV15x8GZY2UKdPZ7Gnf2ZCHRgB720RBidx8ald68Dd5n12s
# y+iEZLRS8nZH92GDGd1ftFQLIWhuNyG7QKxfst5Kfc71ORJn7w6lY2zkpsUdzTYN
# XNXmG6jBZHRAp8ByxbpOH7G1WE15/tePc5OsLDnipUjW8LAxE6lXKZYnLvWHpo9O
# dhVVJnCYJn+gGkcgQ+NDY4B7dW4nJZCYOjgRs/b2nuY7W+yB3iIU2YIqx5K/oN7j
# PqJz+ucfWmyU8lKVEStYdEAoq3NDzt9KoRxrOMUp88qqlnNCaJ+2RrOdOqPVA+C/
# 8KI8ykLcGEh/FDTP0kyr75s9/g64ZCr6dSgkQe1CvwWcZklSUPRR8zZJTYsg0ixX
# NXkrqPNFYLwjjVj33GHek/45wPmyMKVM1+mYSlg+0wOI/rOP015LdhJRk8mMDDtb
# iiKowSYI+RQQEgN9XyO7ZONj4KbhPvbCdLI/Hgl27KtdRnXiYKNYCQEoAA6EVO7O
# 6V3IXjASvUaetdN2udIOa5kM0jO0zbECAwEAAaOCAV0wggFZMBIGA1UdEwEB/wQI
# MAYBAf8CAQAwHQYDVR0OBBYEFLoW2W1NhS9zKXaaL3WMaiCPnshvMB8GA1UdIwQY
# MBaAFOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUE
# DDAKBggrBgEFBQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6
# Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMu
# ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDww
# OjA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3Rl
# ZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0G
# CSqGSIb3DQEBCwUAA4ICAQB9WY7Ak7ZvmKlEIgF+ZtbYIULhsBguEE0TzzBTzr8Y
# +8dQXeJLKftwig2qKWn8acHPHQfpPmDI2AvlXFvXbYf6hCAlNDFnzbYSlm/EUExi
# HQwIgqgWvalWzxVzjQEiJc6VaT9Hd/tydBTX/6tPiix6q4XNQ1/tYLaqT5Fmniye
# 4Iqs5f2MvGQmh2ySvZ180HAKfO+ovHVPulr3qRCyXen/KFSJ8NWKcXZl2szwcqMj
# +sAngkSumScbqyQeJsG33irr9p6xeZmBo1aGqwpFyd/EjaDnmPv7pp1yr8THwcFq
# cdnGE4AJxLafzYeHJLtPo0m5d2aR8XKc6UsCUqc3fpNTrDsdCEkPlM05et3/JWOZ
# Jyw9P2un8WbDQc1PtkCbISFA0LcTJM3cHXg65J6t5TRxktcma+Q4c6umAU+9Pzt4
# rUyt+8SVe+0KXzM5h0F4ejjpnOHdI/0dKNPH+ejxmF/7K9h+8kaddSweJywm228V
# ex4Ziza4k9Tm8heZWcpw8De/mADfIBZPJ/tgZxahZrrdVcA6KYawmKAr7ZVBtzrV
# FZgxtGIJDwq9gdkT/r+k0fNX2bwE+oLeMt8EifAAzV3C+dAjfwAL5HYCJtnwZXZC
# pimHCUcr5n8apIUP/JiW9lVUKx+A+sDyDivl1vupL0QVSucTDh3bNzgaoSv27dZ8
# /DCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAw
# ZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBS
# b290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjAN
# BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUu
# ySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8
# Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0M
# G+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldX
# n1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQecN4x7axxLVq
# GDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFE
# mjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6
# SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXf
# SwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b23
# 5kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ
# 6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRp
# L5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
# BBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1R
# i6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYB
# BQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0
# cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADAN
# BgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVe
# qRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3vot
# Vs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum
# 6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJ
# aISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/
# ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDGCA3YwggNyAgEBMHcw
# YzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQD
# EzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGlu
# ZyBDQQIQBUSv85SdCDmmv9s/X+VhFjANBglghkgBZQMEAgEFAKCB0TAaBgkqhkiG
# 9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI0MDQwNjE5MDQ1
# OFowKwYLKoZIhvcNAQkQAgwxHDAaMBgwFgQUZvArMsLCyQ+CXc6qisnGTxmcz0Aw
# LwYJKoZIhvcNAQkEMSIEILa6AVH+Z02Y/mf5nQwvzObaLjHHHT8UKoqxvVraqUai
# MDcGCyqGSIb3DQEJEAIvMSgwJjAkMCIEINL25G3tdCLM0dRAV2hBNm+CitpVmq4z
# Fq9NGprUDHgoMA0GCSqGSIb3DQEBAQUABIICADGttaqw9t+2uWItqvLerBt9bu37
# NG0fKZ4kFTLd7WcsL88EaRij5A7qLIz64y8XJ6Bi2oR9kl4YrMuZ+rLPbk4suW8E
# Hjb3FMwrv42Hrb4Yek+n7Vqf844LYUfhts2wyycy+zlLgty1IqQzat9xd0h/1G17
# pH1h3Z2VSCUYvEa11BeUQc8Ajl+Rn2sO99QnIA6AF0nbfxE0neLcoevebLhmp28h
# 4GOCfpfIFl9oC5hm/6mw9gfcRxSg5Gp0qR1TRRsWAADQAAoHQl0ph51/oZPXtxFy
# 12AkQ5d3+ZbfgHnSKiHSL61ReLxyCA2Q4FNUqx5rJfraxB6w7wB5fvkURLFnM92K
# yQ4DxqHxT6+eyYoG6F6BnENsxYnF5S3F+Qx4CfYF5jMnbBobPG3NfJ92qMTxnM4k
# EZVHK0yCzpOTA7meR4xdI+KExFWBB4XhmrJ4a1cGx1aslHX20uOmbpX7yWb+kyCj
# 4sXZPPZaEjalF03SKydMoegbQV4qYIaWYhbQlIDZmz8kVGNzxgIxsHnWDZH/olsu
# 9TBWAIVk3Du6yVkSyP+iGKRr+mjbMSgVXcNcfkXaMdFiyCMXp9jCubjerZdFBUhW
# HUobEeBGi9T1B4LONqvehHbUDWL+rhpD2KzYOGE5/vqug24N+5lS+SKV9vcB9tWK
# d6AMFJxEMZ4ABNVh
# SIG # End signature block