<# .SYNOPSIS Script 14 - Search Health / Crawl Status Report .DESCRIPTION 50 }.DESCRIPTION } } function Get-Recommendation { param($risk) switch ($risk) { "High" { return "Search service degraded or stale crawl. Investigate crawl failures before migration or validation." } "Medium" { return "Crawl freshness moderate. Consider full or incremental crawl before migration validation." } "Low" { return "Search service healthy. Suitable for validation or migration readiness." } } } # Main $results = New-Object System.Collections.Generic.List[object] try { $searchApps = Get-SPEnterpriseSearchServiceApplication Log ("Found {0} Search Service Application(s)." -f $searchApps.Count) } catch { Add-Error "SearchService" $_.Exception.Message throw } foreach ($app in $searchApps) { try { $status = $app.Status $name = $app.Name # Crawl info (best effort) $lastCrawl = $null try { $crawlContentSources = Get-SPEnterpriseSearchCrawlContentSource -SearchApplication $app if ($crawlContentSources) { $lastCrawl = ($crawlContentSources | Sort-Object LastCrawlTime -Descending | Select -First 1).LastCrawlTime } } catch {} $risk = Get-RiskLevel -Status $status -LastCrawl $lastCrawl $results.Add([pscustomobject]@{ SearchAppName = $name Status = $status LastCrawlTime = $lastCrawl RiskLevel = $risk Score = Get-Score $risk Category = "SearchHealth" ActionRecommendation = Get-Recommendation $risk }) | Out-Null Log ("Processed Search App: {0}" -f $name) } catch { Add-Error $app.Name $_.Exception.Message } } # Export Detail $results | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8 # Export Summary $results | Group-Object RiskLevel | ForEach-Object { [pscustomobject]@{ RiskLevel = $_.Name Count = $_.Count } } | Export-Csv -Path $summaryPath -NoTypeInformation -Encoding UTF8 # Write Logs $log | Set-Content -Path $logPath # Errors if ($errors.Count -gt 0) { $errors | Export-Csv -Path $errorPath -NoTypeInformation Write-Host ("ERROR REPORT: {0}" -f $errorPath) -ForegroundColor Yellow } Write-Host "" Write-Host ("DETAIL REPORT: {0}" -f $OutputCsv) -ForegroundColor Green Write-Host ("SUMMARY REPORT: {0}" -f $summaryPath) -ForegroundColor Green Write-Host ("RUN LOG: {0}" -f $logPath) -ForegroundColor Green Write-Host "" Write-Host "Complete." -ForegroundColor Green READ-ONLY. Evaluates SharePoint Search Service health and crawl status. Captures: - Search Service Application status - Crawl component presence - Last crawl timestamps (if available) - Crawl freshness (calculated) - Risk levels for migration readiness .PARAMETER OutputCsv Full path to output CSV. .PARAMETER NoPrompt Skip confirmation prompt. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$OutputCsv, [switch]$NoPrompt ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" Write-Host "" Write-Host "SCRIPT 14 - SEARCH HEALTH / CRAWL STATUS REPORT" -ForegroundColor Cyan Write-Host "READ-ONLY. NO CHANGES ARE MADE." -ForegroundColor Green Write-Host "" # Load Snap-in try { if (-not (Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue)) { Add-PSSnapin "Microsoft.SharePoint.PowerShell" } } catch { throw "Run in SharePoint Management Shell. Error: $($_.Exception.Message)" } # Output Setup $outDir = Split-Path -Path $OutputCsv -Parent if ([string]::IsNullOrWhiteSpace($outDir)) { throw "Provide full OutputCsv path (e.g., C:\Temp\SearchHealth.csv)" } if (-not (Test-Path $outDir)) { New-Item -Path $outDir -ItemType Directory -Force | Out-Null } $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss") $baseName = [System.IO.Path]::GetFileNameWithoutExtension($OutputCsv) $summaryPath = Join-Path $outDir ("{0}_{1}_Summary.csv" -f $baseName, $timestamp) $logPath = Join-Path $outDir ("{0}_{1}_RunLog.txt" -f $baseName, $timestamp) $errorPath = Join-Path $outDir ("{0}_{1}_Errors.csv" -f $baseName, $timestamp) # Logging $log = New-Object System.Collections.Generic.List[string] $errors = New-Object System.Collections.Generic.List[object] function Log { param($msg) $line = "[{0}] {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $msg $log.Add($line) | Out-Null Write-Host $line } function Add-Error { param($scope, $msg) $errors.Add([pscustomobject]@{ Timestamp = Get-Date Scope = $scope Message = $msg }) | Out-Null } # Prompt if (-not $NoPrompt) { Write-Host "This script inspects SharePoint Search health only." -ForegroundColor Yellow if ((Read-Host "Type YES to continue") -ne "YES") { return } } # Risk Model function Get-RiskLevel { param($Status, $LastCrawl) $days = 9999 try { if ($LastCrawl) { $days = (New-TimeSpan -Start $LastCrawl -End (Get-Date)).Days } } catch {} if ($Status -ne "Online") { return "High" } if ($days -gt 7) { return "High" } if ($days -gt 2) { return "Medium" } return "Low" } function Get-Score { param($risk) switch ($risk) { "High" { return 30 } "Medium" { return 60 } "Low" { return 90 }