ADLAB PowerShell source file: buildup-6.ps1

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




#=====================
# PKGMGR and other late things

DBG ("Build step: {0}" -f $MyInvocation.MyCommand.Definition)
DBG "Feature installations (using PKGMGR) and other late things..."
DBG ("Machine config: {0}, {1}" -f $vmName, $vmConfig.hostName)



#===============                             
[string] $pkgmgrFeatures = $vmConfig.pkgmgr.features
DBG ("Package Manager (pkgmgr) feature installation: {0} | {1} | {2}" -f (Is-ValidString $pkgmgrFeatures), (Count-MultiValue $pkgmgrFeatures), $pkgmgrFeatures)

$pkgmgrAppFeatures = $vmConfig.SelectNodes('.//features[@pkgmgr]')
DBG ('Applications requiring some pkgmgr features: {0}' -f (Get-CountSafe $pkgmgrAppFeatures))

if ((Get-CountSafe $pkgmgrAppFeatures) -ge 1) {

  foreach ($onePkgmgrAppFeatures in $pkgmgrAppFeatures) {
   
    DBG ('Adding one application pkgmgr installation into the list: {0}' -f $onePkgmgrAppFeatures.pkgmgr)
    $pkgmgrFeatures = Add-MultiValue $pkgmgrFeatures (Split-MultiValue $onePkgmgrAppFeatures.pkgmgr) -unique $true
  }
}

DBG ("Package Manager (pkgmgr) feature installation final list: {0} | {1} | {2}" -f (Is-ValidString $pkgmgrFeatures), (Count-MultiValue $pkgmgrFeatures), $pkgmgrFeatures)

if (Is-ValidString $pkgmgrFeatures) {

  [string[]] $features = (Split-MultiValue $pkgmgrFeatures) | ? { $_ -notlike '*-SevecekInstallOnly' }
  DBG ("Features to be installed: #{0} = {1} (original: {2})" -f (Get-CountSafe $features), ($features -join ','), $pkgmgrFeatures)


  if (($thisOSVersionNumber -ge 6.2) -and ((Contains-Safe $features 'NetFx3') -or (Contains-Safe $features 'NetFx3ServerFeatures'))) {
      
    DBG ("Doing 6.2+ NetFx3 installation.")
    DBG ('Install NetFx3 from source media separately')

    $netFx3Source = Get-WindowsSXSOfflineMedia $global:installMediaVolume $global:installISOVolume 

    if ($thisOSRole -notlike '*workstation*') {

        Run-Process 'DISM' ('/online /enable-feature /featurename:NetFx3ServerFeatures /source:"{0}"' -f $netFx3Source)
    }

    Run-Process 'DISM' ('/online /enable-feature /featurename:NetFx3 /source:"{0}"' -f $netFx3Source)
  }

  if ($thisOSVersionNumber -ge 6.2) {

    DBG ('Verify the feature list first')
    DBGSTART
    [string[]] $availablePkgmgrFeatures = dism /online /get-features | % { [regex]::Match($_, '\AFeature Name \: ([a-zA-Z0-9\-]+)\Z').Groups[1].Value } | ? { Is-ValidString $_ }
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBG ('The number of features available: #{0}' -f (Get-CountSafe $availablePkgmgrFeatures))
    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $availablePkgmgrFeatures) -lt 10 }
    
    if ((Get-CountSafe $availablePkgmgrFeatures) -ge 10) {

      foreach ($onePkgmgrFeatureToEnable in $features) {

        DBGIF ('The requested PKGMGR feature is not available: {0}' -f $onePkgmgrFeatureToEnable) { -not (Contains-Safe $availablePkgmgrFeatures $onePkgmgrFeatureToEnable) }
      }
    }
  }

  $featureList = $features -join ';'

  $logFile = Get-DataFileApp "pkgmgr" $null '.log'
  DBG ("Doing PKGMGR installation. Log: {0}" -f $logFile)
    
  Run-Process "pkgmgr" "/norestart /quiet /l:`"$logFile`" /iu:$featureList"

  ##
  ReDisable-SystemRestore
  ReDisable-Updates
}
  


#===============                             
$pkgmgrRemove = $vmConfig.pkgmgr.remove
DBG ("Package Manager feature removal: {0}" -f (Is-ValidString($pkgmgrRemove)))

if (Is-ValidString($pkgmgrRemove)) {

  $features = Split-MultiValue($pkgmgrRemove)
  DBG ("Features to be removed: #{0} = {1}" -f $features.Count, $pkgmgrRemove)

  $featureList = $features -join ';'


  $logFile = Get-DataFileApp 'pkgmgrRemove' $null '.log'
  DBG ('Doing PKGMGR removal. Log: {0}' -f $logFile)
    
  Run-Process "pkgmgr" "/norestart /quiet /l:`"$logFile`" /uu:$featureList"

  ##
  ReDisable-SystemRestore
  ReDisable-Updates
}
  

#=====================
DBG ('Install newest possible Windows Management Framework: {0}' -f (Parse-BoolSafe $vmConfig.install.latestWmf))
if (Parse-BoolSafe $vmConfig.install.latestWmf) {

  $wmfBasePath = Join-Path $global:installMediaVolume 'WindowsMamagementFramework'
  DBGIF ('Invalid WMF base install path: {0}' -f $wmfBasePath) { -not (Test-Path $wmfBasePath) }

  $wmfBasePath = Join-Path $wmfBasePath ('win-{0}-{1}' -f $thisOSVersionnumber.ToString('N1', ([System.Globalization.CultureInfo]::InvariantCulture)), $global:thisOSArchitecture)
  DBG ('Do we have a newer installation WMF sources for this OS: {0} | {1}' -f $wmfBasePath, (Test-Path $wmfBasePath))
  DBGIF ('Cannot install newer WMF version when installation package does not exist: {0}' -f $wmfBasePath) { -not (Test-Path $wmfBasePath) }

  if (Test-Path $wmfBasePath) {

    $wmfMSU = $null
    $wmfMSU = Get-ChildItem $wmfBasePath -Force | ? { $_.FullName -like '*.msu' } | sort LastWriteTime | select -Expand FullName | select -Last 1
    DBG ('Found latest WMF installation package: {0}' -f $wmfMSU)
    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $wmfMSU }

    if (Is-ValidString $wmfMSU) {

      $wusaRes = Run-Process 'WUSA' ('"{0}" /quiet /norestart' -f $wmfMSU) -returnExitCode $true
      Repair-PsReadLineMissing

      # Note: 0x00240006 = already installed
      if (($wusaRes -eq 0x00240005) -or ($wusaRes -eq $global:win32_ERROR_SUCCESS_REBOOT_REQUIRED)) {

        DBG ('Windows update installation asks for a restart, so do it and rerun after reboot.')
        $global:restartCurrentPhase = $true
        exit
      }

      DBGIF ('WUSA installation of WMF returned unexpected error: {0}' -f $wusaRes) { $wusaRes -ne 0x00240006 }
    }
  }

  ##
  ReDisable-SystemRestore
  ReDisable-Updates
}


#======================
DBG ("Local NTFS folders and shares.")

$localFSs = $vmConfig.SelectNodes('./*/fs[@instance="local"]')
DBG ("Found {0} local NTFS folders" -f $localFSs.Count)

foreach ($oneFldr in $localFSs) {

  $oneFldrPath = Resolve-VolumePath $oneFldr.dir

  Create-NTFSFolderAndShare $oneFldrPath $oneFldr.share $oneFldr.dacl $oneFldr.quota -exists (Parse-BoolSafe $oneFldr.exists) -adminFullEveryoneChange (Parse-BoolSafe $oneFldr.everyoneChangeOnly)

  # Note: mind we are doing the same code once again for remote instances later
  DBG ('Should publish in IIS: {0} | {1} | {2}' -f (Is-ValidString $oneFldr.iis), $oneFldr.iis, $oneFldr.dns)
  if (Is-ValidString $oneFldr.iis) {

    $dnsAlias = Strip-ValueFlags $oneFldr.dns
    $iisVD = Strip-ValueFlags $oneFldr.iis
    DBG ('Flags: dnsAlias = {0} | iisVD = {1} | iis = {2}' -f $dnsAlias, $iisVD, $oneFldr.iis)
    DBG ('Additional IIS params: upload = {0} | maxAge = {1} | highBit = {2} | doubleEsc = {3}' -f $oneFldr.iisUploadSize, $oneFldr.iisMaxAge, $oneFldr.iisHighBit, $oneFldr.iisDoubleEsc)

    ###############################################
    ###############################################
    ## SEE BELOW, THERE IS THIS BLOCK ONCE AGAIN ##
    ###############################################
    ###############################################

    if (Has-ValueFlags $oneFldr.iis S) {

      DBG ('Publish into a separate web site')
      Publish-FSinIIS $oneFldrPath $iisVD $dnsAlias (Join-Path $env:SystemDrive\inetpub $dnsAlias.Replace('.', '-')) $oneFldr.iisExt (Parse-BoolSafe $oneFldr.iisDoubleEsc) (Parse-BoolSafe $oneFldr.iisHighBit) $oneFldr.iisUploadSize $oneFldr.iisMaxAge

    } else {

      DBG ('Publish into the Default Web Site')
      Publish-FSinIIS $oneFldrPath $iisVD $dnsAlias $null $oneFldr.iisExt (Parse-BoolSafe $oneFldr.iisDoubleEsc) (Parse-BoolSafe $oneFldr.iisHighBit) $oneFldr.iisUploadSize $oneFldr.iisMaxAge
    }
  }

  DBG ('Should define NTLM alias: {0} | {1}' -f (Is-ValidString $oneFldr.dns), $oneFldr.dns)
  if (Is-ValidString $oneFldr.dns) {

    Add-BackConnectionHostName (Strip-ValueFlags $oneFldr.dns)
  }


  if (Parse-BoolSafe $oneFldr.copySample) {

    $sampleSourcePath = Join-Path $global:rootDir 'IIS\sampleSMB'
    DBG ('Will copy sample files: {0} | {1}' -f $sampleSourcePath, $oneFldrPath)
    DBGSTART
    Copy-Item -Path $sampleSourcePath -Destination $oneFldrPath -Recurse -Force
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
  }
}



