Microsoft.AVS.Management.psm1

#Requires -Modules PowerShellGet
#Requires -Version 5.0


<#
Applied to a commandlet function indicates that the SDDC should be marked as Building while the function executes.
AVS SDDC in Building state prevents other changes from being made to the SDDC until the function completes/fails.
#>

Class AVSUpdatesSDDC : Attribute {}

<#
=======================================================================================================
    AUTHOR: David Becher
    DATE: 4/22/2021
    Version: 1.0.0
    Comment: Cmdlets for various administrative functions of Azure VMWare Solution products
    Callouts: This script will require the powershell session running it to be able to authenticate to azure to pull secrets from key vault, will need service principal? Also make sure we don't allow code injections
========================================================================================================
#>


# Exported Functions
<#
    .Synopsis
     Allow customers to add an external identity source (Active Directory over LDAP) for use with single sign on to vCenter. Prefaced by Connect-SsoAdminServer
 
    .Parameter Name
     The user-friendly name the external AD will be given in vCenter
 
    .Parameter DomainName
     Domain name of the external active directory, e.g. myactivedirectory.local
 
    .Parameter DomainAlias
     Domain alias of the external active directory, e.g. myactivedirectory
 
    .Example
    # Add the domain server named "myserver.local" to vCenter
    Add-AvsLDAPIdentitySource -Name 'myserver' -DomainName 'myserver.local' -DomainAlias 'myserver' -PrimaryUrl 'ldap://10.40.0.5:389' -BaseDNUsers 'dc=myserver, dc=local' -BaseDNGroups 'dc=myserver, dc=local' -Username 'myserver@myserver.local' -Password 'PlaceholderPassword'
#>

function New-AvsLDAPIdentitySource {
    [CmdletBinding(PositionalBinding = $false)]
    [AVSUpdatesSDDC()]
    Param
    (
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'User-Friendly name to store in vCenter')]
        [ValidateNotNull()]
        [string]
        $Name,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Full DomainName: adserver.local')]
        [ValidateNotNull()]
        [string]
        $DomainName,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'DomainAlias: adserver')]
        [string]
        $DomainAlias,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'URL of your AD Server: ldaps://yourserver:636')]
        [ValidateScript( {
                $_ -match 'ldap:.*((389)|(636)|(3268)(3269))'
            })]
        [string]
        $PrimaryUrl,

        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Optional: URL of a backup server')]
        [ValidateScript( {
                $_ -match 'ldap:.*((389)|(636)|(3268)(3269))'
            })]
        [string]
        $SecondaryUrl,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'BaseDNGroups, "DC=name, DC=name"')]
        [ValidateNotNull()]
        [string]
        $BaseDNUsers,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'BaseDNGroups, "DC=name, DC=name"')]
        [ValidateNotNull()]
        [string]
        $BaseDNGroups,

        [Parameter(Mandatory = $true,
            HelpMessage = "Credential for the LDAP server")]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential
    )
    $Password=$Credential.GetNetworkCredential().Password
    $ExternalSource = 
    Add-LDAPIdentitySource `
        -Name $Name `
        -DomainName $DomainName `
        -DomainAlias $DomainAlias `
        -PrimaryUrl $PrimaryUrl `
        -SecondaryUrl $SecondaryUrl `
        -BaseDNUsers $BaseDNUsers `
        -BaseDNGroups $BaseDNGroups `
        -Username $Credential.UserName `
        -Password $Password `
        -ServerType 'ActiveDirectory' -ErrorAction Stop
    Write-Verbose "PowerCLI Result: $ExternalSource"
    return (Get-IdentitySource -External -ErrorAction Continue)
}

<#
    .Synopsis
     Allow customers to add an LDAPS Secure external identity source (Active Directory over LDAP) for use with single sign on to vCenter. Prefaced by Connect-SsoAdminServer
 
    .Parameter Name
     The user-friendly name the external AD will be given in vCenter
 
    .Parameter DomainName
     Domain name of the external active directory, e.g. myactivedirectory.local
 
    .Parameter DomainAlias
     Domain alias of the external active directory, e.g. myactivedirectory
 
    .Parameter CertificatesSAS
     An array of Shared Access Signature strings to the certificates required to connect to the external active directory, if using LDAPS
 
    .Example
    # Add the domain server named "myserver.local" to vCenter
    Add-AvsLDAPSIdentitySource -Name 'myserver' -DomainName 'myserver.local' -DomainAlias 'myserver' -PrimaryUrl 'ldaps://10.40.0.5:636' -BaseDNUsers 'dc=myserver, dc=local' -BaseDNGroups 'dc=myserver, dc=local' -Username 'myserver@myserver.local' -Password 'PlaceholderPassword' -CertificatesSAS 'https://sharedaccessstring.path/accesskey' -Protocol LDAPS
