ADLAB PowerShell source file: buildup-DTR.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 DTRlib
& "$libDir\lib-modifyActions.ps1"
& "$libDir\lib-buildup.ps1"

$vmName = $args[0]

DBG ("DETERIORATION Library")
Redirect-TempToOutput
Load-VMConfig
Load-PhaseConfig

Find-MarkedVolumes
Get-VMICVersion

#====================
#====================
$deterConfig = $xmlConfig.VMs.DETERIORATE
$doDtrSteps = Is-NonNull $deterConfig
$doReenableUAC = -not (Parse-BoolSafe $vmConfig.commonVM.uacOff)
$doReenableUACStrict = $false

# Note: we should run DTR only if there are any DTR steps requested or if we must reenable UAC back
DBGIF $MyInvocation.MyCommand.Name { (-not $doDtrSteps) -and (-not $doReenableUAC) }

[bool] $global:restartInProgress = $false


#######################################################################
#######################################################################
## 
## DTR utility functions
##
#######################################################################
#######################################################################
               
function global:Restore-DefaultFirewall ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  if ($global:thisOSVersionNumber -ge 6.0) {

    $fwBackupFile = Get-DataFileApp 'firewall-backup-win60' $null '.ini' -doNotPrefixWithOutFile $true
    DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path -Lit $fwBackupFile) }

    if (Test-Path -Lit $fwBackupFile) {

      Restore-AdvFirewallFromBackup $fwBackupFile
    }

  } else {

    DBGIF ('Not restoring Windows Firewall settings on Windows 5.x') { $true }
  }

  DBG ('{0} --finished--' -f $MyInvocation.MyCommand.Name)
}

                
function global:Select-BestEnterpriseExchangeCertificate ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBG ('Pulse autoenrollment to ensure we have all available certificates')
  Run-Process 'certutil' '-pulse'

  DBG ('Give it some 100 seconds to complete assynchronously')
  Start-Sleep -Seconds 100

  Load-ExManagementShell

  DBG ('Determine local Exchange roles')
  DBGSTART
  $exServer = Get-ExchangeServer
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $exServer }
  DBG ('Local exchange server performs the following roles: {0}' -f $exServer.ServerRole)

  [string[]] $exRoles = $exServer.ServerRole.ToString().Replace(' ', '').Split(',')
  [string[]] $certServices = @()
  
  if (Contains-Safe $exRoles 'ClientAccess') {

    $certServices += 'IIS'
  }

  if (Contains-Safe $exRoles 'HubTransport') {

    $certServices += 'SMTP'
  }

  DBG ('Enable an exchange certificate for the following services: #{0} | {1}' -f $certServices.Count, ($certServices -join ','))
  if ($certServices.Count -gt 0) {

    DBG ('Get best exchange certificate')
    DBGSTART
    $bestExchangeCertificate = $null
    $bestExchangeCertificate = Get-ExchangeCertificate | ? { ($_.RootCAType -eq 'Enterprise') -and (-not $_.IsSelfSigned) -and (Contains-Safe $_.CertificateDomains $global:thisComputerFQDN) -and ($_.NotBefore -lt [DateTime]::Now) -and ($_.NotAfter -gt [DateTime]::Now) -and ($_.HasPrivateKey) -and ($_.Status -eq 'Valid') } | Sort -Desc NotAfter | Select -First 1
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $bestExchangeCertificate }

    DBG ('Best certificate identified as: {0} | {1} | {2}' -f $bestExchangeCertificate.Thumbprint, $bestExchangeCertificate.Subject, $bestExchangeCertificate.ServicesStringForm)

    DBGSTART
    $bestExchangeCertificate | Enable-ExchangeCertificate -Services $certServices -Force
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    DBG ('Test if the certificate works in IIS and/or SMTP: iis = {0} | smtp = {1}' -f (Contains-Safe $certServices IIS), (Contains-Safe $certServices SMTP))
    [bool] $testTlsResult = $true
    DBGIF $MyInvocation.MyCommand.Name { (-not (Contains-Safe $certServices IIS)) -and (-not (Contains-Safe $certServices SMTP)) }

    if (Contains-Safe $certServices IIS) {

      $testTlsResult = $testTlsResult -and (Test-Tls -hostName $global:thisComputerFQDN -port 443) #-doNotValidateCertificate $true
    }

    if (Contains-Safe $certServices SMTP) {

      $testTlsResult = $testTlsResult -and (Test-Tls -hostName $global:thisComputerFQDN -port 25) #-doNotValidateCertificate $true
    }

    DBGIF $MyInvocation.MyCommand.Name { -not $testTlsResult }
  }


  DBG ('{0} --finished--' -f $MyInvocation.MyCommand.Name)
}


function global:Secure-BestEnterpriseSqlCertificate ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBG ('Pulse autoenrollment to ensure we have all available certificates')
  Run-Process 'certutil' '-pulse'

  # Note: on Windows 2003/XP this operation takes 1 minute initial sleep delay
  DBG ('Give it some 100 seconds to complete assynchronously')
  Start-Sleep -Seconds 100

  DBG ('Get best SQL certificate')
  DBGSTART
  $bestSQLCertificate = $null
  # Note: we do not support failover cluster yet
  $virtualSQLServerName = $global:thisComputerFQDN
  # Note: 2.5.29.37 = EKU (Enhanced Key Usage)
  #       1.3.6.1.5.5.7.3.1 = Server Authentication
  $bestSQLCertificate = Get-ChildItem Cert:\LocalMachine\My | ? { ($_.Issuer -ne $_.Subject) -and (Contains-Safe $_.DnsNameList $virtualSQLServerName) -and ($_.NotBefore -lt [DateTime]::Now) -and ($_.NotAfter -gt [DateTime]::Now) -and ($_.HasPrivateKey) -and (-not $_.Archived) -and (Is-NonNull $_.Extensions['2.5.29.37'].EnhancedKeyUsages['1.3.6.1.5.5.7.3.1']) } | Sort -Desc NotAfter | Select -First 1
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $bestSQLCertificate }

  DBG ('Best certificate identified as: {0} | {1}' -f $bestSQLCertificate.Thumbprint, $bestSQLCertificate.Subject)

  DBG ('Find all SQL servers and their service identities')
  $sqlInstances = Get-WmiQueryArray '.' 'SELECT * FROM Win32_Service WHERE Name LIKE "MSSQL$%" OR Name LIKE "MSSQLServer"'
  DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $sqlInstances) -lt 1 }

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

    foreach ($oneSqlInstance in $sqlInstances) {

      DBG ('SQL instance found: {0} | {1} | start = {2} | user = {3}' -f $oneSqlInstance.Name, $oneSqlInstance.PathName, $oneSqlInstance.StartMode, $oneSqlInstance.StartName)
      DBGIF $MyInvocation.MyCommand.Name { $oneSqlInstance.PathName -notlike '?*\MSSQL\Binn\sqlservr.exe"*' }
      DBGIF $MyInvocation.MyCommand.Name { $oneSqlInstance.StartMode -ne 'Auto' }

      if (Is-ValidString $oneSqlInstance.StartName) {

        DBG ('Going to set GenericRead permissions on the certificate for the SQL service identity: {0}' -f $oneSqlInstance.StartName)
        DBGSTART
        Set-CertificatePrivateKeyPermissions Cert:\LocalMachine\My $bestSQLCertificate.Thumbprint $oneSqlInstance.StartName GR
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Restart the SQL server instance')
        DBGSTART
        Restart-Service $oneSqlInstance.Name -Force
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }
    }
  }


  DBG ('{0} --finished--' -f $MyInvocation.MyCommand.Name)
}


function global:Assert-DomainControllerCertificate ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))


  Run-Process certutil '-pulse'
  DBG ('Give autoenrollment 1:20 minute to finish easily')
  Start-Sleep 80

  if ((Is-LocalComputerDomainController)) {

    DBG ('Try findind a possible DC certificate to be sure DC can start 636 LDAPS')
    [System.Security.Cryptography.X509Certificates.X509Certificate2] $anyDCCertificate = Get-ChildItem Cert:\LocalMachine\My | ? { ($_.Issuer -ne $_.Subject) -and (Contains-Safe $_.DnsNameList $global:thisComputerFQDN) -and ($_.NotBefore -lt [DateTime]::Now) -and ($_.NotAfter -gt [DateTime]::Now) -and ($_.HasPrivateKey) -and (-not $_.Archived) -and (Is-NonNull $_.Extensions['2.5.29.37'].EnhancedKeyUsages['1.3.6.1.5.5.7.3.1']) -and (Is-NonNull $_.Extensions['2.5.29.37'].EnhancedKeyUsages['1.3.6.1.5.5.7.3.2']) -and (Is-NonNull $_.Extensions['2.5.29.37'].EnhancedKeyUsages['1.3.6.1.4.1.311.20.2.2']) } | Sort -Desc NotAfter | Select -First 1
    DBG ('One valid DC certificate found: {0} | {1} | {2} | {3}' -f $anyDCCertificate.Subject, $anyDCCertificate.Issuer, $anyDCCertificate.Thumbprint, ($anyDCCertificate.DnsNameList -join ','))
  
    if (Is-NonNull $anyDCCertificate) {
    
      DBGIF $MyInvocation.MyCommand.Name { -not (Test-Tls -hostName $global:thisComputerFQDN -port 636 -checkRevocation $true) }
    }    
  }


  DBG ('{0} --finished--' -f $MyInvocation.MyCommand.Name)
}


function global:Fix-DomainMemberDnsNetworkAdapters ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $isLocalDomainMember = Is-LocalComputerMemberOfDomain
  DBGIF $MyInvocation.MyCommand.Name { -not $isLocalDomainMember }

  if ($isLocalDomainMember) {

    $allNICs = Get-WmiQueryArray '.' 'SELECT * FROM Win32_NetworkAdapterConfiguration'
    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $allNICs) -lt 1 }

    $nameServers = Resolve-DnsRecord NS $global:thisComputerDomain
    DBGIF $MyInvocation.MyCommand.Name { $nameServers.Count -lt 1 }

    if ($nameServers.Count -gt 0) {
    
      [int] $nicsUpdated = 0

      foreach ($oneNIC in $allNICs) {

        if (((Get-CountSafe $oneNIC.DNSServerSearchOrder) -gt 0) -and ((Get-CountSafe $oneNIC.IPAddress) -gt 0)) {
        
          DBG ('One NIC possible for DNS IPs fixup found: {0} | {1} | {2} | {3}' -f $oneNIC.Description, ($oneNIC.IPAddress -join ','), ($oneNIC.DNSServerSearchOrder -join ','), $oneNIC.MacAddress)

          [bool] $atLeastOnePresent = $false
          foreach ($oneDnsServerSearchOrderIP in $oneNIC.DNSServerSearchOrder) {

            $atLeastOnePresent = $atLeastOnePresent -bor (Contains-Safe $nameServers $oneDnsServerSearchOrderIP ip)
          }

          DBG ('This NIC already contains at least one domain DNS server IP address: {0}' -f $atLeastOnePresent)


          if ($atLeastOnePresent) {

            [Collections.ArrayList] $newDnsServerSearchOrder = $oneNIC.DNSServerSearchOrder

            foreach ($oneNameServer in $nameServers) {

              DBG ('Adding the domain DNS server IP into the NIC settings: toAdd = {0} | currentList = {1}' -f $oneNameServer.ip, ($newDnsServerSearchOrder -join ','))
              Add-ListUnique ([ref] $newDnsServerSearchOrder) $oneNameServer.ip
            }

            DBG ('Will update the DNS server search order with the following: {0}' -f ($newDnsServerSearchOrder -join ','))
            DBGSTART
            $wmiRs = $oneNIC.SetDNSServerSearchOrder($newDnsServerSearchOrder)
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBGWMI $wmiRs

            $nicsUpdated ++
          }
        }
      }

      DBG ('NICs updated: {0}' -f $nicsUpdated)
      DBGIF $MyInvocation.MyCommand.Name { $nicsUpdated -eq 0 }
    }
  }


  DBG ('{0} --finished--' -f $MyInvocation.MyCommand.Name)
}


