(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