#>

function New-AvsLDAPSIdentitySource {
    [CmdletBinding(PositionalBinding = $false)]
    [AVSUpdatesSDDC()]
    Param
    (
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'User-Friendly name to store in vCenter')]
        [ValidateNotNull()]
        [string]
        $Name,
  
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Full DomainName: adserver.local')]
        [ValidateNotNull()]
        [string]
        $DomainName,
  
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'DomainAlias: adserver')]
        [string]
        $DomainAlias,
  
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'URL of your AD Server: ldaps://yourserver:636')]
        [ValidateScript( {
                $_ -match 'ldaps:.*((389)|(636)|(3268)(3269))'
            })]
        [string]
        $PrimaryUrl,
  
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Optional: URL of a backup server')]
        [ValidateScript( {
                $_ -match 'ldaps:.*((389)|(636)|(3268)(3269))'
            })]
        [string]
        $SecondaryUrl,
  
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'BaseDNGroups, "DC=name, DC=name"')]
        [ValidateNotNull()]
        [string]
        $BaseDNUsers,
  
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'BaseDNGroups, "DC=name, DC=name"')]
        [ValidateNotNull()]
        [string]
        $BaseDNGroups,
  
        [Parameter(Mandatory = $true,
            HelpMessage = "Credential for the LDAP server")]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,
  
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Array of SAS path URI to Certificates for authentication. Ensure permissions to read included. For how to generate see <Insert Helpful Link>')]
        [string[]]
        $CertificatesSAS
    )
    $Password=$Credential.GetNetworkCredential().Password
    if ($CertificatesSAS.count -eq 0) {
        Write-Error "If adding an LDAPS identity source, please ensure you pass in at least one certificate" -ErrorAction Stop
        return "Failed to add LDAPS source"
    }
    $DestinationFileArray = @()
    $Index = 1
    foreach ($CertSas in $CertificatesSAS) {
        Write-Host "Downloading Cert $Index"
        $CertDir = $pwd.Path
        $CertLocation = "$CertDir/cert$Index.cer"
        $Index = $Index + 1
        try {
            $Response = Invoke-WebRequest -Uri $CertSas -OutFile $CertLocation
            Write-Verbose -Message "Following lines will only execute if the download was successful"
            $StatusCode = $Response.StatusCode
            Write-Host("Certificate downloaded. $StatusCode")
            $DestinationFileArray += $CertLocation
        }
        catch {
            Write-Verbose "Stack Trace: $($PSItem.Exception.StackTrace)"
            Write-Verbose "InnerException: $($PSItem.Exception.InnerException)" 
            Write-Warning "Ensure the SAS string is still valid"
            Write-Error $PSItem.Exception.Message -ErrorAction Stop
            return "Failed to download certificate ($Index-1)"
        }
    }
    Write-Verbose "Certificates: $DestinationFileArray"
    Write-Host "Adding the LDAPS Identity Source..."
    $ExternalSource = 
    Add-LDAPIdentitySource `
        -Name $Name `
        -DomainName $DomainName `
        -DomainAlias $DomainAlias `
        -PrimaryUrl $PrimaryUrl `
        -SecondaryUrl $SecondaryUrl `
        -BaseDNUsers $BaseDNUsers `
        -BaseDNGroups $BaseDNGroups `
        -Username $Credential.UserName `
        -Password $Password `
        -ServerType 'ActiveDirectory' `
        -Certificates $DestinationFileArray -ErrorAction Stop
    Write-Verbose "PowerCLI Result: $ExternalSource"
    return (Get-IdentitySource -External -ErrorAction Continue)
}

<#
    .Synopsis
     Creates a Drs Cluster Host Group, a Drs Cluster VM Group, and a Drs Cluster Virtual Machine to Host Rule between the two
 
    .Example
    # Create a should run rule named MyDrsRule on Cluster-1 Hosts using the listed VM's and VMHosts
    New-AvsDrsElevationRule -DrsGroupName "MyDrsGroup" -DrsRuleName "MyDrsRule" -Cluster "Cluster-1" -VMList "vm1", "vm2" -VMHostList "esx01", "esx02"
#>

function New-AvsDrsElevationRule {
    [CmdletBinding(PositionalBinding = $false)]
    [AVSUpdatesSDDC()]
    Param
    (
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'User-Friendly name of the Drs rule to create')]
        [ValidateNotNullOrEmpty()]
        [string]
        $DrsRuleName,
    
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'User-Friendly name of the Drs group to create')]
        [ValidateNotNullOrEmpty()]
        [string]
        $DrsGroupName,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Cluster to create the rule and group on')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Cluster,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'List of the VMs to add to the VM group')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $VMList,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'List of the VMHosts to add to the VMHost group')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $VMHostList
    )
    $ErrorActionPreference = "Stop"

    $DrsVmHostGroupName = $DrsGroupName + "Host"
    Write-Host "Creating DRS Cluster group $DrsGroupName for the VMs $VMList"
    New-DrsClusterGroup -Name $DrsGroupName -VM $VMList -Cluster $Cluster -ErrorAction Stop
    Write-Host "Creating DRS Cluster group $DrsVmHostGroupName for the VMHosts: $VMHostList"
    New-DrsClusterGroup -Name $DrsVmHostGroupName -VMHost $VMHostList -Cluster $Cluster -ErrorAction Stop
    Write-Host "Creating ShouldRunOn DRS Rule $DrsRuleName on cluster $Cluster"
    Write-Verbose "New-DrsVMHostRule -Name $DrsRuleName -Cluster $Cluster -VMGroup $DrsGroupName -VMHostGroup $DrsVmHostGroupName -Type 'ShouldRunOn'"
    $result = New-DrsVMHostRule -Name $DrsRuleName -Cluster $Cluster -VMGroup $DrsGroupName -VMHostGroup $DrsVmHostGroupName -Type "ShouldRunOn" -ErrorAction Stop
    $currentRule = Get-DrsVMHostRule -Type "ShouldRunOn" -ErrorAction Continue
    Write-Output $currentRule
    return $result 
}

<#
    .Synopsis
     Edits a VM Drs Cluster Group
 
    .Example
    # Edit an existing drs group named "MyDrsGroup" on Cluster-1 Hosts adding the listed VM's '
    Set-AvsDrsVMClusterGroup -DrsGroupName "MyDrsGroup" -Cluster "Cluster-1" -VMList "vm1", "vm2" -Action "add"
#>

function Set-AvsDrsVMClusterGroup {
    [CmdletBinding(PositionalBinding = $false)]
    [AVSUpdatesSDDC()]
    Param
    (   
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Name of the Drs group to edit')]
        [ValidateNotNullOrEmpty()]
        [string]
        $DrsGroupName,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'List of the VMs to add to the VM group')]
        [string[]]
        $VMList,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Action to perform: Either "add" or "remove"')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Action
    )
    [string] $groupType = (Get-DrsClusterGroup -Name $DrsGroupName).GroupType.ToString()
    Write-Verbose "The group type for $DrsGroupName is $groupType"
    If ($groupType -eq "VMHostGroup") {
        Get-DrsClusterGroup
        Write-Warning "$DrsGroupName is a $groupType and cannot be modified with VMHosts. Please validate that you're using the correct cmdlet. Did you mean Set-AvsDrsVMHostClusterGroup?"
        return 
    }

    If ($Action -eq "add") {
        Write-Host "Adding VMs to the DrsClusterGroup..."
        $result = Set-DrsClusterGroup -DrsClusterGroup $DrsGroupName -VM $VMList -Add -ErrorAction Stop
    }
    ElseIf ($Action -eq "remove") {
        Write-Host "Removing VMs from the DrsClusterGroup..."
        $result = Set-DrsClusterGroup -DrsClusterGroup $DrsGroupName -VM $VMList -Remove -ErrorAction Stop
    }
    Else {
        $result = Write-Warning "Nothing done. Please select with either -Action Add or -Action Remove"
    }
    return $result
}

<#
    .Synopsis
     Edits a VMHost Drs Cluster Group
 
    .Example
    # Edit an existing drs group named "MyDrsGroup" on Cluster-1 Hosts removing the listed VM Hosts '
    Set-AvsDrsClusterGroup -DrsGroupName "MyDrsGroup" -Cluster "Cluster-1" -VMHostList "vmHost1", "vmHost2" -Action "remove"
#>

function Set-AvsDrsVMHostClusterGroup {
    [CmdletBinding(PositionalBinding = $false)]
    [AVSUpdatesSDDC()]
    Param
    (   
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Name of the Drs group to edit')]
        [ValidateNotNullOrEmpty()]
        [string]
        $DrsGroupName,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'List of the VMHosts to add to the VMHost group')]
        [string[]]
        $VMHostList,

        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Action to perform: Either "add" or "remove"')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Action
    )
    [string] $groupType = (Get-DrsClusterGroup -Name $DrsGroupName).GroupType.ToString()
    Write-Verbose "The group type for $DrsGroupName is $groupType"
    If ($groupType -eq "VMGroup") {
        Get-DrsClusterGroup
        Write-Warning "$DrsGroupName is a $groupType and cannot be modified with VMHosts. Please validate that you're using the correct cmdlet. Did you mean Set-AvsDrsVMClusterGroup?"
        return 
    }

    If ($Action -eq "add") {
        Write-Host "Adding VMHosts to the DrsClusterGroup..."
        $result = Set-DrsClusterGroup -DrsClusterGroup $DrsGroupName -VMHost $VMHostList -Add -ErrorAction Stop
    }
    ElseIf ($Action -eq "remove") {
        Write-Host "Removing VMHosts from the DrsClusterGroup..."
        $result = Set-DrsClusterGroup -DrsClusterGroup $DrsGroupName -VMHost $VMHostList -Remove -ErrorAction Stop
    }
    Else {
        $result = Write-Warning "Nothing done. Please select with either -Action Add or -Action Remove"
    }
    return $result
}

<#
    .Synopsis
     Edits a Drs Elevation Rule. Allowed operations are enable/disable and renaming.
 
    .Example
    # Enable and change the name of a drs rule named "myDrsRule"
    Set-AvsDrsElevationRule -DrsRuleName "myDrsRule" -Enabled $true -NewName "mynewDrsRule"
#>

function Set-AvsDrsElevationRule {
    [CmdletBinding(PositionalBinding = $false)]
    [AVSUpdatesSDDC()]
    Param
    (   
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Name of the Drs rule to edit')]
        [ValidateNotNullOrEmpty()]
        [string]
        $DrsRuleName,
  
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'Enabled switch: $true or $false')]
        [Nullable[boolean]]
        $Enabled,
  
        [Parameter(
            Mandatory = $false,
            HelpMessage = 'New name for the Drs rule')]
        [ValidateNotNullOrEmpty()]
        [string]
        $NewName
    )
    Write-Verbose "Enabled is ne null: ($null -ne $Enabled)"
    if (($null -ne $Enabled) -And $NewName) {
        Write-Host "Changing enabled flag to $Enabled and Name to $NewName"
        Write-Verbose "Set-DrsVMHostRule -Rule $DrsRuleName -Enabled $Enabled -Name $NewName"
        Set-DrsVMHostRule -Rule $DrsRuleName -Enabled $Enabled -Name $NewName -ErrorAction Stop
    }
    ElseIf ($null -ne $Enabled) {
        Write-Host "Changing the enabled flag for $DrsRuleName to $Enabled"
        Write-Verbose "Set-DrsVMHostRule -Rule $DrsRuleName -Enabled $Enabled"
        Set-DrsVMHostRule -Rule $DrsRuleName -Enabled $Enabled -ErrorAction Stop
    }
    ElseIf ($Name) {
        Write-Host "Renaming $DrsRuleName to $NewName"
        Write-Verbose "Set-DrsVMHostRule -Rule $DrsRuleName -Name $NewName"
        Set-DrsVMHostRule -Rule $DrsRuleName -Name $NewName -ErrorAction Stop
    }
    Else {
        Write-Output "Nothing done."
    }
    return
}
  
<#
    .Synopsis
     Edit the storage policy on the VM to a predefined storage policy
 
    .Example
    # Create a should run rule named MyDrsRule on Cluster-1 Hosts using the listed VM's and VMHosts
    Set-AvsVMStoragePolicy -StoragePolicyName "RAID-1 FTT-1" -VMName "EVM02-TNT79"
#>

function Set-AvsVMStoragePolicy {
    [CmdletBinding(PositionalBinding = $false)]
    [AVSUpdatesSDDC()]
    Param
    (
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Name of the storage policy to set')]
        [ValidateNotNullOrEmpty()]
        [string]
        $StoragePolicyName,
  
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'Name of the VM to set the storage policy on')]
        [ValidateNotNullOrEmpty()]
        [string]
        $VMName
    )
    $storagepolicy = Get-SpbmStoragePolicy -Name $StoragePolicyName -ErrorAction Stop
    $result = Set-VM $VMName -StoragePolicy $storagepolicy -SkipHardDisks -ErrorAction Stop -Confirm:$false
    return $result
}

Export-ModuleMember -Function *
# SIG # Begin signature block
# MIIj3QYJKoZIhvcNAQcCoIIjzjCCI8oCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC9MRKCIxvlm3lx
# FfltAjpY9/oBgHRlredd8SdWrbcNuaCCDYEwggX/MIID56ADAgECAhMzAAAB32vw
# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn
# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw
# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS
# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG
# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh
# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH
# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS
# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp
# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok
# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4
# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao
# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD
# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt
# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G
# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+
# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82
# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVsjCCFa4CAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN
# BglghkgBZQMEAgEFAKCB7zAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg2anPhAM2
# bq4KK+53yoXwPDxrgYZo2mJRkaZ3fbXqjigwgYIGCisGAQQBgjcCAQwxdDByoDSA
# MgBBAFYAUwAtAEEAdQB0AG8AbQBhAHQAaQBvAG4ALQBBAGQAbQBpAG4AVABvAG8A
# bABzoTqAOGh0dHBzOi8vZ2l0aHViLmNvbS9BenVyZS9henVyZS1hdnMtYXV0b21h
# dGlvbi1hZG1pbnRvb2xzMA0GCSqGSIb3DQEBAQUABIIBAAXbcykEt9h76bSW3nvE
# 6XA48Ei/OqB/0qsdH31Da3u/Ap9cFGyYotE9st/Ar3EOHZC97VHEiqjh6S9ZX2o0
# DDdBatOSp7VY1Vz5djXGUoEv9CJYAufzHptO/nHRpKHRnjPxEbm6bIWFMHSL1Kbk
# Hmxlff1UOWqd4vbQShVEFf5lO9ymb4MeRyV6s90wh0Av1862NkwIrPqqd7Yyu2xP
# N9lUinnn1DP+lTO8kcNtOVKX5gf9vPPs5frP3LRjOWq4GKspmvUz3IBTsagCP5hf
# zbUCcnAm+h3di4Mga2i/Ojg8I09/AETbyMLwe/qOBmr1UoGC0REtl2PqKqByMTNy
# +BuhghL7MIIS9wYKKwYBBAGCNwMDATGCEucwghLjBgkqhkiG9w0BBwKgghLUMIIS
# 0AIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBWQYLKoZIhvcNAQkQAQSgggFIBIIBRDCC
# AUACAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgMKFvKhvpwxB9tnLL
# tpfywmdLZRXZkDYcvIO5dxFagL8CBmCK+c5mDxgTMjAyMTA1MjYwMDU1NDkuNTgy
# WjAEgAIB9KCB2KSB1TCB0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1p
# dGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4NkRGLTRCQkMtOTMzNTElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCDkowggT5MIID4aAD
# AgECAhMzAAABPs7Kd1LF9zQrAAAAAAE+MA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTIwMTAxNTE3MjgyNVoXDTIyMDExMjE3
# MjgyNVowgdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTAr
# BgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQG
# A1UECxMdVGhhbGVzIFRTUyBFU046ODZERi00QkJDLTkzMzUxJTAjBgNVBAMTHE1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQC8VMTIPNl+nCzjTiBILSS3hVLJf+9rHA5+uLz2BB3G99A2+9AB
# F5spHemWofPRkdlb5uYXHIa1OH3PDbQtJ2kxxZgMVzWvM+4m9M0CcOQrJA/5Oqtb
# uP+UOUItuqLy5ujgSpKmQetrRm3XmPav8gkZlu7dBpFjqpgxnHGSTDhjm5sDBXcT
# Wn5M3MWDyfOAn2TAQzjG9kB/02EeEzYr+PHT3bGYrHIV+nRfS1uhj13U7KF0JeXy
# yk6KATfaDzMfXZjY1dN8jjXjUtBT710o4pDtgUXWTCh+4YbDExTQKwOKY4NaCvpU
# VVw0N3a1Bsa5uB18sEYQF+N7Q/Kg45cQ7WbhAgMBAAGjggEbMIIBFzAdBgNVHQ4E
# FgQUk1rznfi70GIta/C1tlQOtoaI/XswHwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xG
# G8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQu
# Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENBXzIwMTAtMDctMDEuY3Js
# MFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3Nv
# ZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYD
# VR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOC
# AQEAprP5EX1an4aSuRWPpxjl2MJ1V6kkXK58AEnWoqUJZeE6hgBwHvDtnHNELhna
# Jjhtz1BT3exrZgPCFDAU96p8pl9ZKSaty6zj1AH0QY9z0XAiB8FArYAm2FpgTKxN
# rBLjR/rJzrD/Jui0ByWoUCv4E8O3TMZmgTG8ZzxmlUBmm9LJdvMYu4q2bwr5HvdU
# LgNSnixEVyTULHwgu9h1hI1io5HKHQbCLe/gdabDoe61p8U50WNopARxKyfRI0t9
# jbmo6qe7oMv40CjvPeoPR4EMhKKVahvl2WUNw41+y731QS06ett2Xb3bIY0jLGKW
# kjxcY2AZxnEo3pWosHEC4qVY5jCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJ
# KoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
# eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
# AoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+
# Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBX
# JoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa
# +YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1
# ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2k
# AcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEE
# AwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQC
# BAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYD
# VR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZF
# aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcw
# AoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJB
# dXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4D
# MIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2Rv
# Y3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABf
# AFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEB
# CwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVm
# yWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4X
# NZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoA
# b0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM
# /2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUK
# loakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHL
# mtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4
# qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6
# h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFm
# MNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9d
# T+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEqGCAtQwggI9
# AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1p
# dGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4NkRGLTRCQkMtOTMzNTElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIa
# AxUAoEwV6PTGMJOMKTWxN1Mpr5PMkNSggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAORXvkowIhgPMjAyMTA1MjYw
# MjE5NTRaGA8yMDIxMDUyNzAyMTk1NFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA
# 5Fe+SgIBADAHAgEAAgID0jAHAgEAAgIROTAKAgUA5FkPygIBADA2BgorBgEEAYRZ
# CgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0G
# CSqGSIb3DQEBBQUAA4GBAAw6sU2K/WlwsRLMbCCoN9uYUaZ14FdEkARrBUEHLhJ5
# d8uUInZk206RwZBypOvtFp/cvfMYsKR70U44hwdHfF5oysCUuV4K+VtQnQ+FCkz0
# UEyQP6KY59aHxULpLE37Re06GRj4T6senIUl6Rg/3bZtQK2gXSC2HtlKK5XgOx5S
# MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMA
# AAE+zsp3UsX3NCsAAAAAAT4wDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJ
# AzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgzogJYl+sfEm9LUaTdvUZ
# H0ssF8eGbIdQR5K0bGAxWbYwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCL
# 686Nqo1O8o5ka63j0deuq3BSPZkKdU66sHB+BDGbEzCBmDCBgKR+MHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABPs7Kd1LF9zQrAAAAAAE+MCIEIF5w
# rF3DrptrFxACQldbXN226uxx5dTN8SQvZxGQsi5SMA0GCSqGSIb3DQEBCwUABIIB
# ADTIO40n+M5zfRpoijlgY3bsyOkAmPVSiVwZ/BzMavTnPagKAAc/e7P6ZEmqjsuc
# f7Zwb22aqnRcnG8zjR5J81lmh66yYd7egGSpe8iuImGqEYelhElZMRp4/c/f3WKe
# IQbVj6FHVkNwmkAhyFfmiuU2i/g/p4WcsT5NLe2b85gtcHxAAMup0eYx+GpsCLm0
# siTnau64gOmKKmFgSL8UZMEbpNBy28IMkfIhcgPA+ftsnclvHAfQMdj+OGDuhLVa
# EqIbjgbGSttccfKdUW8B15BGt3A0hR3atSm9kU0urEdznNZOpZMlsJTq6mlVEWWz
# B2XzMU40OLX/K0EHWqPHdcI=
# SIG # End signature block