function global:Verify-BitColdKitCode ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))


  DBG ('Do we have any BitLocker configuration: {0}' -f (Is-NonNull $vmConfig.bitLocker))
  if (Is-NonNull $vmConfig.bitLocker) {

    [string] $outputPath = Get-DataFileApp 'BitColdKitVerification' -noExtension $true

    DBG ('Do we have a file share to output: {0}' -f (Is-ValidString $vmConfig.bitLocker.fs.instance))
    if (Is-ValidString $vmConfig.bitLocker.fs.instance) {

      $outputPath = Resolve-ClientFsPath $vmConfig.bitLocker.fs
    }


    UnlockAndVerify-BitLockerRecoveryDecryption -recoveryFiles $outputPath
  }


  DBG ('{0} --finished--' -f $MyInvocation.MyCommand.Name)
}


function global:Enable-CustomGPOs ()
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  $advGPOs = $vmConfig.gpo.SelectNodes('./adv[@dtrEnable="true"]')
  DBG ('Do we have any adv GPOs which require our touch: {0}' -f (Get-CountSafe $advGPOs))

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

    foreach ($oneAdvGPO in $advGPOs) {

      DBG ('Enabling one adv GPO: {0} | wmi = {1} | nameSfx = {2} | ou = {3}' -f $oneAdvGPO.gpo, $oneAdvGPO.wmi, $oneAdvGPO.nameSfx, $oneAdvGPO.ou)

      $gpoName = $oneAdvGPO.gpo

      if (Is-ValidString $oneAdvGPO.nameSfx) {

        $gpoName = '{0} ({1})' -f $gpoName, $oneAdvGPO.nameSfx
      }

      DBG ('Enabling one adv GPO by name: {0}' -f $gpoName)
      Enable-LinkedGPO -gpo $gpoName -ou $oneAdvGPO.ou
    }
  }
}


function global:Reenable-UAC ([bool] $strictUAC)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  #DBGSTART
  #Remove-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' EnableLUA
  #DBGEND

  [string] $luaRegKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'

  DBG ('Verify the value existance: {0}' -f $luaRegKey)
  DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path -Literal $luaRegKey) }
  DBGSTART
  $luaRegValues = Get-ItemProperty $luaRegKey
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBG ('LUA settings: {0} | {1} | {2}' -f $luaRegValues.EnableLUA, $luaRegValues.PromptOnSecureDesktop, $luaRegValues.ConsentPromptBehaviorAdmin)
  DBGIF ('Weird LUA settings: {0},{1},{2} should be either 0,0,0 or 1,1,5' -f $luaRegValues.EnableLUA, $luaRegValues.PromptOnSecureDesktop, $luaRegValues.ConsentPromptBehaviorAdmin) { (-not (($luaRegValues.EnableLUA -eq 0) -and ($luaRegValues.PromptOnSecureDesktop -eq 0) -and ($luaRegValues.ConsentPromptBehaviorAdmin -eq 0))) -and (-not (($luaRegValues.EnableLUA -eq 1) -and ($luaRegValues.PromptOnSecureDesktop -eq 1) -and ($luaRegValues.ConsentPromptBehaviorAdmin -eq 5))) }

  Set-RegistryValue $luaRegKey EnableLUA 1 DWord
  Set-RegistryValue $luaRegKey PromptOnSecureDesktop 1 DWord
  Set-RegistryValue $luaRegKey ConsentPromptBehaviorUser 3 DWord

  if ($strictUAC) {

    Set-RegistryValue $luaRegKey ConsentPromptBehaviorAdmin 4 DWord

  } else {

    Set-RegistryValue $luaRegKey ConsentPromptBehaviorAdmin 5 DWord
  }

  DBGIF 'You must RESTART the machine manually to reenable UAC' { -not $global:restartInProgress }
}

function global:Restart-ComputerCarefully ([int] $wait = 0, [int] $rnd = 0, [switch] $noProtectedHost)
{
  DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator))

  DBG ('Should we get some random ofset: {0}' -f $rnd)

  if ($rnd -gt 0) {

    $rnd = Get-Random -Minimum 0 -Maximum $rnd
    DBG ('The random offset determined: {0}' -f $rnd)
  }

  $wait = $wait + $rnd

  # Note: the shutdown can delay the restart up to 600 seconds only
  if ($wait -gt 600) {

    DBG ('Waiting for some seconds before proceeding to shutdown: {0}' -f ($wait - 600))
    Start-Sleep -Seconds ($wait - 600)
    $wait = 600
  }

  if ($wait -lt 45) {

    DBG ('Will wait at least 45 seconds before restarting')
    $wait = 45
  }

  if (Is-LocalComputerMemberOfDomain) {

    Run-Process gpupdate
  }

  [bool] $restart = $true
  
  if ($noProtectedHost) {

    DBG ('Check if we are not running on a protected host: {0}' -f (Parse-BoolSafe $global:phaseCfg.sevecekBuildup.host.protectedHost))
    
    $restart = -not (Parse-BoolSafe $global:phaseCfg.sevecekBuildup.host.protectedHost)
  }

  DBG ('Will restart really: {0}' -f $restart)

  if ($restart) {

    DBG ('Restarting after: {0}' -f $wait)
    $global:restartInProgress = $true
    $global:restartWaitSec = $wait
  }
}


