{"id":8941,"date":"2012-06-25T00:01:00","date_gmt":"2012-06-25T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2012\/06\/25\/build-a-query-to-search-the-windows-index-from-powershell\/"},"modified":"2012-06-25T00:01:00","modified_gmt":"2012-06-25T00:01:00","slug":"build-a-query-to-search-the-windows-index-from-powershell","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/build-a-query-to-search-the-windows-index-from-powershell\/","title":{"rendered":"Build a Query to Search the Windows Index from PowerShell"},"content":{"rendered":"<p><b>Summary<\/b>: Guest blogger, James O&#8217;Neill, discusses using Windows PowerShell to build a query to search the Windows Index.\nMicrosoft Scripting Guy, Ed Wilson, is here. Today is Part One of three blogs written by guest blogger, James O&rsquo;Neill.\nJames O&#8217;Neill was born in the 1960s and used his first Microsoft product in the 1970s (and has been trying to stop ever since.) He obtained a degree in Computer<br \/>Science in the 1980s and spent most of the 1990s running an IT training company. From 2000 to 2010 he worked for Microsoft in his native England,<br \/>finishing as the evangelist for Windows platform, where he discovered PowerShell. He&rsquo;s probably best known in the PowerShell community for his<br \/>library to manage Hyper-V on Windows Server 2008\/2008-R2.\n.<\/p>\n<p style=\"padding-left: 30px\"><b>Note<\/b>&nbsp;&nbsp;&nbsp;I have two Weekend Scripter blogs where I discuss querying the Windows Search Index. The first is <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2010\/05\/29\/hey-scripting-guy-weekend-scripter.aspx\" target=\"_blank\">Querying the Windows Search Index<\/a>, and the second is <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2010\/05\/30\/hey-scripting-guy-weekend-scripter-using-the-windows-search-index-to-find-specific-files.aspx\" target=\"_blank\">Using the Windows Search Index to Find Specific Files<\/a>. Both of these blogs use COM-based ADO to do the query instead of using the .NET Framework classes that are used by James. The blogs provide a good background for James&rsquo; series.\nTake it away, James&hellip;\nI have spent some time developing and honing a Windows PowerShell function that gets information from the Windows Index, which is the technology behind the search that is integrated into Windows Explorer in Windows 7 and Windows Vista. The Windows Index can be queried by using SQL, and my function builds the SQL query from user input, executes it, and receives rows of data for all the matching items.\nToday, I&#8217;m going to explore the query process. Part Two will look at making user input easier (I don&#8217;t want to make understanding SQL a prerequisite for using the function). In Part Three, I will look at why rows of data are not the best thing for the function to return and what the alternatives might be.\nWe will look at how at how the query is built in a moment. For now, please accept a ready-to-run query that is stored in the variable <b>$SQL<\/b>. Then it only takes a few lines of Windows PowerShell to prepare and run the query as shown here.<\/p>\n<p style=\"padding-left: 30px\">$Provider=&#8221;Provider=Search.CollatorDSO;Extended Properties=&rsquo;Application=Windows&rsquo;;&#8221;<\/p>\n<p style=\"padding-left: 30px\">$adapter = new-object system.data.oledb.oleDBDataadapter -argument $sql, $Provider<\/p>\n<p style=\"padding-left: 30px\">$ds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = new-object system.data.dataset<\/p>\n<p style=\"padding-left: 30px\">if ($adapter.Fill($ds)) { $ds.Tables[0] }\nThe data is fetched by using the <b>oleDBDataAdapter<\/b> and <b>DataSet<\/b> objects. The adapter is created by specifying a &#8220;provider&#8221; (which says <i>where<\/i> the data will come from) and a SQL statement (which says <i>what<\/i> is being requested). The query is run when the adapter is told to <i>fill <\/i>the dataset. The <b>.fill()<\/b> method returns a number that indicates how many data rows were returned by the query. If this is non-zero, my function returns the first table in the dataset. Windows PowerShell sees each data row in the table as a separate object, and these objects have a property for each of the table&#8217;s columns. So a search might return something like this:<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.ITEMNAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : DIVE_1771+.JPG<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.ITEMURL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : file:C:\/Users\/James\/pictures\/DIVE_1771+.JPG<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.FILEEXTENSION&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : .JPG<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.FILENAME&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : DIVE_1771+.JPG<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.FILEATTRIBUTES&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 32<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.FILEOWNER&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : InspironJames<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.ITEMTYPE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : .JPG<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.ITEMTYPETEXT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : JPEG Image<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.KINDTEXT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : Picture<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.KIND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : {picture}<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.MIMETYPE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : image\/jpeg<\/p>\n<p style=\"padding-left: 30px\">SYSTEM.SIZE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; : 971413\nThere are lots of fields to choose from, so the list might be longer. The SQL query to produce it looks something like this:<\/p>\n<p style=\"padding-left: 30px\">SELECT System.ItemName, System.ItemUrl, System.FileExtension, System.FileName, System.FileAttributes, System.FileOwner, System.ItemType, System.ItemTypeText , System.KindText, System.Kind, System.MIMEType, System.Size&nbsp;<\/p>\n<p style=\"padding-left: 30px\">FROM SYSTEMINDEX<\/p>\n<p style=\"padding-left: 30px\">WHERE System.Keywords = &#8216;portfolio&#8217; AND Contains(*,&#8217;stingray&#8217;)\nIn the finished version of the function, the SELECT clause has 60 or so fields. The FROM and WHERE clauses might be more complicated than in the example, and an ORDER BY clause might be used to sort the data. The clauses are built by using parameters that are declared in my function like this:<\/p>\n<p style=\"padding-left: 30px\">Param ( [Alias(&#8220;Where&#8221;,&#8221;Include&#8221;)][String[]]$Filter ,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;[Alias(&#8220;Sort&#8221;)][String[]]$orderby,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Alias(&#8220;Top&#8221;)][String[]]$First,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [String]$Path,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Switch]$Recurse<\/p>\n<p style=\"padding-left: 30px\">)\nIn my functions, I try to use names that are already used in Windows PowerShell. So here I use <b>-Filter<\/b> and <b>-First<\/b>, but I also define aliases for SQL terms like WHERE and TOP. These parameters build into the complete SQL statement, starting with the SELECT clause which uses <b>&ndash;First<\/b>.<\/p>\n<p style=\"padding-left: 30px\">if ($First)&nbsp; {$SQL = &#8220;SELECT TOP $First &#8220;}<\/p>\n<p style=\"padding-left: 30px\">else&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {$SQL = &#8220;SELECT &#8220;}<\/p>\n<p style=\"padding-left: 30px\">$SQL += &#8221; System.ItemName, System.ItemUrl &#8221; # and the other 58 fields\nIf the user specifies <b>&ndash;First 1<\/b>, <b>$SQL<\/b> will be &#8220;SELECT TOP 1 <i>fields<\/i>&#8220;; otherwise, it&#8217;s just &#8220;SELECT <i>fields.<\/i>&#8221; After the fields are added to <b>$SQL<\/b>, the function adds a FROM clause. Windows Search can interrogate remote computers, so if the <b>-Path<\/b> parameter is a UNC name in the form <i>\\computerNameshareName, <\/i>the SQL FROM clause becomes FROM&nbsp;<i>computerName<\/i>.SYSTEMINDEX; otherwise, it is FROM SYSTEMINDEX to search the local computer. <br \/> A regular expression can recognize a UNC name and pick out the computer name, like this:<\/p>\n<p style=\"padding-left: 30px\">if ($Path -match &#8220;\\\\([^\\]+)\\.&#8221;) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $sql += &#8220;FROM $($matches[1]).SYSTEMINDEX WHERE &#8220;<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">else {$sql += &#8221; FROM SYSTEMINDEX WHERE &#8220;}\nThe regular expression in the first line of the example breaks down as follows:<\/p>\n<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"130\">  <strong>Regular expression<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"265\">\n<p><strong>Meaning<\/strong><\/p>\n<\/td>\n<td valign=\"top\" width=\"221\">\n<p><strong>Application<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"130\">\n<p><b><span style=\"text-decoration: underline\">\\\\<\/span><\/b>([^\\]+)\\.<\/p>\n<\/td>\n<td valign=\"top\" width=\"265\">\n<p>2  characters: &#8220;&#8221; is the escape character, so each one needs to be written as \\<\/p>\n<\/td>\n<td valign=\"top\" width=\"221\">\n<p><b><span style=\"text-decoration: underline\">\\<\/span><\/b>computerNameshareName<\/p>\n<p>&nbsp;<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"130\">\n<p>\\\\(<b><span style=\"text-decoration: underline\">[^\\]+<\/span><\/b>)\\.<\/p>\n<\/td>\n<td valign=\"top\" width=\"265\">\n<p>Any non- character, repeated at least once<\/p>\n<\/td>\n<td valign=\"top\" width=\"221\">\n<p>\\<b><span style=\"text-decoration: underline\">computerName<\/span><\/b>shareName<\/p>\n<p>&nbsp;<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"130\">\n<p>&#8220;\\\\([^\\]+)<b><span style=\"text-decoration: underline\">\\.<\/span><\/b>&#8220;<\/p>\n<\/td>\n<td valign=\"top\" width=\"265\">\n<p>A ,followed by <i>any<\/i> character<\/p>\n<\/td>\n<td valign=\"top\" width=\"221\">\n<p>\\computerName<b><span style=\"text-decoration: underline\">s<\/span><\/b>hareName&nbsp;<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"130\">\n<p>&#8220;\\\\<b><span style=\"text-decoration: underline\">(<\/span><\/b>[^\\]+<b><span style=\"text-decoration: underline\">)<\/span><\/b>\\.&#8221;<\/p>\n<\/td>\n<td valign=\"top\" width=\"265\">\n<p>Capture the section that is enclosed by the brackets as a match<\/p>\n<\/td>\n<td valign=\"top\" width=\"221\">\n<p>$matches[0] =\\computerNames<\/p>\n<p>$matches[1] =<b><span style=\"text-decoration: underline\">computerName<\/span><\/b><\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>I allow the function to take different parts of the WHERE clause as a comma separated list, so that<\/p>\n<p style=\"padding-left: 30px\">-filter &#8220;System.Keywords = &#8216;portfolio'&#8221;,&#8221;Contains(*,&#8217;stingray&#8217;)&#8221;\nis equivalent to&nbsp;<\/p>\n<p style=\"padding-left: 30px\">-filter &#8220;System.Keywords = &#8216;portfolio&#8217; AND Contains(*,&#8217;stingray&#8217;)&#8221;\nTo add the filter, we simply need this:<\/p>\n<p style=\"padding-left: 30px\">if ($Filter) { $SQL += $Filter -join &#8221; AND &#8220;}\nThe folders searched can be restricted. A &#8220;SCOPE&#8221; term limits the query to a folder and all of its subfolders, and a &#8220;DIRECTORY&#8221; term limits it to a folder without subfolders. If the request is going to a remote server, the index is smart enough to recognize a UNC path and return only the files that are accessible via that path. If a <b>-Path<\/b> parameter is specified, the function extends the WHERE clause, and the <b>&ndash;Recurse <\/b>switch determines whether to use SCOPE or DIRECTORY, like this:<\/p>\n<p style=\"padding-left: 30px\">if ($Path){<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; if ($Path -notmatch &#8220;w{4}:&#8221;) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Path = &#8220;file:&#8221; + (resolve-path -path $Path).providerPath<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; if ($sql -notmatch &#8220;WHEREs*$&#8221;) {$sql += &#8221; AND &#8221; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; if ($Recurse)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {$sql += &#8221; SCOPE = &#8216;$Path&#8217; &#8221;&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; else&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {$sql += &#8221; DIRECTORY = &#8216;$Path&#8217; &#8220;}<\/p>\n<p style=\"padding-left: 30px\">}\nIn these SQL statements, paths are specified in the form file:c:\/users\/james, which isn&#8217;t how we normally write them (and the way I recognize UNC names won&#8217;t work if they are written as file:\/\/ComputerName\/shareName). This is rectified by the first line inside the <b>If ($Path) {}<\/b> block, which checks for 4 &#8220;word&#8221; characters, followed by a colon.\nDoing this prevents &#8216;File:&#8217; being inserted if any protocol has been specified. The same search syntax works against HTTP:\/\/ (although, not usually when searching on your workstation), MAPI:\/\/ (for Outlook items), and OneIndex14:\/\/ (for OneNote items). If a file path has been given, I ensure it is an absolute one. The need to support UNC paths forces the use of <b>.ProviderPath<\/b> here. It turns out that there is no need to convert <b> <\/b>characters in the path to <b>\/<\/b>, provided <b>file: <\/b>is included.\nAfter taking care of that, the operation -notmatch &#8220;WHEREs*$&#8221; sees to it that an &#8220;AND&#8221; is added if there is anything other than spaces between WHERE and the end of the line (that is, if any conditions specified by <b>&ndash;Filter<\/b> have been inserted).\nIf neither <b>-Path<\/b> nor &#8211;<b>Filter<\/b> was specified, there will be a dangling WHERE at the end of the SQL statement. Initially I removed this with <b>&ndash;Replace<\/b>. Then I decided that I didn&#8217;t want the function to respond to a lack of input by returning the whole index, so I changed it to write a warning and exit.\nWith the WHERE clause completed, the final clause in the SQL statement is ORDER BY, which, like WHERE, joins a multipart condition.<\/p>\n<p style=\"padding-left: 30px\">if ($sql -match &#8220;WHEREs*$&#8221;)&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; Write-warning &#8220;You need to specify either a path, or a filter.&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; Return<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">if ($orderby) { $sql += &#8221; ORDER BY &#8221; + ($OrderBy -join &#8221; , &#8220;) }\nWhen the whole function is put together, it takes three dozen lines of Windows PowerShell to handle the parameters, build and run the query, and return the result. Put together, it looks like this:<\/p>\n<p style=\"padding-left: 30px\"><strong>Function Get-IndexedItem{<\/strong><\/p>\n<p style=\"padding-left: 30px\">Param ( [Alias(&#8220;Where&#8221;,&#8221;Include&#8221;)][String[]]$Filter ,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Alias(&#8220;Sort&#8221;)][String[]]$OrderBy,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Alias(&#8220;Top&#8221;)][String[]]$First,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [String]$Path,<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Switch]$Recurse )<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">if ($First)&nbsp; {$SQL = &#8220;SELECT TOP $First &#8220;}<\/p>\n<p style=\"padding-left: 30px\">else&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {$SQL = &#8220;SELECT &#8220;}<\/p>\n<p style=\"padding-left: 30px\">$SQL += &#8221; System.ItemName, System.ItemUrl &#8221; # and the other 58 fields<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">if ($Path -match &#8220;\\\\([^\\]+)\\.&#8221;) {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $SQL += &#8220;FROM $($matches[1]).SYSTEMINDEX WHERE &#8220;<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">else {$SQL += &#8221; FROM SYSTEMINDEX WHERE &#8220;}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">if ($Filter) { $SQL += $Filter -join &#8221; AND &#8220;}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">if ($Path)&nbsp;&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; if ($Path -notmatch &#8220;w{4}:&#8221;)&nbsp; {$Path = &#8220;file:&#8221; + $Path}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; $Path = $Path -replace &#8220;\\&#8221;,&#8221;\/&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; if ($SQL -notmatch &#8220;WHEREs*$&#8221;) {$SQL += &#8221; AND &#8221; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; if ($Recurse)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {$SQL += &#8221; SCOPE = &#8216;$Path&#8217; &#8221;&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp; else&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {$SQL += &#8221; DIRECTORY = &#8216;$Path&#8217; &#8220;}<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">if ($SQL -match &#8220;WHEREs*$&#8221;)&nbsp; {<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; Write-Warning &#8220;You need to specify either a path or a filter.&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp; Return<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">if ($OrderBy) { $SQL += &#8221; ORDER BY &#8221; + ($OrderBy&nbsp;&nbsp; -join &#8221; , &#8221; ) }<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">$Provider=&#8221;Provider=Search.CollatorDSO;Extended Properties=&rsquo;Application=Windows&rsquo;;&#8221;<\/p>\n<p style=\"padding-left: 30px\">$Adapter = New-Object system.data.oledb.oleDBDataadapter -argument $SQL, $Provider<\/p>\n<p style=\"padding-left: 30px\">$DS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = New-Object system.data.dataset<\/p>\n<p style=\"padding-left: 30px\">if ($Adapter.Fill($DS)) { $DS.Tables[0] }<\/p>\n<p style=\"padding-left: 30px\">}\nThe <b>-Path<\/b> parameter is more user-friendly as a result of the way I handle it. But I&#8217;ve made it a general rule that you shouldn&#8217;t expect the user to know too much about the underlying syntax; and at the moment, the function requires too much knowledge of SQL. I don&#8217;t want to type this:<\/p>\n<p style=\"padding-left: 30px\">Get-Indexed-Item &ndash;Filter &#8220;Contains(*,&#8217;Stingray&#8217;)&#8221;, &#8220;System.Photo.CameraManufacturer Like &#8216;Can%'&#8221;\nAnd it seems unreasonable to expect anyone else to do so. I came up with this list that I want the function to do for me:<\/p>\n<ul>\n<li>Don&#8217;t require the user to know whether a search term is prefixed with SYSTEM (SYSTEM.DOCUMENT, SYSTEM.IMAGE or SYSTEM.PHOTO). If the prefix is omitted, add the correct one.<\/li>\n<li>Even without the prefixes, some field names are awkward; for example, &#8220;HorizontalSize&#8221; and &#8220;VerticalSize&#8221; instead of width and height. Provide aliases.<\/li>\n<li>Literal text in searches needs to be enclosed in single quotation marks. Insert quotation marks if the user omits them.<\/li>\n<li>A free text search over all fields is written as Contains(*,&#8217;searchTerm&#8217;). Convert &#8220;orphan&#8221; search terms into <b>Contains <\/b>conditions.<\/li>\n<li>SQL uses <b>%<\/b> (not <b>*<\/b>) for a wild card. Replace <b>*<\/b> with <b>%<\/b> in filters to cope with users adding the familiar <b>*<\/b>.<\/li>\n<li>SQL requires the <b>like<\/b> predicate (not <b>=<\/b>) for wildcards. Replace <b>=<\/b> with <b>like<\/b> for wildcards.<\/li>\n<\/ul>\n<p>In Part Two, I&#8217;ll look at how I accomplish these things.\n~James\nThank you, James, for a great blog.\nI invite you to follow me on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"http:\/\/blogs.technet.commailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><b>Ed Wilson, Microsoft Scripting Guy<\/b>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Guest blogger, James O&#8217;Neill, discusses using Windows PowerShell to build a query to search the Windows Index. Microsoft Scripting Guy, Ed Wilson, is here. Today is Part One of three blogs written by guest blogger, James O&rsquo;Neill. James O&#8217;Neill was born in the 1960s and used his first Microsoft product in the 1970s (and [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[56,353,31,3,4,147,45],"class_list":["post-8941","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-guest-blogger","tag-james-oneill","tag-operating-system","tag-scripting-guy","tag-scripting-techniques","tag-search","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Guest blogger, James O&#8217;Neill, discusses using Windows PowerShell to build a query to search the Windows Index. Microsoft Scripting Guy, Ed Wilson, is here. Today is Part One of three blogs written by guest blogger, James O&rsquo;Neill. James O&#8217;Neill was born in the 1960s and used his first Microsoft product in the 1970s (and [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/8941","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=8941"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/8941\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=8941"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=8941"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=8941"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}