#======================
DBG ("File Server: {0}" -f $vmConfig.fs.instance)

if (Is-ValidString $vmConfig.fs.instance) {

  $fsRoot = Resolve-VolumePath $vmConfig.fs.root
  
  [string] $fsAdminsAce = [string]::Empty
  if (Is-ValidString $vmConfig.fs.app.aGroup) {

    $fsAdminsAce = 'F${0}@{1}' -f $vmConfig.fs.app.aGroup, $vmConfig.fs.app.iDomain
    DBG ('We have some FS admins group ACE to define: {0}' -f $fsAdminsAce)
  }
  
  Create-NTFSFolderAndShare $fsRoot $null $fsAdminsAce $null
  
  DBG ('Searching for shares requested in this instance: {0}' -f $vmConfig.fs.instance)
  $fsShares = $xmlConfig.SelectNodes('./VMs/MACHINE[vm/@do="true"]/*/fs[translate(@instance,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}"]' -f $vmConfig.fs.instance.ToLower())
  DBG ('Found shares: {0}' -f (Get-CountSafe $fsShares))
  
  foreach ($oneFldr in $fsShares) {
  
    $oneFldrPath = Join-Path $fsRoot $oneFldr.dir
    
    Create-NTFSFolderAndShare $oneFldrPath $oneFldr.share ('{0}|{1}' -f $fsAdminsAce, $oneFldr.dacl) $oneFldr.quota -adminFullEveryoneChange (Parse-BoolSafe $oneFldr.everyoneChangeOnly)


    # Note: really, just a plain copy/paste from above
    DBG ('Should publish in IIS: {0} | {1} | {2}' -f (Is-ValidString $oneFldr.iis), $oneFldr.iis, $oneFldr.dns)
    if (Is-ValidString $oneFldr.iis) {

      $dnsAlias = Strip-ValueFlags $oneFldr.dns
      $iisVD = Strip-ValueFlags $oneFldr.iis
      DBG ('Flags: dnsAlias = {0} | iisVD = {1} | iis = {2}' -f $dnsAlias, $iisVD, $oneFldr.iis)
      DBG ('Additional IIS params: upload = {0} | maxAge = {1} | highBit = {2} | doubleEsc = {3}' -f $oneFldr.iisUploadSize, $oneFldr.iisMaxAge, $oneFldr.iisHighBit, $oneFldr.iisDoubleEsc)

      ###############################################
      ###############################################
      ## SEE ABOVE, THERE IS THIS BLOCK ONCE AGAIN ##
      ###############################################
      ###############################################

      if (Has-ValueFlags $oneFldr.iis S) {

        DBG ('Publish into a separate web site')
        Publish-FSinIIS $oneFldrPath $iisVD $dnsAlias (Join-Path $env:SystemDrive\inetpub $dnsAlias.Replace('.', '-')) $oneFldr.iisExt (Parse-BoolSafe $oneFldr.iisDoubleEsc) (Parse-BoolSafe $oneFldr.iisHighBit) $oneFldr.iisUploadSize $oneFldr.iisMaxAge

      } else {

        DBG ('Publish into the Default Web Site')
        Publish-FSinIIS $oneFldrPath $iisVD $dnsAlias $null $oneFldr.iisExt (Parse-BoolSafe $oneFldr.iisDoubleEsc) (Parse-BoolSafe $oneFldr.iisHighBit) $oneFldr.iisUploadSize $oneFldr.iisMaxAge
      }
    }
<#    
    DBG ('Should publish in IIS: {0} | {1} | {2}' -f (Is-ValidString $oneFldr.iis), $oneFldr.iis, $oneFldr.dns)
    if (Is-ValidString $oneFldr.iis) {

      Publish-FSinIIS $oneFldrPath $oneFldr.iis (Strip-ValueFlags $oneFldr.dns)
    }
#>

    DBG ('Should define NTLM alias: {0} | {1}' -f (Is-ValidString $oneFldr.dns), $oneFldr.dns)
    if (Is-ValidString $oneFldr.dns) {

      Add-BackConnectionHostName (Strip-ValueFlags $oneFldr.dns)
    }


    if (Parse-BoolSafe $oneFldr.copySample) {

      $sampleSourcePath = Join-Path $global:rootDir 'IIS\sampleSMB'
      DBG ('Will copy sample files: {0} | {1}' -f $sampleSourcePath, $oneFldrPath)
      DBGSTART
      Get-ChildItem $sampleSourcePath | Copy-Item -Destination $oneFldrPath -Recurse -Force
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }
  }
}


#=========================
DBG ('Install NLB: {0}' -f (Is-ValidString $vmConfig.nlb.instance))

if (Is-ValidString $vmConfig.nlb.instance) {

  DBG ('Will install NLB on the following IP: {0} | {1}' -f $vmConfig.nlb.instance, $vmConfig.nlb.ip)

}


#=========================
DBG ("DNS Server configuration: {0}" -f (Is-ValidString $vmConfig.dns.name))