#######################################################################
#######################################################################
## 
## DTR core
##
#######################################################################
#######################################################################
              

DBG ('Start with loading the VM delivery info')
DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $global:phaseCfg.sevecekBuildup.phaseId.delivery }
$deliveryFile = Get-DataFileApp $global:phaseCfg.sevecekBuildup.phaseId.delivery -doNotPrefixWithOutFile $true -noExtension $true
DBG ('Load the VM delivery data from vmBuilder: {0} | exists = {1}' -f $deliveryFile, (Test-Path $deliveryFile))
DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $deliveryFile) }
DBGSTART
$deliveryInventory = Import-Csv $deliveryFile
DBGER $MyInvocation.MyCommand.Name $error
DBGEND


#====================
DBG ('Are we domain member and should wait until all others are finished as well: {0}' -f ($doDtrSteps -and (Is-LocalComputerMemberOfDomain)))

if ($doDtrSteps -and (Is-LocalComputerMemberOfDomain)) {

  [System.Collections.ArrayList] $deList = @()

  $joinerCred = Get-JoinerCredentials $vmConfig

  DBG ("Waiting for all machines to finish (AUTHENTICATED): user = {0} @ {1} | full = {2}" -f $joinerCred.Login, $joinerCred.Domain, $joinerCred.FullLogin)
  $dtrWaitStarted = [DateTime]::Now

  [Collections.ArrayList] $notReadyYetVms = @()
  do {

    DBG ('Get the local rootDSE')
    # Note: in order to authenticate against rootDSE, the path must be domain/RootDSE. When using just LDAP://RootDSE, the 
    #       authentication does not work on Windows 2012
    # Note: better tested info - when this runs under non-domain (local) user, the non-domain-specific LDAP path does not
    #       work on any system. If we want the LDAP path under a local user, it always must be LDAP://domain/CN=... or 
    #       LDAP://dc1/... or the call fails with "The specified domain either does not exist or could not be contacted"
    $rootDSE = Get-DE "$($joinerCred.Domain)/RootDSE" ([ref] $deList) $joinerCred.FullLogin $joinerCred.Pwd
    
    if (Is-NonNull $rootDSE) {

      $machinesRoot = 'LDAP://{0}/CN=Sevecek VM Buildup,CN=Services,{1}' -f (GDES $rootDSE dNSHostName), (GDES $rootDSE configurationNamingContext)
      DBG ('RootDSE ready, get machine states from: {0}' -f $machinesRoot)

      $machinesRootDE = Get-DE $machinesRoot ([ref] $deList) $joinerCred.FullLogin $joinerCred.Pwd
      $vmsReady = $false
    
      if (Is-NonNull $machinesRootDE) {
            
        $vmStatuses = Get-ADSearch $machinesRootDE 'subTree' '(&(objectClass=sevecekVMBuilderTaskContainer))' @('sevecek-VMB-MachineFinished', 'sevecek-VMB-Hostname', 'cn')
    
        if ($vmStatuses.found) {
    
          $vmsReady = $true
          $notReadyYetVms.Clear()

          foreach ($oneVmStatus in $vmStatuses.result) {
      
            [bool] $oneMachineReady = (GSRS $oneVmStatus sevecek-VMB-MachineFinished) -eq 'VM-Ready'
            DBG ('Machine state: {0} | {1} | ready = {2}' -f (GSRS $oneVmStatus cn), (GSRS $oneVmStatus sevecek-VMB-MachineFinished), $oneMachineReady)

            [string] $oneMachineHostname = GSRS $oneVmStatus sevecek-VMB-Hostname
            DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneMachineHostname }
            
            [bool] $doingMachine = $true
            if (Is-ValidString $oneMachineHostname) {

              DBG ('Find the machine VM name')
              [System.Xml.XmlElement] $oneMachineVmConfig = $null
              $oneMachineVmConfig = $xmlConfig.SelectSingleNode(('./VMs/MACHINE[@hostName="{0}"]' -f $oneMachineHostname))
              DBGIF ('Invalid machine config: {0}' -f $oneMachineHostname) { Is-Null $oneMachineVmConfig }
              $oneMachineVmName = $oneMachineVmConfig.vm.name
              DBG ('The machine VM name: hostname = {0} | vmName = {1}' -f $oneMachineHostname, $oneMachineVmName)
              
              [object[]] $oneMachineVmDelivery = $deliveryInventory | ? { ($_.name -eq $oneMachineVmName) -and ($_.opType -eq 'buildup') }
              #DBG ("The machine delivery info: -->`r`n{0}" -f ($oneMachineVmDelivery | Out-String))
              DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $oneMachineVmDelivery) -ne 1 }
              DBG ('The machine delivery info: {0} | start = {1} | finished = {2}' -f $oneMachineVmDelivery[0].Name, $oneMachineVmDelivery[0].buildStarted, $oneMachineVmDelivery[0].finished)

              #$doingMachine = Parse-BoolSafe $oneMachineVmDelivery[0].deliver
              $doingMachine = Parse-BoolSafe $oneMachineVmDelivery[0].do
              DBG ('The machine is being processed actually: {0}' -f $doingMachine)

            } else {

              DBG ('Cannot determine if we are doing the machine, must wait')
            }

            if (-not $oneMachineReady) {

              [void] $notReadyYetVms.Add($oneMachineHostname)
            }

            $vmsReady = $vmsReady -band ($oneMachineReady -or (-not $doingMachine))
          }
        }

        Dispose-ADSearch ([ref] $vmStatuses)
      }

      if ($vmsReady) {
      
        DBG ('Machines ready, going to proceed...')
        break
      }

      $stopFile = "$env:SystemDrive\stopDTR.txt"
      if (Test-Path $stopFile) {

        DBG ('Stop file found. Going to proceed...')
        break
      }
    }

    Dispose-List ([ref] $deList)

    DBG ('Some machines not ready yet, waiting for another 77 sec: {0}' -f ($notReadyYetVms -join ','))
    Start-Sleep 77

    [double] $dtrWaitTime = (([DateTime]::Now) - $dtrWaitStarted).TotalHours
    DBG ('The DTR alread took: {0:N1} hours' -f $dtrWaitTime)

    if ($dtrWaitTime -gt (Parse-DoubleSafe $deterConfig.timeout)) {

      DBGIF ('DTR wait timed-out after: {0:N1} hours' -f $dtrWaitTime) { $true }
      break
    }

  } while ($true)
  
  Dispose-List ([ref] $deList)
}


