I just noticed that Sean McLeod created a nice cmdlet for PowerShell that lets you query Windows Desktop Search from the PowerShell Command Line. I used to use Windows Desktop Search but got tired of it hanging and switched to, and settled on, for now, Google Desktop Search and I've been very happy with it.
After reading this I said, I'd like to created a cmdlet for Google Desktop Search, but who has the time? (Maybe I'll do it later)
I figured I could do something quick and dirty though. I took a look at the Google Desktop API Developer's Guide and saw some JavaScript samples for their psycho and obscure COM API. (Google does nearly everything in C++. Even their .NET sample code smells like C++.)
I did this at the PowerShell command line, as a client of the Google Desktop Search has to "register" itself, and I got stuck:
PS>$registrar = new-object -com "GoogleDesktop.Registrar"PS>$regArray = "Title","Searching GDS from PowerShell","Descripton","For Fun","Icon","My Icon@1"PS>$regId = [System.Guid]::NewGuid().ToString("B").ToUpper()PS>$registrar.StartComponentRegistration($regId,$regArray)Exception calling "StartComponentRegistration" with "2" argument(s): "The component description must contain a SAFEARRAY of six or eight VARIANTs" At line:1 char:38 + $registrar.StartComponentRegistration( <<<< $regId,$regArray)
...and they lost me at SAFEARRAY. I don't know why this little .NET array didn't get marshalled correctly. Probably because it's marshalled as a SAFEARRAY of BSTRs instead. Anyway, too hard, patience waning.
But, hey, GDS runs a local web server, right? So I should be able to query it locally. The docs say:
The search query URL, including your security token, is stored in the registry at:HKEY_CURRENT_USER\Software\Google\Google Desktop\API\search_urlTo use the example above, the stored query URL would be something like:http://127.0.0.1:4664/search&s=1ftR7c_hVZKYvuYS-RWnFHk91Z0?q=
So from PowerShell:
PS>(Get-Item "HKCU:\Software\Google\Google Desktop\api").GetValue("search_url")http://127.0.0.1:4664/search&s=1ftR7c_hVZKYvuYS-RWnFHk91Z0?q=
Cool. Then I can add the query after the "q=" and "&format=xml" to get something that PowerShell can sink its teeth into. I'll need to UrlEncode the query.
UPDATE: Note the num= and flags= at the end of the queryString. That indicates that we are only interested in files and we'll take as many as 1000.
PS>$query = "PowerShell"PS>$searchUrl = (Get-Item "HKCU:\Software\Google\Google Desktop\api").GetValue("search_url")PS>[System.Reflection.Assembly]::LoadWithPartialName("System.Web") > $nullPS>$newQuery = $searchUrl + [System.Web.HttpUtility]::UrlEncode($query) + "&format=xml&flags=576&num=1000"PS>$webclient = new-object System.Net.WebClientPS>$resultsXml = [xml]($webclient.DownloadString($newQuery))PS>$resultsXml.results.result | select title, snippet, category title snippet category----- ------- --------C:\Temp\fusionlogs\Defa... NET\Framework\v2.0.5072... fileWindows <b>PowerShell</... Windows <b>PowerShell</... webdel.icio.us/shanselman del.icio.us/shanselman ... webC:\Temp\fusionlogs\Defa... NET\Framework\v2.0.5072... fileWindows Desktop Search ... Windows Desktop Search ... webUser Profile: Greg Borota User Profile: Greg Boro... webdel.icio.us/shanselman del.icio.us/shanselman ... webMicrosoft Survey  Microsoft Survey Th... webDiscussions in Windows ... Discussions in Windows ... webSearch Results: <b>powe... Search Results: <b>powe... web
PS>$query = "PowerShell"PS>$searchUrl = (Get-Item "HKCU:\Software\Google\Google Desktop\api").GetValue("search_url")PS>[System.Reflection.Assembly]::LoadWithPartialName("System.Web") > $nullPS>$newQuery = $searchUrl + [System.Web.HttpUtility]::UrlEncode($query) + "&format=xml&flags=576&num=1000"PS>$webclient = new-object System.Net.WebClientPS>$resultsXml = [xml]($webclient.DownloadString($newQuery))PS>$resultsXml.results.result | select title, snippet, category
title snippet category----- ------- --------C:\Temp\fusionlogs\Defa... NET\Framework\v2.0.5072... fileWindows <b>PowerShell</... Windows <b>PowerShell</... webdel.icio.us/shanselman del.icio.us/shanselman ... webC:\Temp\fusionlogs\Defa... NET\Framework\v2.0.5072... fileWindows Desktop Search ... Windows Desktop Search ... webUser Profile: Greg Borota User Profile: Greg Boro... webdel.icio.us/shanselman del.icio.us/shanselman ... webMicrosoft Survey  Microsoft Survey Th... webDiscussions in Windows ... Discussions in Windows ... webSearch Results: <b>powe... Search Results: <b>powe... web
Cool. So now I want just the files, and I want them to be actual FileInfo objects.
PS> $resultsXml.results.result | where { $_.category -eq "file"} | foreach-object { get-item $_.url } Directory: Microsoft.PowerShell.Core\FileSystem:: C:\Temp\fusionlogs\Default\powershell.exe Mode LastWriteTime Length Name---- ------------- ------ -----a--- 7/2/2006 3:08 AM 1625 System.Web.HTM-a--- 7/2/2006 2:36 AM 1695 System.Windows.Forms.HTM ...etc...
PS> $resultsXml.results.result | where { $_.category -eq "file"} | foreach-object { get-item $_.url }
Directory: Microsoft.PowerShell.Core\FileSystem:: C:\Temp\fusionlogs\Default\powershell.exe
Mode LastWriteTime Length Name---- ------------- ------ -----a--- 7/2/2006 3:08 AM 1625 System.Web.HTM-a--- 7/2/2006 2:36 AM 1695 System.Windows.Forms.HTM
...etc...
Now I'll save the whole file in my path in a file called "search-googledesktop.ps1" and change the first line to "$query = $args" to take command line arguments.
Since the objects that are coming out of this script are real FileInfo objects retrieved with the call to get-item in the final line, I can use my script in a larger pipeline like this:
.\search-googledesktop.ps1 "powershell" | get-content
or
.\search-googledesktop.ps1 "powershell" | get-location
or delete any file that has my personal information like a social security number in it.
.\search-googledesktop.ps1 "123" | remove-item -whatif
Seriously, once you have an indexer on your system, search for your Social Security Number. You'd be amazed at the old Excel sheets and crap that number gets into.
Anyway, cool. Sure beats their crap COM API.
File Attachment: search-googledesktop.ps1 (474 bytes)
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.
Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.