Find-File.psm1

<#
.SYNOPSIS
    Finding files by querying the windows search index database over SQL Statements
.DESCRIPTION
    This function offers a method for querying the Windows Search Index Database
    over simple command line arguments, displayed in an interactive GUI for easy use.
    Alternatively it offers a legacy mode if used without the recommended external module.
    Results can also be displayed in Raw mode which does not offer any interactive interface
    but due to it beeing directly handled by the Dataset Adapter itself, it is able to Display
    Large amounts of Data instantly.
.PARAMETER Query
    Specify search term to be used, Allows for Wildcard (or wildcard only) and does accept Arrays
.PARAMETER Path
    Specify Path to search
.PARAMETER Exclude
    Excludes a specific path, exclude can be applied to single Path inside whole search Path
.PARAMETER Type
    Defines what File type should be searched for
.PARAMETER Size
    Specify a Size or Size Range for Items to be searched, sizes must be specified
    with operators: < = > (Supports MB, GB etc.)
.PARAMETER Order
    Specify Order in which results should be sorted by
.PARAMETER View
    Selects the Mode how Results should be Displayed,
    Single/Multi select Interactive Shell, Legacy for external GUI, Raw for fast but not interactable GUI
.PARAMETER Mode
    Selects the Execution Mode in regard to what should happen after a file has been selected
    Shell: Execute file with Windows Explorer equally to what happens by opening a file in File Explorer
    Console: Changes directory to the Path where selected result resides
    None: Does not execute a file after it has been selected
    Files always get stored inside $Item variable
.PARAMETER FreeText
    Switch that causes query to query inside documents and raw text files instead of searching for files themself
.PARAMETER Force
    Switch forces to always use Interactive shell GUI even if there are more than 1000 Results
    (Depending on amount may slow down session considerably)
.PARAMETER Ascending
    Displays the selected order in Ascending direction
.PARAMETER Descending
    Displays the selected order in Descending direction
.INPUTS
    String to be used as Query, options for extensive filters
.OUTPUTS
    Results displayed inside interactive shell GUI for easy selection
.EXAMPLE
    Find-File *test*
    Searches for Files matching query *test*
.EXAMPLE
    Find-File * -Size ">1GB" -Order Size -Ascending
    Searches for Files over 1GB in size, sorted by Size and displayed in Ascending Direction
.EXAMPLE
    Find-File Konfiguration -FreeText -Path '$home\OneDrive\'
    Searches Text documents inside specified Path for string "Konfiguration"
.EXAMPLE
    Find-File *chrome*,*firefox* -Path '$home\Desktop\' -Type link
    Searches for any shortcuts residing on the Desktop, matching *chrome* or *firefox*
.NOTES
    Full Script and Function by CZ
    Array conversion script by TheMadTechnician
    Interactive Shell GUI by PowerShell Team
.LINK
    PowerShell 7.1.4: https://github.com/PowerShell/PowerShell/releases/tag/v7.1.4
    Interactive Shell GUI: https://www.powershellgallery.com/packages/Microsoft.PowerShell.ConsoleGuiTools/0.6.2
#>

#Find File v2.3 (SQL)
function Find-File {
    Param(
        [CmdletBinding()]
        #Search Query
        [Parameter(Mandatory=$true,
        Position=0,
        ValueFromPipeline=$true,
        HelpMessage="Searchterm")]
        [SupportsWildcards()]
        [Alias("Q","Search")]
        [string[]]$Query,
        #Search Path
        [parameter(Mandatory=$false,
        HelpMessage="Path to search")]
        [ValidateScript({$_ | ForEach-Object {Test-Path $_}})]
        [Alias("P")]
        [string[]]$Path,
        #Search Location to Exclude
        [parameter(Mandatory=$false,
        HelpMessage="Exclude specified Path")]
        [ValidateScript({$_ | ForEach-Object {Test-Path $_}})]
        [Alias("exc")]
        [string[]]$Exclude,
        #Content Type
        [parameter(Mandatory=$false,
        HelpMessage="Specify content type to search for")]
        [ValidateSet("folder", "link", "program", "document", "picture", "video", "music", "playlist", "contact", "communication")]
        [Alias("T")]
        [string[]]$Type,
        #File Size
        [parameter(Mandatory=$false,
        HelpMessage="Specify filesize(bytes) Use following Operators < = > (File size only works with single query)")]
        [Alias("S","FileSize")]
        [string[]]$Size,


        #Application Modes
        #Order By
        [parameter(Mandatory=$false,
        HelpMessage="By which row to sort by (Default is Path)")]
        [ValidateSet("FileName", "Type", "Size", "Path", "Date")]
        [Alias("O")]
        [string]$Order,
        #Output View Mode
        [parameter(Mandatory=$false,
        HelpMessage="Output View Type (only Single and Multi Supports direct execution)")]
        [ValidateSet("Single", "Multi", "Raw", "Legacy")]
        [Alias("V")]
        [string]$View,
        #Execution Mode
        [parameter(Mandatory=$false,
        HelpMessage="Execution mode after Item has been selected")]
        [ValidateSet("Shell", "Console", "None")]
        [Alias("M")]
        [string]$Mode,


        #Switches
        #FreeText Switch
        [parameter(Mandatory=$false,
        HelpMessage="Search Inside Document Text Instead")]
        [Alias("FT","text")]
        [switch]$FreeText,
        #Force Switch
        [parameter(Mandatory=$false,
        HelpMessage="Always Display Inside Interactive GUI, ignore too many results")]
        [Alias("F")]
        [switch]$force,
        #Ascending Switch
        [parameter(Mandatory=$false,
        HelpMessage="Order Ascending",
        ParameterSetName="Direction")]
        [Alias("ASC")]
        [switch]$Ascending,
        #Descending Switch
        [parameter(Mandatory=$false,
        HelpMessage="Order Descending",
        ParameterSetName="Direction")]
        [Alias("DESC")]
        [switch]$Descending
    )


    #Check if Search Service is Running
    IF ((Get-Service -Name "WSearch").Status -ne "Running") {
        Write-Host "Windows Search Service not Running" -ForegroundColor Red
        break
    }
    #Import Dependency
    Import-Module Microsoft.PowerShell.ConsoleGuiTools -MinimumVersion 0.6.2

    #Init
    $WHERE = ""; $Filters = $null; $Filters = @(); $IncludePaths = @(); $ExcludePaths = @()
    $QueryTerm = $Query -replace "\*","%"

    #Search Query
    IF ($null -ne $PSBoundParameters.Query){
        IF ($FreeText) {
            $Filters += $QueryTerm | Join-String -OutputPrefix "FREETEXT " -Separator " OR FREETEXT " -FormatString "('{0}')"
        } else {
            $Filters += $QueryTerm | Join-String -OutputPrefix "System.FileName LIKE " -Separator " OR System.FileName LIKE " -FormatString "'{0}'"
        }

    }
    #File Type
    IF ($null -ne $PSBoundParameters.Type){
        $Filters += $PSBoundParameters.Type | Join-String -OutputPrefix "System.kind LIKE " -Separator " OR System.kind LIKE " -FormatString "'{0}'"
    }
    #Path
    IF ($null -ne $PSBoundParameters.Path){
        $IncludePaths += $PSBoundParameters.Path | ForEach-Object {Join-Path $_ -ChildPath "\%"}
        $Filters += $IncludePaths | Join-String -OutputPrefix "System.ItemPathDisplay LIKE " -Separator " OR System.ItemPathDisplay LIKE " -FormatString "'{0}'"
    }
    #Path to exclude
    IF ($null -ne $PSBoundParameters.Exclude){
        $ExcludePaths += $PSBoundParameters.Exclude | ForEach-Object {Join-Path $_ -ChildPath "\%"}
        $Filters += $ExcludePaths | Join-String -OutputPrefix "System.ItemPathDisplay NOT LIKE " -Separator " AND System.ItemPathDisplay NOT LIKE " -FormatString "'{0}'"
    }
    #File Size
    IF ($null -ne $PSBoundParameters.Size){
        #File Size not working when multible queries ???
        $lt=$null;$gt=$null;$eq=$null;$FileSize=$null
        $PSBoundParameters.Size | ForEach-Object {
            IF ($_ -like "<*"){[long]$lt = $_ -replace "<",""}
            IF ($_ -like ">*"){[long]$gt = $_ -replace ">",""}
            IF ($_ -like "=*"){[long]$eq = $_ -replace "=",""}
        }
        IF ($eq){
            $FileSize = "=$eq"
        } elseif ($lt -and $gt) {
            IF ($lt - $gt -lt 0) {
                Write-Host "Specified FileSize Range invalid"
                #$FileSize = "<$lt"
                break
            } else {
                $FileSize = "<$lt",">$gt"
            }
        } elseif ($lt) {
            $FileSize = "<$lt"
        } elseif ($gt) {
            $FileSize = ">$gt"
        }
        $Filters += $FileSize | Join-String -OutputPrefix "System.size " -Separator " AND System.size "
    }
    #Order By
    IF ($null -ne $PSBoundParameters.Order){
        IF ($PSBoundParameters.Order -eq "FileName") {$OrderBy = "System.FileName"}
        IF ($PSBoundParameters.Order -eq "Type") {$OrderBy = "System.KindText"}
        IF ($PSBoundParameters.Order -eq "Size") {$OrderBy = "System.size"}
        IF ($PSBoundParameters.Order -eq "Path") {$OrderBy = "System.ItemPathDisplay"}
        IF ($PSBoundParameters.Order -eq "Date") {$OrderBy = "System.DateAccessed"}

    } else {$OrderBy = "System.ItemPathDisplay"}
    #Order Direction
    #$Direction = "DESC"
    IF ($Ascending) {$Direction = "DESC"} elseif ($Descending) {$Direction = "ASC"} else {$Direction = ""}
    Write-Debug "Order Direction: $Direction"

    $i=0; $Filters | ForEach-Object {$i++;Write-Debug "Filter $i : $_"}
    #Build Filter
    IF ($null -ne $Filters){
        $WHERE = $Filters | Join-String -Separator " AND "
    }
    #Build Select Statement

    IF ($order -eq "Date") {
        $Select = "System.FileName, System.KindText, system.size, System.DateAccessed, System.ItemPathDisplay"
    } else {
        $Select = "System.FileName, System.KindText, system.size, System.ItemPathDisplay"
    }

    #Build SQL Statement
    $SQLQuery = "SELECT $Select FROM SYSTEMINDEX WHERE $WHERE AND SYSTEM.SEARCH.STORE LIKE 'File' ORDER BY $OrderBy $Direction"
    Write-Debug $SQLQuery
    #Search Index DB Connector
    $Connector = [System.Data.OleDb.OleDbConnection]::new("Provider=Search.CollatorDSO;Extended Properties='Application=Windows';")
    $Command = [System.Data.OleDb.OleDbCommand]::new($SQLQuery,$Connector)
    $Adapter = [System.Data.OleDb.OleDbDataAdapter]::new($Command)
    $DS = [System.Data.DataSet]::new("SearchQuery")
    $ResultCount = $Adapter.Fill($DS)
    Write-Debug "Query matches: $ResultCount"

    #Catch Too many Results
    IF ($force) {
        Write-Host "This might take a while..."
    } elseif ($ResultCount -gt 750){
        [int]$DefaultChoice = 1
        $Yes = [System.Management.Automation.Host.ChoiceDescription]::new("&Yes", "Continue with raw output")
        $No = [System.Management.Automation.Host.ChoiceDescription]::new("&No", "Stop")
        $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
        $choice = $host.ui.PromptForChoice("Too many matches ($ResultCount Results)","Continue with Raw View?", $options,$DefaultChoice)
        if ($choice -eq 0) {
            $View = "Raw"
        } else {
            break
        }
    }
    #Display Error if no Results returned
    IF ($ResultCount -eq 0){
        Write-Debug "No matches"
        Write-Host "No matching Items found inside IndexDB" -ForegroundColor Red
    }
    #Convert Table to Array
    #By TheMadTechnician
    IF ($View -ne "raw") {
        $ResultsArray = ForEach($Row in $DS.Tables[0].Rows){
            $Record = New-Object PSObject
            ForEach($Col in $DS.Tables[0].Columns.ColumnName){
                Add-Member -InputObject $Record -NotePropertyName $Col -NotePropertyValue $Row.$Col
            }
            $Record
        }
    }
    #Set Mode Debug
    IF ($View -eq ""){Write-Debug "Set View Mode: Nothing"} else {Write-Debug "Set View Mode: $View"}
    IF ($Mode -eq ""){Write-Debug "Set Execution Mode: Nothing"} else {Write-Debug "Set Execution Mode: $Mode"}
    #Display Output
    $selection = ""
    IF ($View -eq "Single"){
        #View Single
        $ResultsArray | Out-ConsoleGridView -Title "Windows Search Query: $Query ($ResultCount Results)" -OutputMode Single -OutVariable selection | Out-Null
    } elseif ($View -eq "Multi") {
        #View Multi
        $ResultsArray | Out-ConsoleGridView -Title "Windows Search Query: $Query ($ResultCount Results)" -OutputMode Multiple -OutVariable selection | Out-Null
    } elseif ($View -eq "Raw") {
        #View Raw
        $DS.Tables[0] | Select-Object -ExpandProperty SYSTEM.ITEMPATHDISPLAY
        break
    } elseif ($View -eq "Legacy") {
        #View Legacy
        $ResultsArray | Out-GridView -Title "Windows Search Query: $Query ($ResultCount Results)" -OutputMode Single -OutVariable selection | Out-Null
    } else {
        #View Default (Single) if nothing specified
        $ResultsArray | Out-ConsoleGridView -Title "Windows Search Query: $Query ($ResultCount Results)" -OutputMode Single -OutVariable selection | Out-Null
    }
    #Wait for Selection
    While ($null -eq $selection){}

    #Selection
    IF ($null -ne $selection.'SYSTEM.ITEMPATHDISPLAY'){
        $Script:Item = $selection.'SYSTEM.ITEMPATHDISPLAY'
        Write-Host "Selected item stored inside `$Item var" -ForegroundColor Green
        $item; "`n"
        IF ($Mode -eq "Shell") {
            #Selection Shell
            $selection | ForEach-Object {Start-Process explorer.exe $_.'SYSTEM.ITEMPATHDISPLAY'}
        } elseif ($Mode -eq "Console") {
            #Selection Console
            Set-Location -Path (Split-Path $Item)
        } elseif ($Mode -eq "None") {
            #Selection None
        } else {
            #Selection Default (Shell)
            $selection | ForEach-Object {Start-Process explorer.exe $_.'SYSTEM.ITEMPATHDISPLAY'}
        }
    }
}
Export-ModuleMember -Function Find-File