Add-PsSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue # Pre-checks - must pass to continue! Write-Host "Bamboo install prechecks version 18.3.0" Write-Host "Starting pre-checks." # PowerShell's New-Item creates a folder $path = $(get-location).Path $file = $path + "\installPrecheck.txt" "Checking sufficient rights to run this script." if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Read-Host "Please re-run with administrative rights. Press any key to end." exit } "Collecting ULS logs." try { Merge-SPLogFile -path $file -overwrite -Level Unexpected -ErrorAction Stop } catch { Write-Host "During the pre-installation check we were unable to collect informational logs from SharePoint. " Write-Host "This usually indicates a farm health issue that would prevent successful installation." Write-Host "Please remediate your farm health issues before attempting this installation or update. " Write-Host "Having issues with your farm? Contact Bamboo Support (Support@BambooSolutions.com) for information on how we can assist. " Write-Host "Thank you." Read-Host "Press any key to exit." exit } "Restarting timer services on all servers." try { $spFarm=Get-SPFarm $spfTimerServcicesInstances=$spFarm.TimerService.Instances foreach ($spfTimerServiceInstance in $spfTimerServcicesInstances) { Write-Host "Re-starting the timer instance " $spfTimerServiceInstance.TypeName $spfTimerServiceInstance.Stop() $spfTimerServiceInstance.Start() Write-Host "SharePoint Timer Service Instance" $spfTimerServiceInstance.TypeName " restarted successfully" Add-Content -Path $file -Value ("SharePoint Timer Service Instance" + $spfTimerServiceInstance.TypeName + " sestarted successfully") } } catch [System.Exception] { write-host -f red $_.Exception.ToString() Write-Host -f red "SharePoint Timer Service Instance failed to restart. This indicates installs will fail if run." Write-Host -f red "Please correct the issue and re-run the pre-checks." exit } "Verifying Installed Products and the SharePoint Build" Get-SPProduct -Local | out-file $file -width 358 -append (Get-SPfarm).buildversion | out-file $file -width 358 -append "Listing Servers" Add-Content -Path $file -Value "# List Servers" Get-SPServer | select Name, Role, Status | Format-table -AutoSize | out-file $file -append "Listing Service Application Information" Add-Content -Path $file -Value "# List Service Application Information" Get-SPServiceApplication | FT -Autosize | out-file -width 358 $file -append "Listing Web Application Information" Add-Content -Path $file -Value "# List Web Application Information" Get-SPWebApplication | FT -Autosize | out-file $file -width 358 -append "Listing Alternate Access Mappings" Add-Content -Path $file -Value "# List Alternate Access Mappings" Get-SPAlternateURL | FT -Autosize | out-file $file -width 358 -append "Getting installed solution status" Add-Content -Path $file -Value "# Show Solution Status" Get-SPsolution | select Name, ID, Status | Format-table -Autosize | out-file -width 358 $file -append "Determining if any content databases need upgrading" Add-Content -Path $file -Value "# Determine if any content databases need upgrading" Get-SPContentDatabase | ?{$_.NeedsUpgrade –eq $true} | out-file $file -width 358 -append "Listing Content Database Orphaned Items" Add-Content -Path $file -Value "# List any content database orphans" $CDBs = Get-SPContentDatabase ForEach ($CDB in $CDBs) { Write-Host "Detecting Orphans for " $CDB.Name $CDB.Repair($false) } out-file $file -append "Listing failed timer jobs" Add-Content -Path $file -Value "# failed timer jobs" $webApp = Get-SPWebApplication $days = New-TimeSpan -Days 1 $start = (Get-Date) - $days $end = Get-Date $webApp.JobHistoryEntries | Where {($_.StartTime -ge $start) -and ($_.EndTime -le $end) -and ($_.Status -ne 'Succeeded')} | out-file -width 358 $file -append "List All Sites All Webs" Add-Content -Path $file -Value "# List All Sites All Webs" Get-SPWebApplication | Get-SPSite -Limit All | Format-Table -AutoSize -Property URL,ContentDatabase,CompatibilityLevel | out-file -width 358 $file -append "Getting AppFabric Health Status" Add-Content -Path $file -Value "# Display AppFabric Health Status" Use-CacheCluster Get-CacheClusterHealth | out-file $file -width 358 -append "Getting 2013 Workflow Status" Add-Content -Path $file -Value "# Display 2013 Workflow Status" Get-SPWorkFlowConfig -webapplication $webApp | out-file $file -width 358 -append "Getting Timer Jobs Status" Get-SPTimerJob | select Name,DisplayName,status, schedule,IsDisabled , Description | out-file $file -append "Verifying health of Search Topology" Add-Content -Path $file -Value "# Verify health of Search Topology" Function WriteErrorAndExit($errorText) { Write-Host -BackgroundColor Red -ForegroundColor Black $errorText Write-Host -BackgroundColor Red -ForegroundColor Black "Aborting script" exit } # ------------------------------------------------------------------------------------------------------------------ # GetSSA: Get SSA reference # ------------------------------------------------------------------------------------------------------------------ Function GetSSA { $ssas = @(Get-SPEnterpriseSearchServiceApplication) if ($ssas.Count -ne 1) { WriteErrorAndExit("This script only supports a single SSA configuration") } $global:ssa = $ssas[0] if ($global:ssa.Status -ne "Online") { $ssaStat = $global:ssa.Status WriteErrorAndExit("Expected SSA to have status 'Online', found status: $ssaStat") } Add-Content -Path $file -Value "SSA: $($global:ssa.Name)" Add-Content -Path $file -Value "" } # ------------------------------------------------------------------------------------------------------------------ # GetCrawlStatus: Get crawl status # ------------------------------------------------------------------------------------------------------------------ Function GetCrawlStatus { if ($global:ssa.Ispaused()) { switch ($global:ssa.Ispaused()) { 1 { $pauseReason = "ongoing search topology operation" } 2 { $pauseReason = "backup/restore" } 4 { $pauseReason = "backup/restore" } 32 { $pauseReason = "crawl DB re-factoring" } 64 { $pauseReason = "link DB re-factoring" } 128 { $pauseReason = "external reason (user initiated)" } 256 { $pauseReason = "index reset" } 512 { $pauseReason = "index re-partitioning (query is also paused)" } default { $pauseReason = "multiple reasons ($($global:ssa.Ispaused()))" } } Add-Content -Path $file -Value "$($global:ssa.Name): Paused for $pauseReason" } else { $crawling = $false $contentSources = Get-SPEnterpriseSearchCrawlContentSource -SearchApplication $global:ssa if ($contentSources) { foreach ($source in $contentSources) { if ($source.CrawlState -ne "Idle") { Add-Content -Path $file -Value "Crawling $($source.Name) : $($source.CrawlState)" $crawling = $true } } if (! $crawling) { Add-Content -Path $file -Value "Crawler is idle" } } else { Add-Content -Path $file -Value "Crawler: No content sources found" } } } # ------------------------------------------------------------------------------------------------------------------ # GetTopologyInfo: Get basic topology info and component health status # ------------------------------------------------------------------------------------------------------------------ Function GetTopologyInfo { $at = Get-SPEnterpriseSearchTopology -SearchApplication $global:ssa -Active $global:topologyCompList = Get-SPEnterpriseSearchComponent -SearchTopology $at # Check if topology is prepared for HA $adminFound = $false foreach ($searchComp in ($global:topologyCompList)) { if ($searchComp.Name -match "Admin") { if ($adminFound) { $global:haTopology = $true } else { $adminFound = $true } } } # # Get topology component state: # $global:componentStateList=Get-SPEnterpriseSearchStatus -SearchApplication $global:ssa # Find the primary admin component: foreach ($component in ($global:componentStateList)) { if ( ($component.Name -match "Admin") -and ($component.State -ne "Unknown") ) { if (Get-SPEnterpriseSearchStatus -SearchApplication $global:ssa -Primary -Component $($component.Name)) { $global:primaryAdmin = $component.Name } } } if (! $global:primaryAdmin) { Add-Content -Path $file -Value "" Add-Content -Path $file -Value "-----------------------------------------------------------------------------" Add-Content -Path $file -Value "Error: Not able to obtain health state information." Add-Content -Path $file -Value "Recommended action: Ensure that at least one admin component is operational." Add-Content -Path $file -Value "This state may also indicate that an admin component failover is in progress." Add-Content -Path $file -Value "-----------------------------------------------------------------------------" Add-Content -Path $file -Value "" throw "Search component health state check failed" } } # ------------------------------------------------------------------------------------------------------------------ # PopulateHostHaList: For each component, determine properties and update $global:hostArray / $global:haArray # ------------------------------------------------------------------------------------------------------------------ Function PopulateHostHaList($searchComp) { if ($searchComp.ServerName) { $hostName = $searchComp.ServerName } else { $hostName = "Unknown server" } $partition = $searchComp.IndexPartitionOrdinal $newHostFound = $true $newHaFound = $true $entity = $null foreach ($searchHost in ($global:hostArray)) { if ($searchHost.hostName -eq $hostName) { $newHostFound = $false } } if ($newHostFound) { # Add the host to $global:hostArray $hostTemp = $global:hostTemplate | Select-Object * $hostTemp.hostName = $hostName $global:hostArray += $hostTemp $global:searchHosts += 1 } # Fill in component specific data in $global:hostArray foreach ($searchHost in ($global:hostArray)) { if ($searchHost.hostName -eq $hostName) { $partition = -1 if ($searchComp.Name -match "Query") { $entity = "QueryProcessingComponent" $searchHost.qpc = "QueryProcessing " $searchHost.components += 1 } elseif ($searchComp.Name -match "Content") { $entity = "ContentProcessingComponent" $searchHost.cpc = "ContentProcessing " $searchHost.components += 1 } elseif ($searchComp.Name -match "Analytics") { $entity = "AnalyticsProcessingComponent" $searchHost.apc = "AnalyticsProcessing " $searchHost.components += 1 } elseif ($searchComp.Name -match "Admin") { $entity = "AdminComponent" if ($searchComp.Name -eq $global:primaryAdmin) { $searchHost.pAdmin = "Admin(Primary) " } else { $searchHost.sAdmin = "Admin " } $searchHost.components += 1 } elseif ($searchComp.Name -match "Crawl") { $entity = "CrawlComponent" $searchHost.crawler = "Crawler " $searchHost.components += 1 } elseif ($searchComp.Name -match "Index") { $entity = "IndexComponent" $partition = $searchComp.IndexPartitionOrdinal $searchHost.index = "IndexPartition($partition) " $searchHost.components += 1 } } } # Fill in component specific data in $global:haArray foreach ($haEntity in ($global:haArray)) { if ($haEntity.entity -eq $entity) { if ($entity -eq "IndexComponent") { if ($haEntity.partition -eq $partition) { $newHaFound = $false } } else { $newHaFound = $false } } } if ($newHaFound) { # Add the HA entities to $global:haArray $haTemp = $global:haTemplate | Select-Object * $haTemp.entity = $entity $haTemp.components = 1 if ($partition -ne -1) { $haTemp.partition = $partition } $global:haArray += $haTemp } else { foreach ($haEntity in ($global:haArray)) { if ($haEntity.entity -eq $entity) { if (($entity -eq "IndexComponent") ) { if ($haEntity.partition -eq $partition) { $haEntity.components += 1 } } else { $haEntity.components += 1 if (($haEntity.entity -eq "AdminComponent") -and ($searchComp.Name -eq $global:primaryAdmin)) { $haEntity.primary = $global:primaryAdmin } } } } } } # ------------------------------------------------------------------------------------------------------------------ # AnalyticsStatus: Output status of analytics jobs # ------------------------------------------------------------------------------------------------------------------ Function AnalyticsStatus { Add-Content -Path $file -Value "Analytics Processing Job Status:" $analyticsStatus = Get-SPEnterpriseSearchStatus -SearchApplication $global:ssa -JobStatus foreach ($analyticsEntry in $analyticsStatus) { if ($analyticsEntry.Name -ne "Not available") { foreach ($de in ($analyticsEntry.Details)) { if ($de.Key -eq "Status") { $status = $de.Value } } Add-Content -Path $file -Value " $($analyticsEntry.Name) : $status" } # Output additional diagnostics from the dictionary foreach ($de in ($analyticsEntry.Details)) { # Skip entries that is listed as Not Available if ( ($de.Value -ne "Not available") -and ($de.Key -ne "Activity") -and ($de.Key -ne "Status") ) { Add-Content -Path $file -Value " $($de.Key): $($de.Value)" if ($de.Key -match "Last successful start time") { $dLast = Get-Date $de.Value $dNow = Get-Date $daysSinceLastSuccess = $dNow.DayOfYear - $dLast.DayOfYear if ($daysSinceLastSuccess -gt 3) { Add-Content -Path $file -Value " Warning: More than three days since last successful run" $global:serviceDegraded = $true } } } } } Add-Content -Path $file -Value "" } # ------------------------------------------------------------------------------------------------------------------ # SearchComponentStatus: Analyze the component status for one component # ------------------------------------------------------------------------------------------------------------------ Function SearchComponentStatus($component) { # Find host name foreach($searchComp in ($global:topologyCompList)) { if ($searchComp.Name -eq $component.Name) { if ($searchComp.ServerName) { $hostName = $searchComp.ServerName } else { $hostName = "No server associated with this component. The server may have been removed from the farm." } } } if ($component.State -ne "Active") { # String with all components that is not active: if ($component.State -eq "Unknown") { $global:unknownComponents += "$($component.Name):$($component.State)" } elseif ($component.State -eq "Degraded") { $global:degradedComponents += "$($component.Name):$($component.State)" } else { $global:failedComponents += "$($component.Name):$($component.State)" } $global:serviceDegraded = $true } # Skip unnecessary info about cells and partitions if everything is fine $outputEntry = $true # Indent the cell info, logically belongs to the component. if ($component.Name -match "Cell") { if ($component.State -eq "Active") { $outputEntry = $false } else { Add-Content -Path $file -Value " $($component.Name)" } } elseif ($component.Name -match "Partition") { if ($component.State -eq "Active") { $outputEntry = $false } else { Add-Content -Path $file -Value "Index $($component.Name)" } } else { # State for search components $primaryString = "" if ($component.Name -match "Query") { $entity = "QueryProcessingComponent" } elseif ($component.Name -match "Content") { $entity = "ContentProcessingComponent" } elseif ($component.Name -match "Analytics") { $entity = "AnalyticsProcessingComponent" } elseif ($component.Name -match "Crawl") { $entity = "CrawlComponent" } elseif ($component.Name -match "Admin") { $entity = "AdminComponent" if ($global:haTopology) { if ($component.Name -eq $global:primaryAdmin) { $primaryString = " (Primary)" } } } elseif ($component.Name -match "Index") { $entity = "IndexComponent" foreach ($searchComp in ($global:topologyCompList)) { if ($searchComp.Name -eq $component.Name) { $partition = $searchComp.IndexPartitionOrdinal } } # find info about primary role foreach ($de in ($component.Details)) { if ($de.Key -eq "Primary") { if ($de.Value -eq "True") { $primaryString = " (Primary)" foreach ($haEntity in ($global:haArray)) { if (($haEntity.entity -eq $entity) -and ($haEntity.partition -eq $partition)) { $haEntity.primary = $component.Name } } } } } } foreach ($haEntity in ($global:haArray)) { if ( ($haEntity.entity -eq $entity) -and ($component.State -eq "Active") ) { if ($entity -eq "IndexComponent") { if ($haEntity.partition -eq $partition) { $haEntity.componentsOk += 1 } } else { $haEntity.componentsOk += 1 } } } # Add the component entities to $global:compArray for output formatting $compTemp = $global:compTemplate | Select-Object * $compTemp.Component = "$($component.Name)$primaryString" $compTemp.Server = $hostName $compTemp.State = $component.State if ($partition -ne -1) { $compTemp.Partition = $partition } $global:compArray += $compTemp if ($component.State -eq "Active") { $outputEntry = $false } else { Add-Content -Path $file -Value "$($component.Name)" } } if ($outputEntry) { if ($component.State) { Add-Content -Path $file -Value " State: $($component.State)" } if ($hostName) { Add-Content -Path $file -Value " Server: $hostName" } if ($component.Message) { Add-Content -Path $file -Value " Details: $($component.Message)" } # Output additional diagnostics from the dictionary foreach ($de in ($component.Details)) { if ($de.Key -ne "Host") { Add-Content -Path $file -Value " $($de.Key): $($de.Value)" } } if ($global:haTopology) { if ($component.Name -eq $global:primaryAdmin) { Add-Content -Path $file -Value " Primary: True" } elseif ($component.Name -match "Admin") { Add-Content -Path $file -Value " Primary: False" } } } } # ------------------------------------------------------------------------------------------------------------------ # DetailedIndexerDiag: Output selected info from detailed component diag # ------------------------------------------------------------------------------------------------------------------ Function DetailedIndexerDiag { $indexerInfo = @() $generationInfo = @() $generation = 0 foreach ($searchComp in ($global:componentStateList)) { $component = $searchComp.Name if ( (($component -match "Index") -or ($component -match "Content") -or ($component -match "Admin")) -and ($component -notmatch "Cell") -and ($searchComp.State -notmatch "Unknown") -and ($searchComp.State -notmatch "Registering")) { $pl=Get-SPEnterpriseSearchStatus -SearchApplication $global:ssa -HealthReport -Component $component foreach ($entry in ($pl)) { if ($entry.Name -match "plugin: number of documents") { foreach ($haEntity in ($global:haArray)) { if (($haEntity.entity -eq "IndexComponent") -and ($haEntity.primary -eq $component)) { # Count indexed documents from all index partitions: $global:indexedDocs += $entry.Message $haEntity.docs = $entry.Message } } } if ($entry.Name -match "repartition") { $indexerInfo += "Index re-partitioning state: $($entry.Message)" } elseif (($entry.Name -match "splitting") -and ($entry.Name -match "fusion")) { $indexerInfo += "$component : Splitting index partition (appr. $($entry.Message) % finished)" } elseif (($entry.Name -match "master merge running") -and ($entry.Message -match "true")) { $indexerInfo += "$component : Index Master Merge (de-fragment index files) in progress" $global:masterMerge = $true } elseif ($global:degradedComponents -and ($entry.Name -match "plugin: newest generation id")) { # If at least one index component is left behind, we want to output the generation number. $generationInfo += "$component : Index generation: $($entry.Message)" $gen = [int] $entry.Message if ($generation -and ($generation -ne $gen)) { # Verify if there are different generation IDs for the indexers $global:generationDifference = $true } $generation = $gen } elseif (($entry.Level -eq "Error") -or ($entry.Level -eq "Warning")) { $global:serviceDegraded = $true if ($entry.Name -match "fastserver") { $indexerInfo += "$component ($($entry.Level)) : Indexer plugin error ($($entry.Name):$($entry.Message))" } elseif ($entry.Message -match "fragments") { $indexerInfo += "$component ($($entry.Level)) : Missing index partition" } elseif (($entry.Name -match "active") -and ($entry.Message -match "not active")) { $indexerInfo += "$component ($($entry.Level)) : Indexer generation controller is not running. Potential reason: All index partitions are not available" } elseif ( ($entry.Name -match "in_sync") -or ($entry.Name -match "left_behind") ) { # Indicates replicas are out of sync, catching up. Redundant info in this script $global:indexLeftBehind = $true } elseif ($entry.Name -match "full_queue") { $indexerInfo += "$component : Items queuing up in feeding ($($entry.Message))" } elseif ($entry.Message -notmatch "No primary") { $indexerInfo += "$component ($($entry.Level)) : $($entry.Name):$($entry.Message)" } } } } } if ($indexerInfo) { Add-Content -Path $file -Value "" Add-Content -Path $file -Value "Indexer related additional status information:" foreach ($indexerInfoEntry in ($indexerInfo)) { Add-Content -Path $file -Value " $indexerInfoEntry" } if ($global:indexLeftBehind -and $global:generationDifference) { # Output generation number for indexers in case any of them have been reported as left behind, and reported generation IDs are different. foreach ($generationInfoEntry in ($generationInfo)) { Add-Content -Path $file -Value " $generationInfoEntry" } } Add-Content -Path $file -Value "" } } # ------------------------------------------------------------------------------------------------------------------ # VerifyHaLimits: Verify HA status for topology and index size limits # ------------------------------------------------------------------------------------------------------------------ Function VerifyHaLimits { $hacl = @() $haNotOk = $false $ixcwl = @() $ixcel = @() $docsExceeded = $false $docsHigh = $false foreach ($hac in $global:haArray) { if ([int] $hac.componentsOk -lt 2) { if ([int] $hac.componentsOk -eq 0) { # Service is down $global:serviceFailed = $true $haNotOk = $true } elseif ($global:haTopology) { # Only relevant to output if we have a HA topology in the first place $haNotOk = $true } if ($hac.partition -ne -1) { $hacl += "$($hac.componentsOk)($($hac.components)) : Index partition $($hac.partition)" } else { $hacl += "$($hac.componentsOk)($($hac.components)) : $($hac.entity)" } } if ([int] $hac.docs -gt 10000000) { $docsExceeded = $true $ixcel += "$($hac.entity) (partition $($hac.partition)): $($hac.docs)" } elseif ([int] $hac.docs -gt 9000000) { $docsHigh = $true $ixcwl += "$($hac.entity) (partition $($hac.partition)): $($hac.docs)" } } if ($haNotOk) { $hacl = $hacl | sort if ($global:serviceFailed) { Add-Content -Path $file -Value "Critical: Service down due to components not active:" } else { Add-Content -Path $file -Value "Warning: No High Availability for one or more components:" } foreach ($hc in $hacl) { Add-Content -Path $file -Value " $hc" } Add-Content -Path $file -Value "" } if ($docsExceeded) { $global:serviceDegraded = $true Add-Content -Path $file -Value "Warning: One or more index component exceeds document limit:" foreach ($hc in $ixcel) { Add-Content -Path $file -Value " $hc" } Add-Content -Path $file -Value "" } if ($docsHigh) { Add-Content -Path $file -Value "Warning: One or more index component is close to document limit:" foreach ($hc in $ixcwl) { Add-Content -Path $file -Value " $hc" } Add-Content -Path $file -Value "" } } # ------------------------------------------------------------------------------------------------------------------ # VerifyHostControllerRepository: Verify that Host Controller HA (for dictionary repository) is OK # ------------------------------------------------------------------------------------------------------------------ Function VerifyHostControllerRepository { $highestRepVer = 0 $hostControllers = 0 $primaryRepVer = -1 $hcStat = @() $hcs = Get-SPEnterpriseSearchHostController foreach ($hc in $hcs) { $hostControllers += 1 $repVer = $hc.RepositoryVersion $serverName = $hc.Server.Name if ($repVer -gt $highestRepVer) { $highestRepVer = $repVer } if ($hc.PrimaryHostController) { $primaryHC = $serverName $primaryRepVer = $repVer } if ($repVer -ne -1) { $hcStat += " $serverName : $repVer" } } if ($hostControllers -gt 1) { Add-Content -Path $file -Value "Primary search host controller (for dictionary repository): $primaryHC" if ($primaryRepVer -eq -1) { $global:serviceDegraded = $true Add-Content -Path $file -Value "Warning: Primary host controller is not available." Add-Content -Path $file -Value " Recommended action: Restart server or set new primary host controller using Set-SPEnterpriseSearchPrimaryHostController." Add-Content -Path $file -Value " Repository version for existing host controllers:" foreach ($hcs in $hcStat) { Add-Content -Path $file -Value $hcs } } elseif ($primaryRepVer -lt $highestRepVer) { $global:serviceDegraded = $true Add-Content -Path $file -Value "Warning: Primary host controller does not have the latest repository version." Add-Content -Path $file -Value " Primary host controller repository version: $primaryRepVer" Add-Content -Path $file -Value " Latest repository version: $highestRepVer" Add-Content -Path $file -Value " Recommended action: Set new primary host controller using Set-SPEnterpriseSearchPrimaryHostController." Add-Content -Path $file -Value " Repository version for existing host controllers:" foreach ($hcs in $hcStat) { Add-Content -Path $file -Value $hcs } } Add-Content -Path $file -Value "" } } # ------------------------------------------------------------------------------------------------------------------ # Main # ------------------------------------------------------------------------------------------------------------------ Add-Content -Path $file -Value "" Add-Content -Path $file -Value "Search Topology health check" Add-Content -Path $file -Value "============================" Add-Content -Path $file -Value "" Get-Date # ------------------------------------------------------------------------------------------------------------------ # Global variables: # ------------------------------------------------------------------------------------------------------------------ $global:serviceDegraded = $false $global:serviceFailed = $false $global:unknownComponents = @() $global:degradedComponents = @() $global:failedComponents = @() $global:generationDifference = $false $global:indexLeftBehind = $false $global:searchHosts = 0 $global:ssa = $null $global:componentStateList = $null $global:topologyCompList = $null $global:haTopology = $false $global:primaryAdmin = $null $global:indexedDocs = 0 $global:masterMerge = $false # Template object for the host array: $global:hostTemplate = New-Object psobject $global:hostTemplate | Add-Member -MemberType NoteProperty -Name hostName -Value $null $global:hostTemplate | Add-Member -MemberType NoteProperty -Name components -Value 0 $global:hostTemplate | Add-Member -MemberType NoteProperty -Name cpc -Value $null $global:hostTemplate | Add-Member -MemberType NoteProperty -Name qpc -Value $null $global:hostTemplate | Add-Member -MemberType NoteProperty -Name pAdmin -Value $null $global:hostTemplate | Add-Member -MemberType NoteProperty -Name sAdmin -Value $null $global:hostTemplate | Add-Member -MemberType NoteProperty -Name apc -Value $null $global:hostTemplate | Add-Member -MemberType NoteProperty -Name crawler -Value $null $global:hostTemplate | Add-Member -MemberType NoteProperty -Name index -Value $null # Create the empty host array: $global:hostArray = @() # Template object for the HA group array: $global:haTemplate = New-Object psobject $global:haTemplate | Add-Member -MemberType NoteProperty -Name entity -Value $null $global:haTemplate | Add-Member -MemberType NoteProperty -Name partition -Value -1 $global:haTemplate | Add-Member -MemberType NoteProperty -Name primary -Value $null $global:haTemplate | Add-Member -MemberType NoteProperty -Name docs -Value 0 $global:haTemplate | Add-Member -MemberType NoteProperty -Name components -Value 0 $global:haTemplate | Add-Member -MemberType NoteProperty -Name componentsOk -Value 0 # Create the empty HA group array: $global:haArray = @() # Template object for the component/server table: $global:compTemplate = New-Object psobject $global:compTemplate | Add-Member -MemberType NoteProperty -Name Component -Value $null $global:compTemplate | Add-Member -MemberType NoteProperty -Name Server -Value $null $global:compTemplate | Add-Member -MemberType NoteProperty -Name Partition -Value $null $global:compTemplate | Add-Member -MemberType NoteProperty -Name State -Value $null # Create the empty component/server table: $global:compArray = @() try { # Get the SSA object and print SSA name: GetSSA # Get basic topology info and component health status GetTopologyInfo # Traverse list of components, determine properties and update $global:hostArray / $global:haArray foreach ($searchComp in ($global:topologyCompList)) { PopulateHostHaList($searchComp) } # Analyze the component status: foreach ($component in ($global:componentStateList)) { SearchComponentStatus($component) } # Look for selected info from detailed indexer diagnostics: DetailedIndexerDiag # Output list of components with state OK: if ($global:compArray) { $global:compArray | Sort-Object -Property Component | Format-Table -AutoSize } Add-Content -Path $file -Value "" # Verify HA status for topology and index size limits: VerifyHaLimits # Verify that Host Controller HA (for dictionary repository) is OK: VerifyHostControllerRepository # Output components by server (for servers with multiple search components): if ($global:haTopology -and ($global:searchHosts -gt 2)) { $componentsByServer = $false foreach ($hostInfo in $global:hostArray) { if ([int] $hostInfo.components -gt 1) { $componentsByServer = $true } } if ($componentsByServer) { Add-Content -Path $file -Value "Servers with multiple search components:" foreach ($hostInfo in $global:hostArray) { if ([int] $hostInfo.components -gt 1) { Add-Content -Path $file -Value " $($hostInfo.hostName): $($hostInfo.pAdmin)$($hostInfo.sAdmin)$($hostInfo.index)$($hostInfo.qpc)$($hostInfo.cpc)$($hostInfo.apc)$($hostInfo.crawler)" } } Add-Content -Path $file -Value "" } } # Analytics Processing Job Status: AnalyticsStatus if ($global:masterMerge) { Add-Content -Path $file -Value "Index Master Merge (de-fragment index files) in progress on one or more index components." } if ($global:serviceFailed -eq $false) { Add-Content -Path $file -Value "Searchable items: $global:indexedDocs" } GetCrawlStatus Add-Content -Path $file -Value "" if ($global:unknownComponents) { Add-Content -Path $file -Value "The following components are not reachable:" foreach ($uc in ($global:unknownComponents)) { Add-Content -Path $file -Value " $uc" } Add-Content -Path $file -Value "Recommended action: Restart or replace the associated server(s)" Add-Content -Path $file -Value "" } if ($global:degradedComponents) { Add-Content -Path $file -Value "The following components are degraded:" foreach ($dc in ($global:degradedComponents)) { Add-Content -Path $file -Value " $dc" } Add-Content -Path $file -Value "Recommended action for degraded components:" Add-Content -Path $file -Value " Component registering or resolving:" Add-Content -Path $file -Value " This is normally a transient state during component restart or re-configuration. Re-run the script." if ($global:indexLeftBehind) { Add-Content -Path $file -Value " Index component left behind:" if ($global:generationDifference) { Add-Content -Path $file -Value " This is normal after adding an index component or index component/server recovery." Add-Content -Path $file -Value " Indicates that the replica is being updated from the primary replica." } else { Add-Content -Path $file -Value " Index replicas listed as degraded but index generation is OK." Add-Content -Path $file -Value " Will get out of degraded state as soon as new/changed items are being idexed." } } Add-Content -Path $file -Value "" } if ($global:failedComponents) { Add-Content -Path $file -Value "The following components are reported in error:" foreach ($fc in ($global:failedComponents)) { Add-Content -Path $file -Value " $fc" } Add-Content -Path $file -Value "Recommended action: Restart the associated server(s)" Add-Content -Path $file -Value "" } if ($global:serviceFailed) { Write-Host -BackgroundColor Red -ForegroundColor Black "Search service overall state: Failed (no queries served)" Add-Content -Path $file -Value "Search service overall state: Failed (no queries served)" } elseif ($global:serviceDegraded) { Write-Host -BackgroundColor Yellow -ForegroundColor Black "Search service overall state: Degraded" Add-Content -Path $file -Value "Search service overall state: Degraded" } else { Write-Host -BackgroundColor Green -ForegroundColor Black "Search service overall state: OK" Add-Content -Path $file -Value "Search service overall state: OK" } } catch [System.Exception] { write-host -f red $_.Exception.ToString() Write-Host -f red "Could not determine Search Health Status. This will not impact installation but will impact SharePoint functionality." } "Press any key to exit the script and open the log file." Read-Host notepad.exe $file # SIG # Begin signature block # MIINEAYJKoZIhvcNAQcCoIINATCCDP0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUy6EKa0j4NzVtmzPB4ndIk2GT # z9egggoWMIIE0DCCA7igAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UE # BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAY # BgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290 # IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTExMDUwMzA3MDAwMFoXDTMx # MDUwMzA3MDAwMFowgbQxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMw # EQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjEt # MCsGA1UECxMkaHR0cDovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMTMw # MQYDVQQDEypHbyBEYWRkeSBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0g # RzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC54MsQ1K92vdSTYusw # ZLiBCGzDBNliF44v/z5lz4/OYuY8UhzaFkVLVat4a2ODYpDOD2lsmcgaFItMzEUz # 6ojcnqOvK/6AYZ15V8TPLvQ/MDxdR/yaFrzDN5ZBUY4RS1T4KL7QjL7wMDge87Am # +GZHY23ecSZHjzhHU9FGHbTj3ADqRay9vHHZqm8A29vNMDp5T19MR/gd71vCxJ1g # O7GyQ5HYpDNO6rPWJ0+tJYqlxvTV0KaudAVkV4i1RFXULSo6Pvi4vekyCgKUZMQW # OlDxSq7neTOvDCAHf+jfBDnCaQJsY1L6d8EbyHSHyLmTGFBUNUtpTrw700kuH9zB # 0lL7AgMBAAGjggEaMIIBFjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB # BjAdBgNVHQ4EFgQUQMK9J47MNIMwojPX+2yz8LQsgM4wHwYDVR0jBBgwFoAUOpqF # BxBnKLbv9r0FQW4gwZTaD94wNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhho # dHRwOi8vb2NzcC5nb2RhZGR5LmNvbS8wNQYDVR0fBC4wLDAqoCigJoYkaHR0cDov # L2NybC5nb2RhZGR5LmNvbS9nZHJvb3QtZzIuY3JsMEYGA1UdIAQ/MD0wOwYEVR0g # ADAzMDEGCCsGAQUFBwIBFiVodHRwczovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9z # aXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IBAQAIfmyTEMg4uJapkEv/oV9PBO9sPpyI # BslQj6Zz91cxG7685C/b+LrTW+C05+Z5Yg4MotdqY3MxtfWoSKQ7CC2iXZDXtHwl # TxFWMMS2RJ17LJ3lXubvDGGqv+QqG+6EnriDfcFDzkSnE3ANkR/0yBOtg2DZ2HKo # cyQetawiDsoXiWJYRBuriSUBAA/NxBti21G00w9RKpv0vHP8ds42pM3Z2Czqrpv1 # KrKQ0U11GIo/ikGQI31bS/6kA1ibRrLDYGCD+H1QQc7CoZDDu+8CL9IVVO5EFdkK # rqeKM+2xLXY2JtwE65/3YR8V3Idv7kaWKK2hJn0KCacuBKONvPi8BDABMIIFPjCC # BCagAwIBAgIJAN20y3lwzQveMA0GCSqGSIb3DQEBCwUAMIG0MQswCQYDVQQGEwJV # UzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UE # ChMRR29EYWRkeS5jb20sIEluYy4xLTArBgNVBAsTJGh0dHA6Ly9jZXJ0cy5nb2Rh # ZGR5LmNvbS9yZXBvc2l0b3J5LzEzMDEGA1UEAxMqR28gRGFkZHkgU2VjdXJlIENl # cnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTE4MDUxODE4Mzk0M1oXDTE5MDUx # MDE0MDk1NlowfzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMQ8wDQYD # VQQHEwZSZXN0b24xJTAjBgNVBAoTHEJhbWJvbyBTb2x1dGlvbnMgQ29ycG9yYXRp # b24xJTAjBgNVBAMTHEJhbWJvbyBTb2x1dGlvbnMgQ29ycG9yYXRpb24wggEiMA0G # CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC776uioxzGYjxNRRKLg1oP4Rz1cCPM # TipfbVY47c5zjALtQsJ4UQcAx9As2wak/vnxT6G/zNieTW8JMS4ndQspY1BjX844 # aXB7OBlp2Xs3kylK+QUGA67qM8ZhscmlnfFIScALHFwMC+Uo14Ht7Mi2MQPH+cQz # HwztO7ZSgwMeY/FAywAtaqgcvytnr8Q17Dnm2qDIAYnMN2O+JNrDRSDpOeQKLh/J # AoGep0SWEXl4Nj7L7UnusN9S5jXuLGlYdoq467iWhipWap1WmIqoL5sglLzQ0NBi # X6GidkgqOWMhSEb7OXaBkfZs9/RBqSXfnucH3UBn9ErU4R3P81Izm8MDAgMBAAGj # ggGFMIIBgTAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA4GA1Ud # DwEB/wQEAwIHgDA1BgNVHR8ELjAsMCqgKKAmhiRodHRwOi8vY3JsLmdvZGFkZHku # Y29tL2dkaWcyczUtNC5jcmwwXQYDVR0gBFYwVDBIBgtghkgBhv1tAQcXAjA5MDcG # CCsGAQUFBwIBFitodHRwOi8vY2VydGlmaWNhdGVzLmdvZGFkZHkuY29tL3JlcG9z # aXRvcnkvMAgGBmeBDAEEATB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGGGGh0 # dHA6Ly9vY3NwLmdvZGFkZHkuY29tLzBABggrBgEFBQcwAoY0aHR0cDovL2NlcnRp # ZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5L2dkaWcyLmNydDAfBgNVHSME # GDAWgBRAwr0njsw0gzCiM9f7bLPwtCyAzjAdBgNVHQ4EFgQUFQQQE4hAUyYtNw3X # EYzbeAeysdAwDQYJKoZIhvcNAQELBQADggEBAIdJqCOPZzl/+02FGn5bdt44pIIN # bGoM2tKUguVvwx+dwasu3V05JADL8mubFExyHKZW0WepOllprbthoBVaHOrEqEys # xJfc0H2N/fD5WmITH4+gzXVL/3mdti6bXTfUf72lUiOBnOi5ke+li+YPZtahxm2K # irPweHebuBErKR6xouY5XQApXCghz8ICq23mHSKFX2fdCOF3WCxEjK4Oizse4dQE # dSyH8sakxQJ0S5+yKoUuPq574R2k1pIXl7cJy6qbsDryIW076jrKMmIfqGcBMFHW # 4X+EBHt3r23qQLqT4UlpcUgxJ019AGAjEYK5O0MIGtAOAdklB4rlpsYaLGwxggJk # MIICYAIBATCBwjCBtDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzAR # BgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMS0w # KwYDVQQLEyRodHRwOi8vY2VydHMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS8xMzAx # BgNVBAMTKkdvIERhZGR5IFNlY3VyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBH # MgIJAN20y3lwzQveMAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgACh # AoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAM # BgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBTODbmbULsqKkeQCbqlN8T2tmbm # lzANBgkqhkiG9w0BAQEFAASCAQAH43SuZr1+ufaMFavWI6QHCHuA8Trs0KX82iWU # iVo4j2uqS+by/+Qk32oqEkOM2pwazfxMuH28MkLhHWmP3HvQcgXH4Ws3VlsnjVe/ # p00n+8MQ26xAgYHv8rZL+dHEitbzSzFgNYlFLp8qD0fNXXTO4bOaATaI4i5yXmZ3 # lexZRXrOX0aIFLVvhP1DOyCD6SOH9XpeMekl3zkKaNfsSCZyf7Qh5jjE/euqLWX9 # e73CJXcxePiKA2ci2pxVgzZM5k6lYY8ik9nbXpjb05iOQwnLNWzfuXusOQcsyOOh # T4K/fJi14PqKRqnkN3PJq2yQHMhrbqyWZBaZk20HrWdtxjZg # SIG # End signature block