PureStorage.AzureNative.Util.ps1

$PluginPrivileges = @(
    'Datastore.AllocateSpace',
    'Datastore.Browse',
    'Datastore.Config',
    'Datastore.Delete',
    'Datastore.DeleteFile',
    'Datastore.FileManagement',
    'Datastore.Move',
    'Datastore.Rename',
    'Datastore.UpdateVirtualMachineFiles',
    'Datastore.UpdateVirtualMachineMetadata',
    'Extension.Register',
    'Extension.Unregister',
    'Extension.Update',
    'Folder.Create',
    'Folder.Delete',
    'Folder.Move',
    'Folder.Rename',
    'Global.CancelTask',
    'Global.ManageCustomFields',
    'Global.SetCustomField',
    'Host.Config.Storage',
    'ScheduledTask.Create',
    'ScheduledTask.Delete',
    'ScheduledTask.Edit',
    'ScheduledTask.Run',
    'Sessions.ValidateSession',
    'StorageProfile.Update',
    'StorageProfile.View',
    'StorageViews.View',
    'StorageViews.ConfigureService',
    'Task.Create',
    'Task.Update',
    'VirtualMachine.Config.AddExistingDisk',
    'VirtualMachine.Config.AddNewDisk',
    'VirtualMachine.Config.AddRemoveDevice',
    'VirtualMachine.Config.RemoveDisk',
    'VirtualMachine.Interact.PowerOff',
    'VirtualMachine.Interact.PowerOn',
    'VirtualMachine.Inventory.Create',
    'VirtualMachine.Inventory.CreateFromExisting',
    'VirtualMachine.Inventory.Delete',
    'VirtualMachine.Inventory.Move',
    'VirtualMachine.Inventory.Register',
    'VirtualMachine.Inventory.Unregister',
    'VirtualMachine.Provisioning.Clone',
    'VirtualMachine.Provisioning.CloneTemplate',
    'VirtualMachine.Provisioning.CreateTemplateFromVM',
    'VirtualMachine.Provisioning.GetVmFiles',
    'VirtualMachine.State.CreateSnapshot',
    'VirtualMachine.State.RemoveSnapshot',
    'VirtualMachine.State.RenameSnapshot',
    'VirtualMachine.State.RevertToSnapshot'
)

# Service account name prefix
$AccountNamePrefix = "psserviceaccount"

# Service account role name
$RoleName = "PureStorageService"

# Timeout for successful Rest call
$WaitTimeSeconds = 3600

$DefaultManagementHostUri = "https://management.azure.com"

# .SYNOPSIS
# Write a message to console with added timestamp.
function PrintLog {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [ValidateSet('INFO', 'DEBUG', 'WARNING', 'ERROR', 'VERBOSE')]
        [string]$Severity = 'INFO'
    )
    $dateTime = (Get-Date -f "yyyy-MM-dd HH:mm:ss")
    $msg = "$dateTime $Severity $Message"
    switch ($Severity) {
        WARNING {
            Write-Warning $msg
        }
        DEBUG {
            Write-Debug $msg
        }
        ERROR {
            Write-Error $msg
        }
        VERBOSE {
            Write-Verbose $msg
        }
        Default {
            Write-Host $msg
        }
    }
}

function New-RandomPassword {
    param (
        [int]$length = 16,
        [int]$specialCharCount = 4
    )
    
    if ($length -lt 8) {
        throw "Password length should be at least 8 characters."
    }

    if ($specialCharCount -gt $length) {
        throw "Number of special characters cannot exceed total length of the password."
    }

    $upperCaseCount = [math]::Ceiling(($length - $specialCharCount) / 3)
    $lowerCaseCount = [math]::Ceiling(($length - $specialCharCount) / 3)
    $numberCount    = ($length - $specialCharCount) - $upperCaseCount - $lowerCaseCount

    $upperCase = 1..$upperCaseCount | ForEach-Object { [char[]]([char]'A'..[char]'Z') | Get-SecureRandom }
    $lowerCase = 1..$lowerCaseCount | ForEach-Object { [char[]]([char]'a'..[char]'z') | Get-SecureRandom }
    $numbers   = 1..$numberCount | ForEach-Object { [char[]]([char]'0'..[char]'9') | Get-SecureRandom }
    $special   = 1..$specialCharCount | ForEach-Object { ([char[]]"!@#$%^&*()_+-=[]{}|;:',.<>?") | Get-SecureRandom }

    $passwordChars = $upperCase + $lowerCase + $numbers + $special
    $password = ($passwordChars | Sort-Object { Get-SecureRandom }) -join ""

    return $password
}

