ArcHci.psm1

#########################################################################################
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# ARC appliance self-service VM Day 0/2 Operations
#
#########################################################################################

#requires -runasadministrator

using module moc

# Imports Common.psm1 from Moc module
# this also imports $global:defaultworkingDir
Import-Module ((Get-Module "MOC" -ListAvailable | Sort-Object Version -Descending)[0].ModuleBase + "\common.psm1")

$moduleName = "ArcHci"

$global:ArcHciModule = $moduleName

$defaultTokenExpiryDays = 60
$global:kvaCtlFullPath = "C:\Program Files\AksHci\kvactl.exe"
$global:sshPrivateKeyFile = "C:\ProgramData\kva\.ssh\logkey"

New-ModuleEventLog -moduleName $script:moduleName

function Reset-ArcHciConfigurationKey
{
    <#
    .DESCRIPTION
        Resets the ARC HCI module configuration key
    #>

    $global:configurationKeys[$global:ArcHciModule] = "HKLM:SOFTWARE\Microsoft\${global:ArcHciModule}PS";
}

if (!$global:config) {
    $global:config = @{}
}

function Initialize-ArcHciConfiguration
{
    <#
    .DESCRIPTION
        Initialize ARC HCI Configuration
    #>


    Reset-ArcHciConfigurationKey

    if (-Not $global:config.ContainsKey($global:ArcHciModule)) {
        $global:config += @{
            $global:ArcHciModule = @{
            "workingDir" = $global:defaultworkingDir
            };
        }
    }

    if  (-Not (Test-Configuration -moduleName $global:ArcHciModule))
    {
        Set-ConfigurationValue -name "workingDir" -value $global:defaultworkingDir -module $global:ArcHciModule
        Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $global:defaultworkingDir
        Save-Configuration -moduleName $global:ArcHciModule
    }
}

Initialize-ArcHciConfiguration

function Uninitialize-ArcHciConfiguration
{
    <#
    .DESCRIPTION
        Uninitializes ARC HCI Configuration
        Wipes off any existing cached configuration
    #>

    if ($global:config.ContainsKey($global:ArcHciModule)) {
        $global:config.Remove($global:ArcHciModule)
    }
}

function Get-ArcHciConfig
{
    <#
    .DESCRIPTION
        Loads and returns the current ARC HCI configuration.
    #>


    Import-ArcHciConfig
    return $global:config[$global:ArcHciModule]
}

function Get-ArcHciConfigValue
{
    <#
    .DESCRIPTION
        Reads a configuration value from the registry

    .PARAMETER name
        Name of the configuration value
    #>

    param (
        [String] $name
    )

    return Get-ConfigurationValue -name $name -module $global:ArcHciModule
}

function Import-ArcHciConfig
{
    <#
    .DESCRIPTION
        Loads a configuration from persisted storage. If no configuration is present
        then a default configuration can be optionally generated and persisted.
    
    #>



    Reset-ArcHciConfigurationKey
    if  (Test-Configuration -moduleName $global:ArcHciModule)
    {
        Import-Configuration -moduleName $global:ArcHciModule
    }
    else
    {
        throw "ARCHCI doesn't currently have a config file. Please make sure to reload all powershell windows before continuing"
    }
}

function Set-ArcHciConfigValue
{
    <#
    .DESCRIPTION
        Persists a configuration value to the registry

    .PARAMETER name
        Name of the configuration value

    .PARAMETER value
        Value to be persisted
    #>

    param (
        [String] $name,
        [Object] $value
    )
    Reset-ArcHciConfigurationKey
    Set-ConfigurationValue -name $name -value $value -module $global:ArcHciModule
}

function Set-ArcHciConfig
{
    <#
    .DESCRIPTION
        Configures ARCHCI by persisting the specified parameters to the registry.
        Any parameter which is not explictly provided by the user will be defaulted.
    
    .PARAMETER workingDir
        Path to the working directory
    #>


    [CmdletBinding()]
    param (
        [String] $workingDir = $global:defaultworkingDir
    )

    Reset-ArcHciConfigurationKey
    Set-ConfigurationValue -name "workingDir" -value $workingDir -module $global:ArcHciModule
    Save-ConfigurationDirectory -moduleName $global:ArcHciModule -WorkingDir $workingDir
    Save-Configuration -moduleName $global:ArcHciModule
}

function New-MocNetworkSetting {
    <#
    .DESCRIPTION
        Create network settings to be used for the Arc Hci deployment.

    .PARAMETER name
        The name of the vnet

    .PARAMETER vswitchName
        The name of the vswitch

    .PARAMETER MacPoolName
        The name of the mac pool

    .PARAMETER vlanID
        The VLAN ID for the vnet

    .PARAMETER ipaddressprefix
        The address prefix to use for static IP assignment

    .PARAMETER gateway
        The gateway to use when using static IP

    .PARAMETER dnsservers
        The dnsservers to use when using static IP

    .PARAMETER vipPoolStart
        Beginning of the IP address pool

    .PARAMETER vipPoolEnd
        End of the IP address pool
    
    .PARAMETER k8snodeippoolstart
        The starting ip address to use for VM's in the cluster.

    .PARAMETER k8snodeippoolend
        The ending ip address to use for VM's in the cluster.

    .OUTPUTS
        VirtualNetwork object
    .EXAMPLE
        New-MocNetworkSetting -name "External" -vipPoolStart "172.16.0.240" -vipPoolEnd "172.16.0.250"
    #>


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

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

        [Parameter(Mandatory=$false)]
        [String] $MacPoolName = $global:cloudMacPool,

        [Parameter(Mandatory=$false)]
        [int] $vlanID = $global:defaultVlanID,

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

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

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

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

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

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

        [Parameter(Mandatory=$false)]
        [String] $k8snodeippoolend
    )

   return New-VirtualNetwork -name $name -vswitchName $vswitchName -MacPoolName $MacPoolName -vlanID $vlanID -ipaddressprefix $ipaddressprefix -gateway $gateway -dnsservers $dnsservers -vippoolstart $vippoolstart -vippoolend $vippoolend -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend
}

function New-ArcHciMocTokenFile {
    <#
    .DESCRIPTION
        Creates a new MOC Admin Identity for the Appliance and writes it to the given file path.
    .PARAMETER arcTokenFilePath
        Optional parameter. Path to the file where the arc token would be output.
    #>

    Param (
        [Parameter(Mandatory=$false)]
        [String] $arcTokenFilePath = ($(Get-Location).Path + "\kvatoken.tok")
    )

    $clusterName = "Appliance"
    try { Remove-MocIdentity -name $clusterName } catch { }
    $base64Identity = New-MocAdminIdentity -name $clusterName -validityDays $defaultTokenExpiryDays
    $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64Identity))
    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding)

    Write-Output "HCI login file successfully generated in '$arcTokenFilePath'"
}

function New-ArcHciIdentityFiles {
    <#
    .DESCRIPTION
        Creates the Arc HCI token files
    
    .PARAMETER workDirectory
        Optional parameter. Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.

    .PARAMETER azstackhciImage
        Optional parameter. Name of the azstackhci-operator image to use in place of the default image.

    .PARAMETER azstackhciVersion
        Optional parameter. Version of the azstackhci-operator image to use in place of the default image.

    .PARAMETER mocImage
        Optional parameter. Name of the moc-operator image to use in place of the default image.

    .PARAMETER mocVersion
        Optional parameter. Version of the moc-operator image to use in place of the default image.

    .PARAMETER mocConfigFilePath
        Optional parameter. Path to the hci-config.json file.

    .OUTPUTS
        N/A
    
    .EXAMPLE
        New-ArcHciIdentityFiles
    #>

    Param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir"),

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

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

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

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

        [Parameter(Mandatory=$false)]
        [String] $mocConfigFilePath = ($workDirectory + "\hci-config.json")
    )
    
    $mocConfig = Get-MocConfig
    $mocoperator = "moc-operator"
    try { Remove-MocIdentity -name $mocoperator } catch { }
    $mocOperatorIdentity = New-MocIdentity -name $mocoperator -validityDays $defaultTokenExpiryDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode
    New-MocRoleAssignmentWhenAvailable -identityName $mocoperator -roleName "Contributor" -location $mocConfig.cloudLocation | Out-Null

    $configData = @{}
    $configData["secret.loginString"] = $mocOperatorIdentity
    $configData["secret.cloudFQDN"] = $mocConfig.cloudFqdn

    if ($azstackhciImage) {
        $configData["azstackhciOperator.image"] = $azstackhciImage
    }
    if ($azstackhciVersion) {
        $configData["azstackhciOperator.version"] = $azstackhciVersion
    }

    if ($mocImage) {
        $configData["mocOperator.image"] = $mocImage
    }
    if ($mocVersion) {
        $configData["mocOperator.version"] = $mocVersion
    }

    $jsonData = convertto-json $configData
    Remove-Item -Path $mocConfigFilePath -Force -ErrorAction Ignore
    Set-Content -Path $mocConfigFilePath -Value $jsonData -ErrorVariable err

    Write-Output "MOC config file successfully generated in '$mocConfigFilePath'"
    Write-Output "Cloud agent service FQDN/IP: '$($mocConfig.cloudFqdn)'"
}

function New-ArcHciConfigFiles {
    <#
    .DESCRIPTION
        Creates the Arc HCI config files
    
    .PARAMETER subscriptionID
        The Azure subscription GUID
    
    .PARAMETER location
        Azure location (optional parameter. defaults to "eastus")
    
    .PARAMETER resourceGroup
        Name of the Azure resource group in which the Arc HCI appliance will be created
    
    .PARAMETER resourceName
        Name of the Azure resource
    
    .PARAMETER controlPlaneIP
        IP Address to be used for the Arc Appliance control plane
    
    .PARAMETER azstackhciImage
        Optional parameter. Name of the azstackhci-operator image to use in place of the default image.

    .PARAMETER azstackhciVersion
        Optional parameter. Version of the azstackhci-operator image to use in place of the default image.

    .PARAMETER mocImage
        Optional parameter. Name of the moc-operator image to use in place of the default image.

    .PARAMETER mocVersion
        Optional parameter. Version of the moc-operator image to use in place of the default image.

    .PARAMETER cloudFqdn
        Optional parameter. Fully qualified domain name (FQDN) or IP address of the cloud agent service.

    .PARAMETER vnetName
        Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters.

    .PARAMETER vswitchName
        Optional parameter. Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch.

    .PARAMETER vippoolstart
        The starting ip address to use for the vip pool.
        The vip pool addresses will be used by the k8s API server and k8s services

    .PARAMETER vippoolend
        The ending ip address to use for the vip pool.
        The vip pool addresses will be used by the k8s API server and k8s services

    .PARAMETER ipaddressprefix
        The address prefix to use for static IP assignment

    .PARAMETER gateway
        The gateway to use when using static IP

    .PARAMETER dnsservers
        The dnsservers to use when using static IP

    .PARAMETER k8snodeippoolstart
        The starting ip address to use for VM's in the cluster.

    .PARAMETER k8snodeippoolend
        The ending ip address to use for VM's in the cluster.

    .PARAMETER vlanID
        The VLAN ID for the vnet

    .OUTPUTS
        N/A

    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    
    .EXAMPLE
        New-ArcHciConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance"
    #>

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

        [Parameter(Mandatory=$false)]
        [String] $location = "eastus",

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $global:defaultworkingDir
    )

    Test-HCIMachineRequirements

    New-ArcHciConfigFilesInternal -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName `
        -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -controlPlaneIP $controlPlaneIP -workDirectory $workDirectory `
        -cloudFqdn $cloudFqdn -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend -vlanID $vlanID

}

function New-ArcHciConfigFilesInternal {
    <#
    .DESCRIPTION
        Creates the Arc HCI config files
        Documentation:
            See 'New-ArcHciAksConfigFiles' and 'New-ArcHciIdentityFiles'
    #>

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

        [Parameter(Mandatory=$false)]
        [String] $location = "eastus",

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $global:defaultworkingDir
    )

    New-ArcHciIdentityFiles -azstackhciImage $azstackhciImage -azstackhciVersion $azstackhciVersion -mocImage $mocImage -mocVersion $mocVersion -workDirectory $workDirectory

    New-ArcHciAksConfigFiles -subscriptionID $subscriptionID -location $location -resourceGroup $resourceGroup -resourceName $resourceName -workDirectory $workDirectory -controlPlaneIP $controlPlaneIP `
        -cloudFqdn $cloudFqdn -vnetName $vnetName -vswitchName $vswitchName -vippoolstart $vippoolstart -vippoolend $vippoolend -gateway $gateway -dnsservers $dnsservers -ipaddressprefix $ipaddressprefix -k8snodeippoolstart $k8snodeippoolstart -k8snodeippoolend $k8snodeippoolend -vlanID $vlanID
}

function New-ArcHciAksConfigFiles {
    <#
    .DESCRIPTION
        Creates the Arc Appliance config files

    .PARAMETER subscriptionID
        The Azure subscription GUID
    
    .PARAMETER location
        Azure location (optional parameter. defaults to "eastus")
    
    .PARAMETER resourceGroup
        Name of the Azure resource group in which the Arc HCI appliance will be created
    
    .PARAMETER resourceName
        Name of the Azure resource
    
    .PARAMETER controlPlaneIP
        IP Address to be used for the Arc Appliance control plane
    
    .PARAMETER cloudFqdn
        Optional parameter. Fully qualified domain name (FQDN) or IP address of the cloud agent service.

    .PARAMETER vnetName
        Optional parameter. Name of the virtual network the ARC resource bridge will connect to. The vnet will be automatically created if it doesn't exist. NOTE: this name must be all lower characters.

    .PARAMETER vswitchName
        Optional parameter. Name of the virtual switch the ARC resource bridge will connect to. On HCI, this virtual switch must be an external virtual switch.

    .PARAMETER vippoolstart
        The starting ip address to use for the vip pool.
        The vip pool addresses will be used by the k8s API server and k8s services

    .PARAMETER vippoolend
        The ending ip address to use for the vip pool.
        The vip pool addresses will be used by the k8s API server and k8s services

    .PARAMETER ipaddressprefix
        The address prefix to use for static IP assignment

    .PARAMETER gateway
        The gateway to use when using static IP

    .PARAMETER dnsservers
        The dnsservers to use when using static IP

    .PARAMETER k8snodeippoolstart
        The starting ip address to use for VM's in the cluster.

    .PARAMETER k8snodeippoolend
        The ending ip address to use for VM's in the cluster.

    .PARAMETER vlanID
        The VLAN ID for the vnet

    .OUTPUTS
        N/A

    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    
    .OUTPUTS
        N/A

    .EXAMPLE
        New-ArcHciAksConfigFiles -subscriptionID "12345678-4567-1234-8888-c64fc26bd67e" -location "eastus" -resourceGroup "MyResourceGroup" -resourceName "MyAppliance"
    #>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $global:defaultworkingDir
    )
    Set-ArcHciConfig -workingDir $workDirectory
    Set-ArcHciConfigValue -name "configFileLocation" -Value $workDirectory

    $arcTokenFilePath = Join-Path -Path $workDirectory -ChildPath "kvatoken.tok"

    Set-ArcHciConfigValue -name "kvaTokenLocation" -Value $arcTokenFilePath

    New-ArcHciMocTokenFile -arcTokenFilePath $arcTokenFilePath

    Write-Output "Generating ARC HCI configuration files..."

    $mocConfig = Get-MocConfig

    if([string]::IsNullOrEmpty($controlPlaneIP)) {
        $controlPlaneIP = $mocConfig.vnetvippoolend
    }

    $kubectlLoc = $mocConfig.installationPackageDir

    $oldPath = $($Env:PATH)
    if (-Not $oldPath.Split(';').Contains($kubectlLoc))
    {
        $Env:PATH="$($Env:PATH);$kubectlLoc"
    }

    $kubeFolder  = Join-Path -Path $Env:USERPROFILE -ChildPath ".kube"
    New-Item -ItemType Directory $kubeFolder -Force | Out-Null

    if([string]::IsNullOrEmpty($cloudFqdn)) {
        $cloudFqdn = $mocConfig.cloudFqdn
    }

    if([string]::IsNullOrEmpty($vnetName)) {
        $vnetName = $mocConfig.vnetName
    }
    if([string]::IsNullOrEmpty($vswitchName)) {
        $vswitchName = $mocConfig.vswitchName
    }

    if([string]::IsNullOrEmpty($vippoolstart) -or [string]::IsNullOrEmpty($vippoolend)) {
        $vippoolstart = $mocConfig.vnetvippoolstart
        $vippoolend = $mocConfig.vnetvippoolend
    }

    if([string]::IsNullOrEmpty($ipaddressprefix)) {
        $ipaddressprefix = $mocConfig.ipaddressprefix
    }
    if([string]::IsNullOrEmpty($dnsservers)) {
        $dnsservers = $mocConfig.dnsservers
    }
    if([string]::IsNullOrEmpty($gateway)) {
        $gateway = $mocConfig.gateway
    }
    if([string]::IsNullOrEmpty($k8snodeippoolstart) -or [string]::IsNullOrEmpty($k8snodeippoolend)) {
        $k8snodeippoolstart = $mocConfig.k8snodeippoolstart
        $k8snodeippoolend = $mocConfig.k8snodeippoolend
    }

    if([string]::IsNullOrEmpty($vlanID)) {
        $vlanID = $mocConfig.vlanid
    }

    Set-ArcHciConfigValue -name "controlPlaneIP" -Value $controlPlaneIP
    Set-ArcHciConfigValue -name "cloudFqdn" -Value $cloudFqdn
    Set-ArcHciConfigValue -name "vswitchName" -Value $vswitchName
    Set-ArcHciConfigValue -name "vippoolstart" -Value $vippoolstart
    Set-ArcHciConfigValue -name "vippoolend" -Value $vippoolend
    Set-ArcHciConfigValue -name "ipaddressprefix" -Value $ipaddressprefix
    Set-ArcHciConfigValue -name "dnsservers" -Value $dnsservers
    Set-ArcHciConfigValue -name "gateway" -Value $gateway
    Set-ArcHciConfigValue -name "k8snodeippoolstart" -Value $k8snodeippoolstart
    Set-ArcHciConfigValue -name "k8snodeippoolend" -Value $k8snodeippoolend
    Set-ArcHciConfigValue -name "vlanID" -Value $vlanID

    $dnsserver = ""
    if(-Not [string]::IsNullOrEmpty($ipaddressprefix))
    {
        foreach ($dns in $dnsservers.Split(","))
        {
            $dns = $dns.Trim(" ")
            if(-Not [string]::IsNullOrEmpty($dns))
            {
                $dnsserver += "`n - " +  $dns
            }
        }
    }
    $yaml = @"
# hci-appliance.yaml [config file]

# Absolute path to vCenter/HCI/other Fabric/infra specific credentials and configuration details
infrastructureConfigPath: "$($workDirectory.Replace("\","\\"))\\hci-infra.yaml"

# Specify admin cluster settings
applianceClusterConfig:
  # Used to connect to the Kubernetes API
  controlPlaneEndpoint: "$controlPlaneIP"

# Relative or absolute path to Arc Appliance ARM resource definition to be created in Azure
applianceResourceFilePath: "$($workDirectory.Replace("\","\\"))\\hci-resource.yaml"
"@


    $yamlFile = "$($workDirectory)\hci-appliance.yaml"
    Remove-Item -Path $yamlFile -Force -ErrorAction Ignore
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err

    $yaml = @"
# hci-infra.yaml [as referenced in file above]

# infra file
azurestackhciprovider:
  # Cloud agent configuration
  cloudagent:
    # Address of machine on which HCI Environment is deployed
    address: "$cloudFqdn"
    # port and authenticationport
    port: 55000
    authenticationport: 65000
    # Relative path to the cloudconfig file to access HCI Environment
    loginconfigfile: "$($arcTokenFilePath.Replace("\","\\"))"
  location: MocLocation
  group: management
  storagecontainer: MocStorageContainer
  virtualnetwork:
    name: "$vnetname"
    vswitchname: "$vswitchName"
"@


if (-Not [string]::IsNullOrEmpty($vlanID)) {
    $yaml += @"
`n vlanid: $($vlanID)
"@

}

if (-Not [string]::IsNullOrEmpty($vippoolstart)) {
    $yaml += @"
`n vippoolstart: $vippoolstart
    vippoolend: $vippoolend
"@

}

if (-Not [string]::IsNullOrEmpty($ipaddressprefix))
{
    $yaml += @"
`n ipaddressprefix: $ipaddressprefix
    gateway: $gateway
    dnsservers: $dnsserver
    k8snodeippoolstart: $k8snodeippoolstart
    k8snodeippoolend: $k8snodeippoolend
"@

}
    $yaml += @"
`n appliancevm:
    vmsize: Standard_A4_v2
download:
  workingdirectory: "$($workDirectory.Replace("\","\\"))"
"@


    $yamlFile = "$($workDirectory)\hci-infra.yaml"
    Remove-Item -Path $yamlFile -Force -ErrorAction Ignore
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err

    $yaml = @"
# hci-resource.yaml [as reference in hci-appliance.yaml above]

# resource file
resource:
    # Resource group must exist
    resource_group: $resourceGroup
    # Resource name
    name: $resourceName
    # Location
    location: $location
    # Subscription should match CLI context
    subscription: $subscriptionID
"@


    $yamlFile = "$($workDirectory)\hci-resource.yaml"
    Remove-Item -Path $yamlFile -Force -ErrorAction Ignore
    Set-Content -Path $yamlFile -Value $yaml -ErrorVariable err

    Write-Output "Config file successfully generated in '$workDirectory'"
}

function Remove-ArcHciIdentityFiles {
    <#
    .DESCRIPTION
        Removes the Arc HCI token files
    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    .OUTPUTS
        N/A
    .EXAMPLE
        Remove-ArcHciIdentityFiles
    #>


    Param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir")
    )

    # Remove Identities
    Remove-Item -Path "$($workDirectory)\hci-config.json" -Force -ErrorAction Ignore
    $mocoperator = "moc-operator"
    Remove-MocIdentity -name $mocoperator
}

function Remove-ArcHciAksConfigFiles {
    <#
    .DESCRIPTION
        Removes the Arc Appliance config files
    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    .OUTPUTS
        N/A
    .EXAMPLE
        Remove-ArcHciAksConfigFiles
    #>


    Param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir")
    )

    # Remove Identities
    Remove-Item -Path "$($workDirectory)\kvatoken.tok" -Force -ErrorAction Ignore
    $clusterName = "Appliance"
    Remove-MocIdentity -name $clusterName

    # Remove the appliance config files
    Remove-Item -Path "$($workDirectory)\hci-appliance.yaml" -Force -ErrorAction Ignore
    Remove-Item -Path "$($workDirectory)\hci-infra.yaml" -Force -ErrorAction Ignore
    Remove-Item -Path "$($workDirectory)\hci-resource.yaml" -Force -ErrorAction Ignore
    $kubeFolder = "$($Env:USERPROFILE)\.kube"
    Remove-Item -Path "$kubeFolder\config" -Force -ErrorAction Ignore
}

function Remove-ArcHciConfigFiles {
    <#
    .DESCRIPTION
        Removes the Arc HCI config files
    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.
    .OUTPUTS
        N/A
    .EXAMPLE
        Remove-ArcHciConfigFiles
    #>


    Param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory = $(Get-ArcHciConfigValue -name "workingDir")
    )

    Remove-ArcHciIdentityFiles -workDirectory $workDirectory
    Remove-ArcHciAksConfigFiles -workDirectory $workDirectory
}

function Get-ArcHciFirstControlPlaneNodeIp {
    <#
    .DESCRIPTION
        Finds the ARC resource bridge control plane VM and retrieves its non-control plane IPv4.
    #>


    # Assumption: we assume that the ARC control plane VM is the VM that contains "control-plane" in the name and is located in the **management** group
    Get-MocConfig > $null # Workaround for Get-MocVirtualMachine initialization. Call into Get-MocConfig first to initialize MOC context.
    $mocVm = (Get-MocVirtualMachine -group management | where { $_.name -match ".*control-plane.*" })
    if ($mocVm -ne $null) {
        $controlPlaneIp = ([ipaddress](Get-ArcHciConfig).controlPlaneIP)
        $hypervVm = Get-VM -Name "$($mocVm.name)*" -ComputerName $mocVm.virtualmachineproperties.host.id
        $vmIpAddresses = ([ipaddress[]]$hypervVm.NetworkAdapters.IPAddresses) | where { $_.AddressFamily -eq "InterNetwork" -And $_ -ne $controlPlaneIp}
        
        if ($vmIpAddresses -ne $null) {
            return $vmIpAddresses[0]
        }
    }
    return $null
}

function Get-ArcHciLogs {
    <#
    .DESCRIPTION
        Collects all the logs from the deployment and compresses them.

    .PARAMETER workDirectory
        Path to the work directory (optional parameter. Defaults to the local directory.) Please set this parameter to a shared storage location if you are running in an HCI cluster.

    .PARAMETER activity
        Activity name to use when updating progress

    .PARAMETER kubeconfig
        Path to the appliance kubeconfig

    .PARAMETER ip
        IP address of the ARC appliance VM or kubernetes api server

    .PARAMETER logDir
        Path to the directory to store the logs

    .PARAMETER kvaTokenPath
        Path to the KVA token (which was generated during the installation of the ARC resource bridge)
    #>


    param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory =  $(Get-ArcHciConfigValue -name "workingDir"),

        [Parameter()]
        [String]$activity = $MyInvocation.MyCommand.Name,

        [Parameter(Mandatory=$false)]
        [String]$kubeconfig = "$($Env:USERPROFILE)\.kube\config",

        [Parameter(Mandatory=$false)]
        [String]$logDir =  [System.Io.Path]::GetTempPath(),

        [Parameter(Mandatory=$false)]
        [ipaddress]$ip = $(Get-ArcHciFirstControlPlaneNodeIp),

        [Parameter(Mandatory=$false)]
        [String] $kvaTokenPath = (Get-ArcHciConfigValue -name "kvaTokenLocation")
        )

    $arcHciLogDir = $(Join-Path $logDir "archcilogs")

    Remove-Item $arcHciLogDir -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue > $null

    New-Item -ItemType Directory -Path $arcHciLogDir -Force > $null
    New-Item -ItemType Directory -Path "$($arcHciLogDir)\config\" -Force > $null

    $mocConfig = Get-MocConfig -ErrorAction SilentlyContinue
    Get-ArcHciConfig -ErrorAction SilentlyContinue | Format-Table -AutoSize > "$($arcHciLogDir)\config\archciconfig.txt"
    $mocConfig | Format-Table -AutoSize > "$($arcHciLogDir)\config\mocconfig.txt"
    Copy-Item -Path "$($workDirectory)\hci-appliance.yaml" -Destination "$($arcHciLogDir)\config\"  -ErrorAction Ignore
    Copy-Item -Path "$($workDirectory)\hci-infra.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore
    Copy-Item -Path "$($workDirectory)\hci-resource.yaml" -Destination "$($arcHciLogDir)\config\" -ErrorAction Ignore
    
    Get-MocLogs -path $arcHciLogDir -activity $activity -AgentLogs -EventLogs
    Get-DownloadSdkLogs -Path $arcHciLogDir

    Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Collecting ARC appliance Logs...")
    # WORKAROUND: Pipe the cloud FQDN and kva token path to workaround the "az arcappliance logs" interractive prompt
    # TODO: Remove this hack once arcappliance logs has a way to pass these through parameters instead of interractively asking for these.
    if ($ip) {
        "$($mocConfig.cloudFqdn)`n$($kvaTokenPath)`n" | az arcappliance logs hci --kubeconfig $kubeconfig --ip $ip --out-dir $arcHciLogDir > "$($arcHciLogDir)\arcappliancelogsout.txt"
    } else {
        "$($mocConfig.cloudFqdn)`n$($kvaTokenPath)`n" | az arcappliance logs hci --kubeconfig $kubeconfig --out-dir $arcHciLogDir > "$($arcHciLogDir)\arcappliancelogsout.txt"
    }

    Write-StatusWithProgress -activity $activity -moduleName $script:moduleName -status $("Compressing Logs...")
    $zipName = Join-Path $logDir "archcilogs.zip"

    Remove-Item $zipName -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue
    Compress-Directory -ZipFilename $zipName -SourceDir $arcHciLogDir

    Remove-Item $arcHciLogDir -Force -Recurse -Confirm:$false -ErrorAction SilentlyContinue
    Write-Output "Logs were successfully saved to ""$zipName"""
    return $zipName
}