if (Is-ValidString $vmConfig.dns.name) {


  #=========================
  DBG ("Setting DNS forwarders: {0}" -f (Is-ValidString $vmConfig.dns.forwarders))

  if (Is-ValidString($vmConfig.dns.forwarders)) {

    if ($vmConfig.dns.forwarders -eq $emptyValueMarker) {

      DBG ("Resetting forwarders to NONE.")
      Run-Process 'DNSCMD' '/ResetForwarders'
    }

    else {

      $dnsForwarders = Split-MultiValue $vmConfig.dns.forwarders
    
      DBG ("Resetting forwarders to: {0}x = {1}" -f $dnsForwarders.Count, $vmConfig.dns.forwarders)
      Run-Process 'DNSCMD' ('/ResetForwarders {0}' -f ($dnsForwarders -join ' '))
    }
  }


  #=========================
  DBG ('Conditional forwarders: #{0}' -f (Get-CountSafe $vmConfig.dns.fwd))

  if ((Get-CountSafe $vmConfig.dns.fwd) -gt 0) {

    $vmConfig.dns.fwd | % {

      $oneFwd = $_
      $cmdSwitches = ''

      $fwdIPs = (Split-MultiValue $oneFwd.ip) -join ' '

      if ($oneFwd.type -eq 'F') { $cmdSwitches = '/ZoneAdd {0} /DsForwarder {1} /DP /Forest' -f $oneFwd.zone, $fwdIPs }
      if ($oneFwd.type -eq 'O') { $cmdSwitches = '/ZoneAdd {0} /DsForwarder {1} /DP /Domain' -f $oneFwd.zone, $fwdIPs }
      if ($oneFwd.type -eq 'L') { $cmdSwitches = '/ZoneAdd {0} /Forwarder {1}' -f $oneFwd.zone, $fwdIPs }

      Run-Process 'DNSCMD' ('localhost {0}' -f $cmdSwitches)
    }
  }


  #=========================
  DBG ("Additional DNS zones: {0}" -f (Is-ValidString $vmConfig.dns.zones))

  if (Is-ValidString $vmConfig.dns.zones) {

    $dnsZones = Split-MultiValue $vmConfig.dns.zones

    DBG ("DNS Zones to be created: {0}x | {1}" -f $dnsZones.Count, (($dnsZones -join ', ') | Out-String))
    
    $dnsZones | % {
      
      $oneZone = $_
      DBG ('One DNS zone for processing: {0}' -f $oneZone)

      Create-DNSZone $oneZone
    }
  }


  #======================
  $dnsRecords = $vmConfig.dns.SelectNodes('./rr')
  DBG ('Manual DNS records: {0}' -f (Get-CountSafe $dnsRecords))

  $dnsRecords | % {

    Add-DNSRecord $_.type $_.name $_.zone $_.value
  }
  
  
  
  #======================
  # Note: sphere - if there is a split-dns, then it does not ever have more than two spheres
  #                an internal sphere which is inside to the intranet and a public DNS server
  #                If we run more intranets, there never be the same zone in more intranets and/or public DNS servers

  [string] $dnsSphere = $vmConfig.dns.sphere
  # Note: we cannot call .ToLower() on possibly $null object so we wait until it is cast to [string]
  $dnsSphere = $dnsSphere.ToLower() 
  [bool] $areWeRODC = Is-LocalComputerDomainController -rodcOnly $true
  DBG ('Current DNS sphere determined: {0} | rodc = {1}' -f $dnsSphere, $areWeRODC)



  #======================
  # Note: this is the old simple method to create @dns="" record
  #       which has been obsoleted by the new  technology below
  DBG ('Application OBSOLETE DNS records: sphere = {0}' -f $dnsSphere)

  if ($dnsSphere -eq 'intranet') {

    # Note: on Windows 2000, the ZoneType is 0/1 for file/AD primary zones
    #       since Windows 2003, the ZoneType is only 1 regardless its file or AD nature
    $localPrimaryDNSZones = Get-WmiQueryArray '.' 'SELECT * FROM MicrosoftDNS_Zone WHERE ZoneType = 0 OR ZoneType = 1' 'root\MicrosoftDNS'

    if ($localPrimaryDNSZones.Count -gt 0) {

      DBG ('We have some primary DNS zones here.')
      
      [string] $dnsZoneFilter = ''
      foreach ($oneLocalPrimaryDNSZone in $localPrimaryDNSZones) {

        if (Is-DnsZoneUpdatable $oneLocalPrimaryDNSZone) {

          DBG ('Adding XPath filter for primary DNS zone: {0}' -f $oneLocalPrimaryDNSZone.Name)
        

          # Note that there may be more DNS names in the @dns MultiValue

          if (Is-ValidString $dnsZoneFilter) {

            $dnsZoneFilter += ' or substring-before(translate(@dns,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"), ".{0}")' -f $oneLocalPrimaryDNSZone.Name.ToLower()
        
          } else {

            $dnsZoneFilter += 'substring-before(translate(@dns,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"), ".{0}")' -f $oneLocalPrimaryDNSZone.Name.ToLower()
          }
        }
      }

      $dnsZoneFilter = './VMs/MACHINE[vm/@do="true"]/*/*[{0}]' -f $dnsZoneFilter
      DBG ('Final XPath for local app dns records: {0}' -f $dnsZoneFilter)
      $dnsApps = $xmlConfig.SelectNodes($dnsZoneFilter)
      
      DBG ("Found {0} application installations that require OBSOLETE DNS records" -f (Get-CountSafe $dnsApps))

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

        foreach ($oneApp in $dnsApps) {

          $dnsRecords = Split-MultiValue $oneApp.dns
        
          $oneAppResolved = Resolve-DNSNameWithConfig $null $oneApp

          DBGIF ("Application requires OBSOLETE DNS records: machine = {0} | node = {1} | cname = {2} | {3}" -f (Get-MachineFQDN $oneApp), $oneApp.psbase.ParentNode.name, $oneAppResolved.cnameFQDN, ($dnsRecords -join ',')) { $true }
        
          foreach ($oneRecord in $dnsRecords) {

            if (Has-ValueFlags $oneRecord C) { 
          
              $recType = 'CNAME'
              $recData = '{0}.' -f $oneAppResolved.cnameFQDN
            }
          
            if (Has-ValueFlags $oneRecord A) { 
          
              $recType = 'A'
              $recData = $oneAppResolved.ipAddress
            }

            $oneRecordPure = Strip-ValueFlags $oneRecord
            $firstDotIndex = $oneRecordPure.IndexOf('.')
            $recZone = $oneRecordPure.SubString($firstDotIndex + 1)
            $recName = $oneRecordPure.SubString(0, $firstDotIndex)

            Add-DNSRecord $recType $recName $recZone $recData
          }
        }
      }
    }
  }


  Wait-IfRequested dnsrr | Out-Null

  DBG ('Application requested NewAge DNS zones to be created (with @createAt): sphere = {0}' -f $dnsSphere)
  # Note: the @createAt is used here instead of @instance because we do not mark DNS servers with @instance
  #       value of @createAt will usually be the machine domain name, but not always necessarily
  $appDnsZones = $xmlConfig.SelectNodes('./VMs/MACHINE[vm/@do="true"]//dnsrr[translate(@sphere,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}" and @createAt]' -f $dnsSphere)
  DBG ('Found application NewAge DNS zones to be created (with @createAt): {0} | areWeRODC = {1}' -f (Get-CountSafe $appDnsZones), $areWeRODC)

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

    foreach ($oneAppDnsZone in $appDnsZones) {

      [string] $createAtMarker = $oneAppDnsZone.createAt.Trim('.')
      DBG ('One app NewAge DNS zone should be created at: zone = {0} | at = {1} | atNormalized = {2}' -f $oneAppDnsZone.zone, $oneAppDnsZone.createAt, $createAtMarker)
      
      # Note: the ZoneType member for primary DNS zone is 0 on Windows 2000 while is 1 on Windows 2003 and newer
      #       for example conditional forwarder has ZoneType = 4
      DBGIF $MyInvocation.MyCommand.Name { $global:thisOSVersionNumber -lt 5.2 }
      $markerZoneExists = Get-WMIQuerySingleObject '.' ('SELECT * FROM MicrosoftDNS_Zone WHERE Name = "{0}" AND (ZoneType = 0 OR ZoneType = 1)' -f $createAtMarker) -namespace 'root\MicrosoftDNS'

      if ((Is-NonNull $markerZoneExists) -and (Is-DnsZoneUpdatable $markerZoneExists)){

        [string] $oneAppDnsZoneZoneName = (Strip-ValueFlags $oneAppDnsZone.zone).Trim('.').ToLower()
        DBG ('Marker zone found, will create the application requested zone if not existing: {0} | {1}' -f $oneAppDnsZone.zone, $oneAppDnsZoneZoneName)
        $oneAppDnsZoneExists = Get-WMIQuerySingleObject '.' ('SELECT * FROM MicrosoftDNS_Zone WHERE Name = "{0}"' -f $oneAppDnsZoneZoneName) -namespace 'root\MicrosoftDNS'

        if (Is-Null $oneAppDnsZoneExists) {
          
          DBG ('The requested zone does not exist, create: {0} | {1}' -f $oneAppDnsZoneZoneName, $oneAppDnsZone.zone)
          # Note: this is correct, we must pass the flags down as well
          Create-DNSZone $oneAppDnsZone.zone
        }
      }
    }
  }


  DBG ('Application requested NewAge DNS records to be created: sphere = {0}' -f $dnsSphere)
  $appDnsRecords = $xmlConfig.SelectNodes('./VMs/MACHINE[vm/@do="true"]//dnsrr[translate(@sphere,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}"]' -f $dnsSphere)
  DBG ('Found application NewAge DNS records (dnsrr) to be created: {0}' -f (Get-CountSafe $appDnsRecords))

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

    foreach ($oneAppDnsRecord in $appDnsRecords) {

      $ownerZoneName = (Strip-ValueFlags $oneAppDnsRecord.zone).Trim('.')
      DBG ('One app NewAge DNS record (one dnsrr) should be created at: zoneNormalized = {0} | zone = {1} | record = {2} | type = {3}' -f $ownerZoneName, $oneAppDnsRecord.zone, $oneAppDnsRecord.rec, $oneAppDnsRecord.type)
      # Note: MicrosoftDNS_Zone.ZoneType since Windows 2003 values:
      #       1 = primary zone
      #       2 = secondary zone (we do not go into creating records in secondary zones apparently :-)
      #       3 = stub zone
      #       4 = conditional forwarder
      $ownerZoneExists = Get-WMIQuerySingleObject '.' ('SELECT * FROM MicrosoftDNS_Zone WHERE Name = "{0}" AND (ZoneType = 0 OR ZoneType = 1)' -f $ownerZoneName) -namespace 'root\MicrosoftDNS'

      if ((Is-NonNull $ownerZoneExists) -and (Is-DnsZoneUpdatable $ownerZoneExists)) {

        [string] $newRecordTypeName = 'UNSUPPORTED'

        switch ($oneAppDnsRecord.type) {

          'A' { $newRecordTypeName = 'A' }

          'C' { $newRecordTypeName = 'CNAME' }
          'CNAME' { $newRecordTypeName = 'CNAME' }

          'S' { $newRecordTypeName = 'SRV' }
          'SRV' { $newRecordTypeName = 'SRV' }

          'T' { $newRecordTypeName = 'TXT' }
          'TXT' { $newRecordTypeName = 'TXT' }

          'M' { $newRecordTypeName = 'MX' }
          'MX' { $newRecordTypeName = 'MX' }

          default { DBGIF ('Unsupported RR: {0}' -f $oneAppDnsRecord.type) { $true } }
        }

        [string] $newRecordName = $oneAppDnsRecord.rec.Trim('.')
        [string] $newRecordFullName = ('{0}.{1}' -f $newRecordName, $ownerZoneName).Trim('.')

        #
        #

        [string] $newRecordValue = $oneAppDnsRecord.value

        if (Is-ValidString $oneAppDnsRecord.ref) {

          # Note: we may have several instances of a single service on a machine
          #       and each instance may use a different NLB/CLUSTER for example

          DBG ('The record value is referencing: {0}' -f $oneAppDnsRecord.ref)
          
          [string[]] $refTokens = Split-MultiValue $oneAppDnsRecord.ref
          [string] $refType = $refTokens[0]
          DBGIF ('Weird reference type: {0}' -f $refType) { -not (Contains-Safe @('ip', 'fqdn', 'nlb') $refType) }

          [System.Xml.XmlElement] $referedMachine = $null
          [System.Xml.XmlElement] $referedSvc = $null

          if ($refTokens.Length -lt 2) {
             
            DBG ('The DNS record is refering to the local machine')
            $referedMachine = Get-MachineFromElement $oneAppDnsRecord
            $referedSvc = Get-SvcFromElement $oneAppDnsRecord
                
          } else {

            DBGIF $MyInvocation.MyCommand.Name { $refTokens.Length -lt 3 }
            [string] $referedSvcType = $refTokens[1]
            [string] $referedInstance = $refTokens[2]

            DBG ('The DNS record is refering to a machine hosting a service instance: {0} | {1}' -f $referedSvcType, $referedInstance)
            
            if ($referedInstance -eq 'local') {

              DBG ('The DNS record is refering to a local service')
              $referedMachine = Get-MachineFromElement $oneAppDnsRecord
              $referedSvc = Get-SvcFromElement $oneAppDnsRecord
           
            } else {

              DBG ('The DNS record is refering to possibly a foreign service')
              $referedSvc = Get-FirstAppHostInInstance -appTag $referedSvcType -instance $referedInstance -hostType svcConfig
              $referedMachine = Get-MachineFromElement $referedSvc
            }
          }

          DBG ('Refered machine determined: {0}' -f $referedMachine.hostName)
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $referedMachine.hostName }
          DBG ('Refered svc determined: {0} | {1}' -f $referedSvc.psbase.Name, $referedSvc.instance)
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $referedSvc.psbase.Name }
            
          # Note: not all services must have an instance specified
          #DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $referedSvc.instance }

          switch ($refType) {

            'ip' { 

              DBG ('Get the IP address for the refered machine')
              [object[]] $referedMachineIPs = $null
              $referedMachineIPs = Get-NetworkMap $referedMachine.hostName | ? { Is-EmptyString $_.nlbInstance }
              DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $referedMachineIPs) -ne 1 }
              $newRecordValue = $referedMachineIPs[0].ipAddress
            }

            'fqdn' { 
            
              DBG ('Get the FQDN for the refered machine')
              $newRecordValue = Get-MachineFQDN $referedMachine
            }

            'nlb' {
             
              DBG ('Get the NLB IP address for the refered service: {0}' -f $referedSvc.psbase.Name)

              if ($referedSvc.psbase.Name -eq 'nlb') {

                $referedNlbInstance = $referedSvc.instance

              } else {

                [System.Xml.XmlElement] $referedSvcNlbRule = $null
                $referedSvcNlbRule = $referedSvc.SelectSingleNode('./nlbrule')
                DBGIF $MyInvocation.MyCommand.Name { Is-Null $referedSvcNlbRule }
                
                $referedNlbInstance = $referedSvcNlbRule.instance
              }

              DBG ('Refered NLB instance: {0}' -f $referedNlbInstance)
              DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $referedNlbInstance }

              [object[]] $referedMachineNlb = $null
              $referedMachineNlb = Get-NetworkMap $referedMachine.hostName | ? { $_.nlbInstance -eq $referedNlbInstance }
              DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $referedMachineNlb) -ne 1 }
              $newRecordValue = $referedMachineNlb[0].ipAddress
            }
             
            default { DBGIF ('Invalid ref type') { $true } }
          }


          DBG ('Real record value determined: {0}' -f $newRecordValue)
        }

        #
        # 

        DBG ('Normalize and assert final value: {0} | {1}' -f $newRecordValue, $newRecordTypeName)

        if ($newRecordTypeName -eq 'CNAME') {

          $newRecordValue = '{0}.' -f $newRecordValue.Trim('.').ToLower()
        }

        if ($newRecordTypeName -eq 'A') {

          $newRecordValue = $newRecordValue.Trim('.')
        }

        if (Is-EmptyString $newRecordName.Trim('.')) { 

          DBG ('Normalize self-zone-record name')
          $newRecordName = '.'
          DBGIF ('Invalid self-zone-record type: {0}' -f $newRecordTypeName) { $newRecordTypeName -ne 'A' }
        }

        DBGIF $MyInvocation.MyCommand.Name { ($newRecordTypeName -eq 'CNAME') -and (Is-IPv4OrIPv6Address $newRecordValue) }
        DBGIF $MyInvocation.MyCommand.Name { ($newRecordTypeName -eq 'A') -and (-not (Is-IPv4OrIPv6Address $newRecordValue)) }
        DBGIF $MyInvocation.MyCommand.Name { ($newRecordTypeName -ne 'A') -and ($newRecordValue -eq '.') }

        #
        #

        DBG ('Verify whether the exact record already exists: {0} | {1} | {2} | {3}' -f $ownerZoneName, $newRecordName, $newRecordFullName, $newRecordTypeName, $newRecordValue)
        $rrExactExists = Get-WmiQueryArray '.' ('SELECT * FROM MicrosoftDNS_ResourceRecord WHERE __CLASS = "MicrosoftDNS_{0}Type" AND ContainerName = "{1}" AND OwnerName = "{2}" AND RecordData = "{3}"' -f $newRecordTypeName, $ownerZoneName, $newRecordFullName, $newRecordValue) -namespace 'root\MicrosoftDNS'

        if ((Get-CountSafe $rrExactExists) -eq 0) {

          DBG ('Verify whether a similar record already exists: {0} | {1} | {2} | {3}' -f $ownerZoneName, $newRecordName, $newRecordFullName, $newRecordTypeName)
          $rrSimilarExists = Get-WmiQueryArray '.' ('SELECT * FROM MicrosoftDNS_ResourceRecord WHERE __CLASS = "MicrosoftDNS_{0}Type" AND ContainerName = "{1}" AND OwnerName = "{2}"' -f $newRecordTypeName, $ownerZoneName, $newRecordFullName) -namespace 'root\MicrosoftDNS'
          DBGIF ("Similar but not the same DNS resource records already exist: {0} | -->`r`n{1}`r`n{2}" -f $newRecordFullName, ($rrSimilarExists | Out-String), ($rrExactExists | Out-String)) { (Get-CountSafe $rrSimilarExists) -gt 0 }

          Add-DNSRecord -type $newRecordTypeName -name $newRecordName -zone $ownerZoneName -value $newRecordValue
        }
      }
    }
  }



  #================
  DBG ("Restarting DNS server service to finish its configuration")
  Restart-Service -Name DNS -EV er -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $er
}



