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

$vmName = $args[0]

DBG ("SharePoint Installation library")
Redirect-TempToOutput
Load-VMConfig

Find-MarkedVolumes
$global:installSourceMediaSP = '!sevecek-installation-source-media-ExchangeSharePoint.txt'
$global:installMediaVolumeSP = Find-MarkedVolume $global:installSourceMediaSP 3
DBG ('SharePoint install media volume: {0}' -f $installMediaVolumeSP)

$appTag = 'sp'
$appConfig = $vmConfig.$appTag
$firstAppHost = Get-FirstAppHostInInstance $appTag $appConfig.instance 'waitParams'
$firstAppConfig = (Get-FirstAppHostInInstance $appTag $appConfig.instance 'vmConfig').$appTag
$firstAppHostInInstance = Check-FirstAppHostInInstance $appTag $appConfig.instance
$lastAppHostInInstance = Check-LastAppHostInInstance $appTag $appConfig.instance

if (-not $firstAppHostInInstance) {

  $allPreviousAppHosts = Get-AllAppHostsOfInstance $appTag $appConfig.instance 'waitParams' -onlyBeforeMyHostName $vmConfig.hostName
}

$allAppHosts = Get-AllAppHostsOfInstance $appTag $appConfig.instance 'vmConfig'


if (Is-Null $appConfig) { DBG ('Invalid appCofnig, exiting'); exit }
#====================
#====================



#####################################################################
#####################################################################
#
<# Note: these come directly from BUILDUP-SP, keep it in sync ---> #>
#
#

if (Is-ValidString $firstAppConfig.version) {

  $spVersion = $firstAppConfig.version

} else {

  $spVersion = '2010'
}

if ($appConfig.type -eq 'OWA') {

  $spType = 'OWA'

} else {

  if ($firstAppConfig.type -eq 'foundation') {

    $spType = 'foundation'

  } else {

    $spType = 'server'
  }
}

if ($spVersion -eq '2010') { 

  $spVerID = '14.0'
  [int] $spVerIDNumber = 14
  $spProductId = '{90140000-110D-0000-1000-0000000FF1CE}'

} elseif ($spVersion -eq '2013') { 

  $spVerID = '15.0'
  [int] $spVerIDNumber = 15
  $spProductId = '{90150000-110D-0000-1000-0000000FF1CE}'

} elseif ($spVersion -eq '2016') {

  $spVerID = '16.0'
  [int] $spVerIDNumber = 16
  $spProductId = '{90160000-110D-0000-1000-0000000FF1CE}'

} else {

  DBGIF ('Unsupported version of SharePoint requested: {0}' -f $spVersion) { $true }
}

DBG ('SharePoint version requested: type = {0} | version = {1} | verId = {2} | product = {3}' -f $spType, $spVersion, $spVerID, $spProductId)


#====================
DBG ('Installing SharePoint. Version: {0} | {1}' -f $spVersion, $spType)

if ($spType -eq 'OWA') {

  DBGIF ('OWA not supported on SharePoint 2010') { $spVersion -eq '2010' }

  $instRoot = Get-FirstPathFromWildcard (Join-Path $installMediaVolumeSP ('OWA{0}-*' -f $spVersion))
  $customRoot = Join-Path $global:rootDir ('SharePointServer{0}' -f $spVersion)

} elseif ($spType -eq 'foundation') {

    $instRoot = Get-FirstPathFromWildcard (Join-Path $installMediaVolumeSP ('SharePointFoundation{0}-*' -f $spVersion))
    $customRoot = Join-Path $global:rootDir ('SharePointFoundation{0}' -f $spVersion)

} else {

  $instRoot = Get-FirstPathFromWildcard (Join-Path $installMediaVolumeSP ('SharePointServer{0}-*' -f $spVersion))
  $customRoot = Join-Path $global:rootDir ('SharePointServer{0}' -f $spVersion)
}

#
#    
<# Note: <--- these come directly from BUILDUP-SP, keep it in sync #>
# 
#####################################################################
#####################################################################


if ($spType -eq 'OWA') {

  DBG ('Initializing OWA server')
  DBGIF $MyInvocation.MyCommand.Name { $lastAppHostInInstance }

  [string] $owaHostHeader = $firstAppConfig.owa.hostHeader
  [string] $owaUrl = 'http://{0}' -f $owaHostHeader
  [bool] $owaEditingEnabled = (Parse-BoolSafe $firstAppConfig.owa.edit)
  [string] $owaLogs = (Resolve-VolumePath $firstAppConfig.owa.logDir)
  DBG ('Initialize OWA: {0} | editing = {1} | log = {2} | host = {3}' -f $owaUrl, $owaEditingEnabled, $owaLogs, $owaHostHeader)

  Prepare-IISWebServer -cleanIIS $true -iisLogPath $owaLogs -dnsAliases $owaHostHeader

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

  DBG ('Finally initialize the OWA farm with: New-OfficeWebAppsFarm -internalUrl "{0}" -allowHttp -editingEnabled:{1} -logLocation "{2}" -Force' -f $owaUrl, $owaEditingEnabled, $owaLogs)
  DBGSTART
  $owaFarmCreated = $null
  $owaFarmCreated = New-OfficeWebAppsFarm -InternalURL $owaUrl -AllowHttp -EditingEnabled:$owaEditingEnabled -LogLocation $owaLogs -Force
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Get the results of the initialization')
  DBGSTART
  $owaFarmCreated = $null
  $owaFarmCreated = Get-OfficeWebAppsFarm
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBG ('Local OWA farm: url = {0} | logs = {1} | logRetention = {2} | cache = {3}' -f $owaFarmCreated.InternalUrl, $owaFarmCreated.LogLocation, $owaFarmCreated.LogRetentionInDays, $owaFarmCreated.CacheLocation)
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $owaFarmCreated }
  DBGIF $MyInvocation.MyCommand.Name { $owaFarmCreated.InternalUrl.Trim('/') -ne $owaUrl }

  DBG ('Verify the installation')
  [System.Xml.XmlDocument] $owaMeta = [XML] (Download-WebPage ('{0}/hosting/discovery' -f $owaUrl))
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $owaMeta }

} else {


[bool] $farmCredMustBeAdministrator = $false



#================
DBG ('Should prepare farm, PRE-FARMJOIN steps: {0}' -f ((Parse-BoolSafe $appConfig.provisionFarm) -or (Parse-BoolSafe $appConfig.joinFarm) -or (Parse-BoolSafe $appConfig.prepareFarm)))

[string] $dbInstance = [string]::Empty
[Collections.ArrayList] $dbInstancesAll = @()

if ((Parse-BoolSafe $appConfig.provisionFarm) -or (Parse-BoolSafe $appConfig.joinFarm) -or (Parse-BoolSafe $appConfig.prepareFarm)) {

  #$dbInstance = $firstAppConfig.sql.instance
  $dbInstance = $firstAppConfig.SelectSingleNode('./sql[@tag="farm" or not(@tag)]').instance
  DBG ('Farm DB instance determined: {0}' -f $dbInstance)
  DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $dbInstance }

  $dbServerFQDN = Get-FirstAppHostInInstance 'sql' $dbInstance 'fqdn'
  $dbServer = '{0}\{1}' -f $dbServerFQDN, $dbInstance

  $spInstance = $appConfig.instance
  $farmCred = $firstAppConfig.SelectSingleNode('./svc[@appTag="farm"]')
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $farmCred }

  $farmCredSAMLogin = Get-SAMLogin $farmCred.login $farmCred.domain

  #if (((Is-NonNull $farmCred) -and ($spVersion -eq '2010')) -and (-not $farmCredMustBeAdministrator)) {
  if (((Is-NonNull $farmCred) -and (($spVersion -eq '2010') -or ($spVersion -eq '2013'))) -and (-not $farmCredMustBeAdministrator)) {

    # Note: this is a secure enough setting as the SharePoint Timer Service has full control over SPAdminv4 service
    #       which runs under SYSTEM identity anyway
    DBG ('Add the farm account into the local Administrators group: {0}\{1}' -f $farmCred.domain, $farmCred.login)
    Add-MemberLocalGroup 'Administrators' $farmCred.login $farmCred.domain
    $farmCredMustBeAdministrator = $true
  }

  DBG ('Adding the farm DB instance into the list of instances to wait for: {0}' -f $dbInstance)
  [void] $dbInstancesAll.Add($dbInstance)

#
# TODO: there is discrepancy between the @dns record and the
#       possible @hostHeader values. Should be able to define more @hostHeaders
#       and the @hostHeaders should go into DNS, probably instead of the individual @dns records???
#       as well as the @hostHeaders should be add to the backConnectionHostName instead of the @dns???
#

  [Collections.ArrayList] $databaseAliases = @(('spdbFarm|{0}' -f $dbServer))

  $additionalDbInstanceNodes = $firstAppConfig.SelectNodes('./sql[@tag and @tag!="farm"]')
  DBG ('Do we have additional database servers: {0}' -f (Get-CountSafe $additionalDbInstanceNodes))
  if ((Get-CountSafe $additionalDbInstanceNodes) -gt 0) {

    foreach ($oneAdditionalDbInstanceNode in $additionalDbInstanceNodes) {

      [string] $oneAdditionalDbInstance = $oneAdditionalDbInstanceNode.instance
      [string] $additionalDbServerFQDN = Get-FirstAppHostInInstance 'sql' $oneAdditionalDbInstance 'fqdn'
      $additionalDbServer = '{0}\{1}' -f $additionalDbServerFQDN, $oneAdditionalDbInstance

      [string] $additionalDbAliasDef = 'spdb{0}{1}|{2}' -f ([string] $oneAdditionalDbInstanceNode.tag[0]).ToUpper(), $oneAdditionalDbInstanceNode.tag.SubString(1), $additionalDbServer
      DBG ('Defining another DB instance alias: tag = {0} | srv = {1} | def = {2}' -f $oneAdditionalDbInstanceNode.tag, $additionalDbServer, $additionalDbAliasDef)
      [void] $databaseAliases.Add($additionalDbAliasDef)

      DBG ('Adding the additional DB instance into the list of instances to wait for: {0}' -f $oneAdditionalDbInstance)
      [void] $dbInstancesAll.Add($oneAdditionalDbInstance)
    }
  }

  #
  #

  DBG ('We have established the following DB instances for this farm: #{0} | {1}' -f (Get-CountSafe $dbInstancesAll), ($dbInstancesAll -join ', '))

  #
  #

  #Prepare-SPWebServer $spVersion @(('spdb|{0}' -f $dbServer)) $false $true $firstAppConfig.iisLogs @((Strip-ValueFlags $firstAppConfig.app.dns)) $firstAppConfig.dataDir $firstAppConfig.logDir
  Prepare-SPWebServer $spVersion ([string[]] $databaseAliases) $false $true $firstAppConfig.iisLogs $null $firstAppConfig.dataDir $firstAppConfig.logDir
}


#
#

DBG ('Backup IIS site/apppool state before buildup')
$iisSitesBackup = Get-IISSites -structured $true
$iisAppPoolBackup = Get-IISAppPools -structured $true

#
#


DBG ('Load Microsoft.SharePoint.PowerShell snap-in')
Assert-SnapInSafe Microsoft.SharePoint.PowerShell


#================
[string] $configDb = 'SP{0}_Config' -f $spInstance
[string] $adminDb = 'SP{0}_Admin' -f $spInstance
DBG ('Will use configuration database: {0} | {1}' -f $configDb, $adminDb)


#================
DBG ('Should provision new farm: {0}' -f (Parse-BoolSafe $appConfig.provisionFarm))

if (Parse-BoolSafe $appConfig.provisionFarm) {

  ##############################################
  #
  # Note: Error during New-SPConfigurationDatabase
  #       (Source: COM+ SOAP Services, Id: 0, Warning, Installation in the global assembly cache failed: %root%\Policy\Policy.11.0.Microsoft.SharePoint.Security.Dll)
  #       is there just because the assembly has already been registered (has the same public key token)
  #

  DBG ('Must wait for the DB servers to complete first')
  DBG ('We have established the following DB instances for this farm: #{0} | {1}' -f (Get-CountSafe $dbInstancesAll), ($dbInstancesAll -join ', '))
  foreach ($oneDbInstanceToWait in $dbInstancesAll) {

    Wait-Machine (Get-FirstAppHostInInstance 'sql' $oneDbInstanceToWait 'waitParams')
  }
  
  [void] (Test-SQL spdbFarm -encrypt $false)

  if ($spVersion -ne 2016) {

    DBG ('Provision new configuration database with SP 2010/2013: {0} | {1} | {2} | {3}' -f $farmCred.login, $farmCred.domain, $farmCred.pwd, $firstAppConfig.pass)
    DBGSTART
    New-SPConfigurationDatabase -DatabaseName $configDb -DatabaseServer 'spdbFarm' -AdministrationContentDatabaseName $adminDb -FarmCredentials (Get-PwdCredentials $farmCred.login $farmCred.domain $farmCred.pwd $true) -Passphrase (ConvertTo-SecureString -String $firstAppConfig.pass -AsPlainText -Force)
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

  } else {

    DBG ('Provision new configuration database with SP 2016: {0} | {1} | {2} | {3}' -f $farmCred.login, $farmCred.domain, $farmCred.pwd, $firstAppConfig.pass)
    DBGSTART
    New-SPConfigurationDatabase -DatabaseName $configDb -DatabaseServer 'spdbFarm' -AdministrationContentDatabaseName $adminDb -FarmCredentials (Get-PwdCredentials $farmCred.login $farmCred.domain $farmCred.pwd $true) -Passphrase (ConvertTo-SecureString -String $firstAppConfig.pass -AsPlainText -Force) -ServerRoleOptional Custom
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
  }
  


  if ($spVersion -eq '2013') {

    # Note: there is the bug with EXECUTE permission to some stored procedures in the farm database
    #       Insufficient SQL database permissions for user, the EXECUTE permission was denied on the object proc_putObjectTVP, database SP_Config, schema dbo
    #       Reporting Services also need EXECUTE on the following stored procedures on ADMIN DB
    #       Search Application also needs the proc_putClass
    $fixFarmEXECUTEStoredProc = @'
GRANT EXECUTE ON [dbo].[proc_putObjectTVP] TO [WSS_Content_Application_Pools]
GRANT EXECUTE ON [dbo].[proc_putObject] TO [WSS_Content_Application_Pools]
GRANT EXECUTE ON [dbo].[proc_putDependency] TO [WSS_Content_Application_Pools]
GRANT EXECUTE ON [dbo].[proc_putClass] TO [WSS_Content_Application_Pools]
'@

    Execute-NonQueryRemote -sqlServer 'spdbFarm' -database $configDb -nonQuery $fixFarmEXECUTEStoredProc

    $fixFarmEXECUTEStoredProc = @'
GRANT EXECUTE ON [dbo].[proc_ListChildWebsFiltered] TO [WSS_Content_Application_Pools]
GRANT EXECUTE ON [dbo].[proc_GetParentWebUrl] TO [WSS_Content_Application_Pools]
GRANT EXECUTE ON [dbo].[proc_EnumLists] TO [WSS_Content_Application_Pools]
GRANT EXECUTE ON [dbo].[proc_ReturnWebFeatures] TO [WSS_Content_Application_Pools]
GRANT EXECUTE ON [dbo].[proc_GetAllListsPlusProperties] TO [WSS_Content_Application_Pools]
GRANT EXECUTE ON [dbo].[proc_SecGetPrincipalById] TO [WSS_Content_Application_Pools]
'@

    Execute-NonQueryRemote -sqlServer 'spdbFarm' -database $adminDb -nonQuery $fixFarmEXECUTEStoredProc
  }
}


#================
DBG ('Should join an existing farm: {0}' -f (Parse-BoolSafe $appConfig.joinFarm))

if (Parse-BoolSafe $appConfig.joinFarm) {

  Wait-MachineAllPrevious $allPreviousAppHosts

  DBG ('Join an existing configuration database: {0}' -f ('SP{0}_Config' -f $spInstance))
  DBGSTART
  Connect-SPConfigurationDatabase -DatabaseName $configDb -DatabaseServer 'spdbFarm' -Passphrase (ConvertTo-SecureString -String $firstAppConfig.pass -AsPlainText -Force) -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
}



#=================
DBG ('Should prepare farm, POST-FARMJOIN steps: {0}' -f ((Parse-BoolSafe $appConfig.provisionFarm) -or (Parse-BoolSafe $appConfig.joinFarm)))

if ((Parse-BoolSafe $appConfig.provisionFarm) -or (Parse-BoolSafe $appConfig.joinFarm)) {

  DBG ('Initialize local SP instance')
  DBGSTART
  Set-SPDiagnosticConfig -LogLocation (Resolve-VolumePath $firstAppConfig.logDir)
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Call Initialize-SPResourceSecurity')
  DBGSTART
  Initialize-SPResourceSecurity
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  
  DBG ('Call Install-SPService')
  # Note: this should go first as some features might not be installable without having the Service installed before
  DBGSTART
  Install-SPService
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  
  DBG ('Call Install-SPFeature')
  DBGSTART
  Install-SPFeature -AllExistingFeatures -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  
  DBG ('Call Install-SPApplicationContent')
  DBGSTART
  Install-SPApplicationContent -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Call Install-SPHelpCollection')
  # Note: seems like the help collection, during its installation,
  #       references some features, so it comes as a logical attitude
  #       to build it only as the last thing
  DBGSTART
  Install-SPHelpCollection -All -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND


  DBG ('Start the services if not running yet')

  DBGSTART
  Start-Service SPAdminv4 -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBGSTART
  Start-Service SPTimerv4 -EA SilentlyContinue
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND


  DBG ('Give it 5:30min. to complete initialization easily')
  Start-Sleep 330

  #
  #

  DBG ('Export the SP root CA certificate and make it trusted on the local machine')

  DBGSTART
  $spRootCA = Get-SPCertificateAuthority
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBG ('Root CA opened: id = {0} | status = {1} | subject = {2} | thumbprint = {3}', $spRootCA.Id, $spRootCA.Status, $spRootCA.RootCertificate.Subject, $spRootCA.RootCertificate.Thumbprint)

  DBGIF $MyInvocation.MyCommand.Name { Is-Null $spRootCA }
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $spRootCA.RootCertificate }
  DBGIF $MyInvocation.MyCommand.Name { $spRootCA.Status -ne 'Online' }
  DBGIF $MyInvocation.MyCommand.Name { $spRootCA.RootCertificate.Subject -ne 'CN=SharePoint Root Authority, OU=SharePoint, O=Microsoft, C=US' }
  DBGIF $MyInvocation.MyCommand.Name { $spRootCA.RootCertificate.NotBefore -gt (Get-Date) }
  DBGIF $MyInvocation.MyCommand.Name { $spRootCA.RootCertificate.NotAfter -lt (Get-DAte).AddYears(90) }

  $spRootCAFile = Get-DataFileApp -appName 'sp-rootca-certificate' -extension '.cer'
  Save-Certificate $spRootCA.RootCertificate $spRootCAFile

  DBG ('Install the SP root certificate: {0} | {1}' -f $spRootCA.RootCertificate.Subject, $spRootCA.RootCertificate.Thumbprint)
  DBGSTART
  $targetCertStore = Get-Item 'Cert:\LocalMachine\Root'
  $targetCertStore.Open('ReadWrite')
  $targetCertStore.Add($spRootCA.RootCertificate)
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
}


DBG ('Reload Microsoft.SharePoint.PowerShell snap-in')
# Note: reason for this is that some farm features got enabled only after 
#       the previous initialization (such as ExcelServerWebPartStapler and ExcelServer) which
#       enable the New-SPExcelServiceApplication and other related snap-ins and we have
#       to let them load again or the cmdlets would be missing
DBGSTART
Remove-PSSnapin Microsoft.SharePoint.PowerShell
DBGER $MyInvocation.MyCommand.Name $error
DBGEND

Assert-SnapInSafe Microsoft.SharePoint.PowerShell


if (Is-ValidString $appConfig.provisionFarm) {
  
  Reset-SPFileSystemCache


  #=======================
  DBG ('New Central Administration web site: port = {0}' -f $firstAppConfig.caPort)
  DBGSTART
  New-SPCentralAdministration -Port $firstAppConfig.caPort -WindowsAuthProvider NTLM -EA SilentlyContinue
  # Note: this is not probably necessary, but all the guides on manual PS deployment provision the central administration
  #       website just before they call the Install-SPApplicationContent. So I suppose it might be better to just call it
  #       again to be on the safe side.
  Install-SPApplicationContent
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND


  DBG ('Add app admins into farm administrators')
  DBGSTART
  $caApp = Get-SPWebApplication -IncludeCentralAdministration | ? { $_.IsAdministrationWebApplication } 
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Central Administration web application: {0} | {1}' -f $caApp.DisplayName, $caApp.Url)
  DBGIF $MyInvocation.MyCommand.Name { (Is-Null $caApp) -or (Is-EmptyString $caApp.Url) }
  [string] $centralAdminUrl = $caApp.Url

  # Note: Although Add-SPShellAdmin works fine with UPN, the AddUser() requires SAM format of the login
  $spAdmins = Get-SAMLogin $firstAppConfig.app.aGroup $firstAppConfig.app.iDomain

  DBG ('Grant SPAdmins the db_owner role of the config database in order to allow some later actions during regular lifecycle, such as Upgrade-SPContentDatabase')
  Execute-NonQueryRemote -sqlServer 'spdbFarm' -database $configDb -nonQuery ('CREATE USER "{0}" FOR LOGIN "{0}"' -f $spAdmins)
  Execute-NonQueryRemote -sqlServer 'spdbFarm' -database $configDb -nonQuery ('EXECUTE sp_addrolemember @rolename = "db_owner", @membername = "{0}"' -f $spAdmins)


  DBG ('Adding new farm administrators: {0}' -f $spAdmins)
  DBGSTART
  $caApp.Sites[0].RootWeb.SiteGroups['Farm Administrators'].AddUser($spAdmins, $null, $spAdmins, $null)
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND


  DBG ('Get Central Administration help site to update its sitecol admins')
  DBGSTART
  $caHelpSite = Get-SPSite ('{0}/{1}' -f $caApp.Url.Trim('/'), 'sites/Help') -Limit All
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $caHelpSite }

  DBG ('Set the SP Admins group to be sitecol admins of the CA help site')
  DBGSTART
  $caHelpSiteAdmins = New-SPUser -UserAlias $spAdmins -Web $caHelpSite.Url -SiteCollectionAdmin:$true
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND


  DBG ('Add SPShell Admin into CA content DB')
  DBGSTART
  Enable-SPDatabaseAdminAccess -admins $spAdmins -databaseObject (Get-SPContentDatabase -WebApplication $caApp)
  #Add-SPShellAdmin -Database (Get-SPContentDatabase -WebApplication $caApp) -UserName $spAdmins
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND


  #============================
  # Note: exchange settings must be configured on the firstAppHost always
  #       the outgoing mail is farm wide configuration
  #       while the incoming mail is just a single farm member matter
  DBG ('Should we enable outgoing mail settings using Exchange: {0}' -f (Is-NonNull $firstAppConfig.ex))
  
  if (Is-NonNull $firstAppConfig.ex) {

    DBG ('Outgoing mail Exchange SMTP server: {0} | {1}' -f $firstAppConfig.ex.instance, $firstAppConfig.ex.outSmtp.from)
    if ((Is-ValidString $firstAppConfig.ex.instance) -and (Is-ValidString $firstAppConfig.ex.outSmtp.from)) {

      $allExchangeServers = Get-AllAppHostsOfInstance ex $firstAppConfig.ex.instance vmConfig
      DBGIF $MyInvocation.MyCommand.Name { $allExchangeServers.Count -lt 1 }

      [string] $hubTransport = ''
      foreach ($oneExchangeServer in $allExchangeServers) {

        DBG ('One exchange server node: {0} | {1} | {2}' -f $oneExchangeServer.hostName, $oneExchangeServer.ex.instance, $oneExchangeServer.ex.roles)
        if (Contains-Safe $oneExchangeServer.ex.roles.Split(',') HT) {

          $hubTransport = Get-MachineFQDN $oneExchangeServer
          DBG ('One suitable HUB TRANSPORT exchange server found: {0}' -f $hubTransport)
          break
        }
      }

      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $hubTransport }

      DBG ('Going to setup outgoing mail settings to use the Exchange hub transport: {0} | {1} | {2}' -f $hubTransport, $firstAppConfig.ex.outSmtp.from, $firstAppConfig.ex.outSmtp.encoding)
      DBGSTART
      $caApp.UpdateMailSettings($hubTransport, $firstAppConfig.ex.outSmtp.from, '', $firstAppConfig.ex.outSmtp.encoding)
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }
  }


  #
  #

  #============================
  DBG ('Should we process other farm-wide settings: {0}' -f (Is-NonNull $firstAppConfig.farmSettings))
  if (Is-NonNull $firstAppConfig.farmSettings) {


    DBG ('Should we speed-up timer service restarts: {0} | {1}' -f (Is-ValidString $firstAppConfig.farmSettings.timerRecycle), $firstAppConfig.farmSettings.timerRecycle)
    # Note: this settings affects the behavior of "job-timer-recycle" job which restarts Timer Service simultaneously on all farm members
    if (Is-ValidString $firstAppConfig.farmSettings.timerRecycle) {

      DBG ('Get the Time Service object first')
      DBGSTART
      $spTimerObj = (Get-SPFarm).TimerService
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ('Time service recycle timeout: {0}' -f $spTimerObj.RecycleWarningMinutes)
      DBGIF $MyInvocation.MyCommand.Name { (Is-Null $spTimerObj) -or ($spTimerObj.RecycleWarningMinutes -ne 10) }

      DBG ('Change the Timer Service recycling timeout: {0}' -f $firstAppConfig.farmSettings.timerRecycle)
      DBGSTART
      $spTimerObj.RecycleWarningMinutes = $firstAppConfig.farmSettings.timerRecycle
      $spTimerObj.Update()
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }


    DBG ('Should we disable any health analyzer rules: {0} | {1}' -f (Is-ValidString $firstAppConfig.farmSettings.disableHealthRules), $firstAppConfig.farmSettings.disableHealthRules)
    if (Is-ValidString $firstAppConfig.farmSettings.disableHealthRules) {

      [string[]] $healthRulesToDisable = Split-MultiValue $firstAppConfig.farmSettings.disableHealthRules
      DBG ('Health analyzer rules to be disabled: #{0} | {1}' -f $healthRulesToDisable.Length, ($healthRulesToDisable -join ','))

      foreach ($oneHealthRuleToDisable in $healthRulesToDisable) {

        DBG ('Get the health analyzer rule and disable it: {0}' -f $oneHealthRuleToDisable)
        DBGSTART
        Get-SPHealthAnalysisRule $oneHealthRuleToDisable | Disable-SPHealthAnalysisRule -Confirm:$false | Out-Null
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }
    }


    DBG ('Static outgoing SMTP settings requested: {0} | {1}' -f (Is-ValidString $firstAppConfig.farmSettings.outSmtp.server), $firstAppConfig.farmSettings.outSmtp.server)

    if (Is-ValidString $firstAppConfig.farmSettings.outSmtp.server) {

      [string] $smtpServer = $firstAppConfig.farmSettings.outSmtp.server
      [string] $smtpFrom = $firstAppConfig.farmSettings.outSmtp.from

      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $smtpServer }
      DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $smtpFrom }

      DBGSTART
      [int] $smtpEncoding = [System.Text.Encoding]::GetEncoding($firstAppConfig.farmSettings.outSmtp.encoding).CodePage
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBG ('Going to setup outgoing mail settings to use the manual SMTP: {0} | {1} | encoding = {2} | codePage = {3}' -f $smtpServer, $smtpFrom, $firstAppConfig.farmSettings.outSmtp.encoding, $smtpEncoding)
      DBGSTART
      $caApp.UpdateMailSettings($smtpServer, $smtpFrom, '', $smtpEncoding)
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }

    DBG ('Should we disable CEIP: {0}' -f (Parse-BoolSafe $firstAppHost.farmSettings.ceipOff))
    if (Parse-BoolSafe $firstAppHost.farmSettings.ceipOff) {

      DBG ('Get current CEIP (Customer Experience Improvement Program) status first')
      DBGSTART
      $currentCEIPState = Get-SPBrowserCustomerExperienceImprovementProgram -Farm
      $thisFarm = Get-SPFarm
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ('Current CEIP status: bsqm = {0} | farmCEIP = {1}' -f $currentCEIPState.BSqmEnabled, $thisFarm.CEIPEnabled)

      DBG ('And finally disable both CEIP nonsense')
      DBG ('Browser CEIP goes first')
      DBGSTART
      Set-SPBrowserCustomerExperienceImprovementProgram -Farm -Enable:$false
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBG ('Farm CEIP goes second')
      DBGSTART
      $thisFarm.CEIPEnabled = $false
      $thisFarm.Update()
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBG ('And also disable the CEIP job')
      DBGSTART
      Get-SPTimerJob job-ceip-datacollection | Disable-SPTimerJob
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }
  }

  #
  #

  #============================
  $usageAppDbName = 'SP{0}_Usage' -f $spInstance
  DBG ('Configure usage logging. Create usage application: db = {0}' -f $usageAppDbName)
  DBGSTART
  New-SPUsageApplication -Name 'Usage and Health Collection App' -DatabaseServer 'spdbFarm' -DatabaseName $usageAppDbName | Out-Null
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBGSTART
  $usageApp = $null
  $usageApp = Get-SPServiceApplicationProxy -EA SilentlyContinue | ? { $_.Name -eq 'Usage and Health Collection App' }
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $usageApp }

  DBG ('Start usage application proxy: {0}' -f $usageApp.TypeName)
  DBGSTART
  $usageApp.Provision()
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  [string] $usageLogsPath = Resolve-VolumePath $firstAppConfig.usageLogs
  DBG ('Ensure the usage logs directory exists and has correct permissions configured: exists = {0} | {1}' -f (Test-Path $usageLogsPath), $usageLogsPath)
  if (-not (Test-Path $usageLogsPath)) {

    Assert-NtfsFolderWithDacl $usageLogsPath 'F$SYSTEM|F$Administrators|M$Local Service|M$WSS_WPG|M$WSS_ADMIN_WPG|M$WSS_RESTRICTED_WPG_V4'
  }

  DBG ('Start usage logging: {0}' -f $usageLogsPath)
  DBGSTART
  Set-SPUsageService -LoggingEnabled 1 -UsageLogLocation $usageLogsPath -UsageLogMaxSpaceGB 1
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  DBG ('Add SPShell Admin into UsageApp DB')
  Enable-SPDatabaseAdminAccess -db $usageAppDbName -admins $spAdmins
  #DBGSTART
  #Add-SPShellAdmin -Database (Get-SPDatabase | ? { $_.Name -eq 'Usage and Health Collection App' } ) -UserName $spAdmins
  #DBGER $MyInvocation.MyCommand.Name $error
  #DBGEND

  #if ($spVersion -eq '2013') {
  
  DBG ('Enable additional usage logging to show Slowest Pages in Central Administration for SP 2013')
  DBGSTART
  $usageDefinitions = Get-SPUsageDefinition | ? { -not $_.Enabled }
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBGIF ('Unexpected default usage log definitions disabled: #{0} | {1}' -f (Get-CountSafe $usageDefinitions), ($usageDefinitions | Out-String)) { ($spVersion -eq 2010) -and ((Get-CountSafe $usageDefinitions) -ne 0) }
  DBGIF ('Unexpected default usage log definitions disabled: #{0} | {1}' -f (Get-CountSafe $usageDefinitions), ($usageDefinitions | Out-String)) { ($spVersion -eq 2013) -and ((Get-CountSafe $usageDefinitions) -ne 5) }
  DBGIF ('Unexpected default usage log definitions disabled: #{0} | {1}' -f (Get-CountSafe $usageDefinitions), ($usageDefinitions | Out-String)) { ($spVersion -eq 2016) -and ((Get-CountSafe $usageDefinitions) -ne 6) }

  foreach ($oneUsageDefinition in $usageDefinitions) {

    DBG ('Enable one usage log definition: {0}' -f $oneUsageDefinition.Name)
    # Note: this cycle takes some time while the OWSTIMER sometimes makes it already to update the settings
    #       and makes an update conflict with us, so we have to use the .Name explicitly to get the object
    #       dynamically on each round instead of using just the $oneUsageDefinition object itself
    # Note: the update conflicts were not resolved by the use of .Name so we try to just retry if not yet
    #       enabled
    DBGSTART
    Set-SPUsageDefinition -Identity $oneUsageDefinition.Name -Enable
    #DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    DBGSTART
    Get-SPUsageDefinition -Identity $oneUsageDefinition.Name | ? { -not $_.Enabled } | % { DBG ('Update conflict possibly, trying again in 4 sec'); Start-Sleep -Sec 4; Set-SPUsageDefinition -Identity $oneUsageDefinition.Name -Enable }
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
  }
  #}

  DBG ('Should we enable developer toolbar: {0}' -f $firstAppConfig.farmSettings.devDashboard)
  if (Is-ValidString $firstAppConfig.farmSettings.devDashboard) {

    DBG ('Enabling developer dashboard')
    DBGSTART
    $devDashboard = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.DeveloperDashboardSettings
    $devDashboard.DisplayLevel = $firstAppConfig.farmSettings.devDashboard # Off/On/OnDemand
    # Note: do not know what this means exactly
    #$devDashboard.TraceEnabled = $true
    $devDashboard.Update()
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    DBGSTART
    $devDashboard = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.DeveloperDashboardSettings
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    DBG ('Resulting developper dashboard settings: {0} | {1}' -f $devDashboard.Status, $devDashboard.DisplayLevel)
  }


  #============================
  DBG ('Disable server-side view state if requested: {0}' -f $firstAppConfig.disableServerViewState)
  if (Parse-BoolSafe $firstAppConfig.disableServerViewState) {

    DBG ('Disable the view state on server')
    DBGSTART
    $spContentService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
    $spContentService.ViewStateOnServer = $false
    $spContentService.Update()
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND

    #DBG ('Disable the health-analysis-job')
    #DBGSTART
    #Get-SPTimerJob hourly-all-sptimerservice-health-analysis-job | Disable-SPTimerJob
    #DBGER $MyInvocation.MyCommand.Name $error
    #DBGEND
  }
}



DBG ('Load the list of web applications from xml config')
$webApps = $firstAppConfig.SelectNodes('./svc[(@appTag="web") and (@spManaged="true") and (@hostHeader)]')
DBG ('Will provision or configure the following web applications: {0}' -f (Get-CountSafe $webApps))

#
#

function Is-AppAvailableToConfigure ([string] $appTag)
{
  [int] $spVersionNumber = Parse-IntSafe $spVersion
  [bool] $isAvailable = $true

  if (($spVersionNumber -lt 2013) -and (Contains-Safe @('dcache', 'dcach', 'dcch', 'search', 'squery') $appTag)) {

    $isAvailable = $false
  }

  if (($spVersionNumber -ge 2016) -and (Contains-Safe @('excel', 'sync') $appTag)) {

    $isAvailable = $false
  }

  if ((($appTag -eq 'excel') -or ($appTag -eq 'pps') -or ($appTag -eq 'ups') -or ($appTag -eq 'sync') -or ($appTag -eq 'ssrs') -or ($appTag -eq 'ppvt')) -and ($spType -eq 'foundation')) {

    $isAvailable = $false
  }

  DBGIF ('The functionality is not available: {0} | spVersion = {1} | type = {2}' -f $appTag, $spVersion, $spType) { -not $isAvailable }
  return $isAvailable
}


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

  $svcCreds = $xmlConfig.SelectNodes(('./VMs/MACHINE[vm/@do="true"]/sp[translate(@instance,"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz")="{0}"]//svc[@appTag!="farm" and (@spManaged="true")]' -f $appConfig.instance.ToLower()))
  DBG ('Add and change SP managed accounts: {0}' -f (Get-CountSafe $svcCreds))

  [hashtable] $svcCredsHash = @{}

  foreach ($oneSvcCred in $svcCreds) {

    [string] $svcAppTagLowercase = $oneSvcCred.appTag.ToLower()
    if ($svcCredsHash.ContainsKey($svcAppTagLowercase)) {

      DBGIF ('We support single-tenant only yet. Different service accounts used for a single appTag: {0}' -f $svcAppTagLowercase) { ($svcCredsHash[$svcAppTagLowercase].login -ne $oneSvcCred.login) -or ($svcCredsHash[$svcAppTagLowercase].domain -ne $oneSvcCred.domain) -or ($svcCredsHash[$svcAppTagLowercase].pwd -ne $oneSvcCred.pwd) }

    } else {

      DBG ('Adding one svc credentials: appTag = {0} | domain = {1} | login = {2} | pwd = {3}' -f $oneSvcCred.appTag, $oneSvcCred.domain, $oneSvcCred.login, $oneSvcCred.pwd)

      if (Is-AppAvailableToConfigure $oneSvcCred.appTag) {
          
        [void] $svcCredsHash.Add($svcAppTagLowercase, $oneSvcCred)
      }
    }
  }

  DBG ('Following svc credentials found: #{0} | {1}' -f $svcCredsHash.Count, ($svcCredsHash.Keys -join ', '))
  return $svcCredsHash
}

$svcCredsHash = Get-SpSvcCredsFromXmlConfig

#
#

if (Parse-BoolSafe $appConfig.provisionFarm) { # -or (Parse-BoolSafe $appConfig.joinFarm)) {

  #
  # Note: service accounts that do not require web applications already defined
  #

  DBG ('First define all managed accounts to be usable later')
  foreach ($oneCred in $svcCredsHash.Values) {
  
    DBG ('Creating SP managed service account: {0} | {1}@{2} | pwd = {3}' -f $oneCred.appTag, $oneCred.login, $oneCred.domain, $oneCred.pwd)
    DBGSTART
    New-SPManagedAccount -Credential (Get-PwdCredentials $oneCred.login $oneCred.domain $oneCred.pwd $true) -EA SilentlyContinue | Out-Null
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
  }

  #
  #

  DBG ('Next process all managed accounts that can be initialized before web applications')
  foreach ($oneCred in $svcCredsHash.Values) {
  
    DBG ('Processing SP managed service account: {0} | {1}@{2}' -f $oneCred.appTag, $oneCred.login, $oneCred.domain)
    $samLogin = Get-SAMLogin $oneCred.login $oneCred.domain

    if (-not (Is-AppAvailableToConfigure $oneCred.appTag)) {

      continue
    }

    switch -regex ($oneCred.appTag) {
    
      'token' { Update-SPServiceAppPoolAccount 'SecurityTokenServiceApplicationPool' $samLogin }

      'topology|topo' { Update-SPServiceAppPoolAccount 'SharePoint Web Services System' $samLogin }

      'fsearch|fsrc' { 

          Update-SPServiceInstanceAccount 'SharePoint Foundation Search' $samLogin
          # Note: the service renames itself automatically once started, so we should just search for both names
          Update-SPServiceInstanceAccount 'SharePoint Foundation Help Search' $samLogin


          DBG ('Get SP Foundation Search Service')
          DBGSTART
          $fsearchSvc = Get-SPSearchService -EV er -EA SilentlyContinue
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          
          DBG ('Get crawl account settings')
          $crawlNode = $firstAppConfig.SelectSingleNode('./svc[@appTag="crawl"]')
          #$crawlSAM = (Get-PwdCredentials $crawlNode.login $crawlNode.domain $crawlNode.pwd $true)
          
          DBG ('Configure basic settings for the SP Foundation Search Service: {0} @ {1}' -f $crawlNode.login, $crawlNode.domain)
          DBGSTART
          $fsearchSvc | Set-SPSearchService -CrawlAccount (Get-SAMLogin $crawlNode.login $crawlNode.domain) -CrawlPassword (ConvertTo-SecureString -String $crawlNode.pwd -AsPlainText -Force) -AddStartAddressForNonNTZone $false -MaxBackupDuration 2880 -PerformanceLevel Reduced -EV er -EA SilentlyContinue
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND


          DBG ('Get local instance of the SP Foundation Search Service')
          DBGSTART
          $fsearchSvcInst = Get-SPSearchServiceInstance -Local -EV er -EA SilentlyContinue
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          $fsearchDBName = 'SP{0}_FSearch_{1}' -f $spInstance, $global:thisComputerNetBIOS
          DBG ('Provision new SP Foundation Search database: {0}' -f $fsearchDBName)
          DBGSTART
          $fsearchDBconn = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
          $fsearchDBconn.psbase.DataSource = 'spdbFarm'
          $fsearchDBconn.psbase.InitialCatalog = $fsearchDBName
          $fsearchDBconn.psbase.IntegratedSecurity = $true
          $fsearchDb = [Microsoft.SharePoint.Search.Administration.SPSearchDatabase]::Create($fsearchDBconn)
          $fsearchDb.Provision()
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          DBG ('Assign the SP Foundation Search database to the current service instance')
          DBGSTART
          $fsearchSvcInst.SearchDatabase = $fsearchDb
          $fsearchSvcInst.Update()
          $fsearchSvcInst.Provision()
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          Enable-SPDatabaseAdminAccess -db $fsearchDBName -admins $spAdmins

          EnableDisable-SPServiceInstanceOnThisServer 'SharePoint Foundation Search' '+'
          EnableDisable-SPServiceInstanceOnThisServer 'SharePoint Foundation Help Search' '+'
      }
        
      'dcache|dcach|dcch' {

          #if (Is-AppAvailableToConfigure 'dcache') {

            #DBGIF ('Distributed cache cannot be configured in version 2010. Skipping.') { $true }
            #
            #} else {

            Update-SPServiceInstanceAccount 'Distributed Cache' $samLogin -assertServiceName 'AppFabricCachingService'
          #}
      }

      'claims' { 

          Update-SPServiceInstanceAccount 'Claims to Windows Token Service' $samLogin -assertServiceName 'c2wts'
          #EnableDisable-SPServiceInstanceOnThisServer 'Claims to Windows Token Service' '+'
      }
      
      'sandbox|sandbx' { 
      
          Update-SPServiceInstanceAccount 'Microsoft SharePoint Foundation Sandboxed Code Service' $samLogin -assertServiceName 'SPUserCodeV4'
      }

      'state' {

          DBGIF $MyInvocation.MyCommand.Name { $spType -ne 'server' }

          $stateSvcDbName = 'SP{0}_State' -f $spInstance
          DBG ('Create new state service database: {0}' -f $stateSvcDbName)
          DBGSTART
          $stateSvcDb = New-SPStateServiceDatabase -Name $stateSvcDbName
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          Enable-SPDatabaseAdminAccess -db $stateSvcDbName -admins $spAdmins

          $stateSvcAppName = 'State Service'
          DBG ('Create new state service application: {0}' -f $stateSvcAppName)
          DBGSTART
          $stateSvcApp = New-SPStateServiceApplication -Name $stateSvcAppName -Database $stateSvcDb
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          $stateSvcAppProxyName = 'State Service Proxy'
          DBG ('Associate the new state service application with default app proxy: {0}' -f $stateSvcAppProxyName)
          DBGSTART
          $stateSvcAppProxy = New-SPStateServiceApplicationProxy -Name $stateSvcAppProxyName -ServiceApplication $stateSvcApp -DefaultProxyGroup
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
      }

      'excel' {

        #
        # ,========================================================================================
        # | Note: service architecture example
        # |       
        # |                                        [ApplicationPool]. <---,---- ApplicationPool.[WebApplication].ServiceApplicationProxyGroup                             
        # |                                                                                       "http://intranet"         |
        # |                                                                                                                 |
        # |       [ServiceApplicationPool]. <---,---- ApplicationPool.[ServiceApplication].ServiceApplicationProxyGroup ----+---> .[ServiceApplicationProxyGroup].Proxies ----,---> .[ServiceApplicationProxy].
        # |                                     |                       "ExcelSvc"                                          |             "[default]"                         |        "SteateServiceProxy"
        # |                                     |                                                                           |                                                 |
        # |                                     '---- ApplicationPool.[ServiceApplication].ServiceApplicationProxyGroup ----|                                                 '---> .[ServiceApplicationProxy].
        # |                                                             "PerformancePointSvc"                               |                                                          "ExcelSvc" (automatically created with ServiceApplication)
        # |                                                                                                                 |
        # |       [ServiceApplicationPool]. <-------- ApplicationPool.[ServiceApplication].ServiceApplicationProxyGroup ----|                                                 '---> .[ServiceApplicationProxy].
        # |                                                             "SecureStore"                                       |                                                          "SecureStoreProxy" (must be created manually after ServiceApplication)
        # |                                                                                                                 |
        # |                                                           [ServiceApplication].ServiceApplicationProxyGroup ----'
        # |                                                             "StateSvc"                                         
        # |       

        [string] $excelSpPool = $oneCred.binding.pool
        [string] $excelSpApp = $oneCred.binding.svcApp
        DBG ('Excel Service account should have any usage: pool = {0} | app = {1}' -f $excelSpPool, $excelSpApp)

        if ((Is-ValidString $excelSpPool) -and (Is-ValidString $excelSpApp)) {

          DBG ('Verify if the Excel AppPool and the Excel web service application does not exist yet: {0} | {1}' -f $excelSpPool, $excelSpApp)
          DBGSTART
          $existingExcelServicePool = $null
          $existingExcelServicePool = Get-SPServiceApplicationPool | ? { $_.Name -eq $excelSpPool }
          $existingExcelServiceApp = $null
          # Note: one specificum of Excel Service is this command let
          $existingExcelServiceApp = Get-SPExcelServiceApplication | ? { ($_.TypeName -eq 'Excel Services Application Web Service Application') -and ($_.Name -eq $excelSpApp) }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('We got existing Excel Services objects: app = {0} | pool = {1}' -f $existingExcelServiceApp.Name, $existingExcelServicePool.Id)
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingExcelServiceApp) -gt 0 }
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingExcelServicePool) -gt 0 }

          if ((Is-Null $existingExcelServiceApp) -and (Is-Null $existingExcelServicePool)) {

            DBG ('Excel service is not provisioned yet. Create the pool first: {0} | sam = {1}' -f $excelSpPool, $samLogin)
            DBGSTART
            $existingExcelServicePool = New-SPServiceApplicationPool -Name $excelSpPool -Account $samLogin
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('Excel Service app pool created: {0}' -f $existingExcelServicePool.Id)
            DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingExcelServicePool }

            if (Is-NonNull $existingExcelServicePool) {

              DBG ('Create the Excel Services web application as well: {0}' -f $excelSpApp)
              DBGSTART
              $existingExcelServiceApp = $null
              $existingExcelServiceApp = New-SPExcelServiceApplication -Name $excelSpApp -ApplicationPool $existingExcelServicePool
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Created the Excel Services service application: {0} | ver = {1} | status = {2} | iis = {3}' -f $existingExcelServiceApp.Id, $existingExcelServiceApp.ApplicationVersion, $existingExcelServiceApp.Status, $existingExcelServiceApp.IisVirtualDirectoryPath)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingExcelServiceApp }
              
              if (Is-NonNull $existingExcelServiceApp) {

                Wait-SPServiceApplicationOnline $existingExcelServiceApp.Id

                DBG ('Excel Service service application proxy group: {0}' -f $existingExcelServiceApp.ServiceApplicationProxyGroup.FriendlyName)
                DBGIF $MyInvocation.MyCommand.Name { $existingExcelServiceApp.ServiceApplicationProxyGroup.FriendlyName -ne '[default]' }
            
                [string] $excelSpProxyName = $excelSpApp

                # Note: another specificum of Excel Service is that it creates the proxy automatically and with the same name as the application itself
                DBG ('Verify we have the service application proxy created automatically: {0}' -f $excelSpProxyName)
                DBGSTART
                $existingExcelServiceProxy = $null
                $existingExcelServiceProxy = Get-SPServiceApplicationProxy | ? { ($_.TypeName -eq 'Excel Services Application Web Service Application Proxy') -and ($_.Name -eq $excelSpProxyName) }
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('We got Excel Service proxy created automatically: {0} | {1} | name = {2} | display = {3}' -f $existingExcelServiceProxy.Id, $existingExcelServiceProxy.Status, $existingExcelServiceProxy.Name, $existingExcelServiceProxy.DisplayName)
                DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingExcelServiceProxy }
                DBGIF $MyInvocation.MyCommand.Name { $existingExcelServiceProxy.Status -ne 'Online' }
                DBGIF $MyInvocation.MyCommand.Name { $existingExcelServiceProxy.Name -ne $existingExcelServiceProxy.DisplayName }

                if (Is-NonNull $existingExcelServiceProxy) {

                  DBG ('Associate the automatically created proxy with the Excel Service proxy group')
                  DBGSTART
                  Add-SPServiceApplicationProxyGroupMember -Identity $existingExcelServiceApp.ServiceApplicationProxyGroup -Member $existingExcelServiceProxy
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                }

                #
                #

                [object[]] $trustedLocations = $null
                $trustedLocations = $oneCred.binding.SelectNodes('./trustedLoc[@address]')
                DBG ('Trusted locations requested: {0}' -f (Get-CountSafe $trustedLocations))

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

                  foreach ($oneTrustedLocation in $trustedLocations) {

                    DBG ('One Excel trusted location: {0} | {1} | {2} | {3}' -f $oneTrustedLocation.address, $oneTrustedLocation.includeChildren, $oneTrustedLocation.type, $oneTrustedLocation.externalData)
                    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneTrustedLocation.address }
                    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneTrustedLocation.type }
                    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneTrustedLocation.externalData }

                    [bool] $includeChildrenBool = Parse-BoolSafe $oneTrustedLocation.includeChildren
                    DBGSTART
                    $newTrustedLocation = $null
                    $newTrustedLocation = New-SPExcelFileLocation -Address $oneTrustedLocation.address -IncludeChildren:$includeChildrenBool -LocationType $oneTrustedLocation.type -ExternalDataAllowed $oneTrustedLocation.externalData -ExcelServiceApplication $existingExcelServiceApp
                    DBGER $MyInvocation.MyCommand.Name $error
                    DBGEND
                    DBGIF $MyInvocation.MyCommand.Name { Is-Null $newTrustedLocation }
                  }
                }
              }
            }
          }
        }
      }

      'sec' {

        [string] $secsvcSpPool = $oneCred.binding.pool
        [string] $secsvcSpApp = $oneCred.binding.svcApp
        [bool] $secsvcAuditing = (Parse-BoolSafe $oneCred.binding.audit)
        [string] $secsvcDbServer = 'spdb{0}' -f $oneCred.binding.dbSrvTag
        [string] $secsvcDb = 'SP{0}_SecureStore' -f $spInstance
        [string] $secsvcMasterPwd = $oneCred.binding.masterPwd

        if (Is-ValidString $oneCred.binding.db) {

          $secsvcDb = '{0}_{1}' -f $secsvcDb, $oneCred.binding.db
        }

        DBG ('Secure Store Service account should have any usage: pool = {0} | app = {1} | audit = {2} | db = {3} | dbSrv = {4}' -f $secsvcSpPool, $secsvcSpApp, $secsvcAuditing, $secsvcDb, $secsvcDbServer)

        if ((Is-ValidString $secsvcSpPool) -and (Is-ValidString $secsvcSpApp)) {

          DBG ('Verify if the Secure Store AppPool and the Secure Store web service application does not exist yet: {0} | {1}' -f $secsvcSpPool, $secsvcSpApp)
          DBGSTART
          $existingSecsvcServicePool = $null
          $existingSecsvcServicePool = Get-SPServiceApplicationPool | ? { $_.Name -eq $secsvcSpPool }
          $existingSecsvcServiceApp = $null
          # Note: as against Excel Services, specificum of Secure Store service is that it does not have its own cmdlet to obtain the service application object
          $existingSecsvcServiceApp = Get-SPServiceApplication | ? { ($_.TypeName -eq 'Secure Store Service Application') -and ($_.Name -eq $secsvcSpApp) }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('We got existing Secure Store service objects: app = {0} | pool = {1}' -f $existingSecsvcServiceApp.Name, $existingSecsvcServicePool.Id)
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingSecsvcServiceApp) -gt 0 }
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingSecsvcServicePool) -gt 0 }

          if ((Is-Null $existingSecsvcServiceApp) -and (Is-Null $existingSecsvcServicePool)) {

            DBG ('Secure Store service is not provisioned yet. Create the pool first: {0} | sam = {1}' -f $secsvcSpPool, $samLogin)
            DBGSTART
            $existingSecsvcServicePool = $null
            $existingSecsvcServicePool = New-SPServiceApplicationPool -Name $secsvcSpPool -Account $samLogin
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('Secure Store service app pool created: {0}' -f $existingSecsvcServicePool.Id)
            DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSecsvcServicePool }

            if (Is-NonNull $existingSecsvcServicePool) {

              DBG ('Create the Secure Store service web application as well: {0} | audit = {1} | dbSrv = {2} | db = {3}' -f $secsvcSpApp, $secsvcAuditing, $secsvcDb, $secsvcDbServer)
              DBGSTART
              $existingSecsvcServiceApp = $null
              $existingSecsvcServiceApp = New-SPSecureStoreServiceApplication -Name $secsvcSpApp -ApplicationPool $existingSecsvcServicePool -AuditingEnabled:$secsvcAuditing -DatabaseName $secsvcDb -DatabaseServer $secsvcDbServer
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Created the Secure Store service service application: {0} | ver = {1} | status = {2} | iis = {3}' -f $existingSecsvcServiceApp.Id, $existingSecsvcServiceApp.ApplicationVersion, $existingSecsvcServiceApp.Status, $existingSecsvcServiceApp.IisVirtualDirectoryPath)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSecsvcServiceApp }

              if (Is-NonNull $existingSecsvcServiceApp) {

                Wait-SPServiceApplicationOnline $existingSecsvcServiceApp.Id
            
                # Note: this one does not work without granting the install account the DB_OWNER first
                Execute-NonQueryRemote -sqlServer $secsvcDbServer -database $secsvcDb -nonQuery ('EXECUTE sp_addrolemember @rolename = "db_owner", @membername = "{0}\{1}"' -f ([Environment]::UserDomainName), ([Environment]::UserName)) -login $farmCred.login -domain $farmCred.domain -password $farmCred.pwd
                Enable-SPDatabaseAdminAccess -db $secsvcDb -admins $spAdmins


                DBG ('Secure Store service application proxy group: {0}' -f $existingSecsvcServiceApp.ServiceApplicationProxyGroup.FriendlyName)
                DBGIF $MyInvocation.MyCommand.Name { $existingSecsvcServiceApp.ServiceApplicationProxyGroup.FriendlyName -ne '[default]' }
            
                [string] $secsvcSpProxyName = '{0} Proxy' -f $secsvcSpApp

                # Note: as against Excel Services, the Secure Store has this specificum that it does NOT create the proxy itself
                DBG ('Verify we DO NOT have the service application proxy created automatically: {0}' -f $secsvcSpProxyName)
                DBGSTART
                $existingSecsvcServiceProxy = $null
                $existingSecsvcServiceProxy = Get-SPServiceApplicationProxy | ? { ($_.TypeName -eq 'Secure Store service application Web Service Application Proxy') -and (($_.Name -eq $secsvcSpProxyName) -or ($secsvcSpApp)) }
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('We got Secure Store service proxy created automatically: {0} | {1} | name = {2} | display = {3}' -f $existingSecsvcServiceProxy.Id, $existingSecsvcServiceProxy.Status, $existingSecsvcServiceProxy.Name, $existingSecsvcServiceProxy.DisplayName)
                DBGIF $MyInvocation.MyCommand.Name { Is-NonNull $existingSecsvcServiceProxy }

                if (Is-Null $existingSecsvcServiceProxy) {

                  DBG ('Create the Secure Store application proxy and let it associate automatically with the default proxy group: {0}' -f $secsvcSpProxyName)
                  DBGSTART
                  $existingSecsvcServiceProxy = $null
                  $existingSecsvcServiceProxy = New-SPSecureStoreServiceApplicationProxy -Name $secsvcSpProxyName -ServiceApplication $existingSecsvcServiceApp -DefaultProxyGroup
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSecsvcServiceProxy }
                }

                Wait-SPServiceApplicationProxyOnline $existingSecsvcServiceProxy.Id

                if ((Is-NonNull $existingSecsvcServiceProxy) -and (Is-ValidString $secsvcMasterPwd)) {

                  DBG ('We should provision the master key for Secure Store: {0}' -f $secsvcMasterPwd)

                  EnableDisable-SPServiceInstanceOnThisServer 'Secure Store Service' '+'

                  DBGSTART
                  Update-SPSecureStoreMasterKey -ServiceApplicationProxy $existingSecsvcServiceProxy -Passphrase $secsvcMasterPwd
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND

                  EnableDisable-SPServiceInstanceOnThisServer 'Secure Store Service' '-'
                }
              }
            }
          }
        }
      }

      'pps' {

        [string] $ppssvcSpPool = $oneCred.binding.pool
        [string] $ppssvcSpApp = $oneCred.binding.svcApp
        [string] $ppssvcDbServer = 'spdb{0}' -f $oneCred.binding.dbSrvTag
        [string] $ppssvcDb = 'SP{0}_PerformancePoint' -f $spInstance

        if (Is-ValidString $oneCred.binding.db) {

          $ppssvcDb = '{0}_{1}' -f $ppssvcDb, $oneCred.binding.db
        }

        DBG ('Performance Point service account should have any usage: pool = {0} | app = {1} | db = {2} | dbSrv = {3}' -f $ppssvcSpPool, $ppssvcSpApp, $ppssvcDb, $ppssvcDbServer)

        if ((Is-ValidString $ppssvcSpPool) -and (Is-ValidString $ppssvcSpApp)) {

          DBG ('Verify if the Performance Point AppPool and the Performance Point web service application does not exist yet: {0} | {1}' -f $ppssvcSpPool, $ppssvcSpApp)
          DBGSTART
          $existingPpssvcServicePool = $null
          $existingPpssvcServicePool = Get-SPServiceApplicationPool | ? { $_.Name -eq $ppssvcSpPool }
          $existingPpssvcServiceApp = $null
          # Note: as against Excel Services and Secure Store service, specificum of Performance Point service is that 
          #       although it has its own cmdlet Get-SPPerformancePointServiceApplication, this does NOT actually return ServiceApplication object
          $existingPpssvcServiceApp = Get-SPServiceApplication | ? { ($_.TypeName -eq 'PerformancePoint Service Application') -and ($_.Name -eq $ppssvcSpApp) }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('We got existing Performance Point service objects: app = {0} | pool = {1}' -f $existingPpssvcServiceApp.Name, $existingPpssvcServicePool.Id)
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingPpssvcServiceApp) -gt 0 }
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingPpssvcServicePool) -gt 0 }

          if ((Is-Null $existingPpssvcServiceApp) -and (Is-Null $existingPpssvcServicePool)) {

            DBG ('Performance Point service is not provisioned yet. Create the pool first: {0} | sam = {1}' -f $ppssvcSpPool, $samLogin)
            DBGSTART
            $existingPpssvcServicePool = $null
            $existingPpssvcServicePool = New-SPServiceApplicationPool -Name $ppssvcSpPool -Account $samLogin
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('Performance Point service app pool created: {0}' -f $existingPpssvcServicePool.Id)
            DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingPpssvcServicePool }

            if (Is-NonNull $existingPpssvcServicePool) {

              DBG ('Create the Performance Point service web application as well: {0} | dbSrv = {1} | db = {2}' -f $ppssvcSpApp, $ppssvcDb, $ppssvcDbServer)
              DBGSTART
              $createdPpsApplication = $null
              # Note: this return value is different from what the Get-SPServiceApplication would return. This one is actually the result of Get-SPPerformancePointServiceApplication
              #       still it is correct to use this one with New-SPPerformancePointServiceApplicationProxy
              $createdPpsApplication = New-SPPerformancePointServiceApplication -Name $ppssvcSpApp -ApplicationPool $existingPpssvcServicePool -DatabaseName $ppssvcDb -DatabaseServer $ppssvcDbServer
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Created the Performance Point service service application weird: {0} | name = {1}' -f $createdPpsApplication.Id, $createdPpsApplication.Name)

              # Note: As noted above, the object returned from the New-SPPerformancePointServicesApplication is not the ServiceApplication itself, but its ID is the same
              DBG ('Get the serviceapplication object for the just created Performance Point service application: {0}' -f $createdPpsApplication.Id)
              DBGSTART
              $existingPpssvcServiceApp = $null
              $existingPpssvcServiceApp = Get-SPServiceApplication $createdPpsApplication.Id
              DBG ('Created the Performance Point service service application internal: {0} | ver = {1} | status = {2} | iis = {3}' -f $existingPpssvcServiceApp.Id, $existingPpssvcServiceApp.ApplicationVersion, $existingPpssvcServiceApp.Status, $existingPpssvcServiceApp.IisVirtualDirectoryPath)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingPpssvcServiceApp }

              if (Is-NonNull $existingPpssvcServiceApp) {

                Wait-SPServiceApplicationOnline $existingPpssvcServiceApp.Id

                DBG ('Performance Point service application proxy group: {0}' -f $existingPpssvcServiceApp.ServiceApplicationProxyGroup.FriendlyName)
                DBGIF $MyInvocation.MyCommand.Name { $existingPpssvcServiceApp.ServiceApplicationProxyGroup.FriendlyName -ne '[default]' }
            
                [string] $ppssvcSpProxyName = ('{0} Proxy' -f $ppssvcSpApp)

                # Note: as against Excel Services, the Performance Point has this specificum that it does NOT create the proxy itself
                DBG ('Verify we DO NOT have the service application proxy created automatically: {0}' -f $ppssvcSpProxyName)
                DBGSTART
                $existingPpssvcServiceProxy = $null
                $existingPpssvcServiceProxy = Get-SPServiceApplicationProxy | ? { ($_.TypeName -eq 'PerformancePoint Service Application Proxy') -and (($_.Name -eq $ppssvcSpProxyName) -or ($_.Name -eq $ppssvcSpApp)) }
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('We got Performance Point service proxy created automatically: {0} | {1} | name = {2} | display = {3}' -f $existingPpssvcServiceProxy.Id, $existingPpssvcServiceProxy.Status, $existingPpssvcServiceProxy.Name, $existingPpssvcServiceProxy.DisplayName)
                DBGIF $MyInvocation.MyCommand.Name { Is-NonNull $existingPpssvcServiceProxy }

                if (Is-Null $existingPpssvcServiceProxy) {

                  DBG ('Create the Performance Point application proxy and let it associate automatically with the default proxy group: {0}' -f $ppssvcSpProxyName)
                  DBGSTART
                  $existingPpssvcServiceProxy = $null
                  # Note: a specificum here is the parameter -Default as against Secure Store proxy which has -DefaultProxyGroup parameter itself
                  #       also note that the -ServiceApplication receives the weird object created during New-SPPerformancePointServiceApplication and not the standard ServiceApplication one
                  $existingPpssvcServiceProxy = New-SPPerformancePointServiceApplicationProxy -Name $ppssvcSpProxyName -ServiceApplication $createdPpsApplication -Default
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingPpssvcServiceProxy }
                }
              }
            }
          }
        }
      }

      'bdc' {

        [string] $bdcsvcSpPool = $oneCred.binding.pool
        [string] $bdcsvcSpApp = $oneCred.binding.svcApp
        [string] $bdcsvcDbServer = 'spdb{0}' -f $oneCred.binding.dbSrvTag
        [string] $bdcsvcDb = 'SP{0}_BDC' -f $spInstance

        if (Is-ValidString $oneCred.binding.db) {

          $bdcsvcDb = '{0}_{1}' -f $bdcsvcDb, $oneCred.binding.db
        }

        DBG ('BDC service account should have any usage: pool = {0} | app = {1} | db = {2} | dbSrv = {3}' -f $bdcsvcSpPool, $bdcsvcSpApp, $bdcsvcDb, $bdcsvcDbServer)

        if ((Is-ValidString $bdcsvcSpPool) -and (Is-ValidString $bdcsvcSpApp)) {

          DBG ('Verify if the BDC AppPool and the BDC web service application does not exist yet: {0} | {1}' -f $bdcsvcSpPool, $bdcsvcSpApp)
          DBGSTART
          $existingBdcsvcServicePool = $null
          $existingBdcsvcServicePool = Get-SPServiceApplicationPool | ? { $_.Name -eq $bdcsvcSpPool }
          $existingBdcsvcServiceApp = $null
          $existingBdcsvcServiceApp = Get-SPServiceApplication | ? { ($_.TypeName -eq 'Business Data Connectivity Service Application') -and ($_.Name -eq $bdcsvcSpApp) }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('We got existing BDC service objects: app = {0} | pool = {1}' -f $existingBdcsvcServiceApp.Name, $existingBdcsvcServicePool.Id)
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingBdcsvcServiceApp) -gt 0 }
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingBdcsvcServicePool) -gt 0 }

          if ((Is-Null $existingBdcsvcServiceApp) -and (Is-Null $existingBdcsvcServicePool)) {

            DBG ('BDC service is not provisioned yet. Create the pool first: {0} | sam = {1}' -f $bdcsvcSpPool, $samLogin)
            DBGSTART
            $existingBdcsvcServicePool = $null
            $existingBdcsvcServicePool = New-SPServiceApplicationPool -Name $bdcsvcSpPool -Account $samLogin
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('BDC service app pool created: {0}' -f $existingBdcsvcServicePool.Id)
            DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingBdcsvcServicePool }

            if (Is-NonNull $existingBdcsvcServicePool) {

              DBG ('Create the BDC service web application as well: {0} | dbSrv = {1} | db = {2}' -f $bdcsvcSpApp, $bdcsvcDb, $bdcsvcDbServer)
              DBGSTART
              $existingBdcsvcServiceApp = $null
              $existingBdcsvcServiceApp = New-SPBusinessDataCatalogServiceApplication -Name $bdcsvcSpApp -ApplicationPool $existingBdcsvcServicePool -DatabaseName $bdcsvcDb -DatabaseServer $bdcsvcDbServer
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Created the BDC service application: {0} | ver = {1} | status = {2} | iis = {3}' -f $existingBdcsvcServiceApp.Id, $existingBdcsvcServiceApp.ApplicationVersion, $existingBdcsvcServiceApp.Status, $existingBdcsvcServiceApp.IisVirtualDirectoryPath)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingBdcsvcServiceApp }

              if (Is-NonNull $existingBdcsvcServiceApp) {

                Wait-SPServiceApplicationOnline $existingBdcsvcServiceApp.Id

                DBG ('BDC service application proxy group: {0}' -f $existingBdcsvcServiceApp.ServiceApplicationProxyGroup.FriendlyName)
                DBGIF $MyInvocation.MyCommand.Name { $existingBdcsvcServiceApp.ServiceApplicationProxyGroup.FriendlyName -ne '[default]' }
            
                [string] $bdcsvcSpProxyName = ('{0}' -f $bdcsvcSpApp)

                # Note: specificum here is that BDC actually creates the proxy itself whith service application
                DBG ('Verify we DO have the service application proxy created automatically: {0}' -f $bdcsvcSpProxyName)
                DBGSTART
                $existingBdcsvcServiceProxy = $null
                $existingBdcsvcServiceProxy = Get-SPServiceApplicationProxy | ? { ($_.TypeName -eq 'Business Data Connectivity Service Application Proxy') -and ($_.Name -eq $bdcsvcSpProxyName) }
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('We got BDC service proxy created automatically: {0} | {1} | name = {2} | display = {3}' -f $existingBdcsvcServiceProxy.Id, $existingBdcsvcServiceProxy.Status, $existingBdcsvcServiceProxy.Name, $existingBdcsvcServiceProxy.DisplayName)
                DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingBdcsvcServiceProxy }

                if (Is-NonNull $existingBdcsvcServiceProxy) {

                  DBG ('The BDC service associates its default proxy with the default proxy group automatically, verify: {0} | {1}' -f $existingBdcsvcServiceApp.Id, $existingBdcsvcServiceProxy.Id)
                  DBGSTART
                  $existingBdcsvcServiceProxyBinding = $null
                  $existingBdcsvcServiceProxyBinding = $existingBdcsvcServiceApp.ServiceApplicationProxyGroup.Proxies | ? { $_.Id -eq $existingBdcsvcServiceProxy.Id }
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingBdcsvcServiceProxyBinding }
                }
              }
            }
          }
        }
      }

    }
  }


  DBG ('Establish a warmup account if any requested')
  $warmup = $firstAppConfig.SelectSingleNode('./svc[(@appTag="warmup")]')
  
  if (Is-NonNull $warmup) {

    $warmupLogin = Get-SAMLogin $warmup.login $warmup.domain
  }
  
  #
  #

  DBG ('Proceed with web applications')
  DBG ('Will provision the following web applications: {0}' -f (Get-CountSafe $webApps))

  foreach ($oneWebApp in $webApps) {

    [string[]] $hostHeaders = Split-MultiValue $oneWebApp.hostHeader
    DBG ('One web application requested: {0}' -f $oneWebApp.hostheader)
    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $hostHeaders) -le 0 }

    [string] $primaryHostHeader = $hostHeaders[0]
    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $primaryHostHeader }
    
    #
    #

    DBG ('Going to normalize some basic input params: primaryHostHeader = {0} | login = {1} | domain = {2} | firstAppInstall = {3} | firstAppAdmin = {4} | fistAppDomain = {5} | dbPrefix = {6} | appDisk = {7}' -f $primaryHostHeader, $oneWebApp.login, $oneWebApp.domain, $firstAppConfig.app.iLogin, $firstAppConfig.app.aLogin, $firstAppConfig.app.iDomain, $appDatabaseNamePrefix, $oneWebApp.appDisk)
    $appAccountSAM = Get-SAMLogin $oneWebApp.login $oneWebApp.domain

    if (Is-ValidString $firstAppConfig.app.aLogin) {

      $appAdminSAM = Get-SAMLogin $firstAppConfig.app.aLogin $firstAppConfig.app.iDomain
    
    } else {

      $appAdminSAM = Get-SAMLogin $firstAppConfig.app.iLogin $firstAppConfig.app.iDomain
    }

    $appDatabaseNamePrefix = 'SP{0}_Cnt' -f $primaryHostHeader
    $appDatabaseName = '{0}_Root' -f $appDatabaseNamePrefix

    [bool] $appUseSsl = Parse-BoolSafe $oneWebApp.ssl

    [string] $appProtocol = 'http'
    [int] $appPort = 80
    
    if ($appUseSsl) {

      $appProtocol = 'https'
      $appPort = 443
    }

    if (Is-ValidString $oneWebApp.port) {

      $appPort = Parse-IntSafe $oneWebApp.port
    }

    [string] $appDir = Join-Path (Resolve-VolumePath $oneWebApp.appDisk) ('SP-{0}-{1}-{2}' -f $primaryHostHeader, $appProtocol.ToUpper(), $appPort)
    [string] $appUrl = '{0}://{1}:{2}' -f $appProtocol, $primaryHostHeader, $appPort

    [string] $appHostHeader = $primaryHostHeader

    if (Parse-BoolSafe $oneWebApp.ipBindingOnly) {

      $appHostHeader = $null
    }

    #
    #

    DBG ('Does the web app requests classic authentication explicitly: {0}' -f $oneWebApp.claims)
    $classicAuthentication = (($spVersion -eq '2010') -and (-not (Parse-BoolSafe $oneWebApp.claims))) -or ((Is-ValidString $oneWebApp.claims) -and (-not (Parse-BoolSafe $oneWebApp.claims)))

    [Microsoft.SharePoint.Administration.SPWebApplication] $newWebApp = $null

    if ($classicAuthentication) {
    
      DBG ('Provision new web application with classic authentication: {0}, {1}, {2}, {3}, {4}' -f $appUrl, $appAccountSAM, $appAdminSAM, $appDatabaseName, $appDir)
      DBGSTART
      $newWebApp = New-SPWebApplication `
        -Name $primaryHostHeader `
        -DatabaseName $appDatabaseName `
        -DatabaseServer 'spdbFarm' `
        -HostHeader $appHostHeader `
        -Port $appPort `
        -AuthenticationMethod 'Kerberos' `
        -ApplicationPool ('SP{0}AppPool' -f $primaryHostHeader) `
        -ApplicationPoolAccount $appAccountSAM `
        -Path $appDir `
        -SecureSocketsLayer:$appUseSsl `
        -Url $appUrl
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      Wait-SPWebApplication ([ref] $newWebApp)

    } else {

      DBG ('New claims authentication provider')

      DBGSTART
      $newClaimsProv = $null
      $newClaimsProv = New-SPAuthenticationProvider
      $newClaimsProv.DisableKerberos = $false
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBG ('Provision new web application with claims authentication: {0}, {1}, {2}, {3}, {4}' -f $appUrl, $appAccountSAM, $appAdminSAM, $appDatabaseName, $appDir)

      DBGSTART
      $newWebApp = New-SPWebApplication `
        -Name $primaryHostHeader `
        -DatabaseName $appDatabaseName `
        -DatabaseServer 'spdbFarm' `
        -HostHeader $appHostHeader `
        -Port $appPort `
        -AuthenticationProvider $newClaimsProv `
        -ApplicationPool ('SP{0}AppPool' -f $primaryHostHeader) `
        -ApplicationPoolAccount $appAccountSAM `
        -Path $appDir `
        -SecureSocketsLayer:$appUseSsl `
        -Url $appUrl
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      Wait-SPWebApplication ([ref] $newWebApp)
    }


    DBG ('Get web application root content database')
    DBGSTART
    $appDatabase = Get-SPContentDatabase $appDatabaseName
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBG ('Application database loaded: {0} | {1} | {2} | {3} | {4}' -f $appDatabase.Name, $appDatabase.BuildVersion, $appDatabase.CurrentSiteCount, $appDatabase.MaximumSiteCount, $appDatabase.Server)
  
    DBG ('Assign SP Admins shell access to the content DB: {0}' -f $spAdmins)
    DBGSTART
    Enable-SPDatabaseAdminAccess -admins $spAdmins -databaseObject $appDatabase
    #Add-SPShellAdmin -Database $appDatabase -UserName $spAdmins
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND


    DBG ('Add SP Admins to the web application policy with Full Control')
    Add-SPWebApplicationPolicy $newWebApp $spAdmins ('Farm Admins (Web & PowerShell) ({0})' -f $spAdmins) FullControl -loginType group

    DBG ('Add SP Admins to the web application policy with Full Control')
    Add-SPWebApplicationPolicy $newWebApp $spAdmins ('Farm Admins (PowerShell) ({0})' -f $spAdmins) FullControl -doNotTranslateIdentity $true -loginType group

    #
    #

    DBG ('Check if any additional explicit content databases are required')
    $explicitContentDBs = $oneWebApp.SelectNodes('./cntdb')
    DBG ('Do we have to precreate any content DBs: #{0}' -f (Get-CountSafe $explicitContentDBs))

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

      foreach ($oneExplicitContentDB in $explicitContentDBs) {

        if (Is-ValidString $oneExplicitContentDB.dbSrvTag) {

          $cntdbDBServer = 'spdb{0}' -f $oneExplicitContentDB.dbSrvTag

        } else {

          $cntdbDBServer = 'spdbFarm'
        }

        $cntdbName = '{0}_{1}' -f $appDatabaseNamePrefix, $oneExplicitContentDB.nameSfx

        DBG ('One explicit content DB to precreate: {0} | srv = {1} | {2} | db = {3} | app = {4}' -f $oneExplicitContentDB.dbSrvTag, $cntdbDBServer, $oneExplicitContentDB.nameSfx, $cntdbName, $newWebApp.Url)
        DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneExplicitContentDB.nameSfx }

        DBGSTART
        $newExplicitCntDB = $null
        $newExplicitCntDB = New-SPContentDatabase -WebApplication $newWebApp.Url -Name $cntdbName -DatabaseServer $cntdbDBServer
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { Is-Null $newExplicitCntDB }

        if (Is-NonNull $newExplicitCntDB) {

          DBG ('Add SP Admins to the content database: {0} | {1}' -f $newExplicitCntDB.Name, $spAdmins)
          DBGSTART
          Enable-SPDatabaseAdminAccess -admins $spAdmins -databaseObject $newExplicitCntDB
          #Add-SPShellAdmin -Database $newExplicitCntDB -UserName $spAdmins
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }
      }
    }

    #
    #

    Reopen-SPWebApplication ([ref] $newWebApp)

    #
    #

    if (Is-ValidString $warmupLogin) {

      DBG ('Warmup account specified, add it to the web application policy with Full Read and explicit Deny Write')
      Add-SPWebApplicationPolicy $newWebApp $warmupLogin ('Warmup Account ({0})' -f $warmupLogin) FullRead
      Add-SPWebApplicationPolicy $newWebApp $warmupLogin ('Warmup Account ({0})' -f $warmupLogin) DenyWrite
    }


    DBG ('Check if any additional host header extensions are required')
    $oneWebAppExtensions = $oneWebApp.SelectNodes('./ext')
    DBG ('Found additional web application extensions: {0}' -f (Get-CountSafe $oneWebAppExtensions))

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

      foreach ($oneWebAppExtension in $oneWebAppExtensions) {

        DBG ('One web application extension requested: {0} | {1}' -f $oneWebAppExtension.zone, $oneWebAppExtension.hostHeader)
        DBGIF $MyInvocation.MyCommand.Name { -not (Contains-Safe @('Custom', 'Intranet', 'Internet', 'Extranet') $oneWebAppExtension.zone) }
        DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $oneWebAppExtension.hostHeader }

        [string] $newExtName = $oneWebAppExtension.hostHeader
        [string] $newExtZone = $oneWebAppExtension.zone
        [bool] $newExtSsl = Parse-BoolSafe $oneWebAppExtension.ssl

        [string] $newExtProtocol = 'http'
        [int] $newExtPort = 80

        if ($newExtSsl) {

          $newExtProtocol = 'https'
          $newExtPort = 443
        }

        if (Is-ValidString $oneWebAppExtension.port) {

          $newExtPort = Parse-IntSafe $oneWebAppExtension.port
        }

        [string] $newExtDir = Join-Path (Resolve-VolumePath $oneWebApp.appDisk) ('SP-{0}-{1}-{2}' -f $newExtName, $newExtProtocol.ToUpper(), $newExtPort)
        [string] $newExtUrl = '{0}://{1}:{2}' -f $newExtProtocol, $newExtName, $newExtPort

        [string] $newExtHostHeader = $newExtName

        if (Parse-BoolSafe $oneWebAppExtension.ipBindingOnly) {

          $newExtHostHeader = $null
        }

        if (-not $classicAuthentication) {

          DBG ('Create the claims authentication provider first')
          DBGSTART
          $newClaimsProv = $null
          $newClaimsProv = New-SPAuthenticationProvider
          $newClaimsProv.DisableKerberos = $false
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

          DBG ('Provisioning the web application extension with claims authentication: id = {0} | name = {1} | zone = {2} | port = {3} | host = {4} | path = {5} | url = {6} | ssl = {7}' -f $newWebApp, $newExtName, $newExtZone, $newExtPort, $newExtHostHeader, $newExtDir, $newExtUrl, $newExtSsl)
          DBGSTART
          $newExtension = New-SPWebApplicationExtension -Identity $newWebApp -Name $newExtName -Zone $newExtZone -Port $newExtPort -HostHeader $newExtHostHeader -Path $newExtDir -Url $newExtUrl -AuthenticationProvider $newClaimsProv -SecureSocketsLayer:$newExtSsl
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

        } else {

          DBG ('Provisioning the web application extension with classic mode authentication: id = {0} | name = {1} | zone = {2} | port = {3} | host = {4} | path = {5} | url = {6} | ssl = {7}' -f $newWebApp, $newExtName, $newExtZone, $newExtPort, $newExtHostHeader, $newExtDir, $newExtUrl, $newExtSsl)
          DBGSTART
          $newExtension = New-SPWebApplicationExtension -Identity $newWebApp -Name $newExtName -Zone $newExtZone -Port $newExtPort -HostHeader $newExtHostHeader -Path $newExtDir -Url $newExtUrl -AuthenticationMethod Kerberos -SecureSocketsLayer:$newExtSsl
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }
      }
    }

    #
    #

    Reopen-SPWebApplication ([ref] $newWebApp)

    #
    #

    if ($spType -ne 'foundation') {

      DBG ('Obtain super reader and super user accounts for the application')
      $superReader = $oneWebApp.SelectSingleNode('./svc[@appTag="cread"]')
      $superUser = $oneWebApp.SelectSingleNode('./svc[@appTag="cfull"]')

      DBGIF ('Web application does not utilize SuperReader account, ensure it does not use Publishing Feature: {0}' -f $primaryHostHeader) { Is-EmptyString $superReader.login }

      if (Is-ValidString $superReader) {
 
        DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $superUser.login }

        [string] $superUserSAM = Get-SAMLogin $superUser.login $superUser.domain
        [string] $superReaderSAM = Get-SAMLogin $superReader.login $superReader.domain

        DBG ('Going to configure portal super-reader and portal super-user accounts: superReader = {0} ({1}) | superuser = {2} (3)' -f $superReader.login, $superReaderSAM, $superUser.login, $superUserSAM)

        [string] $superUserID = Add-SPWebApplicationPolicy $newWebApp $superUserSAM ('Cache super user ({0})' -f $superUserSAM) FullControl -loginType user -returnAccountId $true
        [string] $superReaderID = Add-SPWebApplicationPolicy $newWebApp $superReaderSAM ('Cache super reader ({0})' -f $superReaderSAM) FullRead -loginType user -returnAccountId $true

        DBG ('Super accounts to be set: superReader = {0} | superUser = {1}' -f $superReaderID, $superUserID)
        DBGSTART
        $newWebApp.Properties['portalsuperuseraccount'] = $superUserID
        $newWebApp.Properties['portalsuperreaderaccount'] = $superReaderID
        $newWebApp.Update()
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }
    }


    DBG ('We should grant access to requesting service accounts')
    foreach ($oneSvcAccountToGrant in @('ssrs', 'excel', 'pps')) {

      if (Is-NonNull $svcCredsHash[$oneSvcAccountToGrant]) {

        $oneSAMLoginToGrant = Get-SAMLogin $svcCredsHash[$oneSvcAccountToGrant].login $svcCredsHash[$oneSvcAccountToGrant].domain
        DBG ('Granting the svc account access to the web application: {0} | {1}' -f $oneSvcAccountToGrant, $oneSAMLoginToGrant)
        DBGSTART
        $newWebApp.GrantAccessToProcessIdentity($oneSAMLoginToGrant)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }
    }


    DBG ('Proceed with custom application user policy entries, if any')
    $userPolicies = $oneWebApp.SelectNodes('./userPolicy')
    DBG ('Found requested user policies: {0}' -f (Get-CountSafe $userPolicies))

    foreach ($oneUserPolicy in $userPolicies) {

      DBG ('One user policy to be assigned to the application: who = {0} | what = {1} | type = {2}' -f $oneUserPolicy.who, $oneUserPolicy.what, $oneUserPolicy.type)
      Add-SPWebApplicationPolicy $newWebApp $oneUserPolicy.who $oneUserPolicy.who $oneUserPolicy.what -doNotTranslateIdentity $classicAuthentication -loginType $oneUserPolicy.type
    }


    DBG ('Default time zone for new site collections: {0}' -f $oneWebApp.timeZone)
    if (Is-ValidString $oneWebApp.timeZone) {

      DBG ('Available time zones: {0}' -f ([Microsoft.SharePoint.SPRegionalSettings]::GlobalTimeZones | Select Id, Description | Sort Id | ft -Auto | Out-String))
      $requestedTimeZone = [Microsoft.SharePoint.SPRegionalSettings]::GlobalTimeZones | ? { $_.Id -eq $oneWebApp.timeZone }
      DBGIF $MyInvocation.MyCommand.Name { (Is-Null $requestedTimeZone) -or (Is-EmptyString $requestedTimeZone.Description) }
      DBG ('Default timezone for new site collections specified as: {0} | {1}' -f $requestedTimeZone.Id, $requestedTimeZone.Description)
      DBGSTART
      Set-SPWebApplication -Identity $newWebApp.Url -DefaultTimeZone $oneWebApp.timeZone
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }



    DBG ('Proceed with quota templates, if any')
    $quotaTemplates = $oneWebApp.SelectNodes('./quotaTemplate')
    DBG ('Found requested quota templates: {0}' -f (Get-CountSafe $quotaTemplates))

    foreach ($oneQuotaTemplate in $quotaTemplates) {

      Assert-SPQuotaTemplate $oneQuotaTemplate.name (1MB * $oneQuotaTemplate.stgMax) (1MB * $oneQuotaTemplate.stgWrn) $oneQuotaTemplate.codeMax $oneQuotaTemplate.codeWrn
    }


    DBG ('Proceed with site collections, if any')
    $siteCols = $oneWebApp.SelectNodes('./sitecol')
    DBG ('Found requested sitecols: {0}' -f (Get-CountSafe $siteCols))

    foreach ($oneSiteCol in $siteCols) {

      DBG ('New sitecol requested: {0} | {1} | {2} | {3}' -f $oneSiteCol.path, $oneSiteCol.display, $oneSiteCol.lng, $oneSiteCol.template)

      Assert-SPWebTemplate $oneSiteCol.template $oneSiteCol.lng

      [bool] $isWildcardPath = $false
      [string] $sitecolPath = $oneSiteCol.path
      DBGIF $MyInvocation.MyCommand.Name { $sitecolPath -like '*\*' }
      [string] $managedPath = $sitecolPath

      if ($sitecolPath -like '*/`*/*') {

        $isWildcardPath = $true
        $sitecolPath = $sitecolPath.Replace('*/', '')
        $managedPath = $sitecolPath.SubString(0, $sitecolPath.LastIndexOf('/'))
      }


      DBG ('Test if the managed path exists: {0}' -f $managedPath)
      DBGSTART
      $foundManagedPath = $null
      $foundManagedPath = Get-SPManagedPath -WebApplication $appUrl -Identity $managedPath
      # Note: ignore the error in case the path does not exist
      #DBGER $MyInvocation.MyCommand.Name $error 
      DBGEND

      if (Is-Null $foundManagedPath) {

        DBG ('The managed path does not exist. Create: {0}' -f $managedPath)
        DBGSTART
        $newManagedPath = New-SPManagedPath -WebApplication $appUrl -RelativeURL $managedPath -Explicit:(-not $isWildcardPath)
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { Is-Null $newManagedPath }

      } else {

        DBG ('The managed path exists, do nothing')
        DBGIF $MyInvocation.MyCommand.Name { $isWildcardPath -and ($foundManagedPath.Type -ne 'WildcardInclusion') }
        DBGIF $MyInvocation.MyCommand.Name { (-not $isWildcardPath) -and ($foundManagedPath.Type -eq 'WildcardInclusion') }
      }


      $sitecolDB = $appDatabase

      DBG ('Does the user specified a custom database: {0}' -f (Is-ValidString $oneSiteCol.db))
      if (Is-ValidString $oneSiteCol.db) {

        $sitecolDBName = '{0}_{1}' -f $appDatabaseNamePrefix, $oneSiteCol.db
        DBG ('Sitecol database specified, going to test if it exists: {0} | {1}' -f $oneSiteCol.db, $sitecolDBName)
        DBGSTART
        $foundDB = Get-SPContentDatabase $sitecolDBName
        # Note: ignore the error in case the database does not exist
        #DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        if (Is-Null $foundDB) {

          DBG ('Sitecol database does not exist. Create: {0}' -f $sitecolDBName)
          DBGSTART
          $foundDB = New-SPContentDatabase -WebApplication $appUrl -Name $sitecolDBName
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBGIF $MyInvocation.MyCommand.Name { Is-Null $foundDB }
          DBG ('New content database created: {0} | {1} | {2}' -f $foundDB.Id, $foundDB.Server, $foundDB.Name)

          DBG ('Assign SP Admins shell access to the content DB: {0}' -f $spAdmins)
          DBGSTART
          Enable-SPDatabaseAdminAccess -admins $spAdmins -databaseObject $foundDB
          #Add-SPShellAdmin -Database $foundDB -UserName $spAdmins
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }

        $sitecolDB = $foundDB
      }


      $sitecolURL = '{0}/{1}' -f $appUrl.Trim('/'), $sitecolPath.Trim('/')


      [int] $siteCompatibility = $spVerID
      if (Is-ValidString $oneSiteCol.compat) {

        DBG ('Custom compatibility specified: {0}' -f $oneSiteCol.compat)
        [int] $customSiteCompatibility = Parse-IntSafe $oneSiteCol.compat
        DBGIF $MyInvocation.MyCommand.Name { $customSiteCompatibility -lt 14 }
        DBGIF $MyInvocation.MyCommand.Name { $customSiteCompatibility -gt 15 }

        $customSiteCompatibility = [Math]::Min(14, $customSiteCompatibility)
        $siteCompatibility = [Math]::Min($customSiteCompatibility, $siteCompatibility)

        DBG ('Site compatibility downgraded to: {0}' -f $siteCompatibility)
        DBGIF ('Sitecol compatibility is not available with 2010 version. Ignoring') { $spVersion -eq '2010' }
      }

      DBGIF ('Site compatibility: {0}' -f $siteCompatibility) { $siteCompatibility -lt 14 }
      DBGIF ('Site compatibility: {0}' -f $siteCompatibility) { $siteCompatibility -gt 16 }


      $newSitecol = $null
      if (Is-EmptyString $oneSiteCol.quota) {

        DBG ('Create new sitecol without quota: {0} | adm = {1} | disp = {2} | lng = {3} | tmpl = {4} | compat = {5}' -f $sitecolURL, $appAdminSAM, $oneSiteCol.display, $oneSiteCol.lng, $oneSiteCol.template, $siteCompatibility)

        if ($spVersion -eq '2010') {

          DBG ('SP 2010 style sitecol creation')
          DBGSTART
          $newSitecol = New-SPSite `
            -Url $sitecolURL `
            -OwnerAlias $appAdminSAM `
            -ContentDatabase $sitecolDB `
            -Name $oneSiteCol.display `
            -Language $oneSiteCol.lng `
            -Template $oneSiteCol.template
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

        } elseif ($spVersion -eq '2013') {

          DBG ('SP 2013 style sitecol creation')
          DBGSTART
          $newSitecol = New-SPSite `
            -Url $sitecolURL `
            -OwnerAlias $appAdminSAM `
            -ContentDatabase $sitecolDB `
            -Name $oneSiteCol.display `
            -Language $oneSiteCol.lng `
            -Compatibility $siteCompatibility `
            -Template $oneSiteCol.template
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
  
        } elseif ($spVersion -eq '2016') {

          if ($siteCompatibility -lt ([int] $spVerID)) {

            DBG ('SP 2016 style sitecol creation with degraded compatibility')
            DBGSTART
            $newSitecol = New-SPSite `
              -Url $sitecolURL `
              -OwnerAlias $appAdminSAM `
              -ContentDatabase $sitecolDB `
              -Name $oneSiteCol.display `
              -Language $oneSiteCol.lng `
              -Compatibility $siteCompatibility `
              -Template $oneSiteCol.template
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND

          } else {

            DBG ('SP 2016 style sitecol creation with current compatibility')
            DBGSTART
            $newSitecol = New-SPSite `
              -Url $sitecolURL `
              -OwnerAlias $appAdminSAM `
              -ContentDatabase $sitecolDB `
              -Name $oneSiteCol.display `
              -Language $oneSiteCol.lng `
              -Template $oneSiteCol.template
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
          }
        }

      } else {

        DBG ('Create new sitecol with quota: {0} | adm = {1} | disp = {2} | lng = {3} | tmpl = {4} | quota = {5} | compat = {6}' -f $sitecolURL, $appAdminSAM, $oneSiteCol.display, $oneSiteCol.lng, $oneSiteCol.template, $oneSiteCol.quota, $siteCompatibility)

        if ($spVersion -eq '2010') {

          DBG ('SP 2010 style sitecol creation')
          DBGSTART
          $newSitecol = New-SPSite `
            -Url $sitecolURL `
            -OwnerAlias $appAdminSAM `
            -ContentDatabase $sitecolDB `
            -Name $oneSiteCol.display `
            -Language $oneSiteCol.lng `
            -Template $oneSiteCol.template `
            -QuotaTemplate $oneSiteCol.quota
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

        } elseif ($spVersion -eq '2013') {

          DBG ('SP 2013 style sitecol creation')
          DBGSTART
          $newSitecol = New-SPSite `
            -Url $sitecolURL `
            -OwnerAlias $appAdminSAM `
            -ContentDatabase $sitecolDB `
            -Name $oneSiteCol.display `
            -Language $oneSiteCol.lng `
            -Compatibility $siteCompatibility `
            -Template $oneSiteCol.template `
            -QuotaTemplate $oneSiteCol.quota
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND

        } elseif ($spVersion -eq '2016') {

          if ($siteCompatibility -lt ([int] $spVerID)) {

            DBG ('SP 2016 style sitecol creation with degraded compatibility')
            DBGSTART
            $newSitecol = New-SPSite `
              -Url $sitecolURL `
              -OwnerAlias $appAdminSAM `
              -ContentDatabase $sitecolDB `
              -Name $oneSiteCol.display `
              -Language $oneSiteCol.lng `
              -Compatibility $siteCompatibility `
              -Template $oneSiteCol.template `
              -QuotaTemplate $oneSiteCol.quota
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND

          } else {

            DBG ('SP 2016 style sitecol creation with current compatibility')
            DBGSTART
            $newSitecol = New-SPSite `
              -Url $sitecolURL `
              -OwnerAlias $appAdminSAM `
              -ContentDatabase $sitecolDB `
              -Name $oneSiteCol.display `
              -Language $oneSiteCol.lng `
              -Template $oneSiteCol.template `
              -QuotaTemplate $oneSiteCol.quota
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
          }
        }
      }

      DBGIF ('The sitecol not created: {0}' -f $sitecolURL) { Is-Null $newSitecol }

      DBG ('Reload the new sitecol in order to be on the safe side: {0}' -f $sitecolURL)
      DBGSTART
      $newSitecol = Get-SPSite $sitecolURL
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      if (Is-NonNull $newSitecol) {
      
        if (Is-ValidString $oneSiteCol.selfSvcUpgrade) {

          DBG ('AllowSelfServiceUpgrade setting requested: {0}' -f $oneSiteCol.selfSvcUpgrade)
          DBGSTART
          $newSitecol.AllowSelfServiceUpgrade = Parse-BoolSafe $oneSiteCol.selfSvcUpgrade
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }
      


        DBG ('Create the default associated groups')
        DBG ('Get the SPWeb object for the web: {0}' -f $sitecolURL)
        DBGSTART
        $spWebToCreateAssociatedGroups = Get-SPWeb $sitecolURL
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { Is-Null $spWebToCreateAssociatedGroups }
        DBG ('Current associated groups: url = {0} | owner = {1} | member = {2} | visitor = {3} | web = {4}' -f $sitecolURL, $spWebToCreateAssociatedGroups.AssociatedOwnerGroup, $spWebToCreateAssociatedGroups.AssociatedMemberGroup, $spWebToCreateAssociatedGroups.AssociatedVisitorGroup, $spWebToCreateAssociatedGroups.Title)

        DBG ('Ensure the user exists in the web site: {0}' -f $appAdminSAM)
        DBGSTART
        $appAdminSAMtoDefaultAssociatedGroups = $spWebToCreateAssociatedGroups.EnsureUser($appAdminSAM).UserLogin
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $appAdminSAMtoDefaultAssociatedGroups }

        DBG ('Create the default associated groups: {0} | {1}' -f $appAdminSAMtoDefaultAssociatedGroups, $spWebToCreateAssociatedGroups.Title)
        DBGSTART
        $spWebToCreateAssociatedGroups.CreateDefaultAssociatedGroups($appAdminSAMtoDefaultAssociatedGroups, $null, $spWebToCreateAssociatedGroups.Title)
        $spWebToCreateAssociatedGroups.Update()
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND


    
        DBG ('Grant user permissions if any')
        $sitecolAccounts = $oneSiteCol.SelectNodes('./account')
        DBG ('Found sitecol accounts: {0}' -f (Get-CountSafe $sitecolAccounts))

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

          $sitecolAdminTouched = $false

          foreach ($oneSitecolAccount in $sitecolAccounts) {

            DBG ('Should assign one account: {0} | {1} | {2}' -f $oneSitecolAccount.appTag, $oneSitecolAccount.login, $oneSitecolAccount.domain)
            
            $userSAMlogin = Get-SAMLogin $oneSitecolAccount.login $oneSitecolAccount.domain

            if ($oneSitecolAccount.appTag -like '*Perm') {

              [string] $permLevel = $oneSitecolAccount.appTag.SubString(0, ($oneSitecolAccount.appTag.Length - 'Perm'.Length));
            
              switch ($permLevel) {

                { $_ -like 'Full'} { $permLevel = 'Full Control' }
                { $_ -like 'FullControl'} { $permLevel = 'Full Control' }

                { $_ -like 'View'} { $permLevel = 'View Only' }
                { $_ -like 'ViewOnly'} { $permLevel = 'View Only' }

                { $_ -like 'Limited'} { $permLevel = 'Limited Access' }
                { $_ -like 'LimitedAccess'} { $permLevel = 'Limited Access' }
              }

              DBG ('Will assign permission level: {0} | {1} | {2}' -f $permLevel, $userSAMlogin, $newSitecol.RootWeb.Url)

              # Note: a matter of some assertions only
              $permLevelDef = $newSitecol.RootWeb.RoleDefinitions[$permLevel]
              DBGIF ('Unknown permission level. Will not assign: {0} | {1}' -f $permLevel, $userSAMlogin) { Is-Null $permLevelDef }
            
              if (Is-NonNull $permLevelDef) {

                DBG ('The permission level contains base permissions: {0} = {1}' -f $permLevel, $permLevelDef.BasePermissions)

                $basePermissionsPerPermissionLevel = @{
                  'Limited Access' = 'ViewFormPages,Open,BrowseUserInfo,UseClientIntegration,UseRemoteAPIs' ;
                  'View Only' = 'ViewListItems,ViewVersions,ViewFormPages,Open,ViewPages,CreateSSCSite,BrowseUserInfo,UseClientIntegration,UseRemoteAPIs,CreateAlerts';
                  'Read' = 'ViewListItems,OpenItems,ViewVersions,ViewFormPages,Open,ViewPages,CreateSSCSite,BrowseUserInfo,UseClientIntegration,UseRemoteAPIs,CreateAlerts';
                  'Contribute' = 'ViewListItems,AddListItems,EditListItems,DeleteListItems,OpenItems,ViewVersions,DeleteVersions,ManagePersonalViews,ViewFormPages,Open,ViewPages,CreateSSCSite,BrowseDirectories,BrowseUserInfo,AddDelPrivateWebParts,UpdatePersonalWebParts,UseClientIntegration,UseRemoteAPIs,CreateAlerts,EditMyUserInfo';
                  'Edit' = 'ViewListItems,AddListItems,EditListItems,DeleteListItems,OpenItems,ViewVersions,DeleteVersions,ManagePersonalViews,ManageLists,ViewFormPages,Open,ViewPages,CreateSSCSite,BrowseDirectories,BrowseUserInfo,AddDelPrivateWebParts,UpdatePersonalWebParts,UseClientIntegration,UseRemoteAPIs,CreateAlerts,EditMyUserInfo';
                  'Design' = 'ViewListItems,AddListItems,EditListItems,DeleteListItems,ApproveItems,OpenItems,ViewVersions,DeleteVersions,CancelCheckout,ManagePersonalViews,ManageLists,ViewFormPages,Open,ViewPages,AddAndCustomizePages,ApplyThemeAndBorder,ApplyStyleSheets,CreateSSCSite,BrowseDirectories,BrowseUserInfo,AddDelPrivateWebParts,UpdatePersonalWebParts,UseClientIntegration,UseRemoteAPIs,CreateAlerts,EditMyUserInfo';
                  'Full Control' = 'FullMask';
                  }
                DBGIF ('Non-standard base permissions: level = {0} | is = {1} | shouldBe = {2}' -f $permLevel, $permLevelDef.BasePermissions, $basePermissionsPerPermissionLevel[$permLevel])  { $permLevelDef.BasePermissions -ne $basePermissionsPerPermissionLevel[$permLevel] }
        
                DBG ('Add the user to the sitecol')
                DBGSTART
                $newUserAssigned = New-SPUser -UserAlias $userSAMlogin -PermissionLevel $permLevel -Web $newSitecol.Url
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
              }

            } elseif ($oneSitecolAccount.appTag -like 'SitecolAdmin') {

              DBGIF $MyInvocation.MyCommand.Name { $sitecolAdminTouched }

              DBG ('Will assign the account as sitecol admin: {0}' -f $userSAMlogin)
              DBGSTART
              Set-SPSite -Identity $newSitecol -SecondaryOwnerAlias $userSAMlogin
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
    
              $sitecolAdminTouched = $true
            }
          }
        }


        DBG ('Any features to be enabled/disabled: {0} | {1} | siteCol = {2}' -f (Is-ValidString $oneSiteCol.features), $oneSiteCol.features, $sitecolURL)
        if (Is-ValidString $oneSiteCol.features) {

          EnableDisable-SPFeatures $sitecolURL $oneSiteCol.features
        }



        DBG ('Check if any additional sub-webs requested')
        $subWebSites = $oneSiteCol.SelectNodes('./web')
        DBG ('Found sub web sites: {0}' -f (Get-CountSafe $subWebSites))

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

          foreach ($oneSubWebSite in $subWebSites) {
        
            $subWebSiteUrl = '{0}/{1}' -f $newSitecol.Url.TrimEnd('/'), $oneSubWebSite.path.TrimStart('/')

            DBG ('Define new web site: {0} = {1} | {2} | {3} | {4}' -f $oneSubWebSite.path, $subWebSiteUrl, $oneSubWebSite.display, $oneSubWebSite.lng, $oneSubWebSite.template)

            Assert-SPWebTemplate $oneSubWebSite.template $oneSubWebSite.lng
          
            DBG ('Create the new web site')
            DBGSTART
            New-SPWeb -Url $subWebSiteUrl -Name $oneSubWebSite.display -Language $oneSubWebSite.lng -Template $oneSubWebSite.template -AddToTopNav:(Parse-BoolSafe $oneSubWebSite.addToTopNav) -AddToQuickLaunch:(Parse-BoolSafe $oneSubWebSite.addToQuickLaunch)
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND

            DBG ('Any features to be enabled/disabled: {0} | {1} | web = {2}' -f (Is-ValidString $oneSubWebSite.features), $oneSubWebSite.features, $subWebSiteUrl)
            if (Is-ValidString $oneSubWebSite.features) {

              EnableDisable-SPFeatures $subWebSiteUrl $oneSubWebSite.features
            }
          }
        }
      }

    }

    DBG ('Do we have to enable self-service site creation (auto site creation): {0}' -f (Parse-BoolSafe $oneWebApp.autoSite))
    if (Parse-BoolSafe $oneWebApp.autoSite) {

      DBG ('We should enable self-service site creation, get the web application first to prevent update conflicts when modifying the same object: {0}' -f $primaryHostHeader)
      DBGSTART
      $theWebAppForUpdate = Get-SPWebApplication -Identity $primaryHostHeader
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $theWebAppForUpdate }

      if (Is-NonNull $theWebAppForUpdate) {

        DBG ('Current state of self service site creation: {0}' -f $theWebAppForUpdate.SelfServiceSiteCreationEnabled)
        DBGIF $MyInvocation.MyCommand.Name { $theWebAppForUpdate.SelfServiceSiteCreationEnabled }

        DBG ('Enable the auto-site creation on the web application')
        DBGSTART
        $theWebAppForUpdate.SelfServiceSiteCreationEnabled = $true
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Check if we have any quota template to assign') 
        DBGSTART
        $autoSiteQuotaTemplate = $null
        $autoSiteQuotaTemplate = $oneWebApp.SelectSingleNode('./quotaTemplate[@autoSiteQuota="true"]')
        DBGER $MyInvocation.MyCommand.Name $error
        if (Is-NonNull $autoSiteQuotaTemplate) {

          DBG ('Assign the quota as self-service site quota: {0}' -f $autoSiteQuotaTemplate.name)
          DBGSTART
          $theWebAppForUpdate.SelfServiceCreationQuotaTemplate = $autoSiteQuotaTemplate.name
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
        }
      
        DBG ('Update the settings into the configuration database finally')
        DBGSTART
        $theWebAppForUpdate.Update()
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }
    }
  }

  #
  # Note: service accounts again which require web applications already in place
  #

  foreach ($oneCred in $svcCredsHash.Values) {
  
    DBG ('Processing SP managed service account again: {0} | {1}@{2} | pwd = {3}' -f $oneCred.appTag, $oneCred.login, $oneCred.domain, $oneCred.pwd)
    $samLogin = Get-SAMLogin $oneCred.login $oneCred.domain

    switch -regex ($oneCred.appTag) {

      'ups' {

        [string] $upssvcSpPool = $oneCred.binding.pool
        [string] $upssvcSpApp = $oneCred.binding.svcApp
        [string] $upssvcDbServerProfile = 'spdb{0}' -f $oneCred.binding.profileDbSrvTag
        [string] $upssvcDbServerSocial = 'spdb{0}' -f $oneCred.binding.socialDbSrvTag
        [string] $upssvcDbServerSync = 'spdb{0}' -f $oneCred.binding.syncDbSrvTag
        [string] $upssvcDbProfile = 'SP{0}_UPSProfile' -f $spInstance
        [string] $upssvcDbSocial = 'SP{0}_UPSSocial' -f $spInstance
        [string] $upssvcDbSync = 'SP{0}_UPSSync' -f $spInstance
        [string] $upssvcWebHost = $oneCred.binding.siteHost
        [string] $upssvcMgtPath = $oneCred.binding.managedPath
        [string] $upssvcResolution = $oneCred.binding.resolution

        if (Is-ValidString $oneCred.binding.db) {

          $upssvcDbProfile = '{0}_{1}' -f $upssvcDbProfile, $oneCred.binding.db
          $upssvcDbSocial = '{0}_{1}' -f $upssvcDbSocial, $oneCred.binding.db
          $upssvcDbSync = '{0}_{1}' -f $upssvcDbSync, $oneCred.binding.db
        }

        DBG ('User Profile Service account should have any usage: pool = {0} | app = {1} | dbProf = {2} | dbProfSrv = {3} | dbSoc = {4} | dbSocSrv = {5} | dbSync = {6} | dbSyncSrv = {7}' -f $upssvcSpPool, $upssvcSpApp, $upssvcDbProfile, $upssvcDbServerProfile, $upssvcDbSocial, $upssvcDbServerSocial, $upssvcDbSync, $upssvcDbServerSync)

        if ((Is-ValidString $upssvcSpPool) -and (Is-ValidString $upssvcSpApp)) {

          DBG ('Verify if the UPS AppPool and the UPS web service application does not exist yet: {0} | {1}' -f $upssvcSpPool, $upssvcSpApp)
          DBGSTART
          $existingUpssvcServicePool = $null
          $existingUpssvcServicePool = Get-SPServiceApplicationPool | ? { $_.Name -eq $upssvcSpPool }
          $existingUpssvcServiceApp = $null
          $existingUpssvcServiceApp = Get-SPServiceApplication | ? { ($_.TypeName -eq 'User Profile Service Application') -and ($_.Name -eq $upssvcSpApp) }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('We got existing UPS service objects: app = {0} | pool = {1}' -f $existingUpssvcServiceApp.Name, $existingUpssvcServicePool.Id)
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingUpssvcServiceApp) -gt 0 }
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingUpssvcServicePool) -gt 0 }

          if ((Is-Null $existingUpssvcServiceApp) -and (Is-Null $existingUpssvcServicePool)) {

            DBG ('UPS service is not provisioned yet. Create the pool first: {0} | sam = {1}' -f $upssvcSpPool, $samLogin)
            DBGSTART
            $existingUpssvcServicePool = $null
            $existingUpssvcServicePool = New-SPServiceApplicationPool -Name $upssvcSpPool -Account $samLogin
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('UPS service app pool created: {0}' -f $existingUpssvcServicePool.Id)
            DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingUpssvcServicePool }

            if (Is-NonNull $existingUpssvcServicePool) {

              DBG ('Create the UPS service web application as well: {0} | dbProf = {1} | dbProfSrv = {2} | dbSoc = {3} | dbSocSrv = {4} | dbSync = {5} | dbSyncSrv = {6} | host = {7} | path = {8} | resolve = {9}' -f $upssvcSpApp, $upssvcDbProfile, $upssvcDbServerProfile, $upssvcDbSocial, $upssvcDbServerSocial, $upssvcDbSync, $upssvcDbServerSync, $upssvcWebHost, $upssvcMgtPath, $upssvcResolution)
              DBGSTART
              $existingUpssvcServiceApp = $null
              $existingUpssvcServiceApp = New-SPProfileServiceApplication -Name $upssvcSpApp -ApplicationPool $existingUpssvcServicePool -ProfileDBName $upssvcDbProfile -ProfileDBServer $upssvcDbServerProfile -SocialDBName $upssvcDbSocial -SocialDBServer $upssvcDbServerSocial -ProfileSyncDBName $upssvcDbSync -ProfileSyncDBServer $upssvcDbServerSync -MySiteHostLocation $upssvcWebHost -MySiteManagedPath $upssvcMgtPath -SiteNamingConflictResolution $upssvcResolution
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Created the UPS service service application: {0} | ver = {1} | status = {2} | iis = {3}' -f $existingUpssvcServiceApp.Id, $existingUpssvcServiceApp.ApplicationVersion, $existingUpssvcServiceApp.Status, $existingUpssvcServiceApp.IisVirtualDirectoryPath)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingUpssvcServiceApp }

              if (Is-NonNull $existingUpssvcServiceApp) {

                Wait-SPServiceApplicationOnline $existingUpssvcServiceApp.Id

                #
                #

                DBG ('Enable NetBIOS domain names on the UPS service application and configure the sync server: {0} | {1}' -f (Parse-BoolSafe $oneCred.binding.differentNetBIOSNames), $farmCredSAMLogin)

                if (Parse-BoolSafe $oneCred.binding.differentNetBIOSNames) {

                  DBG ('Obtain the service application for further configurations: {0}' -f $existingUpssvcServiceApp.Id)
                  DBGSTART
                  $upsServiceAppToConfigure = $null
                  $upsServiceAppToConfigure = Get-SPServiceApplication -Id $existingUpssvcServiceApp.Id
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND

                  DBGSTART
                  $upsServiceAppToConfigure.NetBIOSDomainNamesEnabled = $true
                  $upsServiceAppToConfigure.Update()
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                }

                DBG ('Fix the default schema owner on the UPSSync DB: {0}' -f $farmCredSAMLogin)
                $fixUpsSyncDbDefaultSchema = @'
ALTER USER [{0}] WITH DEFAULT_SCHEMA=[dbo]
'@ -f $farmCredSAMLogin

                Execute-NonQueryRemote -sqlServer $upssvcDbServerSync -database $upssvcDbSync -nonQuery $fixUpsSyncDbDefaultSchema

                #
                #

                DBG ('UPS service application proxy group: {0}' -f $existingUpssvcServiceApp.ServiceApplicationProxyGroup.FriendlyName)
                DBGIF $MyInvocation.MyCommand.Name { $existingUpssvcServiceApp.ServiceApplicationProxyGroup.FriendlyName -ne '[default]' }
            
                [string] $upssvcSpProxyName = '{0} Proxy' -f $upssvcSpApp

                # Note: as against Excel Services, the UPS has this specificum that it does NOT create the proxy itself
                DBG ('Verify we DO NOT have the service application proxy created automatically: {0}' -f $upssvcSpProxyName)
                DBGSTART
                $existingUpssvcServiceProxy = $null
                $existingUpssvcServiceProxy = Get-SPServiceApplicationProxy | ? { ($_.TypeName -eq 'User Profile Service Application Proxy') -and (($_.Name -eq $upssvcSpProxyName) -or ($_.Name -eq $upssvcSpApp)) }
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('We got UPS service proxy created automatically: {0} | {1} | name = {2} | display = {3}' -f $existingUpssvcServiceProxy.Id, $existingUpssvcServiceProxy.Status, $existingUpssvcServiceProxy.Name, $existingUpssvcServiceProxy.DisplayName)
                DBGIF $MyInvocation.MyCommand.Name { Is-NonNull $existingUpssvcServiceProxy }

                if (Is-Null $existingUpssvcServiceProxy) {

                  DBG ('Create the UPS application proxy and let it associate automatically with the default proxy group: {0}' -f $upssvcSpProxyName)
                  DBGSTART
                  $existingUpssvcServiceProxy = $null
                  $existingUpssvcServiceProxy = New-SPProfileServiceApplicationProxy -Name $upssvcSpProxyName -ServiceApplication $existingUpssvcServiceApp -DefaultProxyGroup
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingUpssvcServiceProxy }
                }
              }
            }
          }
        }
      }

      'ssearch|ssrc' {

          #if ($spVersion -eq '2010') {
          #
          #  Update-SPServiceInstanceAccount 'SharePoint Server Search' $samLogin -assertServiceName 'OSearch14'
          #
          #} else {

            DBG ('Starting Enterprise Search account configuration')
            DBGSTART
            $searchNode = $null
            $searchNode = $oneCred.psbase.ParentNode
            DBGER $MyInvocation.MyCommand.Name $error
            DBGIF $MyInvocation.MyCommand.Name { (Is-Null $searchNode) -or ($searchNode.psbase.Name -ne 'search') }

            if (Is-NonNull $searchNode) {

              DBG ('Establish some input parameters')

              [string] $ssQueryPool = $searchNode.binding.qyeryPool
              [string] $ssAdminPool = $searchNode.binding.adminPool
              [string] $ssServiceApp = $searchNode.binding.serviceApp
              
              [string] $ssDbServer = 'spdb{0}' -f $searchNode.binding.dbSrvTag
              [string] $ssServiceDb = 'SP{0}_Search' -f $spInstance

              if (Is-ValidString $searchNode.binding.db) {

                $ssServiceDb = '{0}_{1}' -f $ssServiceDb, $searchNode.binding.db
              }

              $ssQuerySvc = $searchNode.SelectSingleNode('./svc[@appTag="sapp"]')
              $ssAdminSvc = $searchNode.SelectSingleNode('./svc[@appTag="sadm"]')
              $ssSearchSvc = $searchNode.SelectSingleNode('./svc[@appTag="ssrc"]')
              $ssContentSvc = $searchNode.SelectSingleNode('./svc[@appTag="scnt"]')

              DBG ('Search service parameters: queryPool = {0} | adminPool = {1} | serviceApp = {2}' -f $ssQueryPool, $ssAdminPool, $ssServiceApp)
              DBG ('Search service parameters: dbSrv = {0} | db = {1}' -f $ssDbServer, $ssServiceDb)
              DBG ('Service accounts: query = {0} | admin = {1} | search = {2} | content = {3}' -f $ssQuerySvc.login, $ssAdminSvc.login, $ssSearchSvc.login, $ssContentSvc.login)
              DBGIF $MyInvocation.MyCommand.Name { $ssQueryPool -eq $ssAdminPool }
              DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $ssServiceApp }
              DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $ssQuerySvc.login }
              DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $ssAdminSvc.login }
              DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $ssSearchSvc.login }
              DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $ssContentSvc.login }

              #
              #

              if ($spVersion -eq '2010') {

                Update-SPServiceInstanceAccount 'SharePoint Server Search' $samLogin -assertServiceName 'OSearch14'

              } elseif ($spVersion -eq '2013') {

                #Set-SPEnterpriseSearchService -IgnoreSSLWarnings $true
                Update-SPServiceInstanceAccount 'SharePoint Server Search' $samLogin -assertServiceName 'OSearch15' -password $ssSearchSvc.pwd

              } elseif ($spVersion -eq '2016') {

                Update-SPServiceInstanceAccount 'SharePoint Server Search' $samLogin -assertServiceName 'OSearch16' -password $ssSearchSvc.pwd
              }

              #
              #

              DBG ('Provision Server Search service accounts and applications on SharePoint 2013 and newer')
              # Note: regardles if we will host the service instance here, we have to have it running during provisioning
              #       or we get something like "Value cannot be null" errors
              EnableDisable-SPServiceInstanceOnThisServer 'SharePoint Server Search' '+'
              EnableDisable-SPServiceInstanceOnThisServer 'Search Query and Site Settings Service' '+'

              if ($spVersion -ne '2010') {

                $ssSearchHostControllerSvc = Get-SPServiceInstanceOnThisServer 'Search Host Controller Service'
                DBGIF ('Invalid Search Host Controller Service state: {0}' -f $ssSearchHostControllerSvc.Status) { $ssSearchHostControllerSvc.Status -ne 'Online' }
              }

              #
              #
                          
              DBG ('Going to provision Search Query apppool: {0}' -f $ssQueryPool)
              DBGSTART
              $existingSsQueryPool = $null
              $existingSsQueryPool = New-SPServiceApplicationPool -Name $ssQueryPool -Account $ssQuerySvc.login
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Search Query app pool created: {0}' -f $existingSsQueryPool.Id)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSsQueryPool }

              DBG ('Going to provision Search Admin apppool: {0}' -f $ssAdminPool)
              DBGSTART
              $existingSsAdminPool = $null
              $existingSsAdminPool = New-SPServiceApplicationPool -Name $ssAdminPool -Account $ssAdminSvc.login
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Search Admin app pool created: {0}' -f $existingSsAdminPool.Id)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSsAdminPool }

              if ((Is-NonNull $existingSsQueryPool) -and (Is-NonNull $existingSsAdminPool)) {

                DBG ('Create the Search Query and Search Admin service applications: {0} | {1} | {2}' -f $ssServiceApp, $ssQueryPool, $ssAdminPool)
                DBGSTART
                # Note: the specificum of the Search Service application is that it is actually represented by two virtual directories
                #       and four different databases placed at a single database server
                $existingSsApp = $null
                $existingSsApp = New-SPEnterpriseSearchServiceApplication -Name $ssServiceApp -ApplicationPool $ssQueryPool -AdminApplicationPool $ssAdminPool -DatabaseName $ssServiceDb -DatabaseServer $ssDbServer
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('Created the Enterprise Search service application: {0} | ver = {1} | status = {2} | iis = {3}' -f $existingSsApp.Id, $existingSsApp.ApplicationVersion, $existingSsApp.Status, $existingSsApp.IisVirtualDirectoryPath)
                DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSsApp }

                if (Is-NonNull $existingSsApp) {

                  Wait-SPServiceApplicationOnline $existingSsApp.Id

                  DBG ('Search Service service application proxy group: {0}' -f $existingSsApp.ServiceApplicationProxyGroup.FriendlyName)
                  DBGIF $MyInvocation.MyCommand.Name { $existingSsApp.ServiceApplicationProxyGroup.FriendlyName -ne '[default]' }

                  #
                  #

                  DBG ('Get the search admin database to make SP Admins the owners of it')
                  DBGSTART
                  [Microsoft.Office.Server.Search.Administration.SearchAdminDatabase] $ssAdminDatabase = $null
                  $ssAdminDatabase = Get-SPDatabase | ? { $_.Name -eq $ssServiceDb }
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBG ('Search admin database opened: {0} | {1} | {2}' -f $ssAdminDatabase.Name, $ssAdminDatabase.Id, $ssAdminDatabase.Type)
                  DBGIF $MyInvocation.MyCommand.Name { Is-Null $ssAdminDatabase }
                  DBGIF $MyInvocation.MyCommand.Name { $ssAdminDatabase.Type -ne 'Microsoft.Office.Server.Search.Administration.SearchAdminDatabase' }

                  DBG ('Add SP Admins group as dbowners of the search admin database')
                  DBGSTART
                  Enable-SPDatabaseAdminAccess -admins $spAdmins -databaseObject $ssAdminDatabase
                  #Add-SPShellAdmin -Database $ssAdminDatabase -UserName $spAdmins
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND

                  Enable-SPDatabaseAdminAccess -db ('{0}_CrawlStore' -f $ssServiceDb) -admins $spAdmins

                  if ($spVersion -ne '2010') {

                    Enable-SPDatabaseAdminAccess -db ('{0}_AnalyticsReportingStore' -f $ssServiceDb) -admins $spAdmins
                    Enable-SPDatabaseAdminAccess -db ('{0}_LinksStore' -f $ssServiceDb) -admins $spAdmins
                  }

                  #
                  #

                  if ($spVersion -eq '2010') {

                    DBG ('We must create the adminitration component just now in order to perform the latter steps')
                    DBGSTART
                    [Microsoft.Office.Server.Search.Administration.SearchServiceInstance] $localSs2k10Instance = $null
                    $localSs2k10Instance = Get-SPEnterpriseSearchServiceInstance ?Local
                    DBGER $MyInvocation.MyCommand.Name $error
                    DBGEND
                    DBG ('2k10 search service instance obtained: {0} | {1} | {2}' -f $localSs2k10Instance.Status, $localSs2k10Instance.Id, $localSs2k10Instance.TypeName)
                    DBGIF $MyInvocation.MyCommand.Name { Is-Null $localSs2k10Instance }
                    
                    if (Is-NonNull $localSs2k10Instance) {

                      DBGIF $MyInvocation.MyCommand.Name { $localSs2k10Instance.Status -ne 'Online' }
                      DBGIF $MyInvocation.MyCommand.Name { $localSs2k10Instance.TypeName -ne 'SharePoint Server Search' }

                      DBG ('Create the administration component finally: app = {0} | instance = {1}' -f $existingSsApp.Id, $localSs2k10Instance.Id)
                      DBGSTART
                      Set-SPEnterpriseSearchAdministrationComponent ?SearchApplication $existingSsApp ?SearchServiceInstance $localSs2k10Instance
                      DBGER $MyInvocation.MyCommand.Name $error
                      DBGEND

                      Wait-Periodically -maxTrialCount 37 -sleepSec 3 -sleepMsg 'Waiting for the 2k10 search admin component to become initialized' -scriptBlockWhichReturnsTrueToStop {
                      
                        DBG ('Get the administration component and verify it has been provisioned well')
                        DBGSTART
                        [Microsoft.Office.Server.Search.Administration.AdminComponent] $ssAdminComponent = $null
                        $ssAdminComponent = Get-SPEnterpriseSearchAdministrationComponent -SearchApplication $existingSsApp
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                        DBG ('Obtained admin component: init = {0} | idx = {1}' -f $ssAdminComponent.Initialized, $ssAdminComponent.IndexLocation)
                        DBGIF $MyInvocation.MyCommand.Name { Is-Null $ssAdminComponent }

                        return ($ssAdminComponent.Initialized)
                      }
                    }
                  }
            
                  #
                  # Note: rather configure these immediatelly in order to let the proxy define the correct application policy itself

                  $ssContentSAM = Get-SAMLogin $ssContentSvc.login
                  DBG ('Continue setting some basic search service parameters: content = {0}' -f $ssContentSvc.login, $ssContentSAM)
                  DBGSTART
                  Set-SPEnterpriseSearchServiceApplication -Identity $existingSsApp -DefaultContentAccessAccountName $ssContentSAM -DefaultContentAccessAccountPassword (ConvertTo-SecureString $ssContentSvc.pwd -AsPlainText -Force)
                  DBGER ('SEARCH provisioning') $error
                  DBGEND

                  #
                  #

                  [string] $ssAppProxyName = '{0} Proxy' -f $ssServiceApp

                  DBG ('Verify we do not have the Enterprise Search service application proxy created automatically: {0}' -f $ssAppProxyName)
                  DBGSTART
                  $existingSsAppProxy = $null
                  $existingSsAppProxy = Get-SPServiceApplicationProxy | ? { ($_.TypeName -eq 'Search Service Application Proxy') -and (($_.Name -eq $ssAppProxyName) -or ($_.Name -eq $ssServiceApp)) }
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBG ('We got the search app proxy created automatically: {0} | {1} | name = {2} | display = {3}' -f $existingSsAppProxy.Id, $existingSsAppProxy.Status, $existingSsAppProxy.Name, $existingSsAppProxy.DisplayName)
                  DBGIF $MyInvocation.MyCommand.Name { Is-NonNull $existingSsAppProxy }

                  if (Is-Null $existingSsAppProxy) {

                    DBG ('Going to create the Search Service service application proxy: {0}' -f $ssServiceApp)
                    DBGSTART
                    # Note: specificum here is that the proxy is automatically connected with the default proxy group
                    $existingSsAppProxy = $null
                    $existingSsAppProxy = New-SPEnterpriseSearchServiceApplicationProxy -Name $ssAppProxyName -SearchApplication $existingSsApp
                    DBGER $MyInvocation.MyCommand.Name $error
                    DBGEND
                  }
                }
              }
            }


            EnableDisable-SPServiceInstanceOnThisServer 'Search Query and Site Settings Service' '-'
            
            if ($spVersion -ne '2010') {

              # Note: we cannot disable the instance on the first server as we had to assign the administration component
              #       to this server and until the admin component is reassigned, its owning service instance cannot be stopped
              EnableDisable-SPServiceInstanceOnThisServer 'SharePoint Server Search' '-'
            }
          #}
      }

    }
  }    
}



if ((Parse-BoolSafe $appConfig.provisionFarm) -or (Parse-BoolSafe $appConfig.joinFarm)) {

  DBG ('Update c2wts service account local rights')

  $claimsLogin = $firstAppConfig.SelectSingleNode('./svc[(@appTag="claims") and (@spManaged="true")]')
  DBGIF $MyInvocation.MyCommand.Name { Is-Null $claimsLogin }

  if (Is-NonNull $claimsLogin) {

    $claimsSAMLoging = Get-SAMLogin $claimsLogin.login $claimsLogin.domain

    Run-Process (Join-Path $global:rootDir 'ForeignBinaries\ntrights.exe') ('-u "{0}" +r SeTcbPrivilege' -f $claimsSAMLoging)
    Run-Process (Join-Path $global:rootDir 'ForeignBinaries\ntrights.exe') ('-u "{0}" +r SeCreateGlobalPrivilege' -f $claimsSAMLoging)
    Run-Process (Join-Path $global:rootDir 'ForeignBinaries\ntrights.exe') ('-u "{0}" +r SeImpersonatePrivilege' -f $claimsSAMLoging)

    # Note: Excel Services does not require C2WTS to be member of Administrators group while 
    #       PeformancePoint and Reporting Services do require the service to be member of Administrators group
    Add-MemberLocalGroup 'Administrators' $claimsLogin.login $claimsLogin.domain
  }


  DBG ('Copy scripts into the Jobs folder if requested')
  $warmupJobNode = $firstAppConfig.SelectSingleNode('./fs[@appTag="jobs"]')
  DBG ('Copy scripts into the Jobs folder if requested: {0}' -f (Is-NonNull $warmupJobNode))

  [string] $warmupScriptTarget = ''

  if (Is-NonNull $warmupJobNode) {

    $warmupScriptTarget = Resolve-ClientFsPath $warmupJobNode
    DBG ('Copy the script files into the Jobs folder: exists = {0} | {1}' -f (Test-Path $warmupScriptTarget), $warmupScriptTarget)

    # Note: at this point we just do not copy the scripts if we do not have the folder present locally
    #       such as in case of back-end servers that do not need it
    #       We verify the target existing during "+ca" and "+frontend" provisioning later
    #DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $warmupScriptTarget) }
    
    if (Test-Path $warmupScriptTarget) {

      DBGSTART
      Get-ChildItem (Join-Path $global:rootDir 'SharePoint\sharepoint-warmup-*.*') | Copy-Item -Destination $warmupScriptTarget -Force
      Get-ChildItem (Join-Path $global:rootDir 'SharePoint\UPDATE-sharepoint-warmup-*.*') | Copy-Item -Destination $warmupScriptTarget -Force
      Copy-Item -Path $libDir\lib-common.ps1 -Destination $warmupScriptTarget -Force
      Copy-Item -Path $libDir\lib-modifyActions.ps1 -Destination $warmupScriptTarget -Force
      Copy-Item -Path $libDir\lib-utils.ps1 -Destination $warmupScriptTarget -Force
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
    }
  }


  $svcCredsHash = Get-SpSvcCredsFromXmlConfig


  DBG ('Provision or deprovision Services on this farm member according to roles requested')
  $roleDefs = $appConfig.SelectNodes('./role[@roles]')
  DBG ('Found role definitions: {0}' -f (Get-CountSafe $roleDefs))
  DBGIF ('No roles requested, leaving the default roles intact') { (Get-CountSafe $roleDefs) -lt 1 }

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

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

    foreach ($oneRoleDef in $roleDefs) {

      DBG ('One role definition: {0}' -f $oneRoleDef)
      (Split-MultiValue $oneRoleDef.roles) | % { $_.Trim() } | ? { Is-ValidString $_ } | % { [void] $roles.Add($_) }
    }

    # Note: disable everything first and enable the rest only after that
    #       sometimes we have opposite online/offline requirements comming
    #       from the config XML structure which may use COPY macros etc.
    $roles.Sort()
    $roles.Reverse()

    DBG ('Roles requested: {0}' -f ($roles -join ','))
    DBGIF $MyInvocation.MyCommand.Name { $roles.Count -lt 1 }

    $roleMappings = @{

      # Note: available with Foundation
      'frontend' = 'Microsoft SharePoint Foundation Web Application';
      'sandbox' = 'Microsoft SharePoint Foundation Sandboxed Code Service';
      'ca' = 'Central Administration';
      'bdc' = 'Business Data Connectivity Service';
      'sec' = 'Secure Store Service';
      'insmtp' = 'Microsoft SharePoint Foundation Incoming E-Mail';
      'dcache' = 'Distributed Cache';
      'claims' = 'Claims to Windows Token Service';

      # Note: available with Server
      'excel' = 'Excel Calculation Services|Claims to Windows Token Service';
      'pps' = 'PerformancePoint Service|Claims to Windows Token Service';
      'ups' = 'User Profile Service';
      'sync' = 'User Profile Synchronization Service';

      # Note: available with Server after separate installation
      'ssrs' = 'SQL Server Reporting Services Service|Claims to Windows Token Service';
      'ppvt' = '';

      # Note: Enterprise Search has its own command-let to enable the instances
      'search' = 'SharePoint Server Search';
      'squery' = 'Search Query and Site Settings Service';
    }
    
    foreach ($oneRole in $roles) {

      [string] $roleMoniker = $oneRole.SubString(1)
      [string] $roleOperation = $oneRole[0]
      
      DBG ('Configure one role: {0} | {1} | {2}' -f $roleOperation, $roleMoniker, $roleMappings[$roleMoniker])
      
      if (-not (Is-AppAvailableToConfigure $roleMoniker)) {

        continue
      }

      
      #
      #
      #
      DBG ('Preconfiguration for some roles')

      if (($roleMoniker -eq 'ssrs') -and ($roleOperation -eq '+')) {
      
        # Note: SSRS are installed before SharePoint in the SharePointFilesOnly mode so we have to
        #       finalize the installation into SharePoint only at this point.
        
        # Note: it is also stated that sp-farm account must be member of local Administrators during the following
        #       operations but it does not seem yet to be the case in my precious pre-setup

        if (-not $farmCredMustBeAdministrator) {

          DBG ('Add the farm account into the local Administrators group at least for the duration of SSRS provisioning: {0}\{1}' -f $farmCred.domain, $farmCred.login)
          Add-MemberLocalGroup 'Administrators' $farmCred.login $farmCred.domain

          DBG ('And restart SharePoint Timer Service')
          # Note: the default recycling timeout is 10 minutes, while we usually decrease the value to something smaller with timerRecycle attribute in farmSettings
          StartWait-SPJob 'job-timer-recycle' -maxTrialCount 100
          DBGIF $MyInvocation.MyCommand.Name { (Get-Service SPTimerv4).Status -ne 'Running' }
        }

        #
        #

        DBG ('Register SSRS binaries into this non-first SharePoint farm member')
        DBGSTART
        Install-SPRSService
		Install-SPRSServiceProxy
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
    
        DBG ('Instantiate the Reporting Services service application if necessary')

        [string] $ssrsSvcSpPool = $svcCredsHash['ssrs'].binding.pool
        [string] $ssrsSvcSpApp = $svcCredsHash['ssrs'].binding.svcApp
        [string] $ssrsSvcDbServer = 'spdb{0}' -f $svcCredsHash['ssrs'].binding.dbSrvTag
        [string] $ssrsSvcDb = 'SP{0}_ReportingServices' -f $spInstance

        if (Is-ValidString $svcCredsHash['ssrs'].binding.db) {

          $ssrsSvcDb = '{0}_{1}' -f $ssrsSvcDb, $svcCredsHash['ssrs'].binding.db
        }

        DBG ('Reporting Services account should have any usage: pool = {0} | app = {1} | db = {2} | dbSrv = {1}' -f $ssrsSvcSpPool, $ssrsSvcSpApp, $ssrsSvcAuditing, $ssrsSvcDb, $ssrsSvcDbServer)

        if ((Is-ValidString $ssrsSvcSpPool) -and (Is-ValidString $ssrsSvcSpApp)) {

          DBG ('Verify if the Reporting Services AppPool and the Reporting Services web service application does not exist yet: {0} | {1}' -f $ssrsSvcSpPool, $ssrsSvcSpApp)
          DBGSTART
          $existingSsrsSvcServicePool = $null
          $existingSsrsSvcServicePool = Get-SPServiceApplicationPool | ? { $_.Name -eq $ssrsSvcSpPool }
          $existingSsrsSvcServiceApp = $null
          # Note: similarly to Excel Services, specificum of Reporting Services service is that it does have its own cmdlet to obtain the service application object
          #       yet we do not need it and could use the generic Get-SPServiceApplication instead
          $existingSsrsSvcServiceApp = Get-SPRSServiceApplication | ? { ($_.TypeName -eq 'SQL Server Reporting Services Service Application') -and ($_.Name -eq $ssrsSvcSpApp) }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('We got existing Reporting Services service objects: app = {0} | pool = {1}' -f $existingSsrsSvcServiceApp.Name, $existingSsrsSvcServicePool.Id)
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingSsrsSvcServiceApp) -gt 1 }
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $existingSsrsSvcServicePool) -gt 1 }
          DBGIF $MyInvocation.MyCommand.Name { ((Is-NonNull $existingSsrsSvcServiceApp) -and (Is-Null $existingSsrsSvcServicePool)) -or ((Is-Null $existingSsrsSvcServiceApp) -and (Is-NonNull $existingSsrsSvcServicePool)) }

          if ((Is-Null $existingSsrsSvcServiceApp) -and (Is-Null $existingSsrsSvcServicePool)) {

            $ssrsSvcSAMLogin = Get-SAMLogin ($svcCredsHash['ssrs'].login) ($svcCredsHash['ssrs'].domain)
            DBG ('Reporting Services service is not provisioned yet. Create the pool first: {0} | sam = {1}' -f $ssrsSvcSpPool, $ssrsSvcSAMLogin)
            DBGSTART
            $existingSsrsSvcServicePool = $null
            $existingSsrsSvcServicePool = New-SPServiceApplicationPool -Name $ssrsSvcSpPool -Account $ssrsSvcSAMLogin
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('Reporting Services service app pool created: {0}' -f $existingSsrsSvcServicePool.Id)
            DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSsrsSvcServicePool }

            if (Is-NonNull $existingSsrsSvcServicePool) {

              DBG ('Create the Reporting Services service web application as well: {0} | dbSrv = {1} | db = {2}' -f $ssrsSvcSpApp, $ssrsSvcDb, $ssrsSvcDbServer)
              DBGSTART
              $existingSsrsSvcServiceApp = $null
              $existingSsrsSvcServiceApp = New-SPRSServiceApplication -Name $ssrsSvcSpApp -ApplicationPool $existingSsrsSvcServicePool -DatabaseName $ssrsSvcDb -DatabaseServer $ssrsSvcDbServer
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Created the Reporting Services service service application: {0} | ver = {1} | status = {2} | iis = {3}' -f $existingSsrsSvcServiceApp.Id, $existingSsrsSvcServiceApp.ApplicationVersion, $existingSsrsSvcServiceApp.Status, $existingSsrsSvcServiceApp.IisVirtualDirectoryPath)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSsrsSvcServiceApp }

              if (Is-NonNull $existingSsrsSvcServiceApp) {

                Wait-SPServiceApplicationOnline $existingSsrsSvcServiceApp.Id

                DBG ('Reporting Services service application proxy group: {0}' -f $existingSsrsSvcServiceApp.ServiceApplicationProxyGroup.FriendlyName)
                DBGIF $MyInvocation.MyCommand.Name { $existingSsrsSvcServiceApp.ServiceApplicationProxyGroup.FriendlyName -ne '[default]' }
            
                [string] $ssrsSvcSpProxyName = '{0} Proxy' -f $ssrsSvcSpApp

                # Note: as against Excel Services, the Reporting Services has this specificum that it does NOT create the proxy itself
                #       while this behavior is the same as with Secure Store or Performance Point Services
                #       Another specificum is that Reporting Services have their own cmdlet that produces the same object as the generic Get-SPServiceApplicationProxy
                DBG ('Verify we DO NOT have the service application proxy created automatically: {0}' -f $ssrsSvcSpProxyName)
                DBGSTART
                $existingSsrsSvcServiceProxy = $null
                $existingSsrsSvcServiceProxy = Get-SPRSServiceApplicationProxy | ? { ($_.TypeName -eq 'SQL Server Reporting Services Service Application Proxy') -and ($_.Name -eq $ssrsSvcSpProxyName) }
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('We got Reporting Services service proxy created automatically: {0} | {1} | name = {2} | display = {3}' -f $existingSsrsSvcServiceProxy.Id, $existingSsrsSvcServiceProxy.Status, $existingSsrsSvcServiceProxy.Name, $existingSsrsSvcServiceProxy.DisplayName)
                DBGIF $MyInvocation.MyCommand.Name { Is-NonNull $existingSsrsSvcServiceProxy }

                if (Is-Null $existingSsrsSvcServiceProxy) {

                  DBG ('Create the Reporting Services service application proxy: {0}' -f $ssrsSvcSpProxyName)
                  # Note: specificum of Reporting Services is that they do not have a default parameter to associate the newly created proxy with the default proxy group
                  #       just like Excel Services which has the proxy created automatically but is not associated the same as Reporting Services
                  DBGSTART
                  $existingSsrsSvcServiceProxy = $null
                  $existingSsrsSvcServiceProxy = New-SPRSServiceApplicationProxy -Name $ssrsSvcSpProxyName -ServiceApplication $existingSsrsSvcServiceApp
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBGIF $MyInvocation.MyCommand.Name { Is-Null $existingSsrsSvcServiceProxy }

                  if (Is-NonNull $existingSsrsSvcServiceProxy) {

                    DBG ('Associate the automatically created proxy with the Reporting Services proxy group')
                    DBGSTART
                    Add-SPServiceApplicationProxyGroupMember -Identity $existingSsrsSvcServiceApp.ServiceApplicationProxyGroup -Member $existingSsrsSvcServiceProxy
                    DBGER $MyInvocation.MyCommand.Name $error
                    DBGEND
                  }
                }
              }
            }
          }
        }

        if (-not $farmCredMustBeAdministrator) {

          DBG ('Remove the farm account from the local Administrators group as a finalization of SSRS installation: {0}\{1}' -f $farmCred.domain, $farmCred.login)
          Add-MemberLocalGroup 'Administrators' $farmCred.login $farmCred.domain -removeInstead $true
        }
      }

      if (($roleMoniker -eq 'ppvt') -and ($roleOperation -eq '+')) {

        [string] $powerPivotSolutionPath = Join-Path $env:ProgramFiles 'Microsoft SQL Server\110\Tools\PowerPivotTools\ConfigurationTool\Resources\PowerPivotFarm.wsp'
        DBG ('Register PowerPivot (aka Analysis Services in SharePoint mode) into this non-first farm member: {0}' -f $powerPivotSolutionPath)
        DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path -Literal $powerPivotSolutionPath) }
        DBGSTART
        Add-SPSolution ?Literal $powerPivotSolutionPath
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Install the SP solution just added')
        DBGSTART
        Install-SPSolution ?Identity PowerPivotFarm.wsp ?GACDeployment -Force 
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

      if (($roleMoniker -eq 'sync') -and ($roleOperation -eq '+')) {

        DBG ('We should have the UPSSync service on this machine')

        DBG ('Fix the windows installer errors (MsiInstaller 1004, 1001) about PeopleILM, Microsoft.ResourceManagement.Service.exe')
        $officeServersInstallRoot = Join-Path $env:ProgramFiles ('Microsoft Office Servers\{0}' -f $spVerID)
        Apply-NtfsDacl -ntfs $officeServersInstallRoot -dacl 'X$NT AUTHORITY\Network Service' -addOnly $true -enableSeRestorePrivilege $true

        # Note: no support for more UPS/UPSSync services on a single farm yet
        #       we just assume everything is here only once

        DBG ('Get the only UPS service application of this farm')
        DBGSTART
        $upsServiceApplicationToConfigure = $null
        $upsServiceApplicationToConfigure = Get-SPServiceApplication | ? { $_.TypeName -eq 'User Profile Service Application' }
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { Is-Null $upsServiceApplicationToConfigure }
        DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $upsServiceApplicationToConfigure) -ne 1 }
        DBGIF ('Weird UPS service application status: {0} | {1}' -f $upsServiceApplicationToConfigure.Id, $upsServiceApplicationToConfigure.Status) { $upsServiceApplicationToConfigure.Status -ne 'Online' }

        $upsSyncServiceInstance = Get-SPServiceInstanceOnThisServer $roleMappings[$roleMoniker]

        DBG ('Configure the UPS service application with the sync server: {0} | {1} | {2}' -f $farmCredSAMLogin, $global:thisComputerHost, $upsSyncServiceInstance.Id)
        DBGSTART
        $upsServiceApplicationToConfigure.SetSynchronizationMachine($global:thisComputerHost, $upsSyncServiceInstance.Id, $farmCredSAMLogin, (ConvertTo-SecureString $farmCred.pwd -AsPlain -Force))
        $upsServiceApplicationToConfigure.Update()
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

      #
      #
      #  

      DBG ('Enable or disable the service instances')

      $roleMappingTargets = Split-MultiValue $roleMappings[$roleMoniker]
      DBGIF $MyInvocation.MyCommand.Name { $roleMappingTargets.Count -lt 1 }
      foreach ($oneRoleMappingTarget in $roleMappingTargets) {

        EnableDisable-SPServiceInstanceOnThisServer $oneRoleMappingTarget $roleOperation
      }

      #
      #
      #
      DBG ('Additional configurations for individual roles')

      if (($roleMoniker -eq 'dcache') -and ($roleOperation -eq '-')) {

        # Note: if the "Distributed Cache" service instance is disabled, the service still has the user identity configured and is in Disabled state
        #       which then shows up as "DOWN" in the statistic. So we have to completelly uninstall it (which is also recemended by the Health Analyzer)

        DBG ('Uninstalling the already stopped Distributed Cache service instance')
        DBGSTART
        Remove-SPDistributedCacheServiceInstance
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

      if (($roleMoniker -eq 'dcache') -and ($roleOperation -eq '+')) {

        DBG ('Although it should be enabled by default, ensure it is installed')
        DBGSTART
        Add-SPDistributedCacheServiceInstance
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Verify the distributed cache is configured correctly')
        DBGSTART
        Use-CacheCluster
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Verify all distributed cache hosts are UP correctly')
        DBGSTART
        $allDistCacheHosts = $null
        $allDistCacheHosts = Get-CacheHost
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $allDistCacheHosts) -lt 1 }
        DBGIF ('Some of the AppFabric distributed cache hosts are not up: {0}' -f ($allDistCacheHosts | Out-String)) { (Get-CountSafe ($allDistCacheHosts | ? { $_.Status -ne 'Up' })) -gt 0 }

        DBG ('Get the App Fabric distributed cache host on the local machine specifically to verify the port number as well: {0}' -f ($allDistCacheHosts | Out-String))
        DBGSTART
        $distCacheHostFound = $null
        $distCacheHostFound = Get-CacheHost -ComputerName localhost -CachePort 22233
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { $distCacheHostFound.Status -ne 'Up' }
        DBGIF $MyInvocation.MyCommand.Name { $distCacheHostFound.HostName -ne $global:thisComputerFQDN }
      }

      if (($roleMoniker -eq 'sandbox') -and ($roleOperation -eq '+')) {
      
        DBG ('Fix the SharePoint User Code Host service issue with ProcessNameFormat and/or Performance Log Users')

        $sandboxCred = $null
        DBGSTART
        $sandboxCred = $firstAppConfig.SelectSingleNode('./svc[@appTag="sandbx" or @appTag="sandbox"]')
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { Is-Null $sandboxCred }

        if (Is-NonNull $sandboxCred) {

          DBG ('Sandbox credentials to fix: {0} | {1}' -f $sandboxCred.login, $sandboxCred.domain)

          if ($spVersion -eq '2010') {

            <#
            # Note: the service starts and fails with an exception after several seconds
            #       http://support.microsoft.com/en-us/kb/2509267
            #       BUT THIS IS NOT THE CASE!
            DBGSTART
            Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\PerfProc\Performance -Name ProcessNameFormat -Value 1
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            #>

            # Note: the solution seems to be adding the sandbox account into the local group Performance Log Users
            #       http://support.microsoft.com/en-us/kb/983081 (the PDH_CSTATUS_NO_MACHINE error)
            Add-MemberLocalGroup 'Performance Log Users' ('{0}@{1}' -f $sandboxCred.login, $sandboxCred.domain)
          
          } else {

            # Note: it seams the Performance MONITOR Users group is sufficient for 2013 version as tested service restart
            #       which means the service starts and does not fail in several seconds
            Add-MemberLocalGroup 'Performance Monitor Users' ('{0}@{1}' -f $sandboxCred.login, $sandboxCred.domain)
          }
        }

        DBG ('The sandbox host service must be started manually (SPUCHostService.exe)')
        DBGSTART
        Start-Service -Name SPUserCodeV4
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }

      if (($roleMoniker -eq 'frontend') -and ($roleOperation -eq '+')) {

        DBG ('Enhost all web application host headers that are not local')
        foreach ($oneWebApp in $webApps) {

          [string[]] $hostHeaders = Split-MultiValue $oneWebApp.hostHeader
          DBG ('One web application requested: #{0} | {1}' -f (Get-CountSafe $hostHeaders), $oneWebApp.hostheader)
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $hostHeaders) -le 0 }

          Define-IISDnsAliases $hostHeaders


          DBG ('Check if we dont have any application extension')
          $oneWebAppExtensions = $oneWebApp.SelectNodes('./ext')
          DBG ('Found additional web application extensions: {0}' -f (Get-CountSafe $oneWebAppExtensions))

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

            foreach ($oneWebAppExtension in $oneWebAppExtensions) {

              DBG ('Enhost the web application extension as well: {0}' -f $oneWebAppExtension.hostHeader)
              Define-IISDnsAliases $oneWebAppExtension.hostHeader
            }
          }
        }

        DBG ('Warmup establishment')
        DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $warmupScriptTarget) }

        $warmupScriptTargetUrlsFile = Join-Path $warmupScriptTarget 'sharepoint-warmup-frontend.urls'
        DBG ('Get the frontend web applications files from content DBs and also from the IIS disk: {0}' -f $warmupScriptTargetUrlsFile)
        $frontendSPFiles = Get-AllSPFiles
        DBGSTART
        $frontendSPFiles.urls | Out-File $warmupScriptTargetUrlsFile -Encoding UTF8 -Force
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Establish a warmup account for frontend if any requested')
        $warmup = $null
        $warmup = $firstAppConfig.SelectSingleNode('./svc[(@appTag="warmup")]')
  
        if (Is-NonNull $warmup) {

          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $warmupScriptTarget }
          $warmupLogin = Get-SAMLogin $warmup.login $warmup.domain

          DBG ('Grant the warmup login the SeBatchLogonRight right: {0}' -f $warmupLogin)
          Run-Process (Join-Path $global:rootDir 'ForeignBinaries\ntrights.exe') ('-u "{0}" +r SeBatchLogonRight' -f $warmupLogin)

          if ($global:thisOSVersionNumber -lt 6.2) {

            Schedule-Task 'Sevecek-SP-Warmup-Frontend' (Join-Path $warmupScriptTarget 'sharepoint-warmup-frontend.bat') $warmupLogin $warmup.pwd 'ONSTART /DELAY 01:45' $true # 'DAILY /ST 00:01 /RI 4 /DU 24:00' $true

          } else {

            # Note: since Windows 2012 the /DELAY parameter must be in the format mmmm:ss
            Schedule-Task 'Sevecek-SP-Warmup-Frontend' (Join-Path $warmupScriptTarget 'sharepoint-warmup-frontend.bat') $warmupLogin $warmup.pwd 'ONSTART /DELAY 0001:45' $true # 'DAILY /ST 00:01 /RI 4 /DU 24:00' $true
          }

          # Note: we cannot update the list of items using the warmup account
          #       we would have to do this under farm account and I am not sure if it is good idea
          #       to do it and schedule a task under the account. 
          #       I generally do not thing it is bad as the farm login/password is present in OWSTIMER anyway
          #       yet we allow to switch this off
                
          if (Parse-BoolSafe $warmup.periodicUpdateFE) {

            DBG ('We should update the fronte-end (FE) warmup list with farm account')
            $warmupUpdate = $null
            $warmupUpdate = $firstAppConfig.SelectSingleNode('./svc[(@appTag="farm")]')
            $warmupUpdateLogin = Get-SAMLogin $warmupUpdate.login $warmupUpdate.domain

            DBG ('Make the URLS file writable by the update job: {0} | {1}' -f $warmupScriptTargetUrlsFile, $warmupUpdateLogin)
            Run-Process 'icacls' ('"{0}" /grant "{1}:W"' -f $warmupScriptTargetUrlsFile, $warmupUpdateLogin)

            Schedule-Task 'Sevecek-SP-Warmup-Frontend-Update' (Join-Path $warmupScriptTarget 'UPDATE-sharepoint-warmup-frontend-URLs.bat') $warmupUpdateLogin $warmupUpdate.pwd 'DAILY /ST 01:01' $true
          }
        }
      }

      if (($roleMoniker -eq 'ca') -and ($roleOperation -eq '+')) {

        DBG ('Warmup establishment')
        DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $warmupScriptTarget) }

        $warmupScriptTargetUrlsFile = Join-Path $warmupScriptTarget 'sharepoint-warmup-ca.urls'
        DBG ('Get the CA web application files from the AdminContent DB and also from the IIS disk')
        $caSPFiles = Get-AllSPFiles -includeCA $true -explicitWebApps @()
        DBGSTART
        $caSPFiles.urls | Out-File $warmupScriptTargetUrlsFile -Encoding UTF8 -Force
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND

        DBG ('Establish a warmup account for CA if any requested')
        $warmup = $null
        $warmup = $firstAppConfig.SelectSingleNode('./svc[(@appTag="farm")]')
  
        if (Is-NonNull $warmup) {

          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $warmupScriptTarget }
          $warmupLogin = Get-SAMLogin $warmup.login $warmup.domain

          DBG ('Make the URLS file writable by the update job: {0} | {1}' -f $warmupScriptTargetUrlsFile, $warmupLogin)
          Run-Process 'icacls' ('"{0}" /grant "{1}:W"' -f $warmupScriptTargetUrlsFile, $warmupLogin)

          if ($global:thisOSVersionNumber -lt 6.2) {

            Schedule-Task 'Sevecek-SP-Warmup-CentralAdmin' (Join-Path $warmupScriptTarget 'sharepoint-warmup-ca.bat') $warmupLogin $warmup.pwd 'ONSTART /DELAY 01:30' $true

          } else {

            # Note: since 2012 the /DELAY parameter must be in the form of mmmm:ss
            Schedule-Task 'Sevecek-SP-Warmup-CentralAdmin' (Join-Path $warmupScriptTarget 'sharepoint-warmup-ca.bat') $warmupLogin $warmup.pwd 'ONSTART /DELAY 0001:30' $true
          }


          Schedule-Task 'Sevecek-SP-Warmup-CentralAdmin-Update' (Join-Path $warmupScriptTarget 'UPDATE-sharepoint-warmup-ca-URLs.bat') $warmupLogin $warmup.pwd 'DAILY /ST 02:02' $true
        }
      }

      if (($roleMoniker -eq 'insmtp') -and ($roleOperation -eq '+')) {

        DBG ('Could we enable incoming mail settings: {0}' -f ((Is-NonNull $firstAppConfig.ex) -or (Is-NonNull $firstAppConfig.farmSettings.inSmtp)))
        DBGIF $MyInvocation.MyCommand.Name { -not ((Is-NonNull $firstAppConfig.ex) -or (Is-NonNull $firstAppConfig.farmSettings.inSmtp)) }

        if (Is-NonNull $firstAppConfig.farmSettings.inSmtp) {

          $incomingMailDomain = $firstAppConfig.farmSettings.inSmtp.acceptedDomain
          DBG ('Incoming SMTP mail domain specified manually: {0}' -f $incomingMailDomain)
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $incomingMailDomain }

        } elseif (Is-NonNull $firstAppConfig.ex) {

          DBG ('Incoming Exchange mail domain definition: {0}' -f $firstAppConfig.ex.acceptedDomain)

          $incomingMailDomain = Strip-ValueFlags $firstAppConfig.ex.acceptedDomain
          DBG ('Incoming mail domain from Exchange: {0}' -f $incomingMailDomain)
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $incomingMailDomain }
        
        } 



        if (Is-ValidString $incomingMailDomain) {

            DBG ('Get incoming mail service instance')
            $incomingMailSvc = Get-SPServiceInstanceOnThisServer 'Microsoft SharePoint Foundation Incoming E-Mail'
            DBG ('Incoming mail service: {0} | {1} | {2} | {3}' -f $incomingMailSvc.Status, $incomingMailSvc.Id, $incomingMailSvc.Service.DropFolder, $incomingMailSvc.Service.ServerDisplayAddress)
            DBGIF $MyInvocation.MyCommand.Name { Is-Null $incomingMailSvc.Service }
            DBGIF $MyInvocation.MyCommand.Name { $incomingMailSvc.Status -ne 'Online' }

            if (Is-NonNull $incomingMailSvc.Service) {

              DBG ('Get local SMTP service parameters')
              $w3cSmtpService = Get-WmiQuerySingleObject '.' 'SELECT * FROM Win32_Service WHERE Name = "SMTPSVC"'
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $w3cSmtpService }
              DBG ('W3CSMTP service details: state = {0} | startMode = {1} | path = {2}' -f $w3cSmtpService.State, $w3cSmtpService.StartMode, $w3cSmtpService.PathName)

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

                # Note: the SMTPSVC is set to Manual by default after the sole feature installation
                DBG ('Must change start mode of SMTPSVC to automatic')
                DBGSTART
                $wmiRs = $w3cSmtpService.ChangeStartMode('Automatic')
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBGWMI $wmiRs
              }

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

                DBG ('Start the SMTPSVC')
                DBGSTART
                Start-Service -Name SMTPSVC
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
              }


              DBG ('Get local SMTP server details')
              $adsUtilPath = "$env:SystemDrive\inetpub\AdminScripts\adsutil.vbs"
              DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $adsUtilPath) }
              Run-Process cscript ('/NoLogo "{0}" enum /smtpsvc' -f $adsUtilPath)
              Run-Process cscript ('/NoLogo "{0}" enum /smtpsvc/1' -f $adsUtilPath)
              Run-Process cscript ('/NoLogo "{0}" enum /smtpsvc/1/Domain' -f $adsUtilPath)

              [string] $smtpDropFolderOutput = cscript $adsUtilPath get "/smtpsvc/1/DropDirectory" | ? { $_ -like 'DropDirectory *' }
              DBG ('SMTP drop directory output from adsutil: {0}' -f $smtpDropFolderOutput)
              DBGIF $MyInvocation.MyCommand.Name { $smtpDropFolderOutput -notlike 'DropDirectory*: (STRING) "*"' }
        
              [string] $smtpDropFolder = $smtpDropFolderOutput.Split('"')[1]
              DBG ('SMTP drop directory determined as: {0}' -f $smtpDropFolder)
              DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $smtpDropFolder }
              DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path $smtpDropFolder) }

              DBG ('Add the incoming mail domain as Alias domain into the local SMTP server: {0}' -f $incomingMailDomain)
              Run-Process cscript ('/NoLogo "{0}" create /smtpsvc/1/Domain/{1} IIsSmtpDomain' -f $adsUtilPath, $incomingMailDomain)
              Run-Process cscript ('/NoLogo "{0}" set /smtpsvc/1/Domain/{1}/RouteAction 16' -f $adsUtilPath, $incomingMailDomain)

              DBG ('Enable incoming email: {0} | {1}' -f $incomingMailDomain, $smtpDropFolder)
              DBGSTART
              $incomingMailSvc.Service.Enabled = $true
              $incomingMailSvc.Service.UseAutomaticSettings = $false
              $incomingMailSvc.Service.UseDirectoryManagementService = $false
              $incomingMailSvc.Service.RemoteDirectoryManagementService = $false
              $incomingMailSvc.Service.ServerAddress = $incomingMailDomain
              $incomingMailSvc.Service.ServerDisplayAddress = $incomingMailDomain
              $incomingMailSvc.Service.DLsRequireAuthenticatedSenders = $true
              $incomingMailSvc.Service.DistributionGroupsEnabled = $true
              $incomingMailSvc.Service.DropFolder = $smtpDropFolder
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND

              DBG ('Update the settings')
              DBGSTART
              $incomingMailSvc.Service.Update()
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
            }
        }
      }

      if (($roleMoniker -eq 'ssrs') -and ($roleOperation -eq '+')) {

        # Note: the SSRS binaries get installed only on the local server and not on the first farm member
        #       thus only here we can finalize the creation of service application
        DBG ('No post instance configuration for SSRS')
      }

      if (($roleMoniker -eq 'excel') -and ($roleOperation -eq '+')) {

        DBG ('No post instance configuration for EXCEL')
      }

      if (($roleMoniker -eq 'sec') -and ($roleOperation -eq '+')) {

        DBG ('No post instance configuration for SEC')
      }

      if (($roleMoniker -eq 'ups') -and ($roleOperation -eq '+')) {

        DBG ('No post instance configuration for UPS')
      }
    }


    #
    #
    #
    DBG ('Disable any sensitive roles that were not explicitly configured')
    if (-not (Contains-SafeWildcard $roles '?frontend')) {

      DBG ('Disable Frontend role as it was not configured explicitly')
      foreach ($oneRoleMappingTarget in (Split-MultiValue $roleMappings['frontend'])) { EnableDisable-SPServiceInstanceOnThisServer $oneRoleMappingTarget '-' }
    }

    if (-not (Contains-SafeWildcard $roles '?ca')) {

      DBG ('Disable CA role as it was not configured explicitly')
      foreach ($oneRoleMappingTarget in (Split-MultiValue $roleMappings['ca'])) { EnableDisable-SPServiceInstanceOnThisServer $oneRoleMappingTarget '-' }
    }
  }

  #
  #
  #
  #
  #
  #
  #

  if ($lastAppHostInInstance) {

    #
    #

    DBG ('Finalize OWA installation if necessary')
    [string] $owaHostHeader = $firstAppConfig.owa.hostHeader;
    DBG ('Do we have OWA present in the farm: {0} | {1}' -f (Is-ValidString $owaHostHeader), $owaHostHeader)

    if (Is-ValidString $owaHostHeader) {

      DBG ('First verify that we can get the OWA metadata by pure HTTP GET')
      [System.Xml.XmlDocument] $owaMeta = [XML] (Download-WebPage ('http://{0}/hosting/discovery' -f $owaHostHeader))
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $owaMeta }

      DBG ('Create the OWA WOPI binding: {0}' -f $owaHostHeader)
      DBGSTART
      $newSWOPIResult = New-SPWOPIBinding -ServerName $owaHostHeader -AllowHTTP
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $newSWOPIResult }
    
      DBG ('Get the STS configuration to enable clean HTTP for OAuth')
      DBGSTART
      $stsConfig = Get-SPSecurityTokenServiceConfig
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ('Current STS parameters: oauthOverHTTP = {0}' -f $stsConfig.AllowOAuthOverHttp)
      DBGIF $MyInvocation.MyCommand.Name { $stsConfig.AllowOAuthOverHttp -ne $false }

      DBG ('Enable OAuth over HTTP in SP STS')
      DBGSTART
      $stsConfig.AllowOAuthOverHttp = $true
      $stsConfig.Update()
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBG ('Finally get the current OWA WOPI zone')
      DBGSTART
      $swopiZone = Get-SPWOPIZone
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBG ('Current OWA WOPI zone in use: {0}' -f $swopiZone)
      DBGIF $MyInvocation.MyCommand.Name { $swopiZone -ne 'internal-https' }

      DBG ('And change the OWA WOP to clean http')
      DBGSTART
      Set-SPWOPIZone -Zone "internal-http"
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      # Note: there are two modes
      #       Office Web Apps Server view mode - when the XLSX files are opened with the Office Web Apps server
      #       SharePoint view mode - where Excel Services open the workbooks instead which supports all the BI features
      DBG ('Should we exclude XLS from Excel Web App and let it run in Excel Services instead: {0}' -f (Parse-BoolSafe $firstAppConfig.owa.useExcelServices))
      if (Parse-BoolSafe $firstAppConfig.owa.useExcelServices) {

        DBG ('Excluding Excel from Office Web Apps and setting it into SharePoint view mode')
        DBGSTART
        New-SPWOPISuppressionSetting -Extension xlsx -Action View | Out-Null
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
      }
    }

    #
    #

    DBG ('Go again through all the roles and do any post-farm configuration necessary: appHosts = {0}' -f (Get-CountSafe $allAppHosts))
    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $allAppHosts) -lt 1 }

    [hashtable] $allRolesPerHost = @{}
    [hashtable] $allRolesByRole = @{}

    foreach ($oneAppHost in $allAppHosts) {

      [string] $oneAppHostName = $oneAppHost.hostName

      DBGSTART
      $rolesOnOneAppHost = $oneAppHost.$appTag.SelectNodes('./role[@roles]')
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBG ('Found roles on an app host: {0} | {1}' -f $oneAppHostName, (Get-CountSafe $rolesOnOneAppHost))
      if ((Get-CountSafe $rolesOnOneAppHost) -gt 0) {

        [Collections.ArrayList] $individualRolesOnOneAppHost = @()
        foreach ($oneRoleOnOneAppHost in $rolesOnOneAppHost) {

          DBG ('One role definitions for the app host: {0} | {1}' -f $oneAppHostName, $oneRoleOnOneAppHost.roles)
          [string[]] $individualRolesOnOneAppHostSplit = (Split-MultiValue $oneRoleOnOneAppHost.roles) | % { $_.Trim() } | ? { Is-ValidString $_ }
           
          foreach ($individualRoleOnOneAppHostSplit in $individualRolesOnOneAppHostSplit) {

            $oneRoleOnOneAppHostDef = New-Object PSObject
            Add-Member -Input $oneRoleOnOneAppHostDef -MemberType NoteProperty -Name Role -Value $individualRoleOnOneAppHostSplit
            Add-Member -Input $oneRoleOnOneAppHostDef -MemberType NoteProperty -Name Element -Value $oneRoleOnOneAppHost

            [void] $individualRolesOnOneAppHost.Add($oneRoleOnOneAppHostDef)


            $oneRoleOnOneAppHostDef = New-Object PSObject
            Add-Member -Input $oneRoleOnOneAppHostDef -MemberType NoteProperty -Name Host -Value $oneAppHostName
            Add-Member -Input $oneRoleOnOneAppHostDef -MemberType NoteProperty -Name Element -Value $oneRoleOnOneAppHost

            if ($allRolesByRole.ContainsKey($individualRoleOnOneAppHostSplit)) {

              [void] $allRolesByRole[$individualRoleOnOneAppHostSplit].Add($oneRoleOnOneAppHostDef)

            } else {

              [void] $allRolesByRole.Add($individualRoleOnOneAppHostSplit, ([Collections.ArrayList] @($oneRoleOnOneAppHostDef)))
            }
          }
        }

        [void] $allRolesPerHost.Add($oneAppHostName, $individualRolesOnOneAppHost)
      }
    }

    #
    #

    if ($allRolesByRole.ContainsKey('+search')) {

      DBG ('We have some hosts that require Enterprise Search components: {0}' -f (Get-CountSafe $allRolesByRole['+search']))
      DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $allRolesByRole['+search']) -lt 1 }

      if ((Get-CountSafe $allRolesByRole['+search']) -gt 0) {

        DBG ('Get the Enterprise Search service application and our role config')
        DBGSTART
        $searchCfgNode = $null
        $searchCfgNode = $firstAppConfig.SelectSingleNode('./search')
        DBGER $MyInvocation.MyCommand.Name $error
        DBGEND
        DBGIF $MyInvocation.MyCommand.Name { Is-Null $searchCfgNode }

        if (Is-NonNull $searchCfgNode) {

          [string] $ssServiceApp = $searchCfgNode.binding.serviceApp

          DBG ('Will configure Search Service application: {0}' -f $ssServiceApp)
          DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $ssServiceApp }

          DBG ('Get the Enterprise Search service application: {0}' -f $ssServiceApp)
          DBGSTART
          $ssAppToConfigure = $null
          $ssAppToConfigure = Get-SPEnterpriseSearchServiceApplication $ssServiceApp
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBG ('Obtained the service application: {0} | {1}' -f $ssAppToConfigure.Id, $ssAppToConfigure.Status)
          DBGIF $MyInvocation.MyCommand.Name { Is-Null $ssAppToConfigure }

          if (Is-NonNull $ssAppToConfigure) {

            if ($spVersion -eq '2010') {

              DBG ('Get its currently active crawl topology on 2k10')
              DBGSTART
              $currentSsTopologyCrawl = $null
              $currentSsTopologyCrawl = Get-SPEnterpriseSearchCrawlTopology -SearchApplication $ssAppToConfigure -Active
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Obtained crawl topology: {0} | {1}' -f $ssTopoCrawl.Id, $ssTopoCrawl.State)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $currentSsTopologyCrawl }

              DBG ('Get its currently active query topology')
              DBGSTART
              $currentSsTopologyQuery = $null
              $currentSsTopologyQuery = Get-SPEnterpriseSearchQueryTopology -SearchApplication $ssAppToConfigure -Active
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBG ('Obtained query topology: {0} | {1}' -f $ssTopoQuery.Id, $ssTopoQuery.State)
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $currentSsTopologyQuery }

              if ((Is-NonNull $currentSsTopologyCrawl) -and (Is-NonNull $currentSsTopologyQuery)) {

                DBG ('Clone the current crawl topology')
                DBGSTART
                $ssTopoCrawl = $null
                $ssTopoCrawl = New-SPEnterpriseSearchCrawlTopology -SearchApplication $ssAppToConfigure -Clone -CrawlTopology $currentSsTopologyCrawl
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('New crawl topology: {0} | {1}' -f $ssTopoCrawl.Id, $ssTopoCrawl.State)
                DBGIF $MyInvocation.MyCommand.Name { Is-Null $ssTopoCrawl }

                DBG ('Clone the current query topology')
                DBGSTART
                $ssTopoQuery = $null
                $ssTopoQuery = New-SPEnterpriseSearchQueryTopology -SearchApplication $ssAppToConfigure -Clone -QueryTopology $currentSsTopologyQuery
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('New query topology: {0} | {1}' -f $ssTopoQuery.Id, $ssTopoQuery.State)
                DBGIF $MyInvocation.MyCommand.Name { Is-Null $ssTopoQuery }

                if ((Is-NonNull $ssTopoCrawl) -and (Is-NonNull $ssTopoQuery)) {

                  DBG ('Get the index partition of the newly created query topology')
                  DBGSTART
                  $ssTopoQueryIdx = $null
                  $ssTopoQueryIdx = Get-SPEnterpriseSearchIndexPartition -QueryTopology $ssTopoQuery
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBG ('Got the index partition: {0}' -f $ssTopoQueryIdx.Id)
                  DBGIF $MyInvocation.MyCommand.Name { Is-Null $ssTopoQueryIdx }

                  DBG ('Get the crawl databases')
                  DBGSTART
                  [object[]] $ssCrawlDatabases = $null
                  $ssCrawlDatabases = Get-SPEnterpriseSearchCrawlDatabase -SearchApplication $ssAppToConfigure
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                  DBG ('Obtained crawl databases: #{0} | {1}' -f (Get-CountSafe $ssCrawlDatabases), (($ssCrawlDatabases | Select -Expand Name) -join ','))
                  DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $ssCrawlDatabases) -lt 1 }

                  foreach ($oneEnterpriseSearchHost in $allRolesByRole['+search']) {

                    $componentsCfgNode = $oneEnterpriseSearchHost.Element.searchComponents
                    [string[]] $ssLocalComponents = Split-MultiValue $componentsCfgNode.components

                    DBG ('Search components on a host: {0} | {1} | #{2} | {3}' -f $oneEnterpriseSearchHost.Host, $componentsCfgNode.components, (Get-CountSafe $ssLocalComponents), ($ssLocalComponents -join ','))
                    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $ssLocalComponents) -lt 1 }

                    foreach ($oneSsLocalComponent in $ssLocalComponents) {

                      if ($oneSsLocalComponent -eq 'crawl') {
                      
                        DBG ('Create new crawl search component: topo = {0} | inst = {1} | db = {2} | app = {3}' -f $ssTopoCrawl.Id, $oneEnterpriseSearchHost.Host, $ssCrawlDatabases[0].Name, $ssAppToConfigure.Id)
                        DBGSTART
                        $newSsComponent = $null
                        $newSsComponent = New-SPEnterpriseSearchCrawlComponent -CrawlTopology $ssTopoCrawl -SearchServiceInstance $oneEnterpriseSearchHost.Host -CrawlDatabase $ssCrawlDatabases[0] -SearchApplication $ssAppToConfigure
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND

                      } elseif ($oneSsLocalComponent -eq 'query') {
                      
                        DBG ('Create new query search component: topo = {0} | inst = {1} | idx = {2}' -f $ssTopoQuery.Id, $oneEnterpriseSearchHost.Host, $ssTopoQueryIdx.Id)
                        DBGSTART
                        $newSsComponent = $null
                        $newSsComponent = New-SPEnterpriseSearchQueryComponent -QueryTopology $ssTopoQuery -SearchServiceInstance $oneEnterpriseSearchHost.Host -IndexPartition $ssTopoQueryIdx
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND

                      } else {
                        
                        DBGIF ('Unsupported search component: {0}' -f $oneSsLocalComponent) { $true }
                      }
                    }

                    #
                    #

                    $preJobStart = [DateTime]::Now

                    DBG ('Set the new query topology back to the search application and activate')
                    DBGSTART
                    Set-SPEnterpriseSearchQueryTopology $ssTopoQuery -Active
                    DBGER $MyInvocation.MyCommand.Name $error
                    DBGEND

                    DBG ('Wait until the query topology gets activated: app = {0} | topo = {1}' -f $ssAppToConfigure.Id, $ssTopoQuery.Id)
                    Wait-Periodically -maxTrialCount 57 -sleepSec 3 -sleepMsg 'Wait until the query topology gets activated' -scriptBlockWhichReturnsTrueToStop {

                      $ssTopoQuery = Get-SPEnterpriseSearchQueryTopology -SearchApplication $ssAppToConfigure -Identity $ssTopoQuery
                      DBG ('Query topology state: {0}' -f $ssTopoQuery.State)

                      return ($ssTopoQuery.State -eq 'Active')
                    }

                    Wait-SPJobAlreadyStarted -startedSince $preJobStart -jobNameWildcard QueryTopologyActivationJob* -jobType Microsoft.Office.Server.Search.Administration.QueryTopologyActivationJobDefinition
                    Wait-SPJobAlreadyStarted -startedSince $preJobStart -jobNameWildcard QueryTopologyCleanupJob* -jobType Microsoft.Office.Server.Search.Administration.QueryTopologyCleanupJobDefinition

                    #
                    #

                    $preJobStart = [DateTime]::Now

                    DBG ('Set the new crawl topology back to the search application and activate')
                    DBGSTART
                    Set-SPEnterpriseSearchCrawlTopology $ssTopoCrawl -Active
                    DBGER $MyInvocation.MyCommand.Name $error
                    DBGEND

                    DBG ('Wait until the crawl topology gets activated: app = {0} | topo = {1}' -f $ssAppToConfigure.Id, $ssTopoCrawl.Id)
                    Wait-Periodically -maxTrialCount 57 -sleepSec 3 -sleepMsg 'Wait until the crawl topology gets activated' -scriptBlockWhichReturnsTrueToStop {

                      $ssTopoCrawl = Get-SPEnterpriseSearchCrawlTopology -SearchApplication $ssAppToConfigure -Identity $ssTopoCrawl
                      DBG ('Crawl topology state: {0}' -f $ssTopoCrawl.State)

                      return ($ssTopoCrawl.State -eq 'Active')
                    }

                    Wait-SPJobAlreadyStarted -startedSince $preJobStart -jobNameWildcard CrawlTopologyActivationJob* -jobType Microsoft.Office.Server.Search.Administration.CrawlTopologyActivationJobDefinition
                    Wait-SPJobAlreadyStarted -startedSince $preJobStart -jobNameWildcard CrawlTopologyCleanupJob* -jobType Microsoft.Office.Server.Search.Administration.CrawlTopologyCleanupJobDefinition
                  }
                }
              }

            } else {

              DBG ('Get its currently active components on 2k13+')
              DBGSTART
              $currentSsTopology = $null
              $currentSsTopology = Get-SPEnterpriseSearchTopology -SearchApplication $ssAppToConfigure -Active
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND
              DBGIF $MyInvocation.MyCommand.Name { Is-Null $currentSsTopology }
            
              if (Is-NonNull $currentSsTopology) {

                DBGIF $MyInvocation.MyCommand.Name { $currentSsTopology.State -ne 'Active' }
                  
                DBG ('Clone the current topology')
                DBGSTART
                $ssTopology = $null
                $ssTopology = New-SPEnterpriseSearchTopology -SearchApplication $ssAppToConfigure -Clone -SearchTopology $currentSsTopology
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
                DBG ('Obtained search topology: {0} | {1} | {2}' -f $ssTopology.TopologyId, $ssTopology.State, $ssTopology.ComponentCount)
                DBGIF $MyInvocation.MyCommand.Name { Is-Null $ssTopology }

                if (Is-NonNull $ssTopology) {

                  foreach ($oneEnterpriseSearchHost in $allRolesByRole['+search']) {

                    $componentsCfgNode = $oneEnterpriseSearchHost.Element.searchComponents
                    [string[]] $ssLocalComponents = Split-MultiValue $componentsCfgNode.components

                    DBG ('Search components on a host: {0} | {1} | #{2} | {3}' -f $oneEnterpriseSearchHost.Host, $componentsCfgNode.components, (Get-CountSafe $ssLocalComponents), ($ssLocalComponents -join ','))
                    DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $ssLocalComponents) -lt 1 }


                    foreach ($oneSsLocalComponent in $ssLocalComponents) {

                      if ($oneSsLocalComponent -eq 'admin') {

                        DBG ('Create new Admin search component')
                        DBGSTART
                        $newSsComponent = $null
                        $newSsComponent = New-SPEnterpriseSearchAdminComponent -SearchTopology $ssTopology -SearchServiceInstance $oneEnterpriseSearchHost.Host
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                      }

                      if ($oneSsLocalComponent -eq 'crawl') {

                        DBG ('Create new crawl search component: topo = {0} | inst = {1}' -f $ssTopology.Id, $oneEnterpriseSearchHost.Host)
                        DBGSTART
                        $newSsComponent = $null
                        $newSsComponent = New-SPEnterpriseSearchCrawlComponent -SearchTopology $ssTopology -SearchServiceInstance $oneEnterpriseSearchHost.Host
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                      }

                      if ($oneSsLocalComponent -eq 'content') {

                        DBG ('Create new Content search component')
                        DBGSTART
                        $newSsComponent = $null
                        $newSsComponent = New-SPEnterpriseSearchContentProcessingComponent -SearchTopology $ssTopology -SearchServiceInstance $oneEnterpriseSearchHost.Host
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                      }

                      if ($oneSsLocalComponent -eq 'analytics') {

                        DBG ('Create new Analytics search component')
                        DBGSTART
                        $newSsComponent = $null
                        $newSsComponent = New-SPEnterpriseSearchAnalyticsProcessingComponent -SearchTopology $ssTopology -SearchServiceInstance $oneEnterpriseSearchHost.Host
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                      }

                      if ($oneSsLocalComponent -eq 'query') {

                        DBG ('Create new query search component: topo = {0} | inst = {1}' -f $ssTopology.Id, $oneEnterpriseSearchHost.Host)
                        DBGSTART
                        $newSsComponent = $null
                        $newSsComponent = New-SPEnterpriseSearchQueryProcessingComponent -SearchTopology $ssTopology -SearchServiceInstance $oneEnterpriseSearchHost.Host
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                      }

                      if ($oneSsLocalComponent -eq 'index') {

                        DBG ('Create new Index search component')
                        DBGSTART
                        $newSsComponent = $null
                        $newSsComponent = New-SPEnterpriseSearchIndexComponent -SearchTopology $ssTopology -SearchServiceInstance $oneEnterpriseSearchHost.Host
                        DBGER $MyInvocation.MyCommand.Name $error
                        DBGEND
                      }
                    }
                  }

   
                  DBG ('Set the search topology back to the service application')
                  # Note: the topology must always contain at least one of each Admin, Query, Content, Analytics, Index and Crawl components
                  DBGSTART
                  Set-SPEnterpriseSearchTopology $ssTopology #-SearchApplication $ssAppToConfigure the parameter is unnecessary as the original has been cloned from the SearchApplication
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND

                  DBG ('Wait until all the search components are Active')
                  Wait-Periodically -maxTrialCount 37 -sleepSec 3 -sleepMsg 'Waiting for the Enterprise Search Topology to become active' -scriptBlockWhichReturnsTrueToStop {

                    return ((Get-CountSafe (Get-SPEnterpriseSearchStatus -SearchApplication $ssAppToConfigure | ? { $_.State -ne 'Active' })) -eq 0)
                  }
                }
              }
            }

            #
            #

            DBG ('Verify the default search index location set properly: {0}' -f $ssServiceApp)
            DBGSTART
            $ssServiceAppInitalized = $null
            $ssServiceAppInitalized = Get-SPEnterpriseSearchServiceApplication $ssServiceApp
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('Admin component parameters: {0} | {1}' -f $ssServiceAppInitalized.AdminComponent.Initialized, $ssServiceAppInitalized.AdminComponent.IndexLocation)
            DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $ssServiceAppInitalized.AdminComponent.IndexLocation }
            # Note: we do not have this component locally most probably
            #DBGIF $MyInvocation.MyCommand.Name { -not (Test-Path -Literal $ssServiceAppInitalized.AdminComponent.IndexLocation) }
            DBGIF $MyInvocation.MyCommand.Name { -not $ssServiceAppInitalized.AdminComponent.Initialized }

            #
            #

            DBGIF ('Continuous crawl not available with 2010 version') { (Parse-BoolSafe $searchCfgNode.crawl.continuousCrawl) -and ($spVersion -eq '2010') }

            if ($spVersion -ne '2010') {

              DBG ('Get current continuous crawl interval')
              DBGSTART
              $currentContinuousCrawlInterval = $ssServiceAppInitalized.GetProperty('ContinuousCrawlInterval')
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND

              DBG ('Configure continuous crawl if requested: current = {0} min | enable = {1}' -f $currentContinuousCrawlInterval, (Parse-BoolSafe $searchCfgNode.crawl.continuousCrawl))
              DBG ('Change the continuous crawling frequency: {0} | {1}' -f $searchCfgNode.crawl.continuousCrawlMinutes, (Is-ValidString $searchCfgNode.crawl.continuousCrawlMinutes))

              if (Is-ValidString $searchCfgNode.crawl.continuousCrawlMinutes) {

                DBG ('Set the continous crawling interval: current = {0} | shouldBe = {1}' -f $currentContinuousCrawlInterval, $searchCfgNode.crawl.continuousCrawlMinutes)
                DBGIF $MyInvocation.MyCommand.Name { $currentContinuousCrawlInterval -ne 15 }
                DBGSTART
                $ssServiceAppInitalized.SetProperty('ContinuousCrawlInterval', (Parse-IntSafe $searchCfgNode.crawl.continuousCrawlMinutes))
                $ssServiceAppInitalized.Update()
                DBGER $MyInvocation.MyCommand.Name $error
                DBGEND
              }
            }

            #
            #

            DBG ('Configure incremental daily crawl if requested: {0} | minutes = {1}' -f (Is-ValidString $searchCfgNode.crawl.dailyIncrementalMinutes), $searchCfgNode.crawl.dailyIncrementalMinutes)

            DBG ('Get all current crawl content sources')
            DBGSTART
            $ssAllContentSources = $null
            $ssAllContentSources = Get-SPEnterpriseSearchCrawlContentSource -SearchApplication $ssServiceAppInitalized
            DBGER $MyInvocation.MyCommand.Name $error
            DBGEND
            DBG ('Obtained search crawl content sources: {0}' -f (Get-CountSafe $ssAllContentSources))
            DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $ssAllContentSources) -lt 1 }
             
            if ((Get-CountSafe $ssAllContentSources) -gt 0) {

              foreach ($oneSsContentSource in $ssAllContentSources) {

                if (($spVersion -ne '2010') -and (Parse-BoolSafe $searchCfgNode.crawl.continuousCrawl)) {

                  DBG ('Enabling the continuous crawling on one content source: {0} | {1}' -f $oneSsContentSource.Type, $oneSsContentSource.Name)
                  DBGIF 'Continuous crawling is available with SharePoint content sources only' { $oneSsContentSource.Type -ne 'SharePoint' }
                  DBGSTART
                  Set-SPEnterpriseSearchCrawlContentSource -Identity $oneSsContentSource -EnableContinuousCrawls $true
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                }

                if (Is-ValidString $searchCfgNode.crawl.dailyIncrementalMinutes) {

                  DBG ('Configuring daily incremental crawling: minutes = {0}' -f $searchCfgNode.crawl.dailyIncrementalMinutes)
                  DBGSTART
                  Set-SPEnterpriseSearchCrawlContentSource -Identity $oneSsContentSource -ScheduleType Incremental -DailyCrawlSchedule -CrawlScheduleRunEveryInterval 1 -CrawlScheduleRepeatInterval $searchCfgNode.crawl.dailyIncrementalMinutes -CrawlScheduleRepeatDuration 1440
                  DBGER $MyInvocation.MyCommand.Name $error
                  DBGEND
                }
              }
            }
          }
        }
      }

      #
      #

      DBG ('The search topology got configured, start Incremental creawl on all content sources to update the indexes')

      DBG ('Get all SP Enteprise Search applications')
      DBGSTART
      $allEntSearchApps = $null
      $allEntSearchApps = Get-SPEnterpriseSearchServiceApplication
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $allEntSearchApps) -lt 1 }
      DBG ('Found search applications: {0}' -f (Get-CountSafe $allEntSearchApps))

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

        foreach ($oneEntSearchApp in $allEntSearchApps) {

          DBG ('One search application to start incremental crawl: {0} | {1} | {2}' -f $oneEntSearchApp.Name, $oneEntSearchApp.Id, $oneEntSearchApp.Status)
          DBGIF $MyInvocation.MyCommand.Name { $oneEntSearchApp.Status -ne 'Online' }
      
          DBGSTART
          $allContentSources = $null
          $allContentSources = Get-SPEnterpriseSearchCrawlContentSource -SearchApplication $oneEntSearchApp #| ? { $_.Type -eq 'SharePoint' }
          DBGER $MyInvocation.MyCommand.Name $error
          DBGEND
          DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $allContentSources) -lt 1 }
          DBG ('Found content sources: {0}' -f (Get-CountSafe $allContentSources))

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

            foreach ($oneContentSource in $allContentSources) {

              DBG ('One content source: {0} | {1}' -f $oneContentSource.Name, $oneContentSource.CrawlStatus)
              Wait-Periodically -maxTrialCount 108 -sleepSec 7 -sleepMsg ('Waiting for the content source to become Idle') -scriptBlockWhichReturnsTrueToStop {

                DBG ('Crawl status now: {0}' -f $oneContentSource.CrawlStatus)
                return ($oneContentSource.CrawlStatus -eq 'Idle')
              }

              DBG ('Start the incremental crawl')
              DBGSTART
              $oneContentSource.StartIncrementalCrawl() | Out-Null
              DBGER $MyInvocation.MyCommand.Name $error
              DBGEND

              Wait-Periodically -maxTrialCount 108 -sleepSec 7 -sleepMsg ('Waiting for the content source to finish') -scriptBlockWhichReturnsTrueToStop {

                DBG ('Crawl status now: {0}' -f $oneContentSource.CrawlStatus)
                return ($oneContentSource.CrawlStatus -eq 'Idle')
              }
            }
          }      
        }
      }

      #
      #
    }
  }

  #
  #
  #
  #
  #

  DBG ('Ensure all managed accounts are provisioned correctly')
  DBGSTART
  Repair-SPManagedAccountDeployment
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND

  #
  #

  DBG ('Verify all binaries are installed correctly')
  DBGSTART
  $spProductsStatus = Get-SPProduct
  DBGER $MyInvocation.MyCommand.Name $error
  DBGEND
  DBG ('SharePoint products installed: {0}' -f ($spProductsStatus | fl * | Out-String))
  
  $spProductsStatusMissing = $spProductsStatus | ? { Is-NonNull $_.ServersMissingThis }
  DBGIF ('Some servers are missing a product: {0}' -f (($spProductsStatusMissing | Select -Expand ServersMissingThis) -join ',')) { (Get-CountSafe $spProductsStatusMissing) -gt 0 }
   
  #
  #

  DBG ('Finalize installation by starting Product Upgrade/Product Version job')
  StartWait-SPJob 'job-admin-product-version' -maxTrialCount 40
  
  #
  #

  DBG ('Going to verify local upgrade status with stsadm')
  [string] $stsadmOut = $null
  Run-Process ('C:\Program Files\Common Files\microsoft shared\Web Server Extensions\{0}\BIN\STSADM.EXE' -f $spVerIDNumber) '-o localupgradestatus' -refStdOut ([ref] $stsadmOut)

  DBG ('Extract output XML from the STSADM output and verify')
  [XML] $stsadmOutXml = [XML] ([regex]::Match($stsadmOut.Replace("`r", '').Replace("`n", ''), '.*?<\/objects>').Value)
  $stsAdmObjects = $stsadmOutXml.SelectNodes('objects/object')
  DBG ('Obtained STSADM objects: {0}' -f (Get-CountSafe $stsAdmObjects))
  DBGIF $MyInvocation.MyCommand.Name { (Get-CountSafe $stsAdmObjects) -lt 4 }

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

    foreach ($oneStsAdmObject in $stsAdmObjects) {

      DBGIF ('Invalid STSADM result object: name = {0} | status = {1} | type = {2} | level = {3}' -f $oneStsAdmObject.name, $oneStsAdmObject.status, $oneStsAdmObject.type, $oneStsAdmObject.level) { $oneStsAdmObject.status -ne 'OK' }
    }
  }

  #
  #

  if ($spVersion -eq '2016') {

    DBG ('SP 2016 needs central administration content DB upgrade right out-of-the-box: {0}' -f $centralAdminUrl)
    DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $centralAdminUrl }
    DBGSTART
    $caAppContentDB = $null
    $caAppContentDB = Get-SPContentDatabase -WebApplication (Get-SPWebApplication $centralAdminUrl)
    DBGER $MyInvocation.MyCommand.Name $error
    DBGEND
    DBGIF $MyInvocation.MyCommand.Name { Is-Null $caAppContentDB }
    DBG ('CA content database obtained: {0} | {1} | ver = {2}' -f $caAppContentDB.Name, $caAppContentDB.Id, $caAppContentDB.Version)

    if (Is-NonNull $caAppContentDB) {

      DBGIF $MyInvocation.MyCommand.Name { -not $caAppContentDB.NeedsUpgrade }
      DBG ('Perform CA content DB upgrade')
      DBGSTART
      $dbUpgradeResult = Upgrade-SPContentDatabase -Identity $caAppContentDB -Confirm:$false
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND

      DBG ('Verify database upgrade results again: {0}' -f $centralAdminUrl)
      $caAppContentDB = $null
      $caAppContentDB = Get-SPContentDatabase -WebApplication (Get-SPWebApplication $centralAdminUrl)
      DBGER $MyInvocation.MyCommand.Name $error
      DBGEND
      DBGIF $MyInvocation.MyCommand.Name { Is-Null $caAppContentDB }
      DBG ('CA content database obtained: {0} | {1} | ver = {2}' -f $caAppContentDB.Name, $caAppContentDB.Id, $caAppContentDB.Version)
    }
  }
}


DBG ('Do some final assertions to verify our library code')
# Note: although the SharePoint Services TLS server authentication certificate
#       contains the localhost and $thisComputerHost names, we do not have its
#       certification authority (SharePoint Root Authority) trusted as it is not present outside the
#       SharePoint development environment at all
DBGIF $MyInvocation.MyCommand.Name { -not (Test-Tls -hostName localhost -port 32844 -doNotValidateCertificate $true) }

}


#
#

Restore-IISSiteAndPoolState -iisSitesBackup $iisSitesBackup -iisAppPoolBackup $iisAppPoolBackup

#
#





DBG ('Throw up any remaining errors')
DBGSTART; DBGEND



# SIG # Begin signature block
# MIIc/QYJKoZIhvcNAQcCoIIc7jCCHOoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDy9/PYiJGhBLoO
# bNyb+sxI5AZn+gK2Hzgw6lyt+uzFe6CCGAQwggTlMIIDzaADAgECAhA5vUKe0oFu
# 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
# SIb3DQEJBDEiBCDD9mofxrQEBaht4lr7fT7IKsiX7p9mZEOxYFCvQ3I+fDANBgkq
# hkiG9w0BAQEFAASCAQBSY5I2UL2+PBmQZ4dGigpx73GjWskgv/7twgJa6iThCbzj
# PTcawr2r8ASkrMCd8dM8ppqKqNkpyhKmCC4NuZM9FYe7CTK5WkhflL7CuUUbeUat
# h+32nClKTuf7lip/MlmiLP7Yeso4yibrhBe4qiUHFkjJM5ezE9dAsFbwik7lwSMR
# EX9jrDsjywZDBvaEIDAl3P5SiUz8Z+p14fXqQNuoET6+2WWSbrRlsoTT7a5u2hYF
# HdsgM+SOQXdyJXwp9ziZcmIktXvSjVDWVKclpXwRF6cQJjyHc+wScdxBVAt9/DHM
# x7iFXP5x2O4Xp+1o4es4bFpra6DDcgZOjuui0gLWoYICDzCCAgsGCSqGSIb3DQEJ
# BjGCAfwwggH4AgEBMHYwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0
# IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNl
# cnQgQXNzdXJlZCBJRCBDQS0xAhADAZoCOv9YsWvW1ermF/BmMAkGBSsOAwIaBQCg
# XTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODAx
# MjYxNTIyNTVaMCMGCSqGSIb3DQEJBDEWBBRagrDCE+XnkZUnByuB84rWsz2ZtjAN
# BgkqhkiG9w0BAQEFAASCAQArkc/pOhGj0lAHuk9alHOgBv4qKx4qSYnfdqCRvwug
# 6OF/rs8cy5OWa6tDxMtHsF7eLLutlKARhuA8IOqlv5BbxY1FKBm7B38TFIWIsocx
# KU9Jh+Rq7Gjm/qZCX4I/bDvDRKq5zWgDqPMsryOads6gJzbZGgSIDMiTgaqiV7nD
# CXfZZcpt0aaAa9FBklcBMx1ha4JY8JurOF27QGWDt8mEc1+kQEwL09neyJuaQhl/
# n8wIHzUwT/x4R2Hx/2+1ZRxT6w5IKpr1HCFtzlh+dTOENxaIQR15ZT/BS2tnhVhb
# pAgFO6J78v4WQZYTMgJJKc9tFKBem7MIgzS/EC1HMqmC
# SIG # End signature block