<# .SYNOPSIS Fix common windows search corruption or issues .DESCRIPTION Runs fixes known to resolve windows search issues and windows corruption .EXAMPLE # Run script locally C:\PS> .\Fix-Windows-Search.ps1 # Run script remotely C:\PS> Set-ExecutionPolicy Bypass C:\PS> iex "& { $(irm https://wobig.tech/downloads/scripts/Fix-Windows-Search.ps1) }" .NOTES Author: Rick Wobig Source: https://wobig.tech/downloads/scripts/Fix-Windows-Search.ps1 #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, HelpMessage = "Testing argument, will show verbose output and pause at certain script locations")] [Switch] [bool]$Testing = $false, [Parameter(Mandatory = $false, HelpMessage = "Root directory where logs are generated/cleaned up")] [ValidateNotNullOrEmpty()] [string]$PathLogs = "$((Get-Item .).FullName)\", [Parameter(Mandatory = $false, HelpMessage = "File prefix for generated files like logs, used for consistency and cleanup")] [ValidateNotNullOrEmpty()] [string]$ScriptFilePrefix = "Windows_Search_Fix_", [Parameter(Mandatory = $false, HelpMessage = "Threshold in days where we will remove old files")] [ValidateNotNullOrEmpty()] [int]$OldFileFilterDays = 7 ) #region Script Execution try { # Instantiate Logger, display Script Property values for troubleshooting [Logger]::new($Testing, $PathLogs, $ScriptFilePrefix) [Logger]::Logger.LogDisplay("Starting script execution", "Cyan") # This script requires elevation, enforce elevation [HouseKeeping]::EnsureScriptElevation() $cortanaDecoupled = [WindowsSearch]::DecoupleCortana() if ($cortanaDecoupled){ [Logger]::Logger.LogDisplay("Cortana decoupling is enforced", "Green") } else { [Logger]::Logger.LogDisplay("Failed to enforce Cortana decoupling", "Red") } # Disable Windows Web Search - Only causes search and index slowness based on performance impact $disabledWebSearch = [WindowsSearch]::DisableWebSearch() if ($disabledWebSearch){ [Logger]::Logger.LogDisplay("Successfully disabled web search in windows search", "Green") } else { [Logger]::Logger.LogDisplay("Failed to disable web search in windows search", "Red") } $searchReset = [WindowsSearch]::ResetWindowsSearch() if ($searchReset){ [Logger]::Logger.LogDisplay("Successfully reset windows search", "Green") } else { [Logger]::Logger.LogDisplay("Failed to reset windows search", "Red") } $firstSearchProcessKilled = [WindowsSearch]::KillSearchProcess() if ($firstSearchProcessKilled){ [Logger]::Logger.LogDisplay("Successfully killed search front end", "Green") } else { [Logger]::Logger.LogDisplay("Failed to kill search front end", "Red") } $searchCacheCleared = [WindowsSearch]::CleanupCaches() if ($searchCacheCleared){ [Logger]::Logger.LogDisplay("Successfully cleared search caches", "Green") } else { [Logger]::Logger.LogDisplay("Failed to clear search caches", "Red") } $secondSearchProcessKilled = [WindowsSearch]::KillSearchProcess() if ($secondSearchProcessKilled){ [Logger]::Logger.LogDisplay("Successfully killed search front end", "Green") } else { [Logger]::Logger.LogDisplay("Failed to kill search front end", "Red") } # Wait 5 Seconds For Components To Settle Start-Sleep -s 5 [Logger]::Logger.LogDisplay("Windows search fixes complete, please reboot to finish", "Cyan") # Finish up [Logger]::Logger.Log("Stopping script execution") } catch { [HouseKeeping]::StopOnFailure("GLOBAL FAILURE: $($PSItem.ToString())") } #endregion #region Classes class Logger { static [Logger] $Logger [bool] hidden $TestMode [string] hidden $LogPath [string] hidden $FilePrefix [string] hidden $LogDate = "$(Get-Date -Format %M_%d)" Logger( [bool]$testing, [string]$pathLogs, [string]$filePrefix ) { $this.TestMode = $testing $this.LogPath = $pathLogs $this.FilePrefix = $filePrefix [Logger]::Logger = $this [Logger]::GenerateLogDir($this.LogPath) } Log($message) { $methodName = [Logger]::GetMethodName(2) Add-Content "$($this.LogPath)$($this.FilePrefix)$($this.LogDate).log" "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" if ($this.TestMode) { Write-Host -fore DarkGray "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" } } LogPause($message) { if ($this.TestMode) { $methodName = [Logger]::GetMethodName(2) Add-Content "$($this.LogPath)$($this.FilePrefix)$($this.LogDate).log" "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" Write-Host -fore DarkGray "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" Pause } } LogDisplay($message, $color = "Gray"){ $methodName = [Logger]::GetMethodName(2) Add-Content "$($this.LogPath)$($this.FilePrefix)$($this.LogDate).log" "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" Write-Host -fore $color "[$(Get-Date -UFormat %H:%M:%S)] $($methodName): $($message)" } LogLoading() { if (!($this.TestMode)) { Add-Content "$($this.LogPath)$($this.FilePrefix)$($this.LogDate).log" "." Write-Host -fore DarkGray -NoNewline "." } } static [string] GetMethodName([int]$StackNumber = 1) { return [string]$(Get-PSCallStack)[$StackNumber].FunctionName.ToUpper() } static [string] GenerateLogDir($logDir) { try { if (!(Test-Path -Path $logDir)) { New-Item $logDir -ItemType Directory } return $logDir } catch { Write-Host -fore Red "FAILURE: $($PSItem.ToString())" return $null } } } class HouseKeeping { static [string] ParseCurrentUser($scriptProps){ # Get logged on user instead of run as user $currentUser = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object UserName # Remove domain prefix from user account string $currentUser = ($currentUser -creplace '^[^\\]*\\', '').Replace('}', '') # Force current user if arg was passed if ($scriptProps.ForceCurrentUser -ne "") { $currentUser = $scriptProps.ForceCurrentUser } return $currentUser } static [void] FinishScriptExecution($startTime){ [Logger]::Logger.LogDisplay("Script Finished", "Cyan") $endTime = Get-Date [Logger]::Logger.LogDisplay("Total script time: $(($endTime - $startTime).TotalSeconds)", "Cyan") Exit } static [void] StopOnFailure($exitMessage) { if ($null -ne [Logger]::Logger) { [Logger]::Logger.LogDisplay("$exitMessage | Stopping script execution", "Red") } Exit } static [void] EnsureScriptElevation(){ # Check if the script is running with administrative privileges $isAdmin = [bool](([Security.Principal.WindowsPrincipal] [System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')) [Logger]::Logger.Log("Script run as admin: $isAdmin") if (-not $isAdmin) { # Relaunch the script with administrative privileges [Logger]::Logger.Log("Script isn't running with elevated permissions, attempting to elevate") $scriptContext = "-File `"$($MyInvocation.MyCommand.Path)`" $($MyInvocation.UnboundArguments)" Start-Process -FilePath PowerShell.exe -Verb RunAs -ArgumentList "-NoProfile",$scriptContext [Logger]::Logger.LogDisplay("Unable to auto-elevate script, please re-run the script with elevated/admin permissions", "Red") exit } [Logger]::Logger.LogDisplay("Script has admin permissions! Continuing script execution", "Green") } } class IO { static [void] CleanupOldLogs($scriptProps){ # Set variables for file count display & file cleanup by age $oldLogCounter = 0 $oldFileFilter = (Get-Date).AddDays("-$($scriptProps.OldFileFilterDays)") # Remove existing/previous log files if they exist try{ $fileLogSearch = Get-ChildItem -Path $scriptProps.PathLogs -Filter *.log -ErrorAction SilentlyContinue | Where-Object { $_.CreationTime -lt $($oldFileFilter) } foreach ($oldFile in $fileLogSearch) { [Logger]::Logger.Log("Removing old log file: $($oldFile)") Remove-Item $oldFile.FullName $oldLogCounter++ } } catch{ [Logger]::Logger.LogDisplay("FAILURE: $($PSItem.ToString())", "Red") } # Report old file cleanup results if ($oldLogCounter -eq 0) { [Logger]::Logger.Log("No old log files found") } else { [Logger]::Logger.LogDisplay("Removed $($oldLogCounter) old log files", "DarkGray") } } static [bool] RemoveFolderIfExist($folderPath){ try { if (Test-Path -PathType Container $folderPath) { Remove-Item $folderPath -Recurse -Force -ErrorAction SilentlyContinue [Logger]::Logger.Log("Removed folder recursively: $folderPath") return $true } else { [Logger]::Logger.Log("Folder doesn't exist, skipping removal: $folderPath") return $true } } catch { [Logger]::Logger.LogDisplay("FAILURE: $($PSItem.ToString())", "Red") return $false } } static [bool] CreateFolderIfNotExist($folderPath){ # Create folder path if it doesn't exist if (!(Test-Path -PathType Container $folderPath)) { try { New-Item -Path $folderPath -ItemType "directory" | Out-Null [Logger]::Logger.Log("Created non-existant folder: $folderPath") return $true } catch { [Logger]::Logger.LogDisplay("FAILURE: $($PSItem.ToString())", "Red") return $false } } return $false } } class WindowsSearch { static [bool] DisableWebSearch(){ try { Set-ItemProperty -Path 'HKCU:\SOFTWARE\Policies\Microsoft\Windows' -Name 'DisableSearchBoxSuggestions' -Value '1' -Type Dword return $true } catch { return $false } } static [bool] DecoupleCortana(){ try { New-Item -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\' -Name 'Windows Search' | Out-Null New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search' -Name 'AllowCortana' -PropertyType DWORD -Value '0' | Out-Null return $true } catch { [Logger]::Logger.LogDisplay("Cortana Decouple Failure: $($PSItem.ToString())", "Red") return $false } } static [bool] KillSearchProcess(){ try { $searchProcessName = "searchui" if (Test-Path -Path "$Env:localappdata\Packages\Microsoft.Windows.Search_cw5n1h2txyewy"){ $searchProcessName = "searchapp" } $searchProcess = Get-Process $searchProcessName -ErrorAction SilentlyContinue $endTime = $(Get-Date).AddSeconds(2) $currentTime = $(Get-Date) while ((($endTime - $currentTime) -gt 0) -and $searchProcess){ $currentTime = $(Get-Date) $searchProcess = Get-Process $searchProcessName -ErrorAction SilentlyContinue if ($searchProcess){ $searchProcess.CloseMainWindow() | Out-Null Stop-Process -Id $searchProcess.Id -Force } $searchProcess = Get-Process $searchProcessName -ErrorAction SilentlyContinue } return $true } catch { return $false } } static [bool] ResetWindowsSearch(){ try { $searchRegistryKeys = @( "HKLM:\SOFTWARE\Microsoft\Cortana\Testability", "HKLM:\SOFTWARE\Microsoft\Search\Testability" ) foreach ($registryKey in $searchRegistryKeys){ $keyPointer = Get-Item -LiteralPath $registryKey -ErrorAction SilentlyContinue if ($null -ne $keyPointer){ Remove-Item -Path $registryKey -Recurse -ErrorAction SilentlyContinue [Logger]::Logger.LogDisplay("Cleared search registry key: $registryKey", "Gray") } } return $true } catch { return $false } } static [bool] CleanupCaches(){ try { $cacheList = @( "$Env:localappdata\Packages\Microsoft.Cortana_8wekyb3d8bbwe\AC\AppCache", "$Env:localappdata\Packages\Microsoft.Cortana_8wekyb3d8bbwe\AC\INetCache", "$Env:localappdata\Packages\Microsoft.Cortana_8wekyb3d8bbwe\AC\INetCookies", "$Env:localappdata\Packages\Microsoft.Cortana_8wekyb3d8bbwe\AC\INetHistory", "$Env:localappdata\Packages\Microsoft.Windows.Cortana_cw5n1h2txyewy\AC\AppCache", "$Env:localappdata\Packages\Microsoft.Windows.Cortana_cw5n1h2txyewy\AC\INetCache", "$Env:localappdata\Packages\Microsoft.Windows.Cortana_cw5n1h2txyewy\AC\INetCookies", "$Env:localappdata\Packages\Microsoft.Windows.Cortana_cw5n1h2txyewy\AC\INetHistory", "$Env:localappdata\Packages\Microsoft.Search_8wekyb3d8bbwe\AC\AppCache", "$Env:localappdata\Packages\Microsoft.Search_8wekyb3d8bbwe\AC\INetCache", "$Env:localappdata\Packages\Microsoft.Search_8wekyb3d8bbwe\AC\INetCookies", "$Env:localappdata\Packages\Microsoft.Search_8wekyb3d8bbwe\AC\INetHistory", "$Env:localappdata\Packages\Microsoft.Windows.Search_cw5n1h2txyewy\AC\AppCache", "$Env:localappdata\Packages\Microsoft.Windows.Search_cw5n1h2txyewy\AC\INetCache", "$Env:localappdata\Packages\Microsoft.Windows.Search_cw5n1h2txyewy\AC\INetCookies", "$Env:localappdata\Packages\Microsoft.Windows.Search_cw5n1h2txyewy\AC\INetHistory" ) $pathsCleaned = 0 foreach ($path in $cacheList){ if (Test-Path -Path $path){ Remove-Item -Recurse -Force $path -ErrorAction SilentlyContinue $pathsCleaned++ } } [Logger]::Logger.LogDisplay("Cleaned $pathsCleaned windows search caches", "Gray") return $true } catch { return $false } } } #endregion