$winRMPath = Join-Path $env:windir 'System32\winrm.vbs'
DBG ('WINRM script exists: {0} | {1}' -f (Test-Path $winRMPath), $winRMPath)

#==================
DBG ('Should we enable WINRM server remote access here? {0}' -f (Parse-BoolSafe $vmConfig.winrm.enable))

if (Parse-BoolSafe $vmConfig.winrm.enable) {

  DBGIF 'WinRM already enabled for remote access' { ($global:thisOSVersionNumber -ge 6.2) }

  Run-Process 'cscript' ('"{0}" qc -quiet' -f $winRMPath)
}


#==================
DBG ('Should we enable WINRM client? {0}' -f (Parse-BoolSafe $vmConfig.wks.winrm.enable))

if (Parse-BoolSafe $vmConfig.wks.winrm.enable) {

  $winRmservice = Get-WmiQuerySingleObject '.' 'SELECT * FROM Win32_Service WHERE Name = "winrm"'

  DBGIF 'WinRM client already enabled' { $winRmservice.State -eq 'Running' }
  DBGIF 'WinRM client already enabled' { $winRmservice.StartMode -eq 'Auto' }

  if ($winRmservice.StartMode -ne 'Auto') {

    DBG ('Enable WINRM service to start Auto')
    DBGSTART
    $wmiRs = $null
    $wmiRs = $winRmservice.ChangeStartMode('Automatic')
    DBGER $error
    DBGEND
    DBGWMI $wmiRs
  }

  if ($winRmservice.State -ne 'Running') {

    DBG ('Start WINRM service')
    DBGSTART
    Start-Service winrm
    DBGER $error
    DBGEND
  }
}



DBG ('Should we enable some authentication methods for WINRM? {0}' -f ((Is-ValidString $vmConfig.winrm.auth) -or (Is-ValidString $vmConfig.wks.winrm.auth)))

if ((Is-ValidString $vmConfig.winrm.auth) -or (Is-ValidString $vmConfig.wks.winrm.auth)) {

  DBG ('Get current WinRM config first')
  Run-Process 'cscript' ('"{0}" get winrm/config' -f $winRMPath)
 
  DBGIF $MyInvocation.MyCommand.Name { (Get-Service winrm).Status -ne 'Running' }
  DBGIF $MyInvocation.MyCommand.Name { (Get-WmiQuerySingleObject '.' 'SELECT * FROM Win32_Service WHERE Name = "winrm"').StartMode -ne 'Auto' }


  DBG ('WinRM server authentication methods to enable: {0}' -f $vmConfig.winrm.auth)

  if (Is-ValidString $vmConfig.winrm.auth) {

    foreach ($oneWinrmAuth in (Split-MultiValue $vmConfig.winrm.auth)) {

      DBG ('Will enable WinRM server authentication: {0}' -f $oneWinrmAuth)
      Run-Process 'cscript' "`"$winRMPath`" set winrm/config/service/auth @{$oneWinrmAuth=`"true`"} "
    }
  }


  DBG ('WinRM client authentication methods to enable: {0}' -f $vmConfig.wks.winrm.auth)

  if (Is-ValidString $vmConfig.wks.winrm.auth) {

    foreach ($oneWinrmAuth in (Split-MultiValue $vmConfig.wks.winrm.auth)) {

      DBG ('Will enable WinRM client authentication: {0}' -f $oneWinrmAuth)
      Run-Process 'cscript' "`"$winRMPath`" set winrm/config/client/auth @{$oneWinrmAuth=`"true`"} "

      if ($oneWinrmAuth -eq 'CredSSP') {

        DBG ('Should the client have a CredSSP access to some instances: {0} = {1}' -f (Is-ValidString $vmConfig.wks.winrm.instances), $vmConfig.wks.winrm.instances)

        foreach ($oneCredSSPInstance in (Split-MultiValue $vmConfig.wks.winrm.instances)) {

          $oneCredSSPInstName = Strip-ValueFlags $oneCredSSPInstance
          $oneCredSSPInstType = Get-ValueFlags $oneCredSSPInstance

          $credSSPFQDNs = Get-AllAppHostsOfInstance $oneCredSSPInstType $oneCredSSPInstName fqdn

          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $credSSPFQDNs) -lt 1 }

          foreach ($oneCredFQDN in $credSSPFQDNs) {

            Allow-CredentialsDelegation fresh "wsman/$oneCredFQDN"

            DBGIF $MyInvocation.MyCommand.Name { $oneCredFQDN.IndexOf('.') -lt 1 }
            Allow-CredentialsDelegation fresh ('wsman/{0}' -f $oneCredFQDN.SubString(0, ($oneCredFQDN.IndexOf('.'))))
          }
        }

        DBG ('Should the client have a CredSSP access to some other machines: {0} = {1}' -f (Is-ValidString $vmConfig.wks.winrm.machines), $vmConfig.wks.winrm.machines)

        foreach ($oneCredSSPMachine in (Split-MultiValue $vmConfig.wks.winrm.machines)) {

          Allow-CredentialsDelegation fresh "wsman/$oneCredSSPMachine"
        }
      }
    }
  }
}



DBG ('Should we enable PS Remoting: {0}' -f (Parse-BoolSafe $vmConfig.winrm.psremoting))
if (Parse-BoolSafe $vmConfig.winrm.psremoting) {

  [string] $tmpOutString = $null

  if ($PSVersionTable['PSVersion'].Major -gt 2) {

    DBG ('Enabling psremoting without network profile checking on PS 3.0+')
    DBGSTART
    $tmpOutString = Enable-PSRemoting -Force -Confirm:$false -SkipNetworkProfileCheck | Out-String
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

  } else {

    DBG ('Enabling psremoting with network profile checking on older PS 2.0')
    DBGSTART
    $tmpOutString = Enable-PSRemoting -Force -Confirm:$false | Out-String
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
  }

  DBG ('Enabling PS remoting results: {0}' -f $tmpOutString)
}



DBG ('Should we add any WINRM TrustedHosts? {0}' -f (Is-ValidString $vmConfig.wks.winrm.trustedHosts))

if (Is-ValidString $vmConfig.wks.winrm.trustedHosts) {

  DBG ('Get the current list of trusted hosts')
  DBGSTART
  $currentTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
  DBGEND

  $newTrustedHosts = (Split-MultiValue $vmConfig.wks.winrm.trustedHosts) -join ','

  if (Is-ValidString $currentTrustedHosts) {

    $resultingTrustedHosts = '{0},{1}' -f $currentTrustedHosts.Trim(','), $newTrustedHosts

  } else {

    $resultingTrustedHosts = $newTrustedHosts
  }

  DBG ('Enabling WinRM trusted hosts: orig = {0} | new = {1} | set = {2}' -f $currentTrustedHosts, $newTrustedHosts, $resultingTrustedHosts)
  DBGSTART
  Set-Item WSMan:\localhost\Client\TrustedHosts -Value $resultingTrustedHosts -Force
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
}

#
# 
#
#

if (Is-FirstForestDC $vmConfig) {

    DBG ('Prepare the finalization machinery, only here after FS is ready as well as DNS')

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

    $rootDSE = Get-DE 'RootDSE' ([ref] $deList)
    $domainDN = GDES $rootDSE defaultNamingContext
    $domainDE = Get-DE $domainDN ([ref] $deList)
    $configDN = GDES $rootDSE configurationNamingContext
    $configDE = Get-DE $configDN ([ref] $deList)
    $domainFQDN = Get-NCName $domainDN 'dNSRoot'
    $myForestMachines = Get-ForestMachines $domainFQDN
  
    #
    #

    DBG ('Finalization container in configuration partition')
    $finalCntDnPath = New-Object System.Collections.ArrayList
    Add-DNComponent $finalCntDnPath 'CN=Services' 'container' -rootDN $configDN
    #Add-DNComponent $finalCntDnPath 'CN=Sevecek VM Buildup' 'container' $null '/N /I:T$Domain Admins:GA|/I:T$Authenticated Users:GR|Authenticated Users:CC;sevecekVMBuilderTaskContainer|-'
    # Note: we must configure the object with Administrators group instead of Domain Admins because some actions on DCs are running under install accounts which are made members 
    #       of Administrators group only and are not put into Domain Admins explicitly (an example is RDP role builder script)
    Add-DNComponent $finalCntDnPath 'CN=Sevecek VM Buildup' 'container' $null '/N /I:T$Administrators:GA|/I:T$Authenticated Users:GR|-'
    $finalCntDE = Create-DNPath $finalCntDnPath $configDE ([ref] $deList)

    #
    #

    foreach ($oneMyForestMachine in $myForestMachines) {

      if ($oneMyForestMachine.do) {

        DBG ('Finalization machine object: {0}' -f $oneMyForestMachine.fqdn)

        $finalMachineDnPath = New-Object System.Collections.ArrayList

        if ($oneMyForestMachine.domain -ne $domainFQDN) {

          # We cannot use the joinerCred to delegate write access since subdomain accounts are not yet defined
          #Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null 'Authenticated Users:RPWP;sevecek-VMB-MachineFinished' (GDES $finalCntDE distinguishedName)
          Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null 'Authenticated Users:RPWP;Sevecek VMBuilder Task Attributes' (GDES $finalCntDE distinguishedName)
      
        } elseif (Is-ValidString $oneMyForestMachine.joinerCred) {

          # Not forest root domain first DC case
          #Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null ('{0}:RPWP;sevecek-VMB-MachineFinished' -f $oneMyForestMachine.joinerCred) (GDES $finalCntDE distinguishedName)
          Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null ('{0}:RPWP;Sevecek VMBuilder Task Attributes' -f $oneMyForestMachine.joinerCred) (GDES $finalCntDE distinguishedName)

        } else {

          Add-DNComponent $finalMachineDnPath ('CN={0}.{1}' -f $oneMyForestMachine.hostName, $oneMyForestMachine.domain) 'sevecekVMBuilderTaskContainer' $null $null (GDES $finalCntDE distinguishedName)
        }


        [ADSI] $createdMachineDE = $null
        $createdMachineDE = (Create-DNPath $finalMachineDnPath $finalCntDE ([ref] $deList))

        DBG ('Set some basic machine parameters: {0}' -f $oneMyForestMachine.hostName)
        DBGSTART
        $createdMachineDE.Put('sevecek-VMB-Hostname', $oneMyForestMachine.hostName)
        $adsiRs = $createdMachineDE.SetInfo()
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBG ('ADSI Result: {0}' -f ($adsiRs | Out-String))

        #
        #

        DBG ('Machine finalization file container should be created as well: {0} | {1}' -f (Is-ValidString $vmConfig.finalizationShare), $oneMyForestMachine.fqdn)
        if (Is-ValidString $vmConfig.finalizationShare) {

          [string] $finalizationFile = Prepare-FinalizationFile -share $vmConfig.finalizationShare -hostName $oneMyForestMachine.hostName -domain $oneMyForestMachine.domain
          DBG ('Finalization machine object in the share: {0}' -f $finalizationFile)
        }
      
      } else {

        DBG ('Skipping finalization object for disabled machine: {0}' -f $oneMyForestMachine.fqdn)
      }
    }


    Dispose-List ([ref] $deList)
}
  



#
#
#
#


Finish-Machine $false 'BeforeTrusts'

#
#
#
#

DBG ('Should we build any trusts from/to this domain')
$trusts = $vmConfig.SelectNodes('./domain/trust')
DBG ('Found requested trusts: {0}' -f (Get-CountSafe $trusts))

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

  # Note: just to be on the safe side
  DBGIF $MyInvocation.MyCommand.Name { -not (Is-FirstDomainDC $vmConfig) }
  if (Is-FirstDomainDC $vmConfig) {

    foreach ($oneTrust in $trusts) {

      DBG ('One trust to build: {0} | {1} | {2}' -f $oneTrust.domain, $oneTrust.type, $oneTrust.direction)


      # Note: works only for top level domains where @new=FQDN
      $trustCustomersQuery = './VMs/MACHINE[vm/@do="true"]/domain[translate(@new,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}"]/trust[translate(@domain,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{1}"]' -f $oneTrust.domain.ToLower(), $global:thisComputerDomain.ToLower()
      DBG ('Which customer has the trust with our domain: {0}' -f $trustCustomersQuery)
      $trustCustomers = $xmlConfig.SelectNodes($trustCustomersQuery)
      DBG ('Found trust customers: {0}' -f (Get-CountSafe $trustCustomers))
      DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $trustCustomers) -ne 1 }

      if ((Get-CountSafe $trustCustomers) -eq 1) {

        DBG ('Get wait creds in order to wait for the remote machine several times')
        $waitCred = Define-JoinerCredentials $oneTrust.svc.login $oneTrust.svc.domain $oneTrust.svc.pwd ('{0}@{1}' -f $oneTrust.svc.login, $oneTrust.svc.domain)


        DBG ('We must first wait for the customer to finish to the same point as we did')
        foreach ($oneTrustCustomer in $trustCustomers) {

          Wait-Machine (Get-MachineWaitParams $oneTrustCustomer) -waiterCredentials $waitCred -subPhase 'BeforeTrusts'
        }


        DBG ('The customer finished. Proceed with the trust creation: {0}' -f $oneTrust.type)
        DBGIF $MyInvocation.MyCommand.Name { ($oneTrust.type -ne 'external') -and ($oneTrust.type -ne 'forest') }

        ##
        ##

        # Note: we are using the .NET trust creating which performs anonymous LSARPC lookup into the foreign forest
        #       and fails with "access denied" if the anonymous access is not enabled
        #       May we use NETDOM TRUST it could be better in the future

        [bool] $anynousNamedPipesUpdated = $false
        DBG ('Get the list of named pipes that have anonymous access enabled')
        DBGSTART
        [string[]] $originalAnynymousPipes = (Get-ItemProperty HKLM:\System\CurrentControlSet\Services\LanManServer\Parameters -Name NullSessionPipes).NullSessionPipes
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBGIF ('The LSARPC named pipe does not have anonymous access enabled: {0}' -f ($originalAnynymousPipes -join ',')) { -not (Contains-Safe $originalAnynymousPipes lsarpc) }
        if (-not (Contains-Safe $originalAnynymousPipes lsarpc)) {

          DBG ('Updating the anonymous named pipe list to contain LSARPC as well')
          DBGSTART
          Set-ItemProperty HKLM:\System\CurrentControlSet\Services\LanManServer\Parameters -Name NullSessionPipes -Value ($originalAnynymousPipes + 'lsarpc') -Type MultiString
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          $anynousNamedPipesUpdated = $true
        }

        ##
        ##

        Run-Process 'nslookup' ('-q=SRV _ldap._tcp.dc._msdcs.{0}' -f $oneTrust.domain)


        if ($oneTrust.type -eq 'external') {

          DBG ('Creating external trust: {0}' -f $oneTrust.direction)
          DBGIF $MyInvocation.MyCommand.Name { (-not (Has-MultiValue $oneTrust.direction 'inbound')) -and (-not (Has-MultiValue $oneTrust.direction 'outbound')) }
          DBGIF 'Name suffix routing not supported yet' { (Parse-BoolSafe $oneTrust.enableSfx) }

          if (Has-MultiValue $oneTrust.direction 'inbound') {
          
            Run-Process netdom ('trust {0} /domain:{1} /passwordT:{2} /Add /OneSide:trusted' -f $oneTrust.domain, $global:thisComputerDomain, $oneTrust.pwd)
          }

          if (Has-MultiValue $oneTrust.direction 'outbound') {

            Run-Process netdom ('trust {0} /domain:{1} /passwordT:{2} /Add /OneSide:trusting' -f $global:thisComputerDomain, $oneTrust.domain, $oneTrust.pwd)
          }

        } elseif ($oneTrust.type -eq 'forest') {

          DBG ('Creating forest trust')

          Create-LocalSideForestTrust $oneTrust.domain $oneTrust.pwd ((Split-MultiValue $oneTrust.direction) -join ',')
    
    
          Finish-Machine $false ('TrustCreated:{0}' -f $oneTrust.domain)
           
          DBG ('We must again wait for the customer to finish to the same point as we did')
          foreach ($oneTrustCustomer in $trustCustomers) {

            Wait-Machine (Get-MachineWaitParams $oneTrustCustomer) -waiterCredentials $waitCred -subPhase ('TrustCreated:{0}' -f $global:thisComputerDomain)
          }


          Wait-OutboundForestTrust $oneTrust.domain
          
          DBG ('Should we enable all the alternative name routing suffixes: {0}' -f $oneTrust.enableSfx)
          if (Parse-BoolSafe $oneTrust.enableSfx) {

            Enable-ForestTrustAllNameSuffixes $oneTrust.domain
          }


          Finish-Machine $false ('TrustVerified:{0}' -f $oneTrust.domain)
           
          DBG ('We must again wait for the customer to finish to the same point as we did')
          foreach ($oneTrustCustomer in $trustCustomers) {

            Wait-Machine (Get-MachineWaitParams $oneTrustCustomer) -waiterCredentials $waitCred -subPhase ('TrustVerified:{0}' -f $global:thisComputerDomain)
          }
        }

        ##
        ##

        if ($anynousNamedPipesUpdated) {

          DBG ('Removing the LSARPC from the anonymous named pipe list which we just updated before creating the trusts')
          DBGSTART
          Set-ItemProperty HKLM:\System\CurrentControlSet\Services\LanManServer\Parameters -Name NullSessionPipes -Value $originalAnynymousPipes -Type MultiString
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }
      }
    }
  }
}


# Note: small application installations
#
#
#

#=====================
DBG ('Do we require smart card support: {0}' -f (Parse-BoolSafe $vmConfig.sc.gemaltoMiniDrv))

if (Parse-BoolSafe $vmConfig.sc.gemaltoMiniDrv) {

  if ($global:thisOSVersion -like '5.*') {

    DBG ('Install USBCCID driver on Windows 5.x')
    
    $usbccidDrvFolder = Join-Path $global:installMediaVolume 'DRIVERS\USBCCID\USBCCID'
    $usbccidDrvPath = Join-Path $usbccidDrvFolder 'usbccid.inf'

    Run-Process (Join-Path $env:SystemRoot 'System32\rundll32.exe') ('advpack.dll,LaunchINFSectionEx "{0}",DefaultInstall,,4N' -f $usbccidDrvPath) $false $null $null $null $true $usbccidDrvFolder


    if ($global:thisOSVersionNumber -eq 5.1) {

      DBG ('Update USBCCID driver on Windows XP')

      $usbccidUpdate = Join-Path $global:installMediaVolume 'DRIVERS\USBCCID\WindowsXP-KB967048-v2-x86-ENU.exe'
      Run-Process $usbccidUpdate '/Nobackup /Overwriteoem /Quiet /Passive /Norestart'

    } else {

      DBG ('Update USBCCID driver on Windows 2003')

      $usbccidUpdate = Join-Path $global:installMediaVolume 'DRIVERS\USBCCID\WindowsServer2003-KB967048-x86-ENU.exe'
      Run-Process $usbccidUpdate '/Nobackup /Overwriteoem /Quiet /Passive /Norestart'
    }


    DBG ('Install Base smart card CSP update on Windows 5.x')

    $baseCSPUpdate = Join-Path $global:installMediaVolume 'DRIVERS\SC-Base-Provider-Windows-KB909520-v1.000-x86-ENU.exe'
    Run-Process $baseCSPUpdate '/Nobackup /Overwriteoem /Quiet /Passive /Norestart'
  }



  $gemaltoMiniDrvFolder = Join-Path $global:installMediaVolume 'DRIVERS\Gemalto IDPrime Minidriver'
  $gemaltoMiniDrvPath = Join-Path $gemaltoMiniDrvFolder 'Gemalto.MiniDriver.IDPrime.inf'

  #Run-Process (Join-Path $env:SystemRoot 'System32\rundll32.exe') ('setupapi,InstallHinfSection DefaultInstall 128 "{0}"' -f $gemaltoMiniDrvPath) $false $null $null $null $true $gemaltoMiniDrvFolder
  Run-Process (Join-Path $env:SystemRoot 'System32\rundll32.exe') ('advpack.dll,LaunchINFSectionEx "{0}",DefaultInstall,,4N' -f $gemaltoMiniDrvPath) $false $null $null $null $true $gemaltoMiniDrvFolder

  # Note: this method is not silent, it displays final confirmation dialog box
  #Run-Process (Join-Path $env:SystemRoot 'System32\InfDefaultInstall.exe') ('"{0}",1' -f $gemaltoMiniDrvPath) $false $null $null $null $true $gemaltoMiniDrvFolder


  DBG ('Should we also install the Gemalto Minidriver Manager: {0}' -f (Parse-BoolSafe $vmConfig.sc.gemaltoMgr))
  if (Parse-BoolSafe $vmConfig.sc.gemaltoMgr) {

    $gemaltoMiniDrvManager = Join-Path $global:installMediaVolume 'DRIVERS\Gemalto Minidriver Manager\MiniDriver_Manager_v2301.msi'
    DBG ('Install Gemalto Minidriver Manager: {0} | exists = {1}' -f $gemaltoMiniDrvManager, (Test-Path $gemaltoMiniDrvManager))
    DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $gemaltoMiniDrvManager) }

    if (Test-Path $gemaltoMiniDrvManager) {

      Run-Process msiexec ('-i "{0}" -qb' -f $gemaltoMiniDrvManager)
    }
  }



  DBG ('Install Monet+ ProId')
  if ($global:thisOSArchitecture -eq '64-bit') {

    DBG ('Install MonetPlus ProId on 64-bit platform')
    $proIdInstallPath = Join-Path $global:installMediaVolume 'DRIVERS\ProId\x64\Setup\setup_proid+_v2.0.7_x64.exe'
    Run-Process $proIdInstallPath '/L"1033" /V"-qb ADDLOCAL=Proidplus_CM_Files,Spravce_karty ADMINMODE=1 SSTART=1 SDESKTOP=0 REMOTEUNBLOCK=0"'

  } else {

    DBG ('Install MonetPlus ProId on 32-bit platform')
    $proIdInstallPath = Join-Path $global:installMediaVolume 'DRIVERS\ProId\x86\Setup\setup_proid+_v2.0.7_x86.exe'
    Run-Process $proIdInstallPath '/L"1033" /V"-qb ADDLOCAL=Proidplus_CM_Files,Spravce_karty ADMINMODE=1 SSTART=1 SDESKTOP=0 REMOTEUNBLOCK=0"'
  }


  ##
  ReDisable-SystemRestore
  ReDisable-Updates
}


#=====================
DBG ('Do we require USB over Network client: {0}' -f (Parse-BoolSafe $vmConfig.sc.usbOverNetwork))

if (Parse-BoolSafe $vmConfig.sc.usbOverNetwork) {

  Install-USBoverNetwork 'client' $vmConfig.sc.usbServerConn (Parse-BoolSafe $vmConfig.sc.noAutoConnect)
}


#=====================
DBG ('Install Network Monitor (netmon): {0}' -f (Parse-BoolSafe $vmConfig.install.netMon))

if (Parse-BoolSafe $vmConfig.install.netMon) {

  if ($global:thisOSArchitecture -eq '32-bit') {

    $arch = 'x86'
  
  } else {

    $arch = 'x64'
  }

  [string] $netmonProductId = '{8C5B5A11-CBF8-451B-B201-77FAB0D0B77D}'
  DBG ('First check if the Network Monitor (netmon) is not already installed: {0}' -f $netmonProductId)
  $netmonInstalled = Get-WmiQuerySingleObject '.' ('SELECT * FROM Win32_Product WHERE IdentifyingNumber = "{0}"' -f $netmonProductId)
  
  if (Is-Null $netmonInstalled) {  

    DBG ('Install network monitor (netmon) for platform: {0} | {1}' -f $arch, $global:thisOSArchitecture)

    Run-Process 'msiexec' ('-i "{0}" ALLUSERS=1 -qb' -f (Join-Path $global:installMediaVolume ('NetworkMonitor\network-monitor-NM34_{0}\netmon.msi' -f $arch)))
  
    $nmpSearchPath = Join-Path $global:installMediaVolume ('NetworkMonitor\network-monitor-NM34_{0}' -f $arch)
    DBG ('Find all netmon parsers available in: {0}' -f $nmpSearchPath)
    $nmParsers = Get-ChildItem -Path $nmpSearchPath -Filter 'nmp-??-*.msi' | Select-Object -ExpandProperty FullName | Sort-Object
    DBG ('NetMon parsers found: {0}' -f (Get-CountSafe $nmParsers))

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

      $nmParsers | % {

        DBG ('Install NM parsers from MSI: {0}' -f $_)
        Run-Process 'msiexec' ('-i "{0}" ALLUSERS=1 -qb' -f $_)
      }
    }
  }

  # Note: the netmon preferences go into HKCU, so it would be too heavy a code to make it work
  #DBG ('Update netmon user defaults')
  #Run-Process 'REGEDIT' ('/S "{0}"' -f (Join-Path $global:rootDir 'User-Env\netmon-defaults.reg'))

  ##
  ReDisable-SystemRestore
  ReDisable-Updates
}


#=====================
DBG ('Install Office: {0}' -f (Is-ValidString $vmConfig.install.office))

if (Is-ValidString $vmConfig.install.office) {

  $officeVersionTypes = Split-MultiValue $vmConfig.install.office
  $officeVersion = $officeVersionTypes[0]

  DBGIF ('Unsupported Office version: {0}' -f $officeVersion) { ($officeVersion -ne '2010') -and ($officeVersion -ne '2013') -and ($officeVersion -ne '2016') }

  if ($officeVersionTypes.Count -gt 1) {

    $officeArch = $officeVersionTypes[1]

  } else {

    if ($global:thisOSArchitecture -eq '32-bit') {

      $officeArch = '32bit'
  
    } else {

      $officeArch = '64bit'
    }
  }

  $officeArchShort = $officeArch.Substring(0,2)

  DBGIF $MyInvocation.MyCommand.Name { -not (($officeArch -eq '64bit') -or ($officeArch -eq '32bit')) }
  DBGIF $MyInvocation.MyCommand.Name { -not (($global:thisOSArchitecture -eq '64-bit') -or ($officeArch -eq '32bit')) }

  DBG ('Install Office for platform: {0} | {1} | {2} | {3}' -f $officeVersion, $officeArch, $officeArchShort, $global:thisOSArchitecture)

  $officeSetupPath = Join-Path $global:installMediaVolume ('Office\Office{0}_{1}\setup.exe' -f $officeVersion, $officeArch)
  
  $officeConfigFile = Get-DataFileApp ('office-config-{0}' -f ((Get-Date).ToString('s').Replace(':','-'))) $null '.xml'

  Replace-ArgumentsInFile ('{0}\Office\office-pro-plus-{1}-config.xml' -f $global:rootDir, $officeVersion) ('logPath${0}|bitEdition${1}' -f (Escape-ForMultiValue $global:outPath), $officeArchShort) $officeConfigFile ASCII

  DBG ('Going to install office: {0} | {1} | {2}' -f $officeVersion, $officeSetupPath, $officeConfigFile)
  
  if ($officeVersion -eq '2016') {

    Run-Process $officeSetupPath ('/configure "{0}"' -f $officeConfigFile) -adjustWorkDir $true
    Wait-Periodically -maxTrialCount 170 -sleepSec 7 -sleepMsg 'Waiting until office 2016 installer finishes' -scriptBlockWhichReturnsTrueToStop {

      # Note: it is always 32bit setup regardless of edition installed actually
      $officeSetupEXEs = Get-WmiQueryArray '.' ('SELECT * FROM Win32_Process WHERE Name = "setup32.exe" AND ExecutablePath LIKE "{1}\\Office\\%"' -f $officeArchShort, (Split-Path -Parent $officeSetupPath).Replace('\', '\\'))
      return ((Get-CountSafe $officeSetupEXEs) -eq 0)
    }

  } else {

    Run-Process $officeSetupPath ('/config "{0}"' -f $officeConfigFile)
  }

  #
  #

  [string] $officeUpdatesPath = Join-Path $global:installMediaVolume ('Office\Office{0}_{1}_updates' -f $officeVersion, $officeArch)
  DBG ('Check if any updates available: {0}' -f $officeUpdatesPath)

  if (Test-Path $officeUpdatesPath) {

    [string[]] $officeUpdates = Get-ChildItem $officeUpdatesPath | select -Expand FullName | ? { $_ -like '*.exe' } | sort
    DBG ('Found office updates: #{0}' -f (Get-CountSafe $officeUpdates))

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

      foreach ($oneOfficeUpdate in $officeUpdates) {

        $oneOfficeUpdateLog = Get-DataFileApp ('office-update-{0}-{1}-{2}' -f $officeVersion, $officeArch, ([System.IO.Path]::GetFileName($oneOfficeUpdate))) $null '.log'
        DBG ('Going to install one office update: {0} | log = {1}' -f $oneOfficeUpdate, $oneOfficeUpdateLog)
        Run-Process $oneOfficeUpdate ('/passive /norestart "/log:{0}"' -f $oneOfficeUpdateLog)
      }
    }
  }

  ##
  ReDisable-SystemRestore
  ReDisable-Updates
}


#=====================
DBG ('Install SharePoint Designer (SPD): {0}' -f (Is-ValidString $vmConfig.install.spd))

if (Is-ValidString $vmConfig.install.spd) {

  foreach ($oneSPD in (Split-MultiValue $vmConfig.install.spd)) {

    DBG ('One SharePoint Designer instance to be installed: {0}' -f $oneSPD)

    $officeArch = Get-ValueFlags $oneSPD
    $officeVersion = Strip-ValueFlags $oneSPD

<#    if ($officeVersionTypes.Count -gt 1) {

      $officeArch = $officeVersionTypes[1]

    } else {

      if ($global:thisOSArchitecture -eq '32-bit') {

        $officeArch = '32bit'
  
      } else {

        $officeArch = '64bit'
      }
    } #>

    DBGIF $MyInvocation.MyCommand.Name { -not (($officeArch -eq '64bit') -or ($officeArch -eq '32bit')) }
    DBGIF $MyInvocation.MyCommand.Name { -not (($global:thisOSArchitecture -eq '64-bit') -or ($officeArch -eq '32bit')) }

    DBG ('Install SharePoint Designer for platform: {0} | {1} | {2}' -f $officeVersion, $officeArch, $global:thisOSArchitecture)

    $officeSetupPath = Join-Path $global:installMediaVolume ('SharePointDesigner\SharePointDesigner{0}_{1}\setup.exe' -f $officeVersion, $officeArch)
    $officeConfigFile = Get-DataFileApp ('office-config-{0}' -f ((Get-Date).ToString('s').Replace(':','-'))) $null '.xml'

    Replace-ArgumentsInFile "$global:rootDir\Office\sharepoint-designer-config.xml" ('logPath${0}' -f (Escape-ForMultiValue $global:outPath)) $officeConfigFile ASCII

    DBG ('Going to install SharePoint Designer: {0} | {1}' -f $officeSetupPath, $officeConfigFile)
    Run-Process $officeSetupPath ('/config "{0}"' -f $officeConfigFile)


    ##
    ReDisable-SystemRestore
    ReDisable-Updates
  }
}


#=====================
DBG ('Install Office365 PowerShell aka Windows Azure Active Directory Module for Powershell aka AAD PowerShell aka Azure AD Module for PowerShell: {0}' -f (Parse-BoolSafe $vmConfig.install.office365PowerShell))
if (Parse-BoolSafe $vmConfig.install.office365PowerShell) {

  if ($global:thisOSArchitecture -eq '32-bit') {

    Run-Process msiexec ('-i "{0}" -qb -norestart' -f (Join-Path $global:installMediaVolume 'Azure\azure-msol-service-assistant-v7.250.4556.0-msoidcli_32.msi'))
    DBGIF ('The 32bit version of Azure AD Powershell is not supported anymore') { $true }
    #Run-Process msiexec ('-i "{0}" -qb -norestart' -f (Join-Path $global:installMediaVolume 'Azure\azure-msol-ad-powershell-v1.0.0-x32-AdministrationConfig-en.msi'))

  } else {

    Run-Process msiexec ('-i "{0}" -qb -norestart' -f (Join-Path $global:installMediaVolume 'Azure\azure-msol-service-assistant-v7.250.4556.0-msoidcli_64.msi'))
    Run-Process msiexec ('-i "{0}" -qb -norestart' -f (Join-Path $global:installMediaVolume 'Azure\azure-msol-ad-powershell-v1.0.0-x64-AdministrationConfig-en.msi'))
  }

  DBG ('Check if we do have any newer version of Azure PowerShell available')
  DBGSTART
  $newestAzurePowerShell = Get-ChildItem (Join-Path $global:installMediaVolume 'Azure') | ? { $_.Name -like 'azure-powershell.*.msi' } | select -Expand FullName | sort | select -Last 1
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  if ((Is-ValidString $newestAzurePowerShell) -and ($global:thisOSArchitecture -eq '64-bit')) {

    DBG ('Installing the newest version of Azure PowerShell module: {0}' -f $newestAzurePowerShell)
    Run-Process msiexec ('-i "{0}" -qb -norestart' -f $newestAzurePowerShell)
  }

  ##
  ReDisable-SystemRestore
  ReDisable-Updates
}


#=====================
DBG ('Install Visual Studio Express: {0}' -f (Is-ValidString $vmConfig.install.vsExpress))

if (Is-ValidString $vmConfig.install.vsExpress) {

  DBG ('VS Express installation requirements: {0}' -f $vmConfig.install.vsExpress)

  foreach ($oneVSExpress in (Split-MultiValue $vmConfig.install.vsExpress)) {

    DBG ('One VS Express tool: {0}' -f $oneVSExpress)
    $vsExpressVersion = Strip-ValueFlags $oneVSExpress
    $vsExpressType = Get-ValueFlags $oneVSExpress

    DBG ('Going to install VS Express: {0} | {1}' -f $vsExpressVersion, $vsExpressType)
    Run-Process (Join-Path $global:installMediaVolume ('VSExpress{0}\{1}\vns_full.exe' -f $vsExpressVersion, $vsExpressType)) '/passive /norestart'


    ##
    ReDisable-SystemRestore
    ReDisable-Updates
  }
}




# Note: this does not disable the "builtin admin" actually
#       we just disble the "original admin" that the buildup started with
#       Although it will most probably be the builtin admin account, it may not
#       be 100% guaranteed

DBG ('Should disable original builtin admin: {0}' -f (Parse-BoolSafe($vmConfig.domain.originalAdmin.disable)))
$originalAdmin = $global:phaseCfg.sevecekBuildup.originalAdmin.login

if (Parse-BoolSafe($vmConfig.domain.originalAdmin.disable)) {

  DBGIF ('Disabling current autologin identity. This may not autologon anymore' -f $originalAdmin) { $originalAdmin -eq $global:phaseCfg.sevecekBuildup.login.autoLogin }
  Disable-LocalUser $originalAdmin
}


DBG ('Should auto-rename original builtin admin: {0}' -f (Parse-BoolSafe($vmConfig.domain.originalAdmin.autoName)))

if (Parse-BoolSafe $vmConfig.domain.originalAdmin.autoName) {

  if (($global:thisOSRole -eq 'BDC') -or ($global:thisOSRole -eq 'PDC')) {

    [string] $newNamePrefix = $global:thisComputerDomainNetBIOS

  } else {

    [string] $newNamePrefix = $global:thisComputerNetBIOS
  }


  #$newAdminName = '{0}-{1}' -f ($newNamePrefix.SubString(0, [Math]::Min([Math]::Max((20 - $originalAdmin.Length - 1), 0), $newNamePrefix.Length))), $originalAdmin
  $newAdminName = '{0}-admin' -f ($newNamePrefix.ToLower().SubString(0, [Math]::Min(14, $newNamePrefix.Length)))

  Rename-LocalObj 'user' $originalAdmin $newAdminName
  
  if (($originalAdmin -eq $global:phaseCfg.sevecekBuildup.login.autoLogin) -and
      (Is-LocalDomain $global:phaseCfg.sevecekBuildup.login.domain)) {

    $global:phaseCfg.sevecekBuildup.login.autoLogin = [string] $newAdminName
  }
  
  $global:phaseCfg.sevecekBuildup.originalAdmin.login = $newAdminName


  if (($global:thisOSRole -eq 'BDC') -or ($global:thisOSRole -eq 'PDC')) {

    [System.Collections.ArrayList] $deList = @()
    $foundDE = Get-DEbyDNorSAMorUPN $newAdminName 'user' (Get-NCName $global:thisComputerDomain 'nCName' 'dNSRoot') ([ref] $deList)

    if (Is-ValidString (GDES $foundDE userPrincipalName)) {

      DBGSTART
      $foundDE.Put('userPrincipalName', ('{0}@{1}' -f $newAdminName, $global:thisComputerDomain))
      $adsiRs = $memberDE.SetInfo()
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ("ADSI Result: {0}" -f ($adsiRs | Out-String))
    }

    Rename-Object (GDES $foundDE distinguishedName) ('CN={0}' -f $newAdminName)

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



# SIG # Begin signature block
# MIIYMAYJKoZIhvcNAQcCoIIYITCCGB0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCg2QTf/KTUOKdg
# KM9NtQdt6QwZ5rGOiexd3PqfYxgetaCCE0cwggYEMIID7KADAgECAgoqHIRwAAEA
# 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
# DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg9Trv9eoa346MG3wdAHyzz286
# TRhyYY5/Zv9vqI+/DFAwDQYJKoZIhvcNAQEBBQAEggEAH2jXR3dmjiSD0aMLMlCU
# Rys80nEQKhHAk0pRV5G/P9mJhqU2gGfV2xYkTEvDD1SvpQduueMMqTwp0Zw0ngMw
# ZHYcdOSUeqskiA7VsZQFtBs7wm+TOAbW6va5f+Y7/XhgRUNNSPikcqy8/rjHIOzp
# KcnNMn4TLClWpYAfeQIBfDFYUPX8akvIRwxX5U0oupEqLBBcq+zSC/9KH2ZICzQu
# M11nLwsjvnZUTnuzi8TEsbYD9z65bWVrj0s3WXykcJMjuILh8+0aXRg6ukjd0B8i
# Y/mYTMxIckkD1MdYSO+SxAsNrfXwssOE/Cly9XARULwWF1unQNWDBkz99EJCJqaS
# NaGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr
# 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
# BgkqhkiG9w0BCQUxDxcNMTkwNjEzMDM1MzAwWjAjBgkqhkiG9w0BCQQxFgQUvPnF
# 3omzUbxWoeUKIBDKenRWcSMwDQYJKoZIhvcNAQEBBQAEggEAhXDHggbQkE9dmP2K
# ZErbMRcPTC5padwLR/YvbNUr9CZy6Z7GlBXmN0NWYfnSSQsIPJFEytcz32A/ojyc
# nfxB/W6kE7WdO02v1xILmDE8PvNuI/8Y5zwNTlmr9kBiuAz44eopsf6MSkY05Sqk
# jVAO2C75jX4mcSJ/VT2IyF4u/Tu8UVyciKVQpd51xFKUGStqJKYY84dz+idksOhc
# ObT8LO6OsBYnA2Q6Uj5xUIPPiI7Oi8ryqkH2YTJnFVaMgD0gzUMYMVVFJv5yPj64
# eyEHipsHxA0GpEtLjPdl9WZImZuB0PuC/4pjGOjnrOvUokRLg8waLNatubwcYqd6
# hnDtyQ==
# SIG # End signature block