'Better' way to run Explorer to find files?

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
DaveT1
Posts: 224
Joined: 07 Oct 2014, 11:23

'Better' way to run Explorer to find files?

Post by DaveT1 » 04 Jul 2022, 04:05

Hi,

I want to programmatically run Explorer and use the search box to recursively search for files matching the search text.

I've got it working by doing this:

Code: Select all

;Start Windows Explorer at the designated folder and wait until it's active.
run("D:\AutoHotKey\My AHK projects\PhotoScanner\Test photo files")
WinWaitActive("Test photo files")
Send "^f" 
sleep 1000
send "test text"
sleep 1000
send "{Enter}"
;Wait until the active window (ie., Explorer) is closed before continuing the script.
WinWaitClose WinExist("A")
But I thought there were better ways than just sending keystrokes!? So in an effort to learn, I've spent a fruitless :headwall: couple of days reading around COM, PostMessage and Control Functions topics to see if I could figure out a 'better' way. But I have endup back where I started (ie., the code above).

I'm sure it must be possible to do this via COM or PostMessage or Control Functions, but clearly I'm lacking the intellectual wherewithall to figure it out.

Would someone be kind enough to point me in the right direction / where to find explainers that will help a relative newbie?

Many thanks.

CptRootBeard
Posts: 26
Joined: 16 Nov 2020, 14:47
Contact:

Re: 'Better' way to run Explorer to find files?

Post by CptRootBeard » 06 Jul 2022, 09:04

I personally couldn't find a way to run a simple search directly through the Shell object, and anything I tried with control send wasn't any better than what you have.
You still have a couple (very similar) options here. Both make use of the search-ms application protocol, and thus will open the search in a new window.

Code: Select all

;;The shell application can help with a lot of automation within Windows itself
oShell := COMObject("Shell.Application")

searchTitle := "Looking for Potatoes...."
searchText := "potato"
searchLoc := A_MyDocuments

;;Note that this has to be a URL, so we'll replace spaces, colons, slashes
;;(You should use a complete, proper URL encoder)

buildSearchURL(needleText, title, loc){
	cleanForURL(stringIn){
		noSpaces := StrReplace(stringIn, " ", "%20")
		noColons := StrReplace(noSpaces, ":", "%3A")
		noSlashes := StrReplace(noColons, "\", "%5C")
		return noSlashes
	}
	
	URL := "search-ms:displayname="
	URL .= cleanForURL(title) . "&crumb=System.Generic.String"
	URL .= cleanForURL(":" . needleText) . "&crumb=location:"
	URL .= cleanForURL(loc)
	
	return URL
}

searchURL := buildSearchURL(searchText, searchTitle, searchLoc)
msgbox(searchURL,"Search URL Preview")

;;If a File Explorer window exists, it will navigate to the search URL.
;;(This will open a new window anyway)
if oShell.Windows.Count {
	oShell.Windows(0).Navigate(searchURL)
} else {
	msgbox("There are no explorer windows, so we'll move on.")
}

;;You can also just Run the URL, like so
msgbox("Press OK to search for tomatoes with Run().")
searchURL := buildSearchURL("tomato", "Looking for Tomatoes...", searchLoc)
Run(searchURL)

;;Using the search-ms protocol will ALWAYS open a new window for a new search -
;;The protocol handler registered by default uses the /separate flag
Do some searches in explorer, then click the address bar when viewing the results. You'll get a URL that you can parse out to what you need.
Running or navigating to that URL will open a new explorer window with a search underway. I'd lean towards Run(), personally.

As with most things, be mindful of how you use the protocol - it's recently been abused by bad actors.
Because of this, some systems may have the protocol disabled.

DaveT1
Posts: 224
Joined: 07 Oct 2014, 11:23

Re: 'Better' way to run Explorer to find files?

Post by DaveT1 » 07 Jul 2022, 04:19

Hi @CptRootBeard

Wow, this is such a good reply - thanks :thumbup:. Taking your thoughts in turn:
I personally couldn't find a way to run a simple search directly through the Shell object, and anything I tried with control send wasn't any better than what you have.
Ah, at least it's not just me then ;).
You still have a couple (very similar) options here. Both make use of the search-ms application protocol, and thus will open the search in a new window.
Woa, this is all totally new to me, but turns out to be perfect :D :D .
Do some searches in explorer, then click the address bar when viewing the results. You'll get a URL that you can parse out to what you need.
Running or navigating to that URL will open a new explorer window with a search underway.
I so love it when someone points out 'goodies' on a thing that I've been doing for years without realising it was there - who knew clicking in the address bar would reveal all this goodness!!
I'd lean towards Run(), personally.
Right, I experimented with all the code you kindly provided. It seemed to me (naively maybe?) that the Run() approach seems to work just as well. And I'm wanting a new Explorer window with each search, so it's also perfect in that regard. So I pared down your code, and slightly re-jigged it for my purpose and it seems to work brilliantly. Here it is in case it's useful for anyone else:

Code: Select all

searchText := "IMG-20170518-WA0000"
searchLoc := "D:\AutoHotKey\My AHK projects\PhotoScanner\Test photo files"
searchLocShort := "\Test photo files"
searchTitle := "Looking for <" . searchText . "> in <" . searchLocShort . ">"

URL := "search-ms:displayname="
URL .= cleanForURL(searchTitle) . "&crumb=System.Generic.String"
URL .= cleanForURL(":" . searchText) . "&crumb=location:"
URL .= cleanForURL(searchLoc)

Run(URL)

cleanForURL(stringIn) {
	FormattedURL := StrReplace(stringIn, " ", "%20")	;remove spaces.
	FormattedURL := StrReplace(FormattedURL, ":", "%3A")	;remove colons.
	FormattedURL := StrReplace(FormattedURL, "\", "%5C")	;remove backslashes.
	return FormattedURL
	}
As with most things, be mindful of how you use the protocol - it's recently been abused by bad actors.
Because of this, some systems may have the protocol disabled.
Okay, I'm not in any loop that would tell me about this. Shame that there are always peeps looking for bad outcomes! Anyway the good news is that it seems to work magnificently on my machine.

So, huge thanks for taking the time to educate me on this protocol and for providing code that does the job right-off - very much appreciated :bravo: .

DaveT1
Posts: 224
Joined: 07 Oct 2014, 11:23

Re: 'Better' way to run Explorer to find files?

Post by DaveT1 » 03 Aug 2022, 09:26

For my own purposes I recast this as a library function, and slightly extended the search-ms options to allow exclusion of items during a search. Here it is:

Code: Select all

#Warn  ; Enable warnings to assist with detecting common errors.
SendMode "Input"  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir A_ScriptDir  ; Ensures a consistent starting directory.
SetTitleMatchMode 2 ;this allows comparison of substrings of Window Titles, rather than the full text.
#SingleInstance force ; This suppresses the warning dialogue that a newly launched script is already running.
DetectHiddenWindows True
Critical "Off"

#Requires AutoHotkey 2.0-

{ ;====S Test Code ====
; Example 1
SearchText := "20170311_171420"
; SearchTextNOT := "*.jpg.jpg"
SearchTextNOT := ""
SearchLocation := "D:\AutoHotKey\My AHK projects\PhotoScanner\Test photo files"
SearchLocShort := "\Test photo files"
SearchTitle := "Looking for <" . SearchText . "> in <" . SearchLocShort . ">"
URL := mylib_BuildExplorerSearchURL(SearchTitle, SearchText, SearchTextNOT, SearchLocation)

; The above search information produces the following string. The line returns below have been
; manually added here to help readability of what is a continuous string (as seen by MsgBox(URL)):
; URL = search-ms:
;        displayname=Looking%20for%20<20170311_171420>%20in%20<%5CTest%20photo%20files>&
;        query=20170311_171420&
;        crumb=filename:NOT"*.jpg.jpg"&
;        crumb=location:D%3A%5CAutoHotKey%5CMy%20AHK%20projects%5CPhotoScanner%5CTest%20photo%20files&

Run(URL)

ExitApp
} ;====\S

mylib_BuildExplorerSearchURL(pSearchTitle, pNeedle, pNeedleNOT, pLocation){
   /* Script Information...
   Description:
      This function returns a string in the Explorer search-ms protocol format.
      Running the URL will start a new instance of Explorer and instantiate a search
      based on this URL.
      This is a search in Explorer, so presumably is always a search on filenames?

      Kudos to @CptRootBeard for the idea. Who also says:
         - "Using the search-ms protocol will ALWAYS open a new window for a new search.
            The protocol handler registered by default uses the /separate flag".
         - "Note that this has to be a URL, so we'll replace spaces, colons, slashes
            (You should use a complete, proper URL encoder)"

   Input/Output:
      Input:            n/a

   Basic data:
      Name              mylib_BuildExplorerSearchURL.ahk
      AHK Version       AutoHotkey_2.0-beta.3
      OS Version        Microsoft Windows 10 Pro
      Author            DaveT1 (AUTOHOTKEY forums)
      Topic             <https://www.autohotkey.com/boards/viewtopic.php?f=82&t=106063&e=1&view=unread#unread>

   Parameters:
      pSearchTitle      The title of the search.
      pNeedle           The text to search for.
      pNeedleNOT        What NOT to include in the search.
      pLocation         The location folder to search in.

   Revision History:
      vX(new or changed functionality / may not be backwards compatible).Y(minor revisons).
      [(+) NEW, (*) CHANGED, (!) FIXED]
      v1.0 07-Jul-22	+Initial release.
      v1.1 14-Jul-22 +Added option for NOT excluding items in the results of the search.
                     *Slightly altered the URL string formation to "query" as this seems to permit NOT option.
                     *Improved naming of variables.
   */

   if (pNeedleNOT = "") {
      sNeedleNOTString := ""
      }
   else {
      sNeedleNOTString := "crumb=filename:" . "NOT`"" . CleanStringForURL(pNeedleNOT) . "`"" . "&"
      }

   Local URL := "search-ms:"
                  . "displayname=" . CleanStringForURL(pSearchTitle) . "&"
                  . "query=" . CleanStringForURL(pNeedle) . "&"
                  . sNeedleNOTString
                  . "crumb=location:" . CleanStringForURL(pLocation) . "&"

   Return URL

   CleanStringForURL(pStringIn) {
      tFormattedURL := StrReplace(pStringIn, " ", "%20")	      ;replace spaces.
      tFormattedURL := StrReplace(tFormattedURL, ":", "%3A")	;replace colons.
      tFormattedURL := StrReplace(tFormattedURL, "\", "%5C")	;replace backslashes.
      return tFormattedURL
      }

}
Hope it's useful to someone.

Post Reply

Return to “Ask for Help (v2)”