function Get-EncryptedSignature {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Text,
        [Parameter(Mandatory = $true)]
        [string]$PrivateKey
    )

    $hashAlgorithm = [System.Security.Cryptography.HashAlgorithmName]::SHA256

    # Convert the private key from PEM format to an RSA object
    $rsaKey = [System.Security.Cryptography.RSA]::Create()
    $rsaKey.ImportFromPem($PrivateKey)

    $bytesToEncrypt = [System.Text.Encoding]::UTF8.GetBytes($Text)

    $signature = $rsaKey.SignData($bytesToEncrypt, $hashAlgorithm, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)

    # Convert the encrypted byte array to a base64 string
    $Signature64 = [Convert]::ToBase64String($signature)

    return $Signature64
}

function Test-TextSignarure {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Text,
        [Parameter(Mandatory = $true)]
        [string]$Signature,
        [Parameter(Mandatory = $true)]
        [string]$PublicKey
    )
    
    $key = $PublicKey -replace "-----BEGIN PUBLIC KEY-----", "" -replace "-----END PUBLIC KEY-----", ""
    $key = [Convert]::FromBase64String($key)

    $hashAlgorithm = [System.Security.Cryptography.HashAlgorithmName]::SHA256

    # Convert the private key from PEM format to an RSA object
    $rsaKey = [System.Security.Cryptography.RSA]::Create()
    $rsaKey.ImportSubjectPublicKeyInfo($key, [ref]0)
    
    # Convert the base64-encoded encrypted text to a byte array
    $SignatureBytes = [Convert]::FromBase64String($Signature)

    $TextBytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
    $IsValid =  $rsaKey.VerifyData($TextBytes, $SignatureBytes, $hashAlgorithm, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
    
    return $IsValid
}

function Test-RequestDatetimeInUTC {
    param (
        [Parameter(Mandatory = $true)]
        [string]$RequestDatetime,
        [Parameter(Mandatory = $true)]
        [int]$TimeWindowInMinutes
    )
    # Check time window to validate the request
    $requestDatetimeUTC = Get-Date -Date $RequestDatetime
    $currentDatetimeUTC = Get-Date -AsUTC
    if ($requestDatetimeUTC.AddMinutes($TimeWindowInMinutes) -lt $currentDatetimeUTC) {
        throw "Request is outside the time window: $TimeWindowInMinutes minutes"
    }
}

function ConvertTo-EncryptedText {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Text,
        [Parameter(Mandatory = $true)]
        [string]$PublicKey
    )

    # Convert the private key from PEM format to an RSA object
    $rsaKey = [System.Security.Cryptography.RSA]::Create()
    $rsaKey.ImportFromPem($PublicKey)
 
    # Convert the text to a byte array
    $bytesToEncrypt = [System.Text.Encoding]::UTF8.GetBytes($Text)

    # Encrypt the byte array using the key
    $encryptedBytes = $rsaKey.Encrypt($bytesToEncrypt, [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1)

    # Convert the encrypted byte array to a base64 string
    $encryptedText = [Convert]::ToBase64String($encryptedBytes)

    return $encryptedText
}

function ConvertFrom-EncryptedText {
    param (
        [Parameter(Mandatory = $true)]
        [string]$EncryptedText,
        [Parameter(Mandatory = $true)]
        [string]$PrivateKey
    )

    # Convert the private key from PEM format to an RSA object
    $rsaKey = [System.Security.Cryptography.RSA]::Create()
    $rsaKey.ImportFromPem($PrivateKey)

    # Convert the base64-encoded encrypted text to a byte array
    $encryptedBytes = [Convert]::FromBase64String($EncryptedText)

    # Decrypt the byte array using the key
    $decryptedBytes = $rsaKey.Decrypt($encryptedBytes, [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1)

    # Convert the decrypted byte array back to a string
    $decryptedText = [System.Text.Encoding]::UTF8.GetString($decryptedBytes)

    return $decryptedText
}

function ConvertFrom-Base64 {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Base64Text
    )

    $bytes = [Convert]::FromBase64String($Base64Text)
    $text = [System.Text.Encoding]::UTF8.GetString($bytes)

    return $text
}

function ConvertTo-Base64 {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Text
    )

    $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
    $base64Text = [Convert]::ToBase64String($bytes)

    return $base64Text
}

function CompareAndUpdate-Privileges {
    param (
        [Parameter(Mandatory = $true)]
        [object]$Role,
        [Parameter(Mandatory = $true)]
        [string[]]$RequiredPrivileges
    )
    # These are the default privileges that are always required and cannot be removed
    $DefaultPrivileges = @(
        'System.Anonymous',
        'System.Read',
        'System.View'
    )
    $RequiredPrivileges += $DefaultPrivileges

    $RoleName = $Role.Name
    $RolePrivileges = $Role.PrivilegeList

    # Identify missing and extra privileges
    # Find missing privileges (those in the required list but not in the role)
    $MissingPrivileges = $RequiredPrivileges | Where-Object { $_ -notin $RolePrivileges }
    # Find extra privileges (those in the role but not in the required list)
    $ExtraPrivileges = $RolePrivileges | Where-Object { $_ -notin $RequiredPrivileges }

    $PrivilegesToAdd = @()
    if ($MissingPrivileges) {
        PrintLog "Missing Privileges: $($MissingPrivileges -join ', ')" INFO
        $PrivilegesToAdd += $MissingPrivileges | ForEach-Object { Get-VIPrivilege -Id $_ }
    } else {
        PrintLog "No missing privileges" INFO
    }

    $PrivilegesToRemove = @()
    if ($ExtraPrivileges) {
        PrintLog "Extra Privileges: $($ExtraPrivileges -join ', ')" INFO
        $PrivilegesToRemove += $ExtraPrivileges | ForEach-Object { Get-VIPrivilege -Id $_ }
    } else {
        PrintLog "No extra privileges" INFO
    }

    # Update the role with the missing and extra privileges
    # Apply updates only if needed
    $IsUpdated = $false
    if ($PrivilegesToAdd.Count -gt 0) {
        PrintLog "Adding missing privileges to role '$RoleName'" INFO
        $null = Set-VIRole -Role $Role -AddPrivilege $PrivilegesToAdd
        $IsUpdated = $true
    }

    if ($PrivilegesToRemove.Count -gt 0) {
        PrintLog "Removing extra privileges from role '$RoleName'" INFO
        $null = Set-VIRole -Role $Role -RemovePrivilege $PrivilegesToRemove
        $IsUpdated = $true
    }

    if (-not $IsUpdated) {
        PrintLog "No changes needed for the role: $RoleName" INFO
    }
}

<#
    .SYNOPSIS
    Creates a new service account and assigns it a role with specific privileges.
#>

function _New-AvsServiceAccount {
    param(
        [Parameter(Mandatory = $true)]
        [string]$InitializationHandle
    )

    # Convert the InitializationHandle JSON string to a JSON object
    $DecodedData = $InitializationHandle | ConvertFrom-Json

    # The DecodedData is a JSON object with the following structure:
    # {
    # "sddcResourceId": "string",
    # "serviceAccountUsername": "string"
    # }
    $AccountName = $DecodedData.serviceAccountUsername

    # Validate the prefix of the account name
    if (-not $AccountName.StartsWith($AccountNamePrefix)) {
        throw "The account name must start with '$AccountNamePrefix'"
    }

    # Generate a random password for the service account
    $AccountPassword = New-RandomPassword

    # If the user already exists, update the password
    $User = Get-SsoPersonUser -Domain 'vsphere.local' | Where-Object { $_.Name -eq $AccountName }
    if ($User) {
        PrintLog "User $AccountName already exists, updating the password" WARNING
        $null = Set-SsoPersonUser -User $User -NewPassword $AccountPassword -ErrorAction Stop
    } else {
        $User = New-SsoPersonUser -UserName $AccountName -Password $AccountPassword -Description "Pure Storage Service Account" -ErrorAction Stop
    }
    # Create Role and assign Role to user
    $Role = Get-VIRole -Name $RoleName -ErrorAction SilentlyContinue
    if ($Role) {
        PrintLog "Role $RoleName already exists, checking the role privileges against the required privileges" WARNING
        CompareAndUpdate-Privileges -Role $Role -RequiredPrivileges $PluginPrivileges
    }
    else {
        $Privileges = @()
        foreach ($priv in $PluginPrivileges) {
            PrintLog "Adding privilege: $priv" DEBUG
            $Privileges += Get-VIPrivilege -Id $priv
        }

        $Role = New-VIRole -Name $RoleName -Privilege $Privileges
    }

    $Account = Get-VIAccount -Domain $User.Domain | Where-Object { $_.Id -eq $AccountName }
    if (-not $Account) {
        throw "Failed to create account for user $User"
    }

    $RootFolder = Get-Folder -NoRecursion
    if (-not $RootFolder) {
        throw "Failed to retrieve root folder"
    }
    $RootFolder = $RootFolder | Select-Object -Last 1

    PrintLog "Adding permissions for Account $AccountName on $($RootFolder.Name) with role $RoleName" INFO
    $null = New-VIPermission -Entity $RootFolder -Principal $Account -Role $Role -Propagate $true

    PrintLog "Avs Service Account $AccountName created successfully with role $RoleName" INFO

    # Return the initialization data for the service account
    $vSphereIp = $Account.Server.ServiceUri.Host

    $InitializationData= @{
        "serviceAccountUsername" = $AccountName
        "serviceAccountPassword" = $AccountPassword
        "vSphereIp" = $vSphereIp
    }

    return $InitializationData
}

<#
    .SYNOPSIS
    Removes Pure Storage AVS service account(s) and the role assigned to them.
#>

function _Remove-AvsServiceAccount
{
    param(
        [Parameter(Mandatory = $false)]
        [string]$Suffix,
        [switch]$DryRun
    )

    # If we want to remove all service accounts, we can set the suffix to "*"
    $AccountName = $AccountNamePrefix + $Suffix
    $users = Get-SsoPersonUser -Domain 'vsphere.local' | Where-Object { $_.Name -like $AccountName }
    if (-not $users) {
        PrintLog "User '$AccountName' not found." WARNING
        return
    }
    PrintLog "Found $($users.Count) user(s) in total" INFO

    foreach ($user in $users) {
        $userName = $user.Name
        PrintLog "Found user: $userName" INFO
        $name = "VSPHERE.LOCAL\" + $user.Name
        $accountPermissions = Get-VIPermission -Principal $name

        # Attempt to remove any permissions the user might have
        if ($accountPermissions) {
            if ($DryRun) {
                PrintLog "Dry run: Removing permissions '$($accountPermissions.Role)' from user $userName" INFO
            } else {
                PrintLog "Removing permissions '$($accountPermissions.Role)' from user $userName" INFO
                try {
                    Remove-VIPermission -Permission $accountPermissions -Confirm:$false
                    PrintLog "Successfully removed permissions for user $userName" INFO
                } catch {
                    PrintLog "Failed to remove permissions for user $userName with error: $_" WARNING
                }
            }
        }

        # Remove the user regardless of role
        if ($DryRun) {
            PrintLog "Dry run: Removing user $userName" INFO
        } else {
            PrintLog "Removing user $userName" INFO
            try {
                Remove-SsoPersonUser -User $user
                PrintLog "Successfully removed user $userName." INFO
            } catch {
                throw "Failed to remove user $userName with error: $_"
            }
        }

        # Check and clean up role if it's unused
        $role = Get-VIRole -Name $RoleName
        if ($role) {
            $remainingRoleAssignments = Get-VIPermission | Where-Object { $_.Role -eq $RoleName }
            if ($remainingRoleAssignments.Count -eq 0) {
                if ($DryRun) {
                    PrintLog "Dry run: Removing unused role $RoleName" INFO
                } else {
                    PrintLog "Removing unused role $RoleName" INFO
                    try {
                        Remove-VIRole -Role $role -Force -Confirm:$false
                        PrintLog "Successfully removed role $RoleName" INFO
                    } catch {
                        throw "Failed to remove role $RoleName with error: $_"
                    }
                }
            } else {
                PrintLog "Role $RoleName still has other accounts assigned" WARNING
            }
        } else {
            PrintLog "Role $RoleName not found" WARNING
        }
    }
}

function ConvertTo-HttpHeaders {
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Generic.Dictionary[string, System.Collections.Generic.IEnumerable[string]]]$Headers
    )
    # Create a new HttpRequestMessage to construct HttpHeaders
    $httpRequestMessage = [System.Net.Http.HttpRequestMessage]::new()

    foreach ($key in $Headers.Keys) {
        foreach ($value in $Headers[$key]) {
            $null = $httpRequestMessage.Headers.TryAddWithoutValidation($key, $value)
        }
    }

    $result = $httpRequestMessage.Headers
    Write-Output -NoEnumerate $result
}

<#
    .SYNOPSIS
    Invoke an arm request to Azure REST API and convert the response to an AzRest response object.
#>

function Invoke-ARM{
    param(
        [Parameter(Mandatory = $true)]
        [string]$Uri,
        [Parameter(Mandatory = $true)]
        [string]$Method,
        [Parameter(Mandatory = $false)]
        [string]$Body,
        [Parameter(Mandatory = $false)]
        [hashtable]$RequestHeaders = @{}
    )
    $content = Invoke-ARMRequest -Uri $Uri -Method $Method -Body $Body -Headers $RequestHeaders `
                -StatusCodeVariable "statusCode" -ResponseHeadersVariable "headers"

    $httpHeaders = ConvertTo-HttpHeaders -Headers $headers

    # Structure the output to match Invoke-AzRest response
    $responseObject = [pscustomobject]@{
        Content = $content
        Headers = $httpHeaders
        StatusCode = $statusCode
    }

    Write-Output -NoEnumerate $responseObject
}

function Invoke-AzureRestCallWithVerificationAndRetries {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("GET", "POST", "PUT", "DELETE")]
        [string]$Method,
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Uri,
        [Parameter(Mandatory = $false)]
        [hashtable]$PayLoad = @{},
        [Parameter(Mandatory = $false)]
        [hashtable]$RequestHeaders = @{},
        [Parameter(Mandatory = $false)]
        [int]$MaxRetries = 3,
        [int]$RetryInterval = 1
    )
    if ($Payload.Count -eq 0) {
        # When payload is empty, set it to an empty string to avoid the Azure GET request failure.
        $jsonPayload = ""
    } else {
        $jsonPayLoad = $PayLoad | ConvertTo-Json -Depth 10
    }

    PrintLog "Request URI: $Uri" DEBUG
    PrintLog "Request BODY: $jsonPayLoad" DEBUG

    $attempt = 0
    while ($attempt -lt $MaxRetries) {
        try {
            $attempt++
            $res = Invoke-ARM -Uri $Uri -Method $Method -Body $jsonPayLoad -RequestHeaders $RequestHeaders

            if ($res.StatusCode -ge 200 -and $res.StatusCode -lt 300) {
                return $res
            }

            $errorMsg = "Request failed with status code: $( $res.StatusCode ) and content: $( $res.Content )"
            PrintLog $errorMsg ERROR
            throw $errorMsg
        }
        catch {
            $errorMsg = $_.Exception.Message
            if ($attempt -lt $MaxRetries)
            {
                PrintLog "Attempt $attempt failed. Retrying in $RetryInterval seconds..." DEBUG
                Start-Sleep -Seconds $RetryInterval
            } else {
                $msg = "Maximum retry attempts reached: $errorMsg"
                PrintLog $msg ERROR
                throw $msg
            }
        }
    }
}

function WaitForSuccessStatus {
    param(
        [Parameter(Mandatory = $true)]
        [System.Net.Http.Headers.HttpHeaders]$Headers,
        [Parameter(Mandatory = $false)]
        [string]$CorrelationId,
        [Parameter(Mandatory = $false)]
        [int]$WaitTimeoutSeconds,
        [Parameter(Mandatory = $false)]
        [int]$CheckIntervalSeconds = 30
    )
    $asyncOpUrl = $Headers.GetValues("Azure-AsyncOperation")
    $correlationId = $Headers.GetValues("x-ms-correlation-request-id")
    $requestHeaders = Get-RequestHeader -CorrelationId $CorrelationId
    PrintLog "'x-ms-correlation-request-id' of the request: '$correlationId'" DEBUG
    PrintLog "Op URL: $asyncOpUrl" DEBUG

    PrintLog "Querying the operation status for up to $WaitTimeoutSeconds seconds to complete"
    $endTime = (Get-Date).AddSeconds($WaitTimeoutSeconds)
    while ((Get-Date) -lt $endTime) {
        $content = (Invoke-AzureRestCallWithVerificationAndRetries -Method 'GET' -Uri "$asyncOpUrl" -RequestHeaders $requestHeaders).Content
        $opStatus = $content.status
        PrintLog "Operation status: '$opStatus'" DEBUG
        if ($opStatus -eq "Succeeded") {
            return ""
        }
        elseif ($opStatus -ne "Accepted" -And $opStatus -ne "Deleting" -And $opStatus -ne "Pending" -And $opStatus -ne "Running") {
            $msg = "OP status for '$correlationId' correlation ID: '$opStatus'"
            PrintLog $msg ERROR
            if ($opStatus -eq "Failed") {
                $errorMessage = "{0}" -f $content.error.message
                PrintLog "Error message: $errorMessage" ERROR
                return $errorMessage
            }
            $msg = "Unexpected OP status"
            PrintLog $msg ERROR
            return $msg
        }
        Start-Sleep -Seconds $CheckIntervalSeconds
    }
    $msg = "Timed out waiting for successful operation status, correlation ID: '$correlationId'"
    PrintLog $msg ERROR
    return $msg
}

function Get-RequestHeader {
    param (
        [Parameter(Mandatory = $false)]
        [string]$CorrelationId
    )
    $requestHeaders = @{
        'Content-Type' = 'application/json; charset=utf-8'
    }

    if ($CorrelationId -and $CorrelationId -ne '') {
        $requestHeaders['x-krypton-correlation-id'] = $CorrelationId
    }

    return $requestHeaders
}

function Invoke-EnableAvsConnection {
    param (
        [Parameter(Mandatory = $true)]
        [string]$StoragePoolResourceId,
        [Parameter(Mandatory = $false)]
        [string]$CorrelationId,
        [Parameter(Mandatory = $true)]
        [string]$PureStorageBlockApiVersion
    )
    if ($null -ne $SddcResourceId) {
        PrintLog "SDDC resource ID: $SddcResourceId" DEBUG
        $payload = @{
            'sddcResourceId' = $SddcResourceId
        }
    } else {
        throw "SDDC resource ID is empty"
    }

    $requestHeaders = Get-RequestHeader -CorrelationId $CorrelationId
    $uri = $DefaultManagementHostUri + $StoragePoolResourceId + "/enableAvsConnection?api-version=$PureStorageBlockApiVersion"

    $res = Invoke-AzureRestCallWithVerificationAndRetries -Method 'POST' -Uri $uri -PayLoad $payload -RequestHeaders $requestHeaders
    if ($null -eq $res) {
        $msg =  "Azure rest call failed. No response received."
        PrintLog $msg ERROR
        return $msg
    }

    return WaitForSuccessStatus -Headers $res.Headers -WaitTimeoutSeconds $WaitTimeSeconds -CorrelationId $CorrelationId
}

function Get-AvsConnection {
    param (
        [Parameter(Mandatory = $true)]
        [string]$StoragePoolResourceId,
        [Parameter(Mandatory = $false)]
        [string]$CorrelationId,
        [Parameter(Mandatory = $true)]
        [string]$PureStorageBlockApiVersion
    )
    $requestHeaders = Get-RequestHeader -CorrelationId $CorrelationId
    $uri = $DefaultManagementHostUri + $StoragePoolResourceId + "/getAvsConnection?api-version=$PureStorageBlockApiVersion"

    return Invoke-AzureRestCallWithVerificationAndRetries -Method 'POST' -Uri $uri -RequestHeaders $requestHeaders `
        | Select-Object -ExpandProperty Content
}

function Invoke-FinalizeAvsConnection {
    param (
        [Parameter(Mandatory = $true)]
        [hashtable]$ServiceInitializationData,
        [Parameter(Mandatory = $true)]
        [string]$StoragePoolResourceId,
        [Parameter(Mandatory = $false)]
        [string]$CorrelationId,
        [Parameter(Mandatory = $true)]
        [string]$PureStorageBlockApiVersion
    )

    $payload = @{
        'serviceInitializationDataEnc' = ""
        'serviceInitializationData' = $ServiceInitializationData
    }

    $requestHeaders = Get-RequestHeader -CorrelationId $CorrelationId
    $uri = $DefaultManagementHostUri + $StoragePoolResourceId + "/finalizeAvsConnection?api-version=$PureStorageBlockApiVersion"

    $res = Invoke-AzureRestCallWithVerificationAndRetries -Method 'POST' -Uri $uri -PayLoad $payload -RequestHeaders $requestHeaders
    if ($null -eq $res) {
        $msg =  "Azure rest call failed. No response received."
        PrintLog $msg ERROR
        return $msg
    }

    return WaitForSuccessStatus -Headers $res.Headers -WaitTimeoutSeconds $WaitTimeSeconds -CorrelationId $CorrelationId
}

function Invoke-DisableAvsConnection {
    param (
        [Parameter(Mandatory = $true)]
        [string]$StoragePoolResourceId,
        [Parameter(Mandatory = $false)]
        [string]$CorrelationId,
        [Parameter(Mandatory = $true)]
        [string]$PureStorageBlockApiVersion
    )

    $requestHeaders = Get-RequestHeader -CorrelationId $CorrelationId
    $uri = $DefaultManagementHostUri + $StoragePoolResourceId + "/disableAvsConnection?api-version=$PureStorageBlockApiVersion"

    $res = Invoke-AzureRestCallWithVerificationAndRetries -Method 'POST' -Uri $uri -RequestHeaders $requestHeaders
    if ($null -eq $res) {
        $msg =  "Azure rest call failed. No response received."
        PrintLog $msg ERROR
        return $msg
    }

    return WaitForSuccessStatus -Headers $res.Headers -WaitTimeoutSeconds $WaitTimeSeconds -CorrelationId $CorrelationId
}

# SIG # Begin signature block
# MIIuvwYJKoZIhvcNAQcCoIIusDCCLqwCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDryc9iQbjLm1ba
# uMkTIBqvrW4yGJEdWypGRuk5UQPlNKCCE2gwggVyMIIDWqADAgECAhB2U/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
# S7IxghqtMIIaqQIBATBpMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxT
# aWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25p
# bmcgQ0EgMjAyMAIMXLlutZE5L/uLfM4EMA0GCWCGSAFlAwQCAQUAoIGcMBkGCSqG
# SIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3
# AgEVMC8GCSqGSIb3DQEJBDEiBCDdFp5B5Ft5IO0iveLTaBPaTJ9Tt49YtVXLRBu7
# sufjXTAwBgorBgEEAYI3AgEMMSIwIKACgAChGoAYaHR0cHM6Ly9wdXJlc3RvcmFn
# ZS5jb20gMA0GCSqGSIb3DQEBAQUABIICAJ9kk/CzJZ0ADhxE8CF6xWgGSiRiUBKn
# pKzY2iXxFXbzLPjbVP0Eh3OG+guwwF6xl3Wys9fVemSGQZ5TDjEZG+1sEgMn9SQD
# 7708cx7gqr4ua3oX1CrAyXQY5AHlvhlrNgUCOIIGO8HWdoyVdALrnlPVXjvcSUMy
# 7wsMLtZoiKU6ZTuoZLgM2ZUV8WeBbBMWNWlE4I+QXShf4OqqjOLV5BHLHt8Z4d7e
# UnY3bkWIvm4q0rdWEfPy9r5Auqd/ihIy6cKWYvKfAEulV7PHmKF2Lxc/fdFNDbvF
# faZGtYlVn5UfsgJIY9vBR80BwVLzRkOICQn3r0CSzL3TcIckt1Buq34buEHgwztN
# wg+ykGX+W+iBKDme+RfrLUpQMMbZMHefqyV5YgonVubJ2OPL0Tc8PbFGvlCpUFIe
# lMHFRyQqw0TrzypqGGkRjQNuOfszlxkwZpZiPlKyMfi4HT6UJ1z2wBXKiJyzskQS
# 4NUyAJxrTSaJeVAQrZEF2Miyvu0/vMBkzWJv33Yk2xr4rrgFgXwhtM5odzR8n+8I
# tjy+dNFVx20XjZF2eDDs61GFTgUIxc1Os94sT8BuyxZrd6iw+59joySM1yVlEiji
# Uq/gqDTq0za9+9Cb7FJlcp/Yo9o4CxUIL9VMHAN0/596PNLW+FYMWQ7c4YUSL2R4
# xVPws8knPa3soYIXdjCCF3IGCisGAQQBgjcDAwExghdiMIIXXgYJKoZIhvcNAQcC
# oIIXTzCCF0sCAQMxDzANBglghkgBZQMEAgEFADB3BgsqhkiG9w0BCRABBKBoBGYw
# ZAIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIH9/exe2t3pS3p95HaT1
# 09R5JxKy2UTvBJHjerr0jpLDAhA1uw7DC5/VccrJPTwqGXJsGA8yMDI1MDYxMjE5
# MjkyOVqgghM6MIIG7TCCBNWgAwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG
# 9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x
# QTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQw
# OTYgU0hBMjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1
# OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYD
# VQQDEzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVy
# IDIwMjUgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q
# 6q5gVrMrV7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPn
# Z8ZN+vo8dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSss
# p3qome7MrxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09
# ldQ//nBZZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98ok
# souTMYFOnHoRh6+86Ltc5zjPKHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+
# 3qU8DD+nigNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsn
# qcnpJeItK/DhKbPxTTuGoX7wJNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQ
# PdP51ho1zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbS
# LZ49kPmk8iyyizNDIXj//cOgrY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojT
# dS5PWPsWeupWs7NpChUk555K096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoK
# RR7YufAkprxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8E
# AjAAMB0GA1UdDgQWBBTkO/zyMe39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTv
# b1NK6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAww
# CgYIKwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRw
# Oi8vb2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQw
# OTZTSEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0
# MDk2U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZI
# AYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk
# 9iJP2zWLpQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tsh
# gb4O6Lgjg8K8elC4+oWCqnU/ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9m
# zskgiC3QYIUP2S3HQvHG1FDu+WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQ
# BHMUBaB5bdrPbF6MRYs03h4obEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+
# YkDFkxUGtMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0c
# Ksb+zJNEsuEB7O7/cuvTQasnM9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY
# 7T/lwd6UArb+BOVAkg2oOvol/DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcboj
# BcxlRcGG0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05o
# xYy2zRWVFjF7mcr4C34Mj3ocCVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskK
# PIJgbaP5t2nGj/ULLi49xTcBZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjd
# NXOCIUjsarfNZzCCBrQwggScoAMCAQICEA3HrFcF/yGZLkBDIgw6SYYwDQYJKoZI
# hvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
# MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1
# c3RlZCBSb290IEc0MB4XDTI1MDUwNzAwMDAwMFoXDTM4MDExNDIzNTk1OVowaTEL
# MAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhE
# aWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAy
# MDI1IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALR4MdMKmEFy
# vjxGwBysddujRmh0tFEXnU2tjQ2UtZmWgyxU7UNqEY81FzJsQqr5G7A6c+Gh/qm8
# Xi4aPCOo2N8S9SLrC6Kbltqn7SWCWgzbNfiR+2fkHUiljNOqnIVD/gG3SYDEAd4d
# g2dDGpeZGKe+42DFUF0mR/vtLa4+gKPsYfwEu7EEbkC9+0F2w4QJLVSTEG8yAR2C
# QWIM1iI5PHg62IVwxKSpO0XaF9DPfNBKS7Zazch8NF5vp7eaZ2CVNxpqumzTCNSO
# xm+SAWSuIr21Qomb+zzQWKhxKTVVgtmUPAW35xUUFREmDrMxSNlr/NsJyUXzdtFU
# Ut4aS4CEeIY8y9IaaGBpPNXKFifinT7zL2gdFpBP9qh8SdLnEut/GcalNeJQ55Iu
# wnKCgs+nrpuQNfVmUB5KlCX3ZA4x5HHKS+rqBvKWxdCyQEEGcbLe1b8Aw4wJkhU1
# JrPsFfxW1gaou30yZ46t4Y9F20HHfIY4/6vHespYMQmUiote8ladjS/nJ0+k6Mvq
# zfpzPDOy5y6gqztiT96Fv/9bH7mQyogxG9QEPHrPV6/7umw052AkyiLA6tQbZl1K
# hBtTasySkuJDpsZGKdlsjg4u70EwgWbVRSX1Wd4+zoFpp4Ra+MlKM2baoD6x0VR4
# RjSpWM8o5a6D8bpfm4CLKczsG7ZrIGNTAgMBAAGjggFdMIIBWTASBgNVHRMBAf8E
# CDAGAQH/AgEAMB0GA1UdDgQWBBTvb1NK6eQGfHrK4pBW9i/USezLTjAfBgNVHSME
# GDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0l
# BAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRw
# Oi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8
# MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0
# ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAN
# BgkqhkiG9w0BAQsFAAOCAgEAF877FoAc/gc9EXZxML2+C8i1NKZ/zdCHxYgaMH9P
# w5tcBnPw6O6FTGNpoV2V4wzSUGvI9NAzaoQk97frPBtIj+ZLzdp+yXdhOP4hCFAT
# uNT+ReOPK0mCefSG+tXqGpYZ3essBS3q8nL2UwM+NMvEuBd/2vmdYxDCvwzJv2sR
# UoKEfJ+nN57mQfQXwcAEGCvRR2qKtntujB71WPYAgwPyWLKu6RnaID/B0ba2H3LU
# iwDRAXx1Neq9ydOal95CHfmTnM4I+ZI2rVQfjXQA1WSjjf4J2a7jLzWGNqNX+DF0
# SQzHU0pTi4dBwp9nEC8EAqoxW6q17r0z0noDjs6+BFo+z7bKSBwZXTRNivYuve3L
# 2oiKNqetRHdqfMTCW/NmKLJ9M+MtucVGyOxiDf06VXxyKkOirv6o02OoXN4bFzK0
# vlNMsvhlqgF2puE6FndlENSmE+9JGYxOGLS/D284NHNboDGcmWXfwXRy4kbu4QFh
# Om0xJuF2EZAOk5eCkhSxZON3rGlHqhpB/8MluDezooIs8CVnrpHMiD2wL40mm53+
# /j7tFaxYKIqL0Q4ssd8xHZnIn/7GELH3IdvG2XlM9q7WP/UwgOkw/HQtyRN62JK4
# S1C8uw3PdBunvAZapsiI5YKdvlarEvf8EA+8hcpSM9LHJmyrxaFtoza2zNaQ9k+5
# t1wwggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUA
# MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT
# EHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQg
# Um9vdCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5NTlaMGIxCzAJBgNV
# BAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdp
# Y2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIw
# DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIwaTPswqcl
# LskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLKEdLkX9YF
# PFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4TmdDttceIt
# DBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembud8hIqGZX
# V59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1
# ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1XXhm2Tox
# RJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVldQnaHiZdp
# ekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF
# 30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSmM9GJB+G9
# t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzTQRESW+UQ
# UOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6KxfgommfXk
# aS+YHS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
# DgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEt
# UYunpyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUHAQEEbTBrMCQGCCsG
# AQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0
# dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RD
# QS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAECjAIMAYGBFUdIAAw
# DQYJKoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX979XB72arKGHLOyF
# XqkauyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offyct4kvFIDyE7QKt76
# LVbP+fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3J0TU53/oWajwvy8L
# punyNDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0d1pcVIxv76FQPfx2
# CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6tsds5vIy30fnFqI2si
# /xK4VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQxggN8MIIDeAIBATB9
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEy
# NTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZIAWUDBAIBBQCggdEw
# GgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA2
# MTIxOTI5MjlaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFN1iMKyGCi0wa9o4sWh5
# UjAH+0F+MC8GCSqGSIb3DQEJBDEiBCAUgMt2DPCXd7Mt5fVvbp4Y8OA1dS4ZY7NL
# ahM8RCGkWjA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCBKoD+iLNdchMVck4+Cjmdr
# nK7Ksz/jbSaaozTxRhEKMzANBgkqhkiG9w0BAQEFAASCAgCNCHI/Q9HC2Q9ALw0d
# POBzjNqr+xr/yUQIYlnATUfk7biWWK0oOzBYoRbKp6U+TzmIHpcXEsRNUtU0/XAT
# w/WaM2s1e6Eo5b2L9PDm2tcxsp1BsXARQd58C+HGVAvHVRaJ7kVQZVyHMNfAeIYe
# NGTl66jzwycvZJpQ3BAFFfh7dXuxd+7HETn+bNgOexMZJz/b2D2gwfJrrBdYNOqE
# 6GlgIv8GA+apcg4ze9pj4AIuuUxaO64061nxoAFOOjVEST74ufd15hj+88N5zubt
# upFBlXKC4mzsjdqyMoJ9vEntz3qtnOG0xcXDlSVzwYytdnARLIT221Y3kAj6kmuR
# wUSZHOkv/QVW0I3LOLLe83RLfGKVlzASwjud5k6ILCCW3Q/eqZRrXDIAQj9yv/MS
# 2wEsKFhqTA+O6tch3Z9cM8fu+We8MmPqx4dNYpHCuCqphaSnkDfI+nggte5b87Hp
# B2Y+/lXI8t0CCZE7J4h0qhP/WYhbLiI+rKeD+hV+HxC/JQHqL3qSHZg+zxrBFVzX
# EoteCOCJ8H/+5FZCFBcKPThZRoIAyluuUgiGHfwAQaMl4lWhXx/hat5hkKpVwWxb
# Q3lo3qUDsJ8zIQWk9MX8kbcXioWq3wWURodKfz94TRyxSmrxGnInmfIyLMh95LXM
# xFfjspumAe3/sB8zN1JXPJ+rog==
# SIG # End signature block