Click here to Skip to main content
15,883,749 members
Articles / Programming Languages / C#
Article

Windows Desktop Search Powershell Cmdlet

Rate me:
Please Sign up or sign in to vote.
3.13/5 (6 votes)
28 Jun 2006CPOL6 min read 93.6K   389   17   13
Powershell cmdlet for querying the Windows Desktop Search index

Introduction

Microsoft has introduced a new command line shell called PowerShell which was previously known as Monad. The main difference between PowerShell and other shells like cmd, bash, ksh, etc. is the pipelining and processing of objects as opposed to plain text.

Plain text from existing command line applications, etc. can still be processed, but in general .NET objects are now sent through the pipeline and processed by cmdlets.

As an example, the following scripts from ksh and PowerShell show some of the differences between working with text pipelines and object pipelines.

$ ps –el | awk ‘{ if ( $6 > (1024*10)) { print $3 } }’ | grep –v PID | xargs kill

PS> get-process | where { $_.VM –gt 10M } | stop-process

Powershell cmdlets follow a verb-noun naming convention, e.g. get-process and can be aliased.

set-alias    ps    get-process
set-alias    where    where-object

The objects flowing through the pipeline are .NET objects which are self describing with metadata that the CLR includes. So for example, if you're not sure what properties and methods are available on the Process object returned by the get-process cmdlet, you can issue the following to find out:

get-process | get-member

   TypeName: System.Diagnostics.Process

Name                           MemberType     Definition
----                           ----------     ----------
Handles                        AliasProperty  Handles = Handlecount
Name                           AliasProperty  Name = ProcessName
NPM                            AliasProperty  NPM = NonpagedSystemMemorySize
PM                             AliasProperty  PM = PagedMemorySize
VM                             AliasProperty  VM = VirtualMemorySize
WS                             AliasProperty  WS = WorkingSet
…..

.NET class libraries can also be used directly by PowerShell scripts. See the following example which demonstrates the use of the .NET System.Net.WebClient and XML classes to download an RSS feed and print out the title of the feed and the title for each item in the feed.

$wc = new-object System.Net.WebClient
$rssdata = [xml]$wc.DownloadString(‘http://blogs.msdn.com/PowerShell/rss.xml’)
write-host $rssdata.rss.channel.title
$rssdata.rss.channel.item | foreach { write-host $_.title }

Users or developers can write their own cmdlets to extend the functionality of PowerShell. These cmdlets can either be written using PowerShell script or they can be compiled into a .NET assembly using any programming language that targets the CLR.

For more details, take a look at Microsoft’s Scripting Center.

Windows Desktop Search cmdlet

I have previously used the Windows Desktop Search API in order to write a music browser and thought it would be useful to create a Windows Desktop Search cmdlet to allow users to query their search index from the command line. The results of the query can then be pipelined to further cmdlets to act on the results or to simply display the result set.

A simple example that a user or an administrator of a system may want to execute is to find all files that are larger than 50 MB. You would normally execute something along these lines:

dir c:\* -recurse | where { $_.Length -gt 50M }

The equivalent using the Windows Desktop Search cmdlet would be:

get-wds "size:>52428800"

The main advantage of the WDS cmdlet in this case is that the query is being run against the search engine’s database index and so it is much quicker to execute compared to the standard method of having to recurse the file system and thrashing the disk while reading in all the associated file system data structures in order to retrieve file’s size.
 
Unfortunately it doesn't appear that you can use modifiers like MB or KB for the size argument in the case of using WDS.

In addition, using the WDS cmdlet allows you to issue queries against a large number of attributes that don't exist with a standard FileInfo object. For a couple of examples, take a look at the following WDS cmdlet queries:

get-wds "kind:music genre:rock year:1980" | 
	copy-item –destination c:\rock-collection-1980

get-wds "kind:presentations keywords:conference importance:high"

get-wds "kind:pics datetaken:this month cameramake:pentax" | 
	copy-item –destination c:\LatestPentaxSnaps

For more information on the available attributes that you can use for searching and their associated syntax, take a look at Advanced Query Syntax for Windows Desktop Search.

Implementation

In order to implement a PowerShell cmdlet, you need to create a class that derives from System.Management.Automation.PSCmdlet and overrides one or more of the following methods:

  • BeginProcessing()
  • ProcessRecord()
  • EndProcessing()

The BeginProcessing() method is called before your cmdlet starts receiving pipeline input and the ProcessRecord() method is called once for each object that is passed to your cmdlet via the pipeline. The EndProcessing() method is called once there is no more data in the pipeline for your cmdlet to process.

We specify that we require a Query string either as a value from the pipeline or as the 1st argument to our cmdlet. The details on how we require input via either the pipeline or as command line arguments is done declaratively using CLR attributes as show in the example below. There is no need to write command line processing code to validate the input as the PowerShell will handle that on our behalf based on reading the attributes we specify.

C#
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true), ValidateNotNull()]
public string Query
{
    get { return query; }
    set { query = value; }
}

So once our BeginProcessing() method is called, the PowerShell would already have set our Query property for us either from the pipeline or from the command line argument. In our BeginProcessing() method, we execute the required query using the WDS API. One slight complication is that the WDS API consists of a COM interface that is only supported in a Single Threaded Apartment (STA) whereas PowerShell cmdlets aren't run in STAs. To work around this, we create a new thread to perform the actual query and set it to use an STA. We then block on this query thread waiting for it to finish executing.

C#
protected override void BeginProcessing()
{
    Thread queryThread = new Thread(ExecuteQuery);
    queryThread.SetApartmentState(ApartmentState.STA);
    queryThread.Start();
    queryThread.Join();
}

In the ExcecuteQuery() method, we append the string store:files” in order to select results only from the filesystem store and therefore not to return results from other stores like email, etc. We then pass the user’s query string to the ExecuteQuery() method of the SearchDesktopClass which is a .NET wrapper around the WDS query COM object which has been generated from the original COM IDL file for the WDS query API.

The result is a COM based Recordset which we then use to populate a .NET ADO DataTable. We then iterate over this table and create a FileInfo object for each result using the URL column from the result set to specify the path for the FileInfo object and insert this FileInfo object into a List<FileInfo> collection.

C#
private void ExecuteQuery()
{
    string finalQuery = query + " store:files";

    SearchDesktopClass search = new SearchDesktopClass();

    _Recordset rs = search.ExecuteQuery(finalQuery, "Url", null, "");

    OleDbDataAdapter dbAdapter = new OleDbDataAdapter();

    DataSet ds = new DataSet();
    DataTable table = new DataTable("MyIndex");

    table.Columns.Add("url", typeof(string));
    ds.Tables.Add(table);
    dbAdapter.Fill(ds, rs, "MyIndex");

    foreach (DataRow row in table.Rows)
    {
        Uri uri = new Uri((string)row[0]);
        results.Add(new FileInfo(uri.LocalPath));
    }

    dbAdapter.Dispose();
}

Finally, our implementation of the ProcessRecord() method simply iterates over our List<FileInfo> collection that our query thread has populated and writes each FileInfo object in the collection to the pipeline stream.

C#
protected override void ProcessRecord()
{
    foreach (FileInfo file in results)
    {
        WriteObject(file);
    }
}

Installation

Once the cmdlet class has been compiled into an assembly, we need to make it available to PowerShell.

The first step is to install our assembly:

$env:windir\Microsoft.Net\Framework\v2.0.0.50727\installutil WDSCmdlet.dll

Then the get-pssnapin cmdlet will give us a list of available snapins:

get-pssnapin –registered

Name        : WindowsDesktopSearch
PSVersion   : 1.0
Description : Snapin providing Windows Desktop Search cmdlet

To finally add this particular snapin to our current shell, we need to execute:

add-pssnapin WindowsDesktopSearch

You can now start using the WDS cmdlet get-wds as shown in the example queries earlier.

Conclusion

PowerShell introduces a novel change by working with pipelines of self-describing objects instead of pipelines of plain text. In order to extend the processing options, users and developers can extend the shell by writing cmdlets which simply need to override a couple of simple methods of the PSCmdlet class. By using the WDS query API, certain types of filesystem queries are much more efficient and other types of queries that aren't possible with simple file enumeration are also possible.

History

  • 28th June, 2006: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
South Africa South Africa
Software developer.

Comments and Discussions

 
Generalcannot add reference to system.management.automation.dll [modified] Pin
Member 34192532-Jun-08 23:06
Member 34192532-Jun-08 23:06 
QuestionGet-WDS error, can anyone diagnose... Pin
robertt.3110-Feb-07 3:40
robertt.3110-Feb-07 3:40 
AnswerRe: Get-WDS error, can anyone diagnose... Pin
Sean McLeod10-Feb-07 6:30
Sean McLeod10-Feb-07 6:30 
QuestionRe: Get-WDS error, can anyone diagnose... Pin
robertt.3110-Feb-07 22:12
robertt.3110-Feb-07 22:12 
AnswerRe: Get-WDS error, can anyone diagnose... Pin
Sean McLeod10-Feb-07 22:43
Sean McLeod10-Feb-07 22:43 
QuestionRe: Get-WDS error, can anyone diagnose... Pin
robertt.3111-Feb-07 2:06
robertt.3111-Feb-07 2:06 
GeneralRe: Get-WDS error, can anyone diagnose... Pin
Richard Berg5-Apr-07 20:10
Richard Berg5-Apr-07 20:10 
GeneralRe: Get-WDS error, can anyone diagnose... [modified] Pin
Mycrofts3-May-07 8:43
Mycrofts3-May-07 8:43 
I've adapted Sean's code for v3.0. Only tested with Vista, briefly.

http://rapidshare.com/files/29963582/wds3.0cmdlet.zip.html[^]
GeneralRe: Get-WDS error, can anyone diagnose... Pin
Sean McLeod26-May-07 2:29
Sean McLeod26-May-07 2:29 
QuestionGET-WDS utility - feedback Pin
zardos4220-Aug-06 16:46
zardos4220-Aug-06 16:46 
AnswerRe: GET-WDS utility - feedback Pin
Sean McLeod20-Aug-06 20:13
Sean McLeod20-Aug-06 20:13 
AnswerRe: GET-WDS utility - feedback Pin
zardos4220-Aug-06 22:07
zardos4220-Aug-06 22:07 
GeneralRe: GET-WDS utility - feedback Pin
Sean McLeod20-Aug-06 22:30
Sean McLeod20-Aug-06 22:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.