ADLAB PowerShell source file: buildup-dhcp.ps1

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



$global:outClass = 'main'

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

$vmName = $args[0]

DBG ("DHCP Server Installation library")
Redirect-TempToOutput
Load-VMConfig

Find-MarkedVolumes

$appTag = 'dhcp'
$appConfig = $vmConfig.$appTag
$firstAppHost = Get-FirstAppHostInInstance $appTag $appConfig.instance 'waitParams'
$firstAppHostInInstance = Check-FirstAppHostInInstance $appTag $appConfig.instance


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


#====================
DBG ('Installing DHCP server.')


if ($global:thisOSVersionNumber -ge 6.2) {

  DBG ('Load PowerShell module to support DHCP cmdlets')
  Import-Module DhcpServer -EV er -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $er
}


DBG ('Configure local DHCP server')

DBG ('Configure the DHCP server to start automatically')
Set-Service -Name DHCPServer -StartupType Automatic -EV er -EA SilentlyContinue
DBGER $MyInvocation.MyCommand.Name $er

DBG ('Start the local DHCP server to finish configuraiton')
Start-Service -Name DHCPServer -EV er -EA SilentlyContinue
DBGER $MyInvocation.MyCommand.Name $er


if ($global:thisOSVersionNumber -ge 6) {

  # no such command exists on 5.x, but the groups are created automatically
  DBG ('Add DHCP server local groups with NETSH')
  Run-Process 'netsh' 'dhcp add securitygroups'

} else {

  # Bug?? in 2003 server where the DHCPServer service only creates the "DHCP Administrators" group
  # when started for the first time and our code fails with "trust relationship with the primary domain failed" error
  # Thus we just sleep here to allow some time for the DHCPServer provisioning
  DBG ('One minute sleep after first DHCPServer service start to allow for "DHCP Administrators" group creation')
  Start-Sleep 60
}


DBG ('Add install and admin accounts members of the DHCP Administrators group')
Add-MemberLocalGroup 'DHCP Administrators' $appConfig.app.iLogin $appConfig.app.iDomain
Add-MemberLocalGroup 'DHCP Administrators' $appConfig.app.aGroup $appConfig.app.iDomain

  
DBG ('Restart the local DHCP server to finish creation of its security groups')
Restart-Service -Name DHCPServer -EV er -EA SilentlyContinue
DBGER $MyInvocation.MyCommand.Name $er


# Note: on 2k3, according to the KB867465, we must first open DHCPMGMT.MSC to populate the database with default options
if ($global:thisOSVersion -like '5.*') {

  DBG ('Resolve missing options shitty KB867465 bug on 5.x')
          
  Stop-Service -Name DhcpServer -Force -EV er -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $er

  $dhcpRootFolder = Join-Path $env:SystemRoot 'System32\dhcp'
  Remove-Item -Path ('{0}\*.*' -f $dhcpRootFolder) -Force -EV er -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $er

  Copy-Item (Join-Path $global:rootDir 'Dhcp\dhcp.mdb') $dhcpRootFolder -Force -EV er -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $er

  Start-Service -Name DhcpServer -EV er -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $er
}


DBG ('DHCP scopes found: {0}' -f (Get-CountSafe $appConfig.scope))

if ((Get-CountSafe $appConfig.scope) -ge 1) {

  foreach ($oneScope in $appConfig.scope) {

    DBG ('Going to process a scope. First compute its parameters.')

    $scIPStart = (Split-MultiValue $oneScope.ip)[0]
    $scIPEnd   = (Split-MultiValue $oneScope.ip)[1]
    $scNBOff = Parse-BoolSafe $oneScope.disableNetBIOS
    $scLease = $oneScope.lease
    
    $scSfx  = $oneScope.dnsSfx
    if ($scSfx -eq $global:emptyValueMarker) { 
    
      $scSfx = $global:thisComputerPrimaryDNSSfx
    }

    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $scIPStart }
    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $scIPEnd }


    DBG ('Find a local NIC that will serve the current scope: {0} | {1}' -f $scIPStart, $scIPEnd)
    $bestNIC = $null

    $allValidNICs = Get-WMIQueryArray '.' $global:wmiFltValidNIC 'root/CIMv2' $false

    foreach ($oneValidNIC in $allValidNICs) {

      if ((Get-CountSafe $oneValidNIC.IPAddress) -gt 0) {

        $wkNICSubnet = Get-IPSubnet $oneValidNIC.IPAddress[0] $oneValidNIC.IPSubnet[0]
        $wkStartSubnet = Get-IPSubnet $scIPStart $oneValidNIC.IPSubnet[0]
        $wkEndSubnet = Get-IPSubnet $scIPEnd $oneValidNIC.IPSubnet[0]

        if (($wkNICSubnet -eq $wkStartSubnet) -and ($wkNICSubnet -eq $wkEndSubnet) -and ($wkStartSubnet -eq $wkEndSubnet)) {

          if (Is-Null $bestNIC) {

            $bestNIC = $oneValidNIC
            DBG ('Found first connected NIC: {0} | {1}' -f $bestNIC.IPAddress[0], $bestNIC.IPSubnet[0])

          } elseif ((Get-IPSubnetBits $oneValidNIC.IPSubnet[0]) -gt (Get-IPSubnetBits $bestNIC.IPSubnet[0])) {

            $bestNIC = $oneValidNIC
            DBG ('Found better connected NIC: {0} | {1}' -f $bestNIC.IPAddress[0], $bestNIC.IPSubnet[0])
          }
        }
      }
    }

    if (Is-Null $bestNIC) {

      DBG ('Best NIC not found. Defaulting to the best server internal NIC')
      $bestNIC = Get-BestServerInternalNIC -saveCsvDebugFile (Get-DataFileApp 'nics' $null '.csv' -randomize $true)
      $bestNICFitsWell = $false

    } else {

      $bestNICFitsWell = $true
    }

    $scMask = $oneScope.mask
    if ($scMask -eq $global:emptyValueMarker) { 
    
      $scMask = $bestNIC.IPSubnet[0]
    }
    
    $scDNS  = $oneScope.ipDNS
    if ($scDNS -eq $global:emptyValueMarker) { 
    
      $scDNS = Format-MultiValue $bestNIC.DnsServerSearchOrder
    }
    
    $scSubnet = Get-IPSubnet $scIPStart $scMask

    $scGW   = $oneScope.ipGW
    if ($scGW -eq $global:emptyValueMarker) { 

      DBG ('We should determine default gateway automatically')    
      DBG ('Best NIC fits well? {0}' -f $bestNICFitsWell)

      if ($bestNICFitsWell) {

        $scGW = Format-MultiValue $bestNIC.DefaultIPGateway
      
      } else {

        $scGW = ''
      }

      
      if (Is-EmptyString $scGW) {

        DBG ('Our default dateway is empty, try to find a better alternative')
      
        $routerConfigured = Get-RegValue '.' 'SYSTEM\CurrentControlSet\Services\RemoteAccess' ConfigurationFlags DWord
        $rrasSvcState = Get-WMIValue '.' 'SELECT State FROM Win32_Service WHERE Name = "RemoteAccess"' State
        $rrasSvcStartup = Get-WMIValue '.' 'SELECT StartMode FROM Win32_Service WHERE Name = "RemoteAccess"' StartMode

        if (($routerConfigured -eq 1) -and ($rrasSvcState -eq 'Running') -and ($rrasSvcStartup -eq 'Auto')) {

          $scGW = Format-MultiValue $bestNIC.IPAddress[0]
          DBG ('We are doing routing so distribute us as the default gateway: {0}' -f $scGW)
        }
      }


      DBGIF 'It was requested to apply an automatic gateway but none found' { Is-EmptyString $scGW }
    }
    
    if ($bestNICFitsWell) {

      $scName = 'Net {0} {1}' -f $scSubnet, (Get-NICNamesFromIPs $bestNIC.IPAddress[0])

    } else {

      $scName = 'Net {0}' -f $scSubnet
    }


    DBG ('DHCP scope params determined: {0} | start = {1} | end = {2} | mask = {3} | gw = {4} | dns = {5} | sfx = {6} | nbtOff = {7} | lease = {8}' -f $scName, $scIPStart, $scIPEnd, $scMask, $scGW, $scDNS, $scSfx, $scNBOff, $scLease)

    if ((Is-ValidString $scIPStart) -and (Is-ValidString $scIPEnd) -and (Is-ValidString $scMask)) {

      if ($global:thisOSVersionNumber -lt 6.2) {

        DBG ('Add the scope with NETSH on 6.1 and older')
        Run-Process 'netsh' ('dhcp server add scope "{0}" "{1}" "{2}"' -f $scSubnet, $scMask, $scName)

        Run-Process 'netsh' ('dhcp server scope "{0}" add iprange "{1}" "{2}"' -f $scSubnet, $scIPStart, $scIPEnd)

        # 003 - router
        # 006 - dns
        # 015 - dns name
        # 001, vendor - disable NetBIOS

        if (Is-ValidString $scGW) {

          Run-Process 'netsh' ('dhcp server scope "{0}" set optionvalue 003 IPADDRESS "{1}"' -f $scSubnet, ((Split-MultiValue $scGW) -join ' '))
        }

        if (Is-ValidString $scDNS) {

          if ($global:thisOSVersionNumber -ge 6.0) {

            Run-Process 'netsh' ('dhcp server scope "{0}" set optionvalue 006 IPADDRESS DhcpFullForce "{1}"' -f $scSubnet, ((Split-MultiValue $scDNS) -join ' '))
          
          } else {

            Run-Process 'netsh' ('dhcp server scope "{0}" set optionvalue 006 IPADDRESS "{1}"' -f $scSubnet, ((Split-MultiValue $scDNS) -join ' '))
          }
        }

        if (Is-ValidString $scSfx) {

          Run-Process 'netsh' ('dhcp server scope "{0}" set optionvalue 015 STRING "{1}"' -f $scSubnet, $scSfx)
        }

        if ($scNBOff) {

          Run-Process 'netsh' ('dhcp server scope "{0}" set optionvalue 001 DWORD Vendor="Microsoft Options" 1' -f $scSubnet)
        }

        Run-Process 'netsh' ('dhcp server scope "{0}" set state 1' -f $scSubnet)


        DBG ('Just recap the configured option values to verify their successful configuration')
        Run-Process 'netsh' ('dhcp server scope "{0}" show optionvalue' -f $scSubnet)


        if (Is-ValidString $oneScope.superscope) {
         
          DBG ('Add the current scope into a superscope: {0}' -f $oneScope.superscope)
          Run-Process 'netsh' ('dhcp server scope "{0}" set superscope "{1}" 1' -f $scSubnet, $oneScope.superscope)
        }

      
      } else {

        DBG ('Add the scope with PowerShell on 6.2 and newer')
        DBGSTART
        Add-DhcpServerv4Scope -StartRange $scIPStart -EndRange $scIPEnd -SubnetMask $scMask -Name $scName -LeaseDuration $scLease -Type Both -State Active
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        if (Is-ValidString $scGW) {

          DBGSTART
          Set-DhcpServerv4OptionValue -ScopeId $scSubnet -Router (Split-MultiValue $scGW)
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }

        if (Is-ValidString $scDNS) {

          DBGSTART
          Set-DhcpServerv4OptionValue -ScopeId $scSubnet -DnsServer (Split-MultiValue $scDNS) -Force
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }

        if (Is-ValidString $scSfx) {
        
          DBGSTART
          Set-DhcpServerv4OptionValue -ScopeId $scSubnet -DnsDomain $scSfx
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }

        if ($scNBOff) {

          DBGSTART
          Set-DhcpServerv4OptionValue -ScopeId $scSubnet -OptionId 1 -VendorClass "Microsoft Options" -Value 1
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }

        
        DBG ('Just recap the configured option values to verify their successful configuration: {0}' -f ((Get-DhcpServerv4OptionValue -ScopeId $scSubnet) | Out-String))


        if (Is-ValidString $oneScope.superscope) {
         
          DBG ('Add the current scope into a superscope: {0}' -f $oneScope.superscope)
          DBGSTART
          Add-DhcpServerv4Superscope -SuperscopeName $oneScope.superscope -ScopeId $scSubnet
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }
      }
    }
  }
}



#======================
[string] $bindingIPs = ''

DBG ('Should modify binding: {0}' -f (Is-ValidString $appConfig.binding))
DBGIF $MyInvocation.MyCommand.Name { ($global:thisOSVersionNumber -lt 6.0) -and (Is-ValidString $appConfig.binding) }

if (($global:thisOSVersionNumber -ge 6.0) -and (Is-ValidString $appConfig.binding)) {

  $bindings = Split-MultiValue $appConfig.binding
  DBG ('Found binding specs: {0} | {1}' -f (Get-CountSafe $bindings), $appConfig.binding)

  if ((Get-CountSafe $bindings) -gt 0) {

    foreach ($oneBinding in $bindings) {

      DBG ('Apply binding: {0}' -f $oneBinding)

      [string] $bindingIPs = Strip-ValueFlags $oneBinding
      
      if (Has-ValueFlags $oneBinding 'S') {

        $bindingIPs = Add-MultiValue $bindingIPs (Get-MyStaticIPs) $true
      }

      if (Has-ValueFlags $oneBinding 'P') {

        $bindingIPs = Add-MultiValue $bindingIPs (Get-MyPrivateIPs) $true
      }

      if (Has-ValueFlags $oneBinding 'X') {

        $bindingIPs = Add-MultiValue $bindingIPs (Get-MultiValueIntersection (Get-MyPrivateIPs) (Get-MyStaticIPs)) $true
      }

      DBG ('Binding to: {0}' -f $bindingIPs)

      $bindingNames = Get-NICNamesFromIPs $bindingIPs
      DBG ('Binding to names: {0}' -f $bindingNames)


      if ($global:thisOSVersionNumber -lt 6.2) {

        DBG ('Clear all DHCP bindings with NETSH first')
        (Split-MultiValue (Get-NICNamesFromIPs (Get-MyAllIPs))) | % { Run-Process 'netsh' ('dhcp server set bindings interface = "{0}" mode = disable' -f $_) }

        DBG ('Bind the DHCP server with NETSH')
        (Split-MultiValue $bindingNames) | % { Run-Process 'netsh' ('dhcp server set bindings interface = "{0}" mode = enable' -f $_) }

        DBG ('Display resulting bindings just to be sure')
        Run-Process 'netsh' 'dhcp server show bindings'

      } else {

        DBG ('Clear all DHCP bindings with PowerShell first')
        Get-DhcpServerv4Binding | Set-DhcpServerv4Binding -BindingState $false -EV er -EA SilentlyContinue 
        DBGER $MyInvocation.MyCommand.Name $er
        
        DBG ('Bind the DHCP server with PowerShell')
        Set-DhcpServerv4Binding -InterfaceAlias (Split-MultiValue $bindingNames) -BindingState $true -EV er -EA SilentlyContinue
        DBGER $MyInvocation.MyCommand.Name $er

        DBG ('Display resulting bindings just to be sure: {0}' -f (Get-DhcpServerv4Binding | Out-String))
      }
    }
  }
}


DBG ('Should authorize: {0}' -f ((Parse-BoolSafe $appConfig.authorize) -and (Is-ValidString $global:thisComputerDomain)))
if ((Parse-BoolSafe $appConfig.authorize) -and (Is-ValidString $global:thisComputerDomain)) {

  if ($global:thisOSVersionNumber -lt 6.2) {

    if (Is-EmptyString $bindingIPs) { $bindingIPs = (Get-BestServerInternalNIC).IPAddress[0] }

    DBG ('Authorize the DHCP server in its forest with NETSH')
    Run-Process 'netsh' 'dhcp server initiate auth'

    foreach ($oneBindingIP in (Split-MultiValue $bindingIPs)) { 
    
      DBGIF $MyInvocation.MyCommand.Name { -not (Is-IPv4OrIPv6Address $oneBindingIP) }

      if ($oneBindingIP -notlike 'FE80::*:*:*:*') {

        Run-Process 'netsh' ('dhcp add server "{0}.{1}" "{2}"' -f $global:thisComputerHost, $global:thisComputerDomain, $oneBindingIP)
      
      } else {

        DBG ('Not authorizing a link-locall address: {0}' -f $oneBindingIP)
      }
    }
      
  } else {

    DBG ('Authorize the DHCP server in its forest with PowerShell')
    Add-DhcpServerInDC -EV er -EA SilentlyContinue
    DBGER $MyInvocation.MyCommand.Name $er
  }
}


DBG ('Should disable DNS updating: {0}' -f (Parse-BoolSafe $appConfig.dnsUpdateOff))
if (Parse-BoolSafe $appConfig.dnsUpdateOff) {

  if ($global:thisOSVersionNumber -lt 6.2) {

    DBG ('Disable server wide DNS update with NETSH')
    Run-Process 'netsh' 'dhcp server set dnsconfig 0'
      
  } else {
  
    DBG ('Disable server wide DNS update with PowerShell')
    Set-DhcpServerv4DnsSetting -DynamicUpdates Never -EV er -EA SilentlyContinue
    DBGER $MyInvocation.MyCommand.Name $er
  }
}





# SIG # Begin signature block
# MIIYMAYJKoZIhvcNAQcCoIIYITCCGB0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAySpwf9sj5J6re
# NXcj0nuL6YOwxsvJadcCNbE7/P3Si6CCE0cwggYEMIID7KADAgECAgoqHIRwAAEA
# AAB/MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYTAkNaMRcwFQYDVQQIEw5DemVj
# aCBSZXB1YmxpYzENMAsGA1UEBxMEQnJubzEQMA4GA1UEChMHU2V2ZWNlazEjMCEG
# A1UEAxMaU2V2ZWNlayBFbnRlcnByaXNlIFJvb3QgQ0EwHhcNMTkwNjExMTkyMzMy
# WhcNMjQwNjA5MTkyMzMyWjCBjzELMAkGA1UEBhMCQ1oxFzAVBgNVBAgTDkN6ZWNo
# IFJlcHVibGljMQ0wCwYDVQQHEwRCcm5vMRwwGgYDVQQKExNJbmcuIE9uZHJlaiBT
# ZXZlY2VrMRcwFQYDVQQDEw5PbmRyZWogU2V2ZWNlazEhMB8GCSqGSIb3DQEJARYS
# b25kcmVqQHNldmVjZWsuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
# AQEAnkjWNkK4FfUUN8iAN91ry+wsSn8cFKJbMnROAqTrx8t3H315p2/bUG2DosCF
# Odu0WcaTOLdm5obhT+/3O7BqpdcnlWKlSEz4AL9zQeCbe4++NObBVPBbPE16j9C4
# xELoXW/Ti86C2PEkN5azGUvxGxzQQ45g32OsEI+Bh05qHMkk3oQ6L8O0Fpd5W4e+
# L4HuKS3JOikNhhryTNPD9grF/0wXTzn94TrL1GohuaCPh8g9HOtMoDCd+ExnqV8q
# 4k60D37BOK1I81hYFIBn8MvCsjMRC5TK87MtI7aUUIeve5kopc8ZpxNti3F/+Puh
# 4UUxL3nKjfAM6HE0b7FqkfkRpwIDAQABo4IBgjCCAX4wEwYDVR0lBAwwCgYIKwYB
# BQUHAwMwDgYDVR0PAQH/BAQDAgbAMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYBBQUH
# AwMwHQYDVR0OBBYEFOKbNkkiAht2GxCISJMJxLg4gOC9MB0GA1UdEQQWMBSBEm9u
# ZHJlakBzZXZlY2VrLmNvbTAfBgNVHSMEGDAWgBQNnMgyfdUi8l9UfithS4FQ88Vs
# wDBSBgNVHR8ESzBJMEegRaBDhkFodHRwOi8vcGtpLnNldmVjZWsuY29tL0NBL1Nl
# dmVjZWslMjBFbnRlcnByaXNlJTIwUm9vdCUyMENBKDEpLmNybDCBhgYIKwYBBQUH
# AQEEejB4ME0GCCsGAQUFBzAChkFodHRwOi8vcGtpLnNldmVjZWsuY29tL0NBL1Nl
# dmVjZWslMjBFbnRlcnByaXNlJTIwUm9vdCUyMENBKDEpLmNydDAnBggrBgEFBQcw
# AYYbaHR0cDovL3BraS5zZXZlY2VrLmNvbS9vY3NwMA0GCSqGSIb3DQEBCwUAA4IC
# AQCfr6XDtt/O8OBr+X5l49UBLaJrjUXHkAHofdC7p7BLCXIs4GYIti1lf6pas5yB
# Q428aKITITq/vEHUTyiiyKtzVkafILWXXKPxy+zmmuw9odB3Hea4ECNpcaG8UNtz
# vMm1Dr0ZrkENhcv6I3tNhRr2AOE9AKOfnVEullFD/mZqfmaNkhpnl31jk7OMSUQc
# oY8qD6BDQP9371C10gJOmp57sHfPa4Vn5E4aNzn8+o9C9HI7nNagZF5BamKOFdR2
# ui7K3krMbTuDHo+ZcA9nHnzZqiVKpEBFu8lGv9Mf+GDb9yxz6EjV3xS2RcnywX2v
# z0VUt2NGno8LudrnWpgrRy4Sl7x6FwVVKtS/o7zFSIiHgntIKFv8urSKSTukCLFK
# Y9fBIDDlWFV1ZV1DNpNWxnexWIRv2AH7YlzKQCA4Rysn01hVeBGsWFkCr9J33LmV
# enQYpk9eoYMPRwAYg48r65wOOOzLvmyLSGllH88BMvmTQ9myXqwp6NDH1psljXTl
# PUbpf7w6IZwsY0dhGhP9iyqbcrGdK0Bnf8Za6Qdj3iXtwd1VgpatFZrxOM5KawCL
# pkYl1ABupbzNpWzmC+nfymqwbYiCogPt1vHOyF4EJ73ExVDCqXkpiNvFRqmu1eaZ
# IOdbPCdl00a9rk52NKqo/BUsw16TKsDEYTA/7ACbEsnERzCCBmowggVSoAMCAQIC
# EAMBmgI6/1ixa9bV6uYX8GYwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCVVMx
# FTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv
# bTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMB4XDTE0MTAyMjAw
# MDAwMFoXDTI0MTAyMjAwMDAwMFowRzELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp
# Z2lDZXJ0MSUwIwYDVQQDExxEaWdpQ2VydCBUaW1lc3RhbXAgUmVzcG9uZGVyMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo2Rd/Hyz4II14OD2xirmSXU7
# zG7gU6mfH2RZ5nxrf2uMnVX4kuOe1VpjWwJJUNmDzm9m7t3LhelfpfnUh3SIRDsZ
# yeX1kZ/GFDmsJOqoSyyRicxeKPRktlC39RKzc5YKZ6O+YZ+u8/0SeHUOplsU/UUj
# joZEVX0YhgWMVYd5SEb3yg6Np95OX+Koti1ZAmGIYXIYaLm4fO7m5zQvMXeBMB+7
# NgGN7yfj95rwTDFkjePr+hmHqH7P7IwMNlt6wXq4eMfJBi5GEMiN6ARg27xzdPpO
# 2P6qQPGyznBGg+naQKFZOtkVCVeZVjCT88lhzNAIzGvsYkKRrALA76TwiRGPdwID
# AQABo4IDNTCCAzEwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0l
# AQH/BAwwCgYIKwYBBQUHAwgwggG/BgNVHSAEggG2MIIBsjCCAaEGCWCGSAGG/WwH
# ATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw
# ggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgA
# aQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQA
# ZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcA
# aQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwA
# eQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkA
# YwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEA
# cgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIA
# eQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMB8GA1UdIwQYMBaA
# FBUAEisTmLKZB+0e36K+Vw0rZwLNMB0GA1UdDgQWBBRhWk0ktkkynUoqeRqDS/Qe
# icHKfTB9BgNVHR8EdjB0MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRBc3N1cmVkSURDQS0xLmNybDA4oDagNIYyaHR0cDovL2NybDQuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwdwYIKwYBBQUHAQEE
# azBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYB
# BQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRENBLTEuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQCdJX4bM02yJoFcm4bOIyAP
# gIfliP//sdRqLDHtOhcZcRfNqRu8WhY5AJ3jbITkWkD73gYBjDf6m7GdJH7+IKRX
# rVu3mrBgJuppVyFdNC8fcbCDlBkFazWQEKB7l8f2P+fiEUGmvWLZ8Cc9OB0obzpS
# CfDscGLTYkuw4HOmksDTjjHYL+NtFxMG7uQDthSr849Dp3GdId0UyhVdkkHa+Q+B
# 0Zl0DSbEDn8btfWg8cZ3BigV6diT5VUW8LsKqxzbXEgnZsijiwoc5ZXarsQuWaBh
# 3drzbaJh6YoLbewSGL33VVRAA5Ira8JRwgpIr7DUbuD0FAo6G+OPPcqvao173NhE
# MIIGzTCCBbWgAwIBAgIQBv35A5YDreoACus/J7u6GzANBgkqhkiG9w0BAQUFADBl
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
# d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
# b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMjExMTEwMDAwMDAwWjBiMQswCQYDVQQG
# EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
# cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTEwggEiMA0G
# CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDogi2Z+crCQpWlgHNAcNKeVlRcqcTS
# QQaPyTP8TUWRXIGf7Syc+BZZ3561JBXCmLm0d0ncicQK2q/LXmvtrbBxMevPOkAM
# Rk2T7It6NggDqww0/hhJgv7HxzFIgHweog+SDlDJxofrNj/YMMP/pvf7os1vcyP+
# rFYFkPAyIRaJxnCI+QWXfaPHQ90C6Ds97bFBo+0/vtuVSMTuHrPyvAwrmdDGXRJC
# geGDboJzPyZLFJCuWWYKxI2+0s4Grq2Eb0iEm09AufFM8q+Y+/bOQF1c9qjxL6/s
# iSLyaxhlscFzrdfx2M8eCnRcQrhofrfVdwonVnwPYqQ/MhRglf0HBKIJAgMBAAGj
# ggN6MIIDdjAOBgNVHQ8BAf8EBAMCAYYwOwYDVR0lBDQwMgYIKwYBBQUHAwEGCCsG
# AQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUFBwMIMIIB0gYDVR0gBIIB
# yTCCAcUwggG0BgpghkgBhv1sAAEEMIIBpDA6BggrBgEFBQcCARYuaHR0cDovL3d3
# dy5kaWdpY2VydC5jb20vc3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUF
# BwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUA
# cgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMA
# YwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQA
# IABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAA
# UABhAHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkA
# bQBpAHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4A
# YwBvAHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYA
# ZQByAGUAbgBjAGUALjALBglghkgBhv1sAxUwEgYDVR0TAQH/BAgwBgEB/wIBADB5
# BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0
# LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNy
# bDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEUm9vdENBLmNybDAdBgNVHQ4EFgQUFQASKxOYspkH7R7for5XDStnAs0wHwYD
# VR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEFBQADggEB
# AEZQPsm3KCSnOB22WymvUs9S6TFHq1Zce9UNC0Gz7+x1H3Q48rJcYaKclcNQ5IK5
# I9G6OoZyrTh4rHVdFxc0ckeFlFbR67s2hHfMJKXzBBlVqefj56tizfuLLZDCwNK1
# lL1eT7EF0g49GqkUW6aGMWKoqDPkmzmnxPXOHXh2lCVz5Cqrz5x2S+1fwksW5Etw
# TACJHvzFebxMElf+X+EevAJdqP77BzhPDcZdkbkPZ0XN1oPt55INjbFpjE/7WeAj
# D9KqrgB87pxCDs+R1ye3Fu4Pw718CqDuLAhVhSK46xgaTfwqIa1JMYNHlXdx3LEb
# S0scEJx3FMGdTy9alQgpECYxggQ/MIIEOwIBATB6MGwxCzAJBgNVBAYTAkNaMRcw
# FQYDVQQIEw5DemVjaCBSZXB1YmxpYzENMAsGA1UEBxMEQnJubzEQMA4GA1UEChMH
# U2V2ZWNlazEjMCEGA1UEAxMaU2V2ZWNlayBFbnRlcnByaXNlIFJvb3QgQ0ECCioc
# hHAAAQAAAH8wDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAA
# oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w
# DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgb/rsRt2jGabrnTlgOdgXSSAV
# ZOm0xrCajrsgLKXv5pgwDQYJKoZIhvcNAQEBBQAEggEAhFOp/cBpOQJiG1PajguU
# EPQLH7E/POnbBQ/QC/q/+yDPVj72Gs6n/Fac9U0cclIXg/zgof+Wtd14CwcEhv/B
# sWDnupSRMbIgHciJwZMpsQ+7miLL+VZRIO0eCIP/ntp+VAIKjXKALro4tSSA6Skg
# sHLJ0TBO0olwdwJkEZnklk3IBrSgG13QvCruGJ/m/ulITkIDzxLm4YLyS9G7Mu6Z
# DJtQBAkxRG+phc7o+G8bw5D8V6lVv7iwNyU15kpqyKUZvxWM7z9SGzV+6H76ldfh
# wP78zbZ3O8Ht4GkZtj3FDFE0LuitPsLi5kcoxJaJgfZHn5vK3TnueKXHaaeyvY3H
# cqGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr
# 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
# BgkqhkiG9w0BCQUxDxcNMTkwOTIxMDgxNDA3WjAjBgkqhkiG9w0BCQQxFgQUjlQX
# Xcm2d2xO75/BiwRQYw3TBacwDQYJKoZIhvcNAQEBBQAEggEAL1FQXNwGIEH3shxy
# n5X9vK+IHmtB4e8spUZ6t+ya+ezzxX7/BGpcAxMxLIC8o0fBaIj+vKvF1N603knn
# vI29O9mVaSb5y011wfRqJbVqOu2uZRH/C8NWo9dn/Cg5gpZ6AnH4J/YMUvZWFNDn
# tCwkZ01+XNZh7XpZqKrcaA4ztRAa8/0ouP8775GnarCKdYqljsPPA8JlKD2ClWkX
# izxse6R1VSrht4xjPlC4F6rOTcrYn+y5h2RcYbr/ddT539+m1n0lbPJLexl4FXvT
# b+zMdQgAs+ti0IQ/wpe0PR62SLm35a1mrEqE1xae4fjGoxkZue6h200iUyEqovrC
# NCj+Vg==
# SIG # End signature block