diff --git a/CHANGELOG.md b/CHANGELOG.md index f85f4ef8d..2f2e3c373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- SPSearchServiceApp + - Added optional ProvisionDefaultTopology (boolean) parameter to control should + the SPSearchServiceApp provision search topology as well. See more info how + this works from the resource readme.md + ### Fixed - SPWebApplication - Fixed an issue where the Set method tried to use the Parameter SecureSocketsLayer with Set-SPWebApplication on SharePoint Server older than Subscription Edition. diff --git a/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/MSFT_SPSearchServiceApp.psm1 b/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/MSFT_SPSearchServiceApp.psm1 index 28988a7f0..a13f59e41 100644 --- a/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/MSFT_SPSearchServiceApp.psm1 +++ b/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/MSFT_SPSearchServiceApp.psm1 @@ -86,7 +86,11 @@ function Get-TargetResource [Parameter()] [System.UInt16] - $RecrawlErrorInterval + $RecrawlErrorInterval, + + [Parameter()] + [System.Boolean] + $ProvisionDefaultTopology ) Write-Verbose -Message "Getting Search service application '$Name'" @@ -351,7 +355,11 @@ function Set-TargetResource [Parameter()] [System.UInt16] - $RecrawlErrorInterval + $RecrawlErrorInterval, + + [Parameter()] + [System.Boolean] + $ProvisionDefaultTopology ) Write-Verbose -Message "Setting Search service application '$Name'" @@ -469,8 +477,156 @@ function Set-TargetResource Set-SPEnterpriseSearchServiceApplication @setParams } - Write-Verbose -Message ("NOTE: Don't forget to configure a Search topology " + ` - "using the SPSearchTopology resource!") + # Provision default topology if ProvisionDefaultTopology = $true + if ($params.ContainsKey("ProvisionDefaultTopology") -eq $true) + { + if ($params.ProvisionDefaultTopology -eq $true) + { + $isCustomOrSingleProvisioning = $false + + $possibleSearchServers = Get-SPServer | Where-Object { $_.Role -in ("Search", "ApplicationWithSearch", "Custom", "SingleServerFarm", "SingleServer") } + + if ($possibleSearchServers.Count -eq 0) + { + $message = ("You have specified DefaultSearchTopology=`$true, but we are " + ` + "unable to provision search topology as no servers having " + ` + "one of the supported roles found. Make sure you have at least " + ` + "one server configured using one of the supported roles which are: " + ` + "Search, ApplicationWithSearch, Custom, SingleServer or SingleServerFarm") + + Add-SPDscEvent -Message $message ` + -EntryType 'Error' ` + -EventID 100 ` + -Source $eventSource + throw $message + } + else + { + $searchServers = $possibleSearchServers | Where-Object { $_.Role -in ("Search", "ApplicationWithSearch") } + + if ($searchServers.Count -eq 0) + { + Write-Verbose -Message "Provisioning default search topology" + + $searchServers = $possibleSearchServers | Where-Object { $_.Role -eq "Custom" } + + if ($searchServers.Count -eq 0) + { + $searchServers = $possibleSearchServers | Where-Object { $_.Role -in ("SingleServerFarm", "SingleServer") } + + if ($searchServers.Count -gt 0) + { + $isCustomOrSingleProvisioning = $true + } + } + else + { + $isCustomOrSingleProvisioning = $true + } + } + + # if custom, application, singleserver or singleserverfarm then we provision only to 1 server + if ($isCustomOrSingleProvisioning) + { + $searchServers = $searchServers[0] + } + + # Ensure the search service instance is running on all servers + foreach ($searchServer in $searchServers) + { + $serverName = $searchServer.Address + + Write-Verbose -Message "SEARCH SERVER: $($searchServers)" + + $searchService = Get-SPEnterpriseSearchServiceInstance -Identity $serverName ` + -ErrorAction SilentlyContinue + + if ($null -eq $searchService) + { + $domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain + $searchServer = "$serverName.$domain" + $searchService = Get-SPEnterpriseSearchServiceInstance -Identity $serverName -ErrorAction SilentlyContinue + } + + if (($searchService.Status -eq "Offline") -or ($searchService.Status -eq "Disabled")) + { + Write-Verbose -Message "Start Search Service Instance" + Start-SPEnterpriseSearchServiceInstance -Identity $serverName + } + + # Wait for Search Service Instance to come online + $loopCount = 0 + $online = Get-SPEnterpriseSearchServiceInstance -Identity $serverName -ErrorAction SilentlyContinue + while ($online.Status -ne "Online" -and $loopCount -lt 15) + { + $online = Get-SPEnterpriseSearchServiceInstance -Identity $serverName -ErrorAction SilentlyContinue + Write-Verbose -Message ("$([DateTime]::Now.ToShortTimeString()) - Waiting for " + ` + "search service instance to start on $searchServer " + ` + "(waited $loopCount of 15 minutes)") + $loopCount++ + Start-Sleep -Seconds 60 + } + } + + # Get current topology and prepare a new one + $currentTopology = $app.ActiveTopology + $newTopology = New-SPEnterpriseSearchTopology -SearchApplication $app ` + -Clone ` + -SearchTopology $currentTopology + + #$domain = "." + (Get-CimInstance -ClassName Win32_ComputerSystem).Domain + + foreach ($searchServer in $searchServers) + { + $serverName = $searchServer.Address + + $serviceInstance = Get-SPEnterpriseSearchServiceInstance -Identity $serverName + + $NewComponentParams = @{ + SearchTopology = $newTopology + SearchServiceInstance = $serviceInstance + } + + Write-Verbose -Message "Adding $serverName to run an AdminComponent" + $null = New-SPEnterpriseSearchAdminComponent @NewComponentParams + + Write-Verbose -Message "Adding $serverName to run a CrawlComponent" + $null = New-SPEnterpriseSearchCrawlComponent @NewComponentParams + + Write-Verbose -Message "Adding $serverName to run a ContentProcessingComponent" + $null = New-SPEnterpriseSearchContentProcessingComponent @NewComponentParams + + Write-Verbose -Message "Adding $serverName to run an AnalyticsProcessingComponent" + $null = New-SPEnterpriseSearchAnalyticsProcessingComponent @NewComponentParams + + Write-Verbose -Message "Adding $serverName to run a QueryComponent" + $null = New-SPEnterpriseSearchQueryProcessingComponent @NewComponentParams + + $NewComponentParams += @{ + IndexPartition = 0 + } + + Write-Verbose -Message "Adding $serverName to run an IndexComponent" + $null = New-SPEnterpriseSearchIndexComponent @NewComponentParams + } + + # Apply the new topology to the farm + Write-Verbose -Message "Applying new Search topology" + Set-SPEnterpriseSearchTopology -Identity $newTopology + + + # Stop search service instance on all the other servers not part of search topology + (Get-SPServer) | Where-Object { $_ -notin $searchServers -and $_.Role -ne "Invalid" } | ForEach-Object { + Get-SPEnterpriseSearchServiceInstance -Identity $_.Address | Stop-SPEnterpriseSearchServiceInstance + } + } + } + } + else + { + Write-Verbose -Message ("NOTE: Don't forget to configure a Search topology " + ` + "using the SPSearchTopology resource!") + } } } } @@ -841,7 +997,11 @@ function Test-TargetResource [Parameter()] [System.UInt16] - $RecrawlErrorInterval + $RecrawlErrorInterval, + + [Parameter()] + [System.Boolean] + $ProvisionDefaultTopology ) Write-Verbose -Message "Testing Search service application '$Name'" diff --git a/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/MSFT_SPSearchServiceApp.schema.mof b/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/MSFT_SPSearchServiceApp.schema.mof index 9b10610f5..f0e671301 100644 --- a/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/MSFT_SPSearchServiceApp.schema.mof +++ b/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/MSFT_SPSearchServiceApp.schema.mof @@ -21,4 +21,5 @@ class MSFT_SPSearchServiceApp : OMI_BaseResource [Write, Description("Specifies what items get deleted: 0 - All unvisited items, 1 - (Default) All unvisited items that have the same host as the start address, 2 - None of the unvisited items. You can specify the following three values:")] uint16 DeleteUnvisitedMethod; [Write, Description("Specifies the number of consecutive crawls in which errors were encountered while fetching changes from the SharePoint content database")] uint16 RecrawlErrorCount; [Write, Description("Specifies the number of hours since the first error were encountered while fetching changes from the SharePoint content database")] uint16 RecrawlErrorInterval; + [Write, Description("Should default search topology be provisioned")] boolean ProvisionDefaultTopology; }; diff --git a/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/readme.md b/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/readme.md index a132ea197..e4db92cf7 100644 --- a/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/readme.md +++ b/SharePointDsc/DSCResources/MSFT_SPSearchServiceApp/readme.md @@ -18,8 +18,19 @@ For more information about the Deletion Policy settings, check the following article: https://docs.microsoft.com/en-us/previous-versions/office/sharepoint-server-2010/hh127009(v=office.14)?redirectedfrom=MSDN -**NOTE:** Don't forget to configure a Search topology using the SPSearchTopology -resource! +**NOTE:** Use 'ProvisionDefaultTopology = $true' parameter to provision default search topology. When this parameter is defined +and value is TRUE then topology is created as below: + +1. First we check are there any servers having Search or ApplicationWithSearch role. If there are then all search components are +provisioned to all these servers + +2. If no Search or ApplicationWithSearch role servers exist then we check are there servers having Custom role. If yes then all +search server components are provisioned to one (1) server having Custom role + +3. If no servers exist having roles defined in 1 and 2 then we check is this SingleServer or SingleServerFarm deployment and if yes +the all search server components are provisioned to that single server + +If you do not want to provision default topology then you need to define search topology using the SPSearchTopology resource! **NOTE2:** The resource is also able to add the Farm account as db_owner to all Search databases, to prevent the issue described here: