ADLAB PowerShell source file: buildup-rdp.ps1

(C) Ondrej Sevecek, 2019 - www.sevecek.com, ondrej@sevecek.com



$libDir = Split-Path -parent $MyInvocation.MyCommand.Definition
& "$libDir\lib-common.ps1" -defaultConfig -rootDir $libDir -outFile rdpLib
& "$libDir\lib-modifyActions.ps1"
& "$libDir\lib-buildup.ps1"

$vmName = $args[0]

DBG ("RDP farm installation library")
Redirect-TempToOutput
Load-VMConfig

Find-MarkedVolumes

$appTag = 'rdp'
$appConfig = $vmConfig.$appTag
$allAppHostWaiters = Get-AllAppHostsOfInstance $appTag $appConfig.instance 'waitParams'
$allAppConfigs = Get-AllAppHostsOfInstance $appTag $appConfig.instance 'svcConfig'


#====================
#====================

DBGIF $MyInvocation.MyCommand.Name { $global:thisOSVersionNumber -lt 6.2 }
if ($global:thisOSVersionNumber -lt 6.2) {

  exit 1
}


[string[]] $rolesToInstall = Split-MultiValue $appConfig.roles
DBG ('Will install the following roles on this server: #{0} | {1}' -f (Get-CountSafe $rolesToInstall), ($rolesToInstall -join ','))
DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $rolesToInstall) -lt 1 }

if ((Get-CountSafe $rolesToInstall) -lt 1) {

  exit 2
}


# Note: this phase could be run several times as the Mgr is restarting the machine remotelly
Finish-Machine $false 'RdpRolesWaiting' -doNotAssertAlreadyFinished

#
#

[string[]] $rolesCB = $null
[string[]] $rolesWA = $null
[string[]] $rolesLS = $null
[string[]] $rolesGW = $null
[string[]] $rolesSH = @()
  
DBG ('Obtain role owners: #{0}' -f (Get-CountSafe $allAppConfigs))
foreach ($oneAppConfig in $allAppConfigs) {

  $oneRolesRequested = Split-MultiValue $oneAppConfig.roles
  DBG ('One app config roles requested: #{0} | {1}' -f (Get-CountSafe $oneRolesRequested), ($oneRolesRequested -join ','))
  
  if (Contains-Safe $oneRolesRequested RDCB) {

    $rolesCB += Get-MachineFQDN $oneAppConfig
    DBG ('CB role identified: #{0} | {1}' -f (Get-CountSafe $rolesCB), ($rolesCB -join ','))
  }

  if (Contains-Safe $oneRolesRequested RDWA) {

    $rolesWA += Get-MachineFQDN $oneAppConfig
    DBG ('WA role identified: #{0} | {1}' -f (Get-CountSafe $rolesWA), ($rolesWA -join ','))
  }

  if (Contains-Safe $oneRolesRequested RDLS) {

    $rolesLS += Get-MachineFQDN $oneAppConfig
    DBG ('LS role identified: #{0} | {1}' -f (Get-CountSafe $rolesLS), ($rolesLS -join ','))
  }

  if (Contains-Safe $oneRolesRequested RDGW) {

    $rolesGW += Get-MachineFQDN $oneAppConfig
    DBG ('GW role identified: #{0} | {1}' -f (Get-CountSafe $rolesGW), ($rolesGW -join ','))
  }

  if (Contains-Safe $oneRolesRequested RDSH) {

    $rolesSH += Get-MachineFQDN $oneAppConfig
    DBG ('SH roles identified: #{0} | {1}' -f (Get-CountSafe $rolesSH), ($rolesSH -join ','))
  }
}

DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $rolesCB) -lt 1 }
DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $rolesWA) -lt 1 }
DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $rolesSH) -lt 1 }

$rolesCBPrimary = $rolesCB[0]

#
#

DBG ('Are we builder RDP manager server: {0}' -f (Contains-Safe $rolesToInstall 'Mgr'))

if (Contains-Safe $rolesToInstall 'Mgr') {

  DBG ('First wait for all the RDP role hosts to finish up to our point: #{0}' -f (Get-CountSafe $allAppHostWaiters), ($allAppHostWaiters -join ', '))
  foreach ($oneAppHostWaiter in $allAppHostWaiters) {

    Wait-Machine $oneAppHostWaiter -subPhase 'RdpRolesWaiting'
  }

  DBG ('Import module RemoteDesktop')
  DBGSTART
  Import-Module RemoteDesktop
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Start the deployment: cb = {0} | wa = {1} | sh = {2}' -f $rolesCBPrimary, $rolesWA[0], ($rolesSH -join ','))
  DBGSTART
  New-RDSessionDeployment -ConnectionBroker $rolesCBPrimary -WebAccessServer $rolesWA[0] -SessionHost $rolesSH
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Add the LS server: ls = #{0} {1} | cb = {2}' -f (Get-CountSafe $rolesLS), ($rolesLS -join ','), $rolesCBPrimary)
  
  foreach ($oneRoleLS in $rolesLS) {

    DBG ('Add one LS server: {0}' -f $oneRoleLS)
    DBGSTART
    $newAddedServer = $null
    $newAddedServer = Add-RDServer -Server $oneRoleLS -Role RDS-LICENSING -ConnectionBroker $rolesCBPrimary
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $newAddedServer }

    DBG ('Set the licensing mode: {0}' -f $appConfig.licensing)
    DBGSTART
    Set-RDLicenseConfiguration -LicenseServer $oneRoleLS -Mode $appConfig.licensing -ConnectionBroker $rolesCBPrimary -Force
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
  }

  DBG ('Should we add any other SH role: {0}' -f ((Get-CountSafe $rolesSH) -gt 1))
  if ((Get-CountSafe $rolesSH) -gt 1) {

    for ($i = 1; $i -lt (Get-CountSafe $rolesSH); $i ++) {

      DBG ('Addind one SH server: {0}' -f $rolesSH[$i])
      DBGSTART
      $newAddedServer = $null
      $newAddedServer = Add-RDServer -Server $rolesSH[$i] -Role RDS-RD-SERVER -ConnectionBroker $rolesCBPrimary
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $newAddedServer }
    }
  }


  $sessionCollections = $appConfig.SelectNodes('./sessionColl[@ref]')
  DBG ('Found requested session collections: #{0}' -f (Get-CountSafe $sessionCollections))
  
  if ((Get-CountSafe $sessionCollections) -gt 0) {

    foreach ($oneSessionColl in $sessionCollections) {

      DBG ('One session collection to create: {0} | {1} | {2}' -f $oneSessionColl.ref, $oneSessionColl.desc, $oneSessionColl.access)
      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneSessionColl.ref }
      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneSessionColl.desc }

      DBGSTART
      $createdColl = $null
      $createdColl = New-RDSessionCollection -CollectionName $oneSessionColl.desc -SessionHost $rolesSH -ConnectionBroker $rolesCBPrimary
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $createdColl }

      DBG ('The session collection has some apps: {0} | #{1}' -f $oneSessionColl.ref, (Get-CountSafe $oneSessionColl.remoteApp))
      DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $oneSessionColl.remoteApp) -lt 1 }

      if ((Get-CountSafe $oneSessionColl.remoteApp) -gt 0) {

        foreach ($oneRemoteApp in $oneSessionColl.remoteApp) {

          DBG ('Resolve remote app path: {0}' -f $oneRemoteApp.path)
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneRemoteApp.path }
          DBGSTART
          $oneRemoteAppPath = [string]::Empty
          # Note: we cannot use Get-Item to verify existance of the executable on the local machine as we are not running on the RDSH locally
          $oneRemoteAppPath = [Environment]::ExpandEnvironmentVariables((Resolve-VolumePath $oneRemoteApp.path))
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          [string[]] $allowGroups = $null
          if (Is-ValidString $oneSessionColl.access) {

            $allowGroups = Split-MultiValue $oneSessionColl.access
          }

          DBG ('One remote app: coll = {0} | {1} | {2} | {3} | {4} | groups = {5}' -f $oneSessionColl.ref, $oneRemoteApp.alias, $oneRemoteApp.display, $oneRemoteApp.path, $oneRemoteAppPath, ($allowGroups -join ','))
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneRemoteApp.alias }
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneRemoteApp.display }
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneRemoteAppPath }
          #DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path -Literal $oneRemoteAppPath) }
          DBGSTART
          $createdRemoteApp = $null
          $createdRemoteApp = New-RDRemoteApp -Alias $oneRemoteApp.alias -DisplayName $oneRemoteApp.display -FilePath $oneRemoteAppPath -ShowInWebAccess $true -CollectionName $oneSessionColl.desc -ConnectionBroker $rolesCBPrimary -UserGroups $allowGroups
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBGIF $MyInvocation.MyCommand.Name { Is-Null $createdRemoteApp }
        }
      }
    }
  }


  #DBG ('Set the client access name: {0}' -f $appConfig.clientAccessFQDN)
  #DBGSTART
  #Set-RDClientAccessName -ConnectionBroker $rolesCBPrimary -ClientAccessName $appConfig.clientAccessFQDN
  #DBGER $MyInvocation.MyCommand.Name $error
  #DBGEND


  DBG ('Should we add any GW role: {0}' -f ((Get-CountSafe $rolesGW) -ge 1))
  if ((Get-CountSafe $rolesGW) -ge1) {
  
    foreach ($oneRoleGW in $rolesGW) {

      DBG ('Install one RDGW server: {0} | {1}' -f $oneRoleGW, $appConfig.publicFQDN)
      DBGSTART
      $newAddedServer = $null
      $newAddedServer = Add-RDServer -Server $oneRoleGW -Role RDS-GATEWAY -ConnectionBroker $rolesCBPrimary -GatewayExternalFqdn $appConfig.publicFQDN
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $newAddedServer }
    }
  }

  #
  #

  DBG ('Should we apply a custom RDP certificate: {0}' -f (Is-NonNull $appConfig.cert.one))

  if (Is-NonNull $appConfig.cert.one) {

    $rdpCertificate = Enroll-AppCertificate $appConfig.cert.one

    if (Is-ValidString $rdpCertificate) {

      $exportedPfx = Get-DataFileApp 'rdp-server-certificate' $null '.pfx'

      DBG ('Export the enrolled certificate: {0} | file = {1} | pwd = {1}' -f $rdpCertificate, $exportedPfx, $appConfig.pfxPwd)
      Run-Process 'certutil' ('-p "{0}" -exportpfx My "{1}" "{2}" NoChain,NoRoot' -f $appConfig.pfxPwd, $rdpCertificate, $exportedPfx)

      $pfxPassword = ConvertTo-SecureString $appConfig.pfxPwd -AsPlainText -Force

      DBG ('Apply the certificate to the RDCB: {0}' -f $exportedPfx)
      DBGSTART
      Set-RDCertificate -Role RDRedirector -ImportPath $exportedPfx -Password $pfxPassword -ConnectionBroker $rolesCBPrimary -Force
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { (Get-RDCertificate -Role RDRedirector -ConnectionBroker $rolesCBPrimary).Level -ne 'Trusted' }

      DBG ('Apply the certificate to the RD publishing: {0}' -f $exportedPfx)
      DBGSTART
      Set-RDCertificate -Role RDPublishing -ImportPath $exportedPfx -Password $pfxPassword -ConnectionBroker $rolesCBPrimary -Force
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { (Get-RDCertificate -Role RDPublishing -ConnectionBroker $rolesCBPrimary).Level -ne 'Trusted' }

      DBG ('Apply the certificate to the RDWA: {0}' -f $exportedPfx)
      DBGSTART
      Set-RDCertificate -Role RDWebAccess -ImportPath $exportedPfx -Password $pfxPassword -ConnectionBroker $rolesCBPrimary -Force
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { (Get-RDCertificate -Role RDWebAccess -ConnectionBroker $rolesCBPrimary).Level -ne 'Trusted' }

      DBG ('Apply the certificate to the RDGW: {0} | {1}' -f ((Get-CountSafe $rolesGW) -ge 1), $exportedPfx)
      if (((Get-CountSafe $rolesGW) -ge 1)) {

        DBGSTART
        Set-RDCertificate -Role RDGateway -ImportPath $exportedPfx -Password $pfxPassword -ConnectionBroker $rolesCBPrimary -Force
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { (Get-RDCertificate -Role RDGateway -ConnectionBroker $rolesCBPrimary).Level -ne 'Trusted' }
      }
    }
  }

  #
  #

  DBG ('Builder RDP Mgr finished')
}


if (-not (Contains-Safe $rolesToInstall 'Mgr')) {

  DBG ('We are not RD builder Manager so wait until it finishes with us')

  [string] $builderManagerWaitParams = $null
  foreach ($oneAppConfig in $allAppConfigs) {

    $oneRolesRequested = Split-MultiValue $oneAppConfig.roles
    DBG ('One app config roles requested: #{0} | {1}' -f (Get-CountSafe $oneRolesRequested), ($oneRolesRequested -join ','))
  
    if (Contains-Safe $oneRolesRequested Mgr) {

      DBGIF ('Already one builder RDP manager found: {0}' -f $builderManagerWaitParams) { Is-ValidString $builderManagerWaitParams }
      $builderManagerWaitParams = Get-MachineWaitParams $oneAppConfig 'waitParams'
    }
  }
 
  DBG ('Builder RDP Manager determined to wait for: {0}' -f $builderManagerWaitParams)
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $builderManagerWaitParams } 
  Wait-Machine $builderManagerWaitParams -subPhase 'RdpRolesFinished'
}


Finish-Machine $false 'RdpRolesFinished'

DBG ('Add the other servers to the server manager')
[Collections.ArrayList] $rolesAll = @()
foreach ($oneRoleCB in $rolesCB) { [void] $rolesAll.Add($oneRoleCB) }
foreach ($oneRoleLS in $rolesLS) { [void] $rolesAll.Add($oneRoleLS) }
foreach ($oneRoleWA in $rolesWA) { [void] $rolesAll.Add($oneRoleWA) }
foreach ($oneRoleGW in $rolesGW) { [void] $rolesAll.Add($oneRoleGW) }
foreach ($oneRoleSH in $rolesSH) { [void] $rolesAll.Add($oneRoleSH) }
$rolesAll | Select -Unique | ? { $_ -ne $global:thisComputerFQDN } | % { Add-ServerManagerServer $_ }


# SIG # Begin signature block
# MIIc2AYJKoZIhvcNAQcCoIIcyTCCHMUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUlFQH3r6cO870NizW/udGREuc
# 0G6gghgEMIIE5TCCA82gAwIBAgIQOb1CntKBbrrVvMkDtLpl5zANBgkqhkiG9w0B
# AQsFADB1MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEpMCcG
# A1UECxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIzAhBgNVBAMT
# GlN0YXJ0Q29tIENsYXNzIDIgT2JqZWN0IENBMB4XDTE2MTIwMTE1NTExM1oXDTE4
# MTIwMTE1NTExM1owUTELMAkGA1UEBhMCQ1oxGjAYBgNVBAgMEUppaG9tb3JhdnNr
# eSBLcmFqMQ0wCwYDVQQHDARCcm5vMRcwFQYDVQQDDA5PbmRyZWogU2V2ZWNlazCC
# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK/RPYTY9Om2rIfSV/e5KbKi
# vWf3IjgQurayOJ75VvQj8ISgU2+cAK4cS2GQRvY1j1q3ctZy/3JuajKgW/5qIGMD
# MLxGFjL7CDjlZ+hw/ZVYTQKLw+jx89M4gQSiLM1048lD0GqCJSBC6L8/eu5u/ByX
# VGSRsaoZbgLEvaPf3xMwO111IVLqkSrHrbmVfwLH4ebDr3nqEbG3luHWk1r7c90y
# blCy9MONyzVWCZHeiy/HHoLyz1WVHtTe7JL4nRL57XZaMELCZahXax90hiiDygOj
# m3GPEmVpBEEm1QRfCrptVE/ozdIJvvOBcDoOVqisflb0ytpY1Vpz6wg9Gc7tqCEC
# AwEAAaOCAZMwggGPMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD
# AzAJBgNVHRMEAjAAMB0GA1UdDgQWBBRtr0qNzYUFiC3lLNKDfV8x03o3HzAfBgNV
# HSMEGDAWgBQ+YpOa18cZ7j6PSRCFVRUg45SEHDBtBggrBgEFBQcBAQRhMF8wJAYI
# KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLnN0YXJ0c3NsLmNvbTA3BggrBgEFBQcwAoYr
# aHR0cDovL2FpYS5zdGFydHNzbC5jb20vY2VydHMvc2NhLmNvZGUyLmNydDA2BgNV
# HR8ELzAtMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0c3NsLmNvbS9zY2EtY29kZTIu
# Y3JsMCMGA1UdEgQcMBqGGGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tLzBRBgNVHSAE
# SjBIMAgGBmeBDAEEATA8BgsrBgEEAYG1NwECBTAtMCsGCCsGAQUFBwIBFh9odHRw
# czovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5MA0GCSqGSIb3DQEBCwUAA4IBAQCb
# kYhLx7SGEqbJjJD4U8+EDjkoTd6eSlnX/L5u9QN9wkfX3bNdHfA9gdmhPr45hyev
# RmNG234OaGWNi7Ktnri+w2uf4UvPuk9zBLQ+IZ43x1e5sZqEnxbsvyg4qaFersIS
# PpLKnIV2HVB/ubgkDtDDjvCE4wIM1ELJ2eFCARwRnj/nvt5Y8UmEl1nTyK4MXUfe
# 9T/x4VklXyjZZgYBCjEDOSctGgHeI8UpfwM3WLdPuqZYhSdaXhoXRZ4thZvH7uaD
# evGkyKzbXOVmOy84H/rEU1XljkVkt+/tcwA9Jo83fIMnwS8UbmkUn42MZ8tKy13p
# QMH4gN83tg1NgX4Pf633MIIF2DCCA8CgAwIBAgIQbDvSft08lJ6Vjiips8dXoDAN
# BgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20g
# THRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
# ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcN
# MTUxMjE2MDEwMDA1WhcNMzAxMjE2MDEwMDA1WjB1MQswCQYDVQQGEwJJTDEWMBQG
# A1UEChMNU3RhcnRDb20gTHRkLjEpMCcGA1UECxMgU3RhcnRDb20gQ2VydGlmaWNh
# dGlvbiBBdXRob3JpdHkxIzAhBgNVBAMTGlN0YXJ0Q29tIENsYXNzIDIgT2JqZWN0
# IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuRQEWPeyxYYsCDJg
# rQgmwIF3uWgZ2RUrHRhp5NoalgWXLmR5Gqk9UTNa0Hdq9AKTQcOOunAbq9h7dG+Y
# 6Ne5qT5odqSJoCKsF9Yp+Lu4YZ/SB9BmDjBHICtwAh7+cwkccTS14n6prKin8Y46
# QAZ2ksr3eGzvWAVzfX+DUOmiVQLjAK6Wp8bCZHvj+FhAlS5Ne7/dggDeSVWnMyPm
# 2k/5YKOTVXExJJaAlYkmyH1OiC3soTkkGb6aJjGJPHiaiNJ4pjkySX5l2p4DQ7K1
# /J6ft5Vw9PuqwmYrF0ViGnn38kzB2d9UI9Q+dFmHUbV+cnr+FoGl6CiUDd5ZIF1H
# Mrb8hwIDAQABo4IBWjCCAVYwDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsG
# AQUFBwMDMBIGA1UdEwEB/wQIMAYBAf8CAQAwMgYDVR0fBCswKTAnoCWgI4YhaHR0
# cDovL2NybC5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMGYGCCsGAQUFBwEBBFowWDAk
# BggrBgEFBQcwAYYYaHR0cDovL29jc3Auc3RhcnRzc2wuY29tMDAGCCsGAQUFBzAC
# hiRodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9jYS5jcnQwHQYDVR0OBBYE
# FD5ik5rXxxnuPo9JEIVVFSDjlIQcMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0
# aEPQQa7yMD8GA1UdIAQ4MDYwNAYEVR0gADAsMCoGCCsGAQUFBwIBFh5odHRwOi8v
# d3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3kwDQYJKoZIhvcNAQELBQADggIBAGOlPNWz
# bSco2Ou6U68wC+pKXRLV+ZrKcPpMY4zXTVR+RupS54WhJCManab2P1ncPlHTbRMb
# PjfHnyj0sIdpvwcV49n0nizMF3MBxaKJEnBBEfHs9Krgjc4qKjR2nOywlzxJ0M27
# RthR5XjyjQ1ofHlOisYgMzcyKyMT7YYpxxoC0wTgAh0DNmE5Q/GKFOaDd3S5gTqr
# R9AQzGaC3IxCKBFtcwvk51W98lNRtMbm+oJze5T+dL2wIhyWK58sEIl2paAVfAfW
# GH3umYL46scLn8BXDFchN1Jgrg07DqY6gxCqSdubPhVHZInuVagktWmrnS6N9V/v
# VLz+OaX4Mkas8n1J1RIR+GV8ZQVmTM49l6L+fpv/h95MWLhQOcXanbIY/2cdNEuz
# 5AkhfvDNTQnLxYEMIyMOtW2QIwwZdz92vMTU17G9goxXYjSm09yw+iBniH9G/xGz
# 39BV3bwa8ZtKHzDoZ54HT6JT2AraDhrWTwFXv8Xrvv2cir+k0h5bIWlDtImH7Jm1
# 52edb77f5JI8JrPf6jxcUrhNH4xHxe2kGs8ERA39oYlT0dKQIb0obTN6FOF63hBR
# FFhGB7NuX2FeFjJsZFCkoJkpsEauObb7Rh+C02+fnHfoi6ivKwUC9BOsWlI4xn7G
# Me27niL6k7wpK0L6MTG5/6gxwosqaMA1aukwMIIGajCCBVKgAwIBAgIQAwGaAjr/
# WLFr1tXq5hfwZjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UE
# ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYD
# VQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTEwHhcNMTQxMDIyMDAwMDAwWhcN
# MjQxMDIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzERMA8GA1UEChMIRGlnaUNlcnQx
# JTAjBgNVBAMTHERpZ2lDZXJ0IFRpbWVzdGFtcCBSZXNwb25kZXIwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjZF38fLPggjXg4PbGKuZJdTvMbuBTqZ8f
# ZFnmfGt/a4ydVfiS457VWmNbAklQ2YPOb2bu3cuF6V+l+dSHdIhEOxnJ5fWRn8YU
# Oawk6qhLLJGJzF4o9GS2ULf1ErNzlgpno75hn67z/RJ4dQ6mWxT9RSOOhkRVfRiG
# BYxVh3lIRvfKDo2n3k5f4qi2LVkCYYhhchhoubh87ubnNC8xd4EwH7s2AY3vJ+P3
# mvBMMWSN4+v6GYeofs/sjAw2W3rBerh4x8kGLkYQyI3oBGDbvHN0+k7Y/qpA8bLO
# cEaD6dpAoVk62RUJV5lWMJPzyWHM0AjMa+xiQpGsAsDvpPCJEY93AgMBAAGjggM1
# MIIDMTAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAK
# BggrBgEFBQcDCDCCAb8GA1UdIASCAbYwggGyMIIBoQYJYIZIAYb9bAcBMIIBkjAo
# BggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCCAWQGCCsG
# AQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABD
# AGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABh
# AGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQBy
# AHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBn
# ACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABs
# AGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABp
# AG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBl
# AGYAZQByAGUAbgBjAGUALjALBglghkgBhv1sAxUwHwYDVR0jBBgwFoAUFQASKxOY
# spkH7R7for5XDStnAs0wHQYDVR0OBBYEFGFaTSS2STKdSip5GoNL9B6Jwcp9MH0G
# A1UdHwR2MHQwOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dEFzc3VyZWRJRENBLTEuY3JsMDigNqA0hjJodHRwOi8vY3JsNC5kaWdpY2VydC5j
# b20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNybDB3BggrBgEFBQcBAQRrMGkwJAYI
# KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1
# aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0Et
# MS5jcnQwDQYJKoZIhvcNAQEFBQADggEBAJ0lfhszTbImgVybhs4jIA+Ah+WI//+x
# 1GosMe06FxlxF82pG7xaFjkAneNshORaQPveBgGMN/qbsZ0kfv4gpFetW7easGAm
# 6mlXIV00Lx9xsIOUGQVrNZAQoHuXx/Y/5+IRQaa9YtnwJz04HShvOlIJ8OxwYtNi
# S7Dgc6aSwNOOMdgv420XEwbu5AO2FKvzj0OncZ0h3RTKFV2SQdr5D4HRmXQNJsQO
# fxu19aDxxncGKBXp2JPlVRbwuwqrHNtcSCdmyKOLChzlldquxC5ZoGHd2vNtomHp
# igtt7BIYvfdVVEADkitrwlHCCkivsNRu4PQUCjob4489yq9qjXvc2EQwggbNMIIF
# taADAgECAhAG/fkDlgOt6gAK6z8nu7obMA0GCSqGSIb3DQEBBQUAMGUxCzAJBgNV
# BAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdp
# Y2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAe
# Fw0wNjExMTAwMDAwMDBaFw0yMTExMTAwMDAwMDBaMGIxCzAJBgNVBAYTAlVTMRUw
# EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
# ITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMTCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBAOiCLZn5ysJClaWAc0Bw0p5WVFypxNJBBo/JM/xN
# RZFcgZ/tLJz4FlnfnrUkFcKYubR3SdyJxArar8tea+2tsHEx6886QAxGTZPsi3o2
# CAOrDDT+GEmC/sfHMUiAfB6iD5IOUMnGh+s2P9gww/+m9/uizW9zI/6sVgWQ8DIh
# FonGcIj5BZd9o8dD3QLoOz3tsUGj7T++25VIxO4es/K8DCuZ0MZdEkKB4YNugnM/
# JksUkK5ZZgrEjb7SzgaurYRvSISbT0C58Uzyr5j79s5AXVz2qPEvr+yJIvJrGGWx
# wXOt1/HYzx4KdFxCuGh+t9V3CidWfA9ipD8yFGCV/QcEogkCAwEAAaOCA3owggN2
# MA4GA1UdDwEB/wQEAwIBhjA7BgNVHSUENDAyBggrBgEFBQcDAQYIKwYBBQUHAwIG
# CCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgwggHSBgNVHSAEggHJMIIBxTCC
# AbQGCmCGSAGG/WwAAQQwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2lj
# ZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFW
# HoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBm
# AGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0
# AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAv
# AEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0
# AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAg
# AGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBw
# AG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBu
# AGMAZQAuMAsGCWCGSAGG/WwDFTASBgNVHRMBAf8ECDAGAQH/AgEAMHkGCCsGAQUF
# BwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMG
# CCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRB
# c3N1cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2
# hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290
# Q0EuY3JsMB0GA1UdDgQWBBQVABIrE5iymQftHt+ivlcNK2cCzTAfBgNVHSMEGDAW
# gBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEARlA+ybco
# JKc4HbZbKa9Sz1LpMUerVlx71Q0LQbPv7HUfdDjyslxhopyVw1Dkgrkj0bo6hnKt
# OHisdV0XFzRyR4WUVtHruzaEd8wkpfMEGVWp5+Pnq2LN+4stkMLA0rWUvV5PsQXS
# Dj0aqRRbpoYxYqioM+SbOafE9c4deHaUJXPkKqvPnHZL7V/CSxbkS3BMAIke/MV5
# vEwSV/5f4R68Al2o/vsHOE8Nxl2RuQ9nRc3Wg+3nkg2NsWmMT/tZ4CMP0qquAHzu
# nEIOz5HXJ7cW7g/DvXwKoO4sCFWFIrjrGBpN/CohrUkxg0eVd3HcsRtLSxwQnHcU
# wZ1PL1qVCCkQJjGCBD4wggQ6AgEBMIGJMHUxCzAJBgNVBAYTAklMMRYwFAYDVQQK
# Ew1TdGFydENvbSBMdGQuMSkwJwYDVQQLEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9u
# IEF1dGhvcml0eTEjMCEGA1UEAxMaU3RhcnRDb20gQ2xhc3MgMiBPYmplY3QgQ0EC
# EDm9Qp7SgW661bzJA7S6ZecwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI
# oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB
# CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFGxp9UoyhbjVliatdQsX
# 5/dVz0gUMA0GCSqGSIb3DQEBAQUABIIBABqfCviFr+Fg70TIlakmqaBmrSYMzErK
# rFHa+EtWniR/OuNIwFY5PkulHjQ2h3lqEmSTiz9c09N7nFCnfh8FcFKHrsXcAhTA
# neORlkylIdzRrN72w1d+1AHmmb2Q7iMuei9f/0f1tJfndsLipTGQL7fXrPp2D3q1
# ozfVotpetvivr5TX2JhDpluYMaCmc7TwhJPF+2E6YcbdqLBbp31itgBn5fsQ/E/g
# LaGL9G7LQhZBg5nuA8AcMUWh7oZLpzP1WZq/K2GZP6XO7zUMtSAkh/TkrCs+VaUq
# l+HgLGXwV5bB3GI+cE6Ydr7LfJl1/+aL7GYoKWcdXimFslnwBOY5qFyhggIPMIIC
# CwYJKoZIhvcNAQkGMYIB/DCCAfgCAQEwdjBiMQswCQYDVQQGEwJVUzEVMBMGA1UE
# ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYD
# VQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTECEAMBmgI6/1ixa9bV6uYX8GYw
# CQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcN
# AQkFMQ8XDTE3MDMyNzA3MjMzOVowIwYJKoZIhvcNAQkEMRYEFFuUeyM5xq5kIAd7
# zbjCXdz8DbHWMA0GCSqGSIb3DQEBAQUABIIBAH6zCYP+IRAm9dUNnp1Zs6o9V7VG
# YNN9frcwomNSVWTL6CIBZkAqMg0Ys0mfdbsfHqxnCKUohy2QzvemNGtJgqDTKab8
# Rkksx75V9Ug1NJWybl85L4nLr1gpdNSw8qQP5QJGjUimU6rkL+5CN9nQupTQgVXw
# 1hYo7/HIavyChdd4Ofr+EgBdGniIrZ15Syv56P3c+JKmFn9P0C6Dhc92/PahxUmJ
# +luIjjN30wbfvs9BlAbyIIuVuFGu2+ssy1jP45NahvaeKC5DAIarzSASQm5fOXLl
# ZqHKvi+iSa9QCV26JsqLhTsjvFH6nefpzyrqrIyYPlVo4uN4+TTl/+JDZcI=
# SIG # End signature block