function New-KvaVirtualNetwork {
    <#
    .DESCRIPTION
        Creates a new virtual network usig kvactl

    .PARAMETER name
        The name of the vnet

    .PARAMETER vswitchName
        The name of the vswitch

    .PARAMETER vippoolstart
        The starting ip address to use for the vip pool.
        The vip pool addresses will be used by the k8s API server and k8s services

    .PARAMETER vippoolend
        The ending ip address to use for the vip pool.
        The vip pool addresses will be used by the k8s API server and k8s services

    .PARAMETER ipaddressprefix
        The address prefix to use for static IP assignment

    .PARAMETER gateway
        The gateway to use when using static IP

    .PARAMETER dnsservers
        The dnsservers to use when using static IP

    .PARAMETER k8snodeippoolstart
        The starting ip address to use for VM's in the cluster.

    .PARAMETER k8snodeippoolend
        The ending ip address to use for VM's in the cluster.

    .PARAMETER vlanID
        The VLAN ID for the vnet

    .PARAMETER kubeconfig
        Path to the appliance kubeconfig
    #>


    param (
        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $name,

        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $vswitchName,

        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $vippoolstart,

        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $vippoolend,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $ipaddressprefix,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $gateway,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $dnsservers,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $k8snodeippoolstart,

        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $k8snodeippoolend,

        [Parameter(Mandatory=$false, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $vlanID,

        [Parameter(Mandatory=$true, ParameterSetName="DHCP")]
        [Parameter(Mandatory=$true, ParameterSetName="Static")]
        [ValidateNotNullOrEmpty()]
        [String] $kubeconfig
    )

    $dnsserversYaml = ""
    if(-Not [string]::IsNullOrEmpty($ipaddressprefix))
    {
        foreach ($dns in $dnsservers.Split(","))
        {
            $dns = $dns.Trim(" ")
            if(-Not [string]::IsNullOrEmpty($dns))
            {
                $dnsserversYaml += "`n - " +  $dns
            }
        }
    }

    # Create the network config file

    $akshcinetworkyaml = @"
apiVersion: msft.microsoft/v1
kind: AksHciNetwork
metadata:
  labels:
    msft.microsoft/capi-name: capi-for-moc
  name: $name
spec:
  group: target-group
  location: MocLocation
  name: $name
  type: Transparent
  vippoolstart: $vippoolstart
  vippoolend: $vippoolend
  vswitchname: $vswitchName
  ipaddressprefix: $ipaddressprefix
  gateway: $gateway
  dnsservers: $dnsserversYaml
  k8snodeippoolstart: $k8snodeippoolstart
  k8snodeippoolend: $k8snodeippoolend
"@


if (-Not [string]::IsNullOrEmpty($vlanID)) {
    $akshcinetworkyaml += @"
`n vlanid: $vlanID
"@

}

    $yamlFile = Join-Path $(Get-Location).Path "vnet-$name.yaml"
    Set-Content -Path $yamlFile -Value $akshcinetworkyaml -ErrorVariable err

    # Create the kva virtual network

    $config = Get-MocConfig
    $kubectlPath = Join-Path $config.installationPackageDir "kubectl.exe"

    try {
        # Create the kva virtual network
        Invoke-Expression "$kubectlPath apply -f '$yamlFile' --kubeconfig '$kubeconfig'"

        # Get the list of kva virtual networks
        Invoke-Expression "$kubectlPath get akshcinetworks -A --kubeconfig '$kubeconfig'"
    }
    catch {
        Write-Output "Error while creating KVA virtual network"
    }
}

function Get-TargetClusterAdminCredentials {
    <#
    .DESCRIPTION
        Gets the kubeconfig of target cluster

    .PARAMETER clusterName
        The name of the target cluster
    
    .PARAMETER outfile
        Path to the file in which target cluster kubeconfig will be stored

    .PARAMETER kubeconfig
        Path to the appliance kubeconfig
    #>

    
    param (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $clusterName,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $outfile,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String] $kubeconfig
    )
    
    $config = Get-MocConfig
    $kubectlPath = Join-Path $config.installationPackageDir "kubectl.exe"

    try {
        # Get the target cluster kubeconfig
        $secretName = $clusterName.ToLower() + '-kubeconfig'
        $encodedSecret = Invoke-Expression "$kubectlPath get secrets '$secretName' -o jsonpath='{.data.value}' --kubeconfig '$kubeconfig'"

        $targetKubeconfig = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($encodedSecret))

        Set-Content -Path $outfile -Value $targetKubeconfig
    }
    catch {
        Write-Output "Error while retrieving the target cluster kubeconfig"
    }
}


function Invoke-CommandLine
{
    <#
    .DESCRIPTION
        Executes a command and optionally ignores errors.

    .PARAMETER command
        Comamnd to execute.

    .PARAMETER arguments
        Arguments to pass to the command.

    .PARAMETER showOutput
        Optionally, show live output from the executing command.
    #>


    param (
        [String]$command,
        [String]$arguments,
        [Switch]$showOutput
    )

    if ($showOutput.IsPresent)
    {
        $result = (& $command $arguments.Split(" ") | Out-Default) 2>&1
    }
    else
    {
        $result = (& $command $arguments.Split(" ") 2>&1)
    }
  
    $out = $result | Where-Object {$_.gettype().Name -ine "ErrorRecord"}  # On a non-zero exit code, this may contain the error
    #$outString = ($out | Out-String).ToLowerInvariant()

    if ($LASTEXITCODE)
    {
        $err = $result | Where-Object {$_.gettype().Name -eq "ErrorRecord"}
        $errMessage = "$command $arguments (Error: $LASTEXITCODE [$err])"
        throw $errMessage
    }
    return $out
}

function Repair-ArcHciApplianceCerts {
    <#
    .DESCRIPTION
        Attempts to repair failed TLS to cloudagent

    .PARAMETER sshPrivateKeyFile
        SSH private key file

    .PARAMETER force
        Force repair(without checks)

    .PARAMETER kubeconfig
        Path to the kubeconfig file

    .PARAMETER group
        Appliance group

    .PARAMETER clusterName
        Name of the appliance cluster

    .PARAMETER arcTokenFilePath
        File path to the ARC indentity token
    
    .PARAMETER kvactlPath
        Location of the kvactl.exe tool (which may need to be installed separately)
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter()]
        [String] $sshPrivateKeyFile = $global:sshPrivateKeyFile,

        [Parameter()]
        [Switch] $force,

        [Parameter()]
        [string] $kubeconfig,
        [Parameter()]
        [string] $group,

        [Parameter()]
        [string] $clusterName,

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

        [Parameter(Mandatory=$false)]
        [String] $kvactlpath = $global:kvaCtlFullPath
    )
    if (-Not (Test-Path $kvactlpath)) {
        throw "Please make sure you install KvaCtl.exe in '$kvactlpath' before running repair"
    }

    $kvaIdentity = Invoke-MocIdentityRotate -name $clusterName -encode
    $utf8String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($kvaIdentity)) 
    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
    [System.IO.File]::WriteAllLines($arcTokenFilePath, $utf8String, $Utf8NoBomEncoding)

    Invoke-CommandLine -command $kvactlpath -arguments $("repair --kubeconfig ""$kubeconfig"" --sshprivatekey ""$sshPrivateKeyFile"" --tags ""Group=$group"" --force=$force --verbose") -showOutput
}

function Repair-MocOperatorToken {
    <#
    .DESCRIPTION
        Refresh the moc-operator token
    #>


    Repair-Moc
    $mocConfig = Get-MocConfig
    $mocoperator = "moc-operator"
    try { Remove-MocIdentity -name $mocOperator } catch { }
    $mocOperatorIdentity = New-MocIdentity -name $mocoperator -validityDays $defaultTokenExpiryDays -fqdn $mocConfig.cloudFqdn -location $mocConfig.cloudLocation -port $mocConfig.cloudAgentPort -authport $mocConfig.cloudAgentAuthorizerPort -encode
    New-MocRoleAssignmentWhenAvailable -identityName $mocoperator -roleName "Contributor" -location $mocConfig.cloudLocation | Out-Null

    $loginString = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($mocOperatorIdentity))
    kubectl.exe patch secret controllerconfig -p "{\""data\"":{\""LoginString\"":\""$loginString\""}}" -n moc-operator-system
    kubectl.exe delete pods -n moc-operator-system -l control-plane=controller-manager
}

function Repair-ArcHciVmInfra {
    <#
    .DESCRIPTION
        Rotates the security certificates and tokens in MOC and ARC resource bridge
    
    .PARAMETER workDirectory
    TBD
    #>

    param (
        [Parameter(Mandatory=$false)]
        [String] $workDirectory =  $(Get-ArcHciConfigValue -name "workingDir"),

        [Parameter()]
        [String] $sshPrivateKeyFile = $global:sshPrivateKeyFile,

        [Parameter()]
        [Switch] $force,

        [Parameter()]
        [string] $kubeconfig,
        [Parameter()]
        [string] $group,

        [Parameter()]
        [string] $clusterName,

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

        [Parameter(Mandatory=$false)]
        [String] $kvactlpath = $global:kvaCtlFullPath
    )
    if (-Not (Test-Path $kvactlpath)) {
        throw "Please make sure you install KvaCtl.exe in '$kvactlpath' before running repair"
    }

    Repair-MocOperatorToken
    Repair-ArcHciApplianceCerts -sshprivatekey $sshPrivateKeyFile -force:$force.IsPresent -kubeconfig $($env:USERPROFILE + "\.kube\config") -group "management" -clusterName "Appliance" -arcTokenFilePath $($workDirectory + "\kvatoken.tok") -kvactlpath $kvactlpath
}

function Test-HCIMachineRequirements {
    <#
    .DESCRIPTION
        Checks
         1. The substrate is Azure Stack HCI
         2. HCI node is registered
    #>


    # Check if the machine is Azure Stack HCI
    # The ps module "AzureStackHCI" is available only on the azstackhci machines
    if($null -eq (Get-Module -ListAvailable -Name "AzureStackHCI" -ErrorAction:Ignore)) {
        throw "The machine is not Azure Stack HCI. Installation is not supported in other machines."
    }

    Import-Module -Name "AzureStackHCI"

    $hciStatus = Get-AzureStackHCI

    # Check if the HCI machine is registered; if not, throw error
    if ($hciStatus.RegistrationStatus -ine "Registered") {
        throw "The HCI machine ($env:computername) is not registered. Please register your HCI cluster."
    }
}

# SIG # Begin signature block
# MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD8e87aOfDFW+YX
# CqGZDS3FUhgkE70YEZQRF3vAQ250iaCCDYEwggX/MIID56ADAgECAhMzAAACUosz
# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I
# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O
# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA
# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o
# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8
# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3
# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp
# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7
# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u
# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1
# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti
# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z
# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf
# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK
# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW
# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F
# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS
# 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/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg3Flry/X2
# LKj3j8iSSwGpHyvu1n1imxzTNVmXjdLevsMwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQCCuPc9vnNPpGbyu9tGUoMeFRNbkhPBGg64HXaTHtCl
# OUZ8t0FmMcPvaXzvl+FSfQ2y5bqZPL71IFpKe+Ipu+hj57E/XdFqzefZCN1cp38l
# /f1f09EIsGZszEUf8G+asrB5Bq7VQi+me/kB6QH8VxNyI9NPeA6mX45E3uUhp1Tt
# Oqp1hLtoNMbYaDAqW0OhDc/atoGQxazQWmylT6aMcMrY0B6voKocstN7HaH8FK9n
# /YPpNtTPMeDYQ15ntvn1snGB9C9YQW06K0Uafo9sh0gkjaffCaT4vP0GllsPO/tE
# m/k7hSu7G6eJZyyk2QSzMdUWK+/v4U2Nuf31Z38FMsWeoYIXADCCFvwGCisGAQQB
# gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME
# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIC28ZTmvvqaFkjYq2RioIQk+5ktd81KEd5+fFFYY
# abiVAgZi1Vt5+wwYEzIwMjIwNzIwMTgyODMyLjMxMlowBIACAfSggdCkgc0wgcox
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOjNFN0EtRTM1OS1BMjVEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGg6buMuw6i0XoAAQAAAaAw
# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN
# MjExMjAyMTkwNTIzWhcNMjMwMjI4MTkwNTIzWjCByjELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046M0U3QS1FMzU5LUEy
# NUQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G
# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/2uIOaHGdAOj2YvhhI6C8iFAq7wrl
# /5WpPjj0fEHCi6Ivx/I02Jss/HVhkfGTMGttR5jRhhrJXydWDnOmzRU3B4G525T7
# pwkFNFBXumM/98l5k0U2XiaZ+bulXHe54x6uj/6v5VGFv+0Hh1dyjGUTPaREwS7x
# 98Te5tFHEimPa+AsG2mM+n9NwfQRjd1LiECbcCZFkgwbliQ/akiMr1tZmjkDbxtu
# 2aQcXjEfDna8JH+wZmfdu0X7k6dJ5WGRFwzZiLOJW4QhAEpeh2c1mmbtAfBnhSPN
# +E5yULfpfTT2wX8RbH6XfAg6sZx8896xq0+gUD9mHy8ZtpdEeE1ZA0HgByDW2rJC
# bTAJAht71B7Rz2pPQmg5R3+vSCri8BecSB+Z8mwYL3uOS3R6beUBJ7iE4rPS9WC1
# w1fZR7K44ZSme2dI+O9/nhgb3MLYgm6zx3HhtLoGhGVPL+WoDkMnt93IGoO6kNBC
# M2X+Cs22ql2tPjkIRyxwxF6RsXh/QHnhKJgBzfO+e84I3TYbI0i29zATL6yHOv5s
# Es1zaNMih27IwfWg4Q7+40L7e68uC6yD8EUEpaD2s2T59NhSauTzCEnAp5YrSscc
# 9MQVIi7g+5GAdC8pCv+0iRa7QIvalU+9lWgkyABU/niFHWPjyGoB4x3Kzo3tXB6a
# C3yZ/dTRXpJnaQIDAQABo4IBNjCCATIwHQYDVR0OBBYEFHK5LlDYKU6RuJFsFC9E
# zwthjNDoMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY
# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p
# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF
# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo
# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI
# hvcNAQELBQADggIBADF9xgKr+N+slAmlbcEqQBlpL5PfBMqcLkS6ySeGJjG+LKX3
# Wov5pygrhKftXZ90NYWUftIZpzdYs4ehR5RlaE3eYubWlcNlwsKkcrGSDJKawbbD
# GfvO4h/1L13sg66hPib67mG96CAqRVF0c5MA1wiKjjl/5gfrbdNLHgtREQ8zCpbK
# 4+66l1Fd0up9mxcOEEphhJr8U3whwFwoK+QJ/kxWogGtfDiaq6RyoFWhP8uKSLVD
# V+MTETHZb3p2OwnBWE1W6071XDKdxRkN/pAEZ15E1LJNv9iYo1l1P/RdF+IzpMLG
# DAf/PlVvTUw3VrH9uaqbYr+rRxti+bM3ab1wv9v3xRLc+wPoniSxW2p69DN4Wo96
# IDFZIkLR+HcWCiqHVwFXngkCUfdMe3xmvOIXYRkTK0P6wPLfC+Os7oeVReMj2TA1
# QMMkgZ+rhPO07iW7N57zABvMiHJQdHRMeK3FBgR4faEvTjUAdKRQkKFV82uE7w0U
# MnseJfX7ELDY9T4aWx2qwEqam9l7GHX4A2Zm0nn1oaa/YxczJ7gIVERSGSOWLwEM
# xcFqBGPm9QSQ7ogMBn5WHwkdTTkmanBb/Z2cDpxBxd1vOjyIm4BOFlLjB4pivClO
# 2ZksWKH7qBYloYa07U1O3C8jtbzGUdHyLCaVGBV8DfD5h8eOnyjraBG7PNNZMIIH
# cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB
# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp
# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw
# OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh
# C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx
# WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc
# UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc
# nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo
# veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi
# YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9
# fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH
# GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X
# KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE
# R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/
# eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3
# FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd
# BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE
# AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI
# MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB
# Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud
# HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By
# b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO
# MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy
# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC
# AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk
# bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng
# ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3
# lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC
# gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6
# MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU
# BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh
# VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+
# fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp
# NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI
# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4
# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw
# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjozRTdBLUUzNTktQTI1RDElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAEwa4jWjacbOYU++9
# 5ydJ7hSCi5iggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN
# BgkqhkiG9w0BAQUFAAIFAOaCfOowIhgPMjAyMjA3MjAyMTA4NThaGA8yMDIyMDcy
# MTIxMDg1OFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5oJ86gIBADAKAgEAAgIb
# AAIB/zAHAgEAAgIRsTAKAgUA5oPOagIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor
# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA
# A4GBAHUnyCXXphzXknmTptrPl76kTktbgd6JWuv7ThrksaCzE+XkaBeGaiBsRbYj
# utja+DLAobZ60FGc5xazotndGunes9vfNp8pGEA73loly8fNwtAbdq5m9AHb4q/k
# s9s3bYMEsTOjNOPDAAPkmRasPcYsTPM+oZeYxmCnMVGr2frfMYIEDTCCBAkCAQEw
# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGg6buMuw6i0XoA
# AQAAAaAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B
# CRABBDAvBgkqhkiG9w0BCQQxIgQgs9kd4DX96SPbsKQ/hTJDqAm4A+STYAynJauW
# 000p3DowgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAvR4o8aGUEIhIt3REv
# sx0+svnM6Wiaga5SPaK4g6+00zCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwAhMzAAABoOm7jLsOotF6AAEAAAGgMCIEID3rw5VNA64OcnpMbsO9
# 5r9O5FlWogzkupe76YFoavRfMA0GCSqGSIb3DQEBCwUABIICAHyHbzfiuzfsK9un
# QHMU6gSgR65zY5YAEQfdqdVKXpUyLn1k3A1gF/TXet/efcSrwMwfL1iwDZceI84N
# BZwRgSnmeOhVWUUDmv5B84V8WA1JpKeP7laL5MhPDfYLxdF4sFM4laRec3Kxq6Rx
# SmXr8iIybi2pEMuBsFf5E8e37xwWPdGIEf8BGnqFwfL1ZqInxwTOwXrZMBz5ohIT
# gXkMYwg/nbpdfsjHM7Kt7D38+Ft+hwzL6DnYsL0VkyjwEJbasuFaPL31xzPgc5Gc
# 42JuFVTC704Smrv6v/fOftmj3MHLpnbK55N8lZklxn7jwn+LmYyhIWbWF97CzG+l
# L43RgYL14+s33MA9UQ7XW6XCnKYSDTx+6dJEV4inNbJWOVTCZVdo5fMjs1zdx4sP
# SlFK+x/fbllc/zJHIVSnaCtg6/2LEzrLpALufoobbGjJXfBjBkChhHtmWj9A8E04
# wZCMXtjFPVVEk9NjVwVuwYhsX3bHWSleQPpBkoWS8LhdZT9BvJhYXEuisqLQ9PeX
# Qa7vezfFz9TL2AxWVcgoNFFwzi6WVPnZ1im51Fv7IUkw9WZw5mWyYDw0STs7wL+L
# yLM4ydYcpNIRC4P5IgMmwqi6T5R4RSnZELUvd+XIGs2kBOWcVkBOHeiwschDT56V
# T9DkkoIRcKcIUKiz2gVZlP6FIpd1
# SIG # End signature block