#====================
DBG ('Process sequence steps if any')

$sequenceSteps = $null

if ($doDtrSteps) {

  DBGSTART
  $sequenceSteps = $deterConfig.SelectNodes('./seq')
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
}

DBG ('Found sequence steps: {0}' -f (Get-CountSafe $sequenceSteps))


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

  foreach ($oneSeq in $sequenceSteps) {

    DBG ('Sequence: {0} | {1} | {2}' -f $oneSeq.scope, $oneSeq.target, $oneSeq.type)


    Wait-IfRequested DTR


    $processHere = $false

    switch ($oneSeq.scope) {

      'domain' {

        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $global:thisComputerDomain)
        $processHere = (Is-LocalComputerMemberOfDomain) -and (($global:thisComputerDomain -like $oneSeq.target) -or (Is-EmptyString $oneSeq.target))
      }

      'workgroup' {

        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $vmName)
        $processHere = (-not (Is-LocalComputerMemberOfDomain)) -and (($vmName -like $oneSeq.target) -or (Is-EmptyString $oneSeq.target))
      }
      
      'dc' {

        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $global:thisComputerDomain)
        $processHere = (Is-LocalComputerDomainController) -and (($global:thisComputerDomain -like $oneSeq.target) -or (Is-EmptyString $oneSeq.target))
      }
      
      'pdc' {

        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $global:thisComputerDomain)
        $processHere = (Is-LocalComputerDomainController) -and (($global:thisComputerDomain -like $oneSeq.target) -or (Is-EmptyString $oneSeq.target)) -and ($global:thisOSRole -eq 'PDC')
      }
      
      'wks' {

        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $global:thisComputerDomain)
        $processHere = ((Get-WMIQuerySingleObject '.' 'SELECT * FROM Win32_OperatingSystem').ProductType -eq 1) -and (($global:thisComputerDomain -like $oneSeq.target) -or (Is-EmptyString $oneSeq.target))
      }

      'srv' {

        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $global:thisComputerDomain)
        $processHere = ((Get-WMIQuerySingleObject '.' 'SELECT * FROM Win32_OperatingSystem').ProductType -eq 3) -and (($global:thisComputerDomain -like $oneSeq.target) -or (Is-EmptyString $oneSeq.target))
      }

      'machine' { 
      
        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $vmName)
        $processHere = ($vmName -like $oneSeq.target) -or (Is-EmptyString $oneSeq.target)
      }

      'os' {

        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $global:thisOSVersionNormal)
        $processHere = (Contains-Safe (Split-MultiValue $oneSeq.target) $global:thisOSVersionNormal) -or (Is-EmptyString $oneSeq.target)
      }
      
      'svc' { 
      
        $foundRequestedService = Is-NonNull $vmConfig.SelectSingleNode('./{0}' -f $oneSeq.target)
        DBG ('Scope set as {0}. Name: target = {1} | us = {2}' -f $oneSeq.scope.ToUpper(), $oneSeq.target, $foundRequestedService)
        $processHere = $foundRequestedService
      }

      'default' { DBGIF 'Invalid scope specified' { $true } }
    }


    DBG ('Will process the sequence step here: {0}' -f $processHere)

    if ($processHere) {

      #
      #
      DBG ('Sequence step as reenable UAC: {0}' -f (Parse-BoolSafe $oneSeq.reenableUAC))

      if (Parse-BoolSafe $oneSeq.reenableUAC) {

        DBG ('Will run sequence step: reenableUAC | {1}' -f $oneSeq.reenableUAC, $oneSeq.strict)
        $doReenableUAC = $true
        $doReenableUACStrict = Parse-BoolSafe $oneSeq.strict
      }

      #=================================
      #=================================
      DBG ('Sequence step as executable: {0}' -f (Is-ValidString $oneSeq.cmd))

      if (Is-ValidString $oneSeq.cmd) {

        $commandExt = Resolve-VolumePath $oneSeq.cmd
        DBG ('Will run sequence step: {0} | {1}' -f $commandExt, $oneSeq.param)
        Run-Process $commandExt $oneSeq.param -showWindow -doNotRedirOut $true
      }

      #=================================
      #=================================
      DBG ('Sequence step as function: {0} | {1}' -f (Is-ValidString $oneSeq.fce), $oneSeq.fce)

      if (Is-ValidString $oneSeq.fce) {

        DBG ('Sequence step as function: START')
        DBGSTART
        $fceRs = $null
        $fceRs = Invoke-Expression $oneSeq.fce
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBG ('Sequence step as function: END')
         
        if (Is-NonNull $fceRs) {

          DBG ('Sequence step returned some results: {0}' -f ($fceRs | Out-String))
        }
      }

      #=================================
      #=================================
      DBG ('Sequence step as installation and/or updates: {0}' -f ((Is-ValidString $oneSeq.install) -or (Is-ValidString $oneSeq.msu)))

      if ((Is-ValidString $oneSeq.install) -or (Is-ValidString $oneSeq.msu)) {

        DBG ('Should install program and/or updates: {0} | {1} | {2}' -f $oneSeq.msu, $oneSeq.install, $oneSeq.param)
        Install-ProgramPlusPrerequisites $oneSeq.msu $null $oneSeq.install $null $oneSeq.param
      }

      #=================================
      #=================================
      DBG ('Sequence step as autostart item: {0}' -f (Is-ValidString $oneSeq.autostart))

      if (Is-ValidString $oneSeq.autostart) {

        $autostartCmd = Resolve-VolumePath $oneSeq.run
        DBG ('Should autostart a program: {0} | {1} | {2}' -f $oneSeq.autostart, $autostartCmd, $oneSeq.param)
        Set-RegistryValue 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' $oneSeq.autostart ('"{0}" {1}' -f $autostartCmd, $oneSeq.param) ExpandString
      }

      #=================================
      #=================================
      DBG ('Sequence step as careful restart: {0}' -f (Is-ValidString $oneSeq.restart))

      if (Is-ValidString $oneSeq.restart) {

        DBG ('Going to call careful restart: {0} | {1} | {2} | {3}' -f $oneSeq.restart, $oneSeq.rnd, $oneSeq.noProtectedHost, (Is-ValidString $oneSeq.autoLogon.login))

        if (Is-ValidString $oneSeq.autoLogon.login) {

          DBG ('We should pre-register an autolog account: {0} | {1} | {2}' -f $oneSeq.autoLogon.login, $oneSeq.autoLogon.domain, $oneSeq.autoLogon.count)
          DBGIF $MyInvocation.MyCommand.Name { (Parse-IntSafe $oneSeq.autoLogon.count) -lt 1 }
          
          Restart-WithAutologon -domain $oneSeq.autoLogon.domain -user $oneSeq.autoLogon.login -password $oneSeq.autoLogon.pwd -doNotRestart $true -logonCount (Parse-IntSafe $oneSeq.autoLogon.count) -verifyAndPrelogonUser $true
        }

        Restart-ComputerCarefully -wait (Parse-IntSafe $oneSeq.restart) -rnd (Parse-IntSafe $oneSeq.rnd) -noProtectedHost:(Parse-BoolSafe $oneSeq.noProtectedHost)
      }
    }
  }
}


DBG ('Do we have to re-enable UAC again: {0}' -f $doReenableUAC)
if ($doReenableUAC) {
 
  Reenable-UAC -strictUAC $doReenableUACStrict
}


Finish-Machine $false 'DTR'


DBGSTART ; DBGEND # just grab all remaining unhandled error messages
Grab-ErrorMessages "$global:outPath\$global:dbgFileName" $global:dbgExtension @('????-??-??T??:??:?? : * : Error: *', '????-??-??T??:??:?? : * : Assert: *')
#notepad (GETDBGFILENAME)

$deletedUnsafeOutput = Delete-UnsafeOutput


if ($global:restartInProgress) {

  DBG ('Restarting in order to finish DTR: {0}' -f $global:restartWaitSec)
  DBGIF $MyInvocation.MyCommand.Name { $global:restartWaitSec -lt 45 }
  Run-Process 'shutdown' ('/r /f /t {0} /c "Sevecek BUILDER careful RESTART from DTR"' -f $global:restartWaitSec)

} else {

  DBG ('DTR finished')
}


# SIG # Begin signature block
# MIIc/QYJKoZIhvcNAQcCoIIc7jCCHOoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBCn/sQW+DMMdLK
# cYxlSX9b7g64ySygDlEYDFu7ulvVj6CCGAQwggTlMIIDzaADAgECAhA5vUKe0oFu
# utW8yQO0umXnMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNVBAYTAklMMRYwFAYDVQQK
# Ew1TdGFydENvbSBMdGQuMSkwJwYDVQQLEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9u
# IEF1dGhvcml0eTEjMCEGA1UEAxMaU3RhcnRDb20gQ2xhc3MgMiBPYmplY3QgQ0Ew
# HhcNMTYxMjAxMTU1MTEzWhcNMTgxMjAxMTU1MTEzWjBRMQswCQYDVQQGEwJDWjEa
# MBgGA1UECAwRSmlob21vcmF2c2t5IEtyYWoxDTALBgNVBAcMBEJybm8xFzAVBgNV
# BAMMDk9uZHJlaiBTZXZlY2VrMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
# AQEAr9E9hNj06bash9JX97kpsqK9Z/ciOBC6trI4nvlW9CPwhKBTb5wArhxLYZBG
# 9jWPWrdy1nL/cm5qMqBb/mogYwMwvEYWMvsIOOVn6HD9lVhNAovD6PHz0ziBBKIs
# zXTjyUPQaoIlIELovz967m78HJdUZJGxqhluAsS9o9/fEzA7XXUhUuqRKsetuZV/
# Asfh5sOveeoRsbeW4daTWvtz3TJuULL0w43LNVYJkd6LL8cegvLPVZUe1N7skvid
# EvntdlowQsJlqFdrH3SGKIPKA6ObcY8SZWkEQSbVBF8Kum1UT+jN0gm+84FwOg5W
# qKx+VvTK2ljVWnPrCD0Zzu2oIQIDAQABo4IBkzCCAY8wDgYDVR0PAQH/BAQDAgeA
# MBMGA1UdJQQMMAoGCCsGAQUFBwMDMAkGA1UdEwQCMAAwHQYDVR0OBBYEFG2vSo3N
# hQWILeUs0oN9XzHTejcfMB8GA1UdIwQYMBaAFD5ik5rXxxnuPo9JEIVVFSDjlIQc
# MG0GCCsGAQUFBwEBBGEwXzAkBggrBgEFBQcwAYYYaHR0cDovL29jc3Auc3RhcnRz
# c2wuY29tMDcGCCsGAQUFBzAChitodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0
# cy9zY2EuY29kZTIuY3J0MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuc3Rh
# cnRzc2wuY29tL3NjYS1jb2RlMi5jcmwwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5z
# dGFydHNzbC5jb20vMFEGA1UdIARKMEgwCAYGZ4EMAQQBMDwGCysGAQQBgbU3AQIF
# MC0wKwYIKwYBBQUHAgEWH2h0dHBzOi8vd3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3kw
# DQYJKoZIhvcNAQELBQADggEBAJuRiEvHtIYSpsmMkPhTz4QOOShN3p5KWdf8vm71
# A33CR9fds10d8D2B2aE+vjmHJ69GY0bbfg5oZY2Lsq2euL7Da5/hS8+6T3MEtD4h
# njfHV7mxmoSfFuy/KDipoV6uwhI+ksqchXYdUH+5uCQO0MOO8ITjAgzUQsnZ4UIB
# HBGeP+e+3ljxSYSXWdPIrgxdR971P/HhWSVfKNlmBgEKMQM5Jy0aAd4jxSl/AzdY
# t0+6pliFJ1peGhdFni2Fm8fu5oN68aTIrNtc5WY7Lzgf+sRTVeWORWS37+1zAD0m
# jzd8gyfBLxRuaRSfjYxny0rLXelAwfiA3ze2DU2Bfg9/rfcwggXYMIIDwKADAgEC
# AhBsO9J+3TyUnpWOKKmzx1egMA0GCSqGSIb3DQEBCwUAMH0xCzAJBgNVBAYTAklM
# MRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRh
# bCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDEyBTdGFydENvbSBDZXJ0aWZp
# Y2F0aW9uIEF1dGhvcml0eTAeFw0xNTEyMTYwMTAwMDVaFw0zMDEyMTYwMTAwMDVa
# MHUxCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSkwJwYDVQQL
# EyBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEjMCEGA1UEAxMaU3Rh
# cnRDb20gQ2xhc3MgMiBPYmplY3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
# ggEKAoIBAQC5FARY97LFhiwIMmCtCCbAgXe5aBnZFSsdGGnk2hqWBZcuZHkaqT1R
# M1rQd2r0ApNBw466cBur2Ht0b5jo17mpPmh2pImgIqwX1in4u7hhn9IH0GYOMEcg
# K3ACHv5zCRxxNLXifqmsqKfxjjpABnaSyvd4bO9YBXN9f4NQ6aJVAuMArpanxsJk
# e+P4WECVLk17v92CAN5JVaczI+baT/lgo5NVcTEkloCViSbIfU6ILeyhOSQZvpom
# MYk8eJqI0nimOTJJfmXangNDsrX8np+3lXD0+6rCZisXRWIaeffyTMHZ31Qj1D50
# WYdRtX5yev4WgaXoKJQN3lkgXUcytvyHAgMBAAGjggFaMIIBVjAOBgNVHQ8BAf8E
# BAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAy
# BgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0c3NsLmNvbS9zZnNjYS5j
# cmwwZgYIKwYBBQUHAQEEWjBYMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5zdGFy
# dHNzbC5jb20wMAYIKwYBBQUHMAKGJGh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2Nl
# cnRzL2NhLmNydDAdBgNVHQ4EFgQUPmKTmtfHGe4+j0kQhVUVIOOUhBwwHwYDVR0j
# BBgwFoAUTgvvGqRAW6UXaYcwyjRoQ9BBrvIwPwYDVR0gBDgwNjA0BgRVHSAAMCww
# KgYIKwYBBQUHAgEWHmh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeTANBgkq
# hkiG9w0BAQsFAAOCAgEAY6U81bNtJyjY67pTrzAL6kpdEtX5mspw+kxjjNdNVH5G
# 6lLnhaEkIxqdpvY/Wdw+UdNtExs+N8efKPSwh2m/BxXj2fSeLMwXcwHFookScEER
# 8ez0quCNzioqNHac7LCXPEnQzbtG2FHlePKNDWh8eU6KxiAzNzIrIxPthinHGgLT
# BOACHQM2YTlD8YoU5oN3dLmBOqtH0BDMZoLcjEIoEW1zC+TnVb3yU1G0xub6gnN7
# lP50vbAiHJYrnywQiXaloBV8B9YYfe6ZgvjqxwufwFcMVyE3UmCuDTsOpjqDEKpJ
# 25s+FUdkie5VqCS1aaudLo31X+9UvP45pfgyRqzyfUnVEhH4ZXxlBWZMzj2Xov5+
# m/+H3kxYuFA5xdqdshj/Zx00S7PkCSF+8M1NCcvFgQwjIw61bZAjDBl3P3a8xNTX
# sb2CjFdiNKbT3LD6IGeIf0b/EbPf0FXdvBrxm0ofMOhnngdPolPYCtoOGtZPAVe/
# xeu+/ZyKv6TSHlshaUO0iYfsmbXnZ51vvt/kkjwms9/qPFxSuE0fjEfF7aQazwRE
# Df2hiVPR0pAhvShtM3oU4XreEFEUWEYHs25fYV4WMmxkUKSgmSmwRq45tvtGH4LT
# b5+cd+iLqK8rBQL0E6xaUjjGfsYx7bueIvqTvCkrQvoxMbn/qDHCiypowDVq6TAw
# ggZqMIIFUqADAgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3DQEBBQUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0Et
# MTAeFw0xNDEwMjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJBgNVBAYTAlVT
# MREwDwYDVQQKEwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQgVGltZXN0YW1w
# IFJlc3BvbmRlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNkXfx8
# s+CCNeDg9sYq5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sCSVDZg85vZu7d
# y4XpX6X51Id0iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/USs3OWCmejvmGf
# rvP9Enh1DqZbFP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYtWQJhiGFyGGi5
# uHzu5uc0LzF3gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZbesF6uHjHyQYu
# RhDIjegEYNu8c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJYczQCMxr7GJC
# kawCwO+k8IkRj3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMB
# Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIBtjCCAbIw
# ggGhBglghkgBhv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdp
# Y2VydC5jb20vQ1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUAcwBl
# ACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBu
# AHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0
# AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0
# AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUAbQBl
# AG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5
# ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAAaABl
# AHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCGSAGG/WwD
# FTAfBgNVHSMEGDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNVHQ4EFgQUYVpN
# JLZJMp1KKnkag0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0cDovL2NybDMu
# ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwOKA2oDSGMmh0
# dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3Js
# MHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl
# cnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRBc3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAnSV+
# GzNNsiaBXJuGziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd42yE5FpA+94G
# AYw3+puxnSR+/iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCge5fH9j/n4hFB
# pr1i2fAnPTgdKG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7kA7YUq/OPQ6dx
# nSHdFMoVXZJB2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7Cqsc21xIJ2bI
# o4sKHOWV2q7ELlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIKSK+w1G7g9BQK
# Ohvjjz3Kr2qNe9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArrPye7uhswDQYJ
# KoZIhvcNAQEFBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu
# YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQg
# QXNzdXJlZCBJRCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAw
# MFowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE
# CxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJ
# RCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKV
# pYBzQHDSnlZUXKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5tHdJ3InECtqv
# y15r7a2wcTHrzzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/
# 2DDD/6b3+6LNb3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2xQaPtP77blUjE
# 7h6z8rwMK5nQxl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9IhJtPQLnxTPKv
# mPv2zkBdXPao8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIU
# YJX9BwSiCQIDAQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIG
# CCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcD
# CDCCAdIGA1UdIASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQwOgYIKwYBBQUH
# AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o
# dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0
# AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1
# AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp
# AGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl
# AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo
# AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg
# AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg
# AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB
# /wQIMAYBAf8CAQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6
# MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e
# 36K+Vw0rZwLNMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqG
# SIb3DQEBBQUAA4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvVDQtBs+/sdR90
# OPKyXGGinJXDUOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann
# 4+erYs37iy2QwsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1zh14dpQlc+Qq
# q8+cdkvtX8JLFuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD
# 7eeSDY2xaYxP+1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwIVYUiuOsYGk38
# KiGtSTGDR5V3cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYIETzCCBEsCAQEwgYkwdTEL
# MAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKTAnBgNVBAsTIFN0
# YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSMwIQYDVQQDExpTdGFydENv
# bSBDbGFzcyAyIE9iamVjdCBDQQIQOb1CntKBbrrVvMkDtLpl5zANBglghkgBZQME
# AgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEM
# BgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqG
# SIb3DQEJBDEiBCBda290gnctqeqfut35gzF0RoDvhY/TXCjtZORCA558oTANBgkq
# hkiG9w0BAQEFAASCAQA381MMdCKXhk6Mh7M0OIIZU50bj5Kpdxon0Rg0KMkr8Ynb
# gcFVRY+uKlc1aQcDOBB9bQgvVxdbNvmXZ+LOdzcpEaGerYMlXCFrMk+W/Qoggqpo
# T7BFXtBKS0L28f8kd1TSoefTZ49ezdyUDi82/zwJ7d6JG2OmTHvWrvGhXkFixl4D
# 6evTd67yiKPoOcYU0mT6r/FChtz4LZ8EU55AzTl4kC9e7z4VcMvDeRqJfbfo4Ijs
# HJ9UGrvUe35LcvIYI0wjqQUvWijo2sqXbMrvdfQ08BLjcEeDXe58p6oeJeAT242h
# p6Qdepi8qDdYOMYepuqiedamse69Kimw/utqANnZoYICDzCCAgsGCSqGSIb3DQEJ
# BjGCAfwwggH4AgEBMHYwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0
# IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNl
# cnQgQXNzdXJlZCBJRCBDQS0xAhADAZoCOv9YsWvW1ermF/BmMAkGBSsOAwIaBQCg
# XTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODEx
# MDExNjI2NTVaMCMGCSqGSIb3DQEJBDEWBBRrRzU+J1bmhn3+TTF9NuZ3bmb0xjAN
# BgkqhkiG9w0BAQEFAASCAQAqAvo4zdsbroGrFTh8yMn4gzWRTYqpiNqdP47jx388
# nBCHotpIv178EAND1Zy6Qzoele6E4YTyn/e/PrVo4YifREhofbhrEluW7cKn1cIh
# G2WqW1/IMDPmpS23+1R5s39d6YuGasSoAfoNYYbCc5ZtziXbn8/FXXhwPS+dDJp4
# MfdBtYp0y+zZhae6rQr7zeFTj0ARzu8Z2NlySUN3L8SHPBw7xT1bViVdhbkqwUNw
# /MLcdB7OoAb8fkqDPv3dKjKDx3t2l6Thsnz07Ikz1mUcm+gjns+AhfYOXIBfFFnD
# ptIYlBpP+jER3yMBbDATmXTLh6a1u3doLyhyIRPOHpLc
# SIG # End signature block