Skip to content

Instantly share code, notes, and snippets.

@arebee
Last active September 14, 2023 23:08
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save arebee/1928da03047aee4167fabee0f501c72d to your computer and use it in GitHub Desktop.
Save arebee/1928da03047aee4167fabee0f501c72d to your computer and use it in GitHub Desktop.
Use Windows Search from PowerShell
function Search-FileIndex {
<#
.PARAMETER Path
Absoloute or relative path. Has to be in the Search Index for results to be presented.
.PARAMETER Pattern
File name or pattern to search for. Defaults to *.*. Aliased to Filter to ergonomically match Get-ChildItem.
.PARAMETER Text
Free text to search for in the files defined by the pattern.
.PARAMETER Recurse
Add the parameter to perform a recursive search. Default is false.
.PARAMETER AsFSInfo
Add the parameter to return System.IO.FileSystemInfo objects instead of String objects.
.SYNOPSIS
Uses the Windows Search index to search for files.
.DESCRIPTION
Uses the Windows Search index to search for files. SQL Syntax documented at https://msdn.microsoft.com/en-us/library/windows/desktop/bb231256(v=vs.85).aspx Based on https://blogs.msdn.microsoft.com/mediaandmicrocode/2008/07/13/microcode-windows-powershell-windows-desktop-search-problem-solving/
.OUTPUTS
By default one string per file found with full path.
If the AsFSInfo switch is set, one System.IO.FileSystemInfo object per file found is returned.
#>
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true)]
[string]$Path,
[Parameter(Mandatory=$false,ParameterSetName="FullText")]
[Parameter(Mandatory=$false)]
[alias("Filter")]
[string]$Pattern = "*.*",
[Parameter(Mandatory=$false,ParameterSetName="FullText")]
[string]$Text = $null,
[Parameter(Mandatory=$false)]
[switch]$Recurse = $false,
[Parameter(Mandatory=$false)]
[switch]$AsFSInfo = $false
)
if($Path -eq ""){
$Path = $PWD;
}
$path = (Resolve-Path -Path $path).Path
$pattern = $pattern -replace "\*", "%"
$path = $path.Replace('\','/')
if ((Test-Path -Path Variable:fsSearchCon) -eq $false)
{
$global:fsSearchCon = New-Object -ComObject ADODB.Connection
$global:fsSearchRs = New-Object -ComObject ADODB.Recordset
}
$fsSearchCon.Open("Provider=Search.CollatorDSO;Extended Properties='Application=Windows';")
[string]$queryString = "SELECT System.ItemPathDisplay FROM SYSTEMINDEX WHERE System.FileName LIKE '" + $pattern + "' "
if ([System.String]::IsNullOrEmpty($Text) -eq $false){
$queryString += "AND FREETEXT('" + $Text + "') "
}
if ($Recurse){
$queryString += "AND SCOPE='file:" + $path + "' ORDER BY System.ItemPathDisplay"
}
else {
$queryString += "AND DIRECTORY='file:" + $path + "' ORDER BY System.ItemPathDisplay"
}
$fsSearchRs.Open($queryString, $fsSearchCon)
# return
While(-Not $fsSearchRs.EOF){
if ($AsFSInfo){
# Return a FileSystemInfo object
[System.IO.FileSystemInfo]$(Get-Item -LiteralPath ($fsSearchRs.Fields.Item("System.ItemPathDisplay").Value) -Force)
}
else {
$fsSearchRs.Fields.Item("System.ItemPathDisplay").Value
}
$fsSearchRs.MoveNext()
}
$fsSearchRs.Close()
$fsSearchCon.Close()
}
@salemsap
Copy link

please share sample parameters to check the functionality

@alvalea
Copy link

alvalea commented Nov 28, 2022

I had to make the following changes to make it work:

  1. Rename the function as searchFileIndex: It seems that function names starting with a capital letter are not valid in powershell. I also removed the dash character, just in case special characters are not allowed either.
  2. Call the function at the end of the script forwarding the script arguments as parameters: searchFileIndex @args

All parameters are optional, but this would be an example of calling the script:
SearchFileIndex.ps1 -Path Documentation -Pattern *.pdf -Text "Next release" -Recursive

@poundy
Copy link

poundy commented Dec 14, 2022

There's nothing needed to make this work as it stands @alvalea. This is a PowerShell Function. You can save the file (for example, as search-fileindex.ps1) then you can simply dot-source it and then use it in your other powershell scripts (or in your interactive session):

. c:\filepath\for\this\script\search-fileindex.ps1

then just invoke it like:
search-fileindex -path c:\here -pattern *.docx -recurse -text "Find this text"

I did add some parameters to set the connection and command timeouts to 0 so a long query didn't end with an error. If you want to do that, you can simply add these two lines prior to line 51 above (that opens the search connection)

$fsSearchCon.ConnectionTimeout=0
$fsSearchCon.CommandTimeout=0

@cpbotha
Copy link

cpbotha commented Mar 12, 2023

On my Windows 11 22624.1391 system, certain search strings (the name of a specific super small scale product) would yield the following error:

{Application Error}
The exception %s (0x
At C:\Users\cpbot\OneDrive\configs\powershell\search-fileindex.ps1:76 char:9
+         $fsSearchRs.MoveNext()
+         ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], COMException
    + FullyQualifiedErrorId : System.Runtime.InteropServices.COMException

Weirdly, for these strings, EOF would only evaluate correctly the second time. In other words, if I did e.g. Write-Host $fsSearchRs.EOF right before the while loop, it would correctly not enter. If I did not do that, it would enter, and then MoveNext would fail.

Other gibberish strings that also yield no results do correctly evaluate. This is currently reproducible.

To work around this, I changed the while loop to the following, so that it just gracefully yields no results:

While(-Not $fsSearchRs.EOF -and $fsSearchRs.Fields.Item("System.ItemPathDisplay").Value){

In this strange state, $fsSearchRs evals to the following on first eval:

Properties       : System.__ComObject
AbsolutePosition :
ActiveConnection : System.__ComObject
BOF              : True
Bookmark         :
CacheSize        : 1
CursorType       : 0
EOF              : True
Fields           : System.__ComObject
LockType         : 1
MaxRecords       : 0
RecordCount      : -1
Source           : SELECT System.ItemPathDisplay FROM SYSTEMINDEX WHERE System.FileName LIKE '%.%' AND FREETEXT('telesensi') AND SCOPE='file:C:/users/cpbot/OneDrive' ORDER BY System.ItemPathDisplay
AbsolutePage     : -1
EditMode         : 0
Filter           : 0
PageCount        : -1
PageSize         : 10
Sort             :
Status           :
State            : 1
CursorLocation   : 2
MarshalOptions   : 0
DataSource       : System.__ComObject
ActiveCommand    : System.__ComObject
StayInSync       : True
DataMember       :
Index            :

I'm leaving this here in case anyone else runs into similar issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment