Steven's profileWindows Desktop Search -...BlogLists Tools Help

Radio

Loading...

Windows Desktop Search - Tech

August 02

Adding new search "locations" to Windows Desktop Search...

I’ve been hoping that someday I’d actually have a short post to make on my blog about something Windows Desktop Search (WDS) related… But today isn’t that day…

 

We’ve been hearing some requests that users would like to add new locations to the “All Locations” list in WDS.  Maybe something like this:

 

 

So that when you type in a query and select this added location you get navigated to a set of web search results like this:

 

 

And to be honest the real request we heard was for info about adding new desktop search locations but since we support the adding of both desktop and web search locations to the list I thought I’d unofficially tell you how to add both.  As usual this isn’t an officially supported feature and is likely to change in the future.  But when it does change we’ll most likely have a UI for adding these things but for now you’ll have to add them manually via the registry.  So as a further disclaimer, be comfortable with modifying your registry before making any of these changes.  I’d also recommend you make a backup of your registry settings before making any modifications to them.

 

 

Adding “MSN Music” to your locations list…

 

You’ll want to open RegEdit and navigate “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSN Apps\DS\Views\Locations”.  Under that key you’ll notice a list of sub-keys for each location in the list:

 

 

As you can from the screenshot I’ve already added a new sub-key called Music for the MSN Music location displayed in my “All Locations” list above.  To add this same location to your list just add a Music sub-key under Locations and add the values you see in the screenshot.  Reboot your machine (or restart Explorer.exe) for the changes to take effect and the next time you launch a WDS results window your “All Locations” list should look just like mine.  Well almost like mine.  Yours won’t have a separator between “MSN Music” and “Outlook Express”.  To get a separator you’ll need to modify the OE sub-key as well:

 

 

Change your OE keys Separator value from a 0 to 1.  This should add the separator to your list after another reboot.

 

 

Understanding the “Locations” registry values…

 

So now I’ll drill into the details of what these seemingly random registry values mean and how you can use them to build a wide range of ready to use filters.  As you’ve noticed we currently support two types of locations, Desktop Search and Web Search.  The registry values you need to fill out are largely dependant upon the type of location you want.  But there are a few common values used to control the display of the location in the list so let me talk about those first:

 

  • Title:  Display name of the location in the UI.
  • Icon:  Resource info for the Icon to show next to the location.  “msnlExtRes.dll,-164” will show the globe with a magnifying glass icon but you should be able to assign any icon you want.  The format of this string is expected to be “<module>,<index>”.
  • Position:  The locations order within the list.  It’s super important that the value of Position be unique and sequential for each location starting from 0.  So for the 6 entries in the screenshot their Position values are from 0 – 5.
  • Indent:  The amount of indention if any to apply to the location.  This is used to represent a tree like structure within the list and can contain a value of 0 – 3.  A value of 0 means it’s a root location, 1 means it’s a child location, 2 means it’s a child of a child location, etc.
  • Separator:  Inserts a separator line into the list immediately after the location.  A value of 1 means the location is followed by a separator and a value 0 means it isn’t.  Note that you can temporarily hide locations (see Visible) so we’ll try to be smart about automatically rolling up the separators for hidden locations to the next visible one in the list.
  • Visible:  Temporarily removes the location from the list if set to a value of 0.  In hind site I wish we would have called this value Hide because its default value is 1 if it’s missing.  So its only purpose is to remove a location from the list.
  • Type:  Determines the type of search performed when the location is selected within the UI.  A value of 0 means Desktop Search and a value of 1 means Web Search.

 

As you’ve probably figured out the locations list is built by simply walking the sub-keys of “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSN Apps\DS\Views\Locations” on first use.  So to begin with you’ll need to add a uniquely named sub-key to this hive and fill out the common values listed above.  If you’re adding a Web Search location you’ll need to fill out the following value as well:

 

  • Value:  The URL of the HTTP server to contact when the location is selected.  You can indicate where to insert the URL Encoded terms of the query by inserting a “$w” into the URL.  Note that this is the same URL format as the one used for adding new deskbar shortcuts.

 

Now anytime you select your new Web Search location we’ll navigate to the locations URL using the terms entered into our query box.  For a Desktop Search location things are a little more interesting but follow the same principle as a Web Search location in that you need to tell us how we should filter your query.  But for Desktop Search locations we give you 3 different dimensions to filter the query by:

 

  • ItemStore:  This is Unique ID (UID) we’ve assigned to the “store” you’d like us to filter results too.  Yes you’ve just discovered the part of all this voted “Most likely to change” in a future release.  Internally we use these things called UID’s to identify the various objects within our schema.  An “ItemStore” is one of the objects in our schema and we currently have 4 (see UID Values section for a list).  Just set this value to the UID of the store you want to search over. J
  • TypeFilter:  This is an optional value you can set to filter results to a specific type within a store.  Again this is UID based and the UID’s are for the various “Type” objects within our schema (see UID Values section for a list).  If you omit this value or set it to 0x00000000 we’ll apply the filter to the query without altering the type selection.  That’s why you can select “Outlook” from the locations list and we filter to a new store without change the Type you have selected.
  • Value:  Filters the query by appending a hidden Advanced Query Syntax (AQS) fragment to the query.  Also an optional value but probably the most interesting.  Using this value you can scope the query to anything you want using AQS.  For instance, the “Inbox” location scopes queries the “inbox” folder of the Outlook store by appending the AQS fragment “in:Inbox” to the users query.

 

Another thing worth talking about is the fact these locations get stored in HKEY_LOCAL_MACHINE.  That means the changes you make to this list of locations will be applied to every user on the machine.  But you can avoid that by copying the entire Locations hive to HKEY_CURRENT_USER and making your changes there.  That way each user can have their own list of locations if they’d like.

 

 

How’s this useful?

 

So hopefully all that made sense and you have a hundred ideas for new locations you want to create.  If so great!  Maybe we’ll even turn it into a supported feature in the future. J  But if not here are a couple of ideas worth considering:

 

  • Personal Favorites:  I originally called this feature “favorites” and you can certainly setup your own list of favorite AQS fragments you end up applying all the time.
  • Intranet Locations:  I think the Web Search locations are most interesting when thinking about deployments of WDS within organizations.  If you have a large organization with various SPS servers and such you might think about creating a standard list of locations that point to the searchable resources within your org.  You can then save that list off to a .reg file and install it on users machines as part of your deployment.
  • 3rd Party Add-in Locations:  If you’re a 3rd party who’s developed a new protocol handler you can create a location for your protocol handler by scoping to the results view via AQS fragments.  To do this you'd want to append an AQS fragment of the form “store:myph” where "myph" is the name you've assigned to your protocol handler.  The tricky part of this is installing this location automatically on users’ machines.  You could blindly install it at Position 5 within the users location list but then you risk overwriting entries on machines containing modified location lists.  The safest thing to do would be to programmatically walk the users existing location list and install it as the last position on the list but that will require you writing custom code to do that.

 

Anyway… There are a couple of ideas and please let me know if you find this useful or have any comments… Enjoy!

 

-Steve

 

UID Values

 

ItemStore UIDs:

 

      uidStoreAny       = 0x30000001

      uidStoreFile      = 0x30000002

      uidStoreMAPI      = 0x30000003

      uidStoreOE        = 0x30000004

 

 

TypeFilter UIDs:

 

      uidTypeAny                  = 0x10000

      uidTypeAttachments          = 0x80000

      uidTypeContact              = 0xA0000

      uidTypeCommunications       = 0x60000

      uidTypeEmail                = 0x70000

      uidTypeCalendar             = 0x90000

      uidTypeIM                   = 0x160000

      uidTypeTask                 = 0xB0000

      uidTypeDocuments            = 0x20000

      uidTypeTextDoc              = 0x30000

      uidTypeNote                 = 0xC0000

      uidTypeSpreadsheet          = 0x40000

      uidTypePresentation         = 0x50000

      uidTypeMusic                = 0x100000

      uidTypePictureVideo         = 0xD0000

      uidTypePicture              = 0xE0000

      uidTypeVideo                = 0xF0000

      uidTypeFolder               = 0x110000

      uidTypeFavorite             = 0x120000

      uidTypeProgram              = 0x130000

      uidTypeMultimedia           = 0x140000

 

July 08

Updated API's released for Windows Desktop Search 2.5!

I had actually wanted to blog a few weeks ago the fact we’ve released some new API’s for WDS as part of our 2.5 release.  I chose to hold off on posting any comments here because I knew we had fairly significant update in the works.  So now that we’ve posted an updated set of files let me tell you about them.

 

The 2.5 release was largely about internationalization for us but we were able to squeeze in some new API’s which we’ve been hearing requests for.  The new API’s provide access to a SearchManager component to simplify setup & management of protocol handlers and a SearchDesktop component to provide programmatic access to our index.  Both of these components are implemented as COM objects and require WDS 2.5 or later.  The initial SDK we posted contained a single IDL file which required compilation via Visual Studios MIDL utility into a .tlb file before you could do anything useful with it.  And if you wanted to use the SDK from within a .NET application you then needed to run the .tlb generated by MIDL through another utility called TLBIMP which generates an interop assembly that can then be added as a reference to your .NET project.  Easy right?  Well to simplify both your lives and ours (we have to explain this stuff to you) we decided to do this work for you and give it to you in the form of an updated SDK.  We also talked Brandon Paddock into putting together a sample C# app to get you started in using the new API’s.  I’m sure Brandon will blog about the sample so I’ll just talk a bit about the files in the SDK and the basic principles behind the API.  Be sure and check out the sample as there’s some cool code in there that enables data binding of a queries results to standard WinForms controls.  I’m also going to save discussions about the SearchManager component for another post as there’s enough to talk about with the SearchDesktop component.

 

What’s in the SDK?

Good question.  Here’s a list of the files:

 

            End user agreement

SDK EULA.rtf

 

COM Interface definitions

wdsQuery.idl

wdsSetup.idl

 

Output of MIDL

wdsQuery.tlb

wdsQuery.h

wdsQuery_i.c

wdsQuery_p.c

wdsSetup.tlb

wdsSetup.h

wdsSetup_i.c

wdsSetup_p.c

 

Output of TLBIMP

WDSQuery.dll

WDSSetup.dll

 

The EULA is mandatory, the .idl files define the COM objects we’re exposing, the files built by MIDL are the files you’ll need if you plan to call the API from C++, and the two .dll’s are the .NET interop assemblies generated after we ran the .tlb’s through TLBIMP.  For .NET developers the two .dll’s are the only files you’ll need to make your applications work.  And in most cases you’ll only need one or the other. 

 

If you create a new C# project and add references to WDSQuery.dll & WDSSetup.dll, you’ll notice from within the Object Browser that two new namespaces are available to your project:

 

            Microsoft.Windows.DesktopSearch.Query

            Microsoft.Windows.DesktopSearch.Setup

 

If you browse into those namespaces you’ll notice a SearchDesktopClass class along with a bunch of ADO stuff in the Microsoft.Windows.DesktopSearch.Query namespace and a SearchManagerClass class in the Microsoft.Windows.DesktopSearch.Setup namespace.  The two classes are interop wrappers around the two COM components we’ve exposed via the SDK.  Like I said above the SearchManager component is only relevant to building new protocol handlers so we’ll save that discussion for another time.

 

Tell me about the classy SearchDesktopClass class…

Ok so now I’m just getting corny…  The SearchDesktopClass is the class you’ll use to create a connection and issue a query to the indexer.  If you look at the methods exposed by this class you’ll see it’s pretty simple:

 

public virtual new Microsoft.Windows.DesktopSearch.Query._Recordset ExecuteQuery ( System.String lpcwstrQuery , System.String lpcwstrColumn , System.String lpcwstrSort , System.String lpcwstrRestriction )

 

public virtual new Microsoft.Windows.DesktopSearch.Query._Recordset ExecuteSQLQuery ( System.String lpcwstrSQL )

 

Just two methods of which you’re probably only ever going to call one, ExecuteQuery().  Why so simple?  It’s a really low level wrapper around the core component used by the UI when it calls the indexer.  The UI and the SearchDesktopClass both communicate with the indexer via an OLE-DB Provider exposed off the indexer.  More accurately they both communicate with the indexers OLE-DB provider via ADO so you can think of the SearchDesktopClass as a thin wrapper around ADO calls to the indexer which is why results come back as an ADO recordset.

 

Such low level access to the indexer has PRO’s and CON’s.  The PRO’s are that it’s fast and you can pretty much ask for anything you want.  The CON is that since you can ask for pretty much anything you want you have to first know what it is that you want. :)  Which leads into my explanation of the differences between ExecuteQuery() and  ExecuteSQLQuery().  Let’s start with ExecuteSQLQuery().

 

Calling SearchDesktopClass.ExecuteSQLQuery()

The ExecuteSQLQuery() method is pretty much a direct call to the indexers OLE-DB provider via ADO.  The only thing we’ve done is hidden some connection setup details from you.  It wants as an argument the complete SQL statement that should be evaluated by the indexer.  Here’s the SQL generated when you query for “test query” and don’t filter to a specific type:

 

SELECT DocFormat, Url, HasAttach, Characterization, PerceivedType, IsDeleted, WorkID, IsAttachment, ConversationID, FileExt, Rank, DocTitlePrefix, DocTitle, FlagText, IsFlagged, Create, DueDate, Importance, ToName, CcName, AttachmentNames, DocCompany, Location, DocCategory, DocKeywords, MusicAlbum, FileName, MusicGenre, DocAuthor, PrimaryDate, Size, FileExtDesc, DisplayFolder, PrimaryTelephone, EmailAddress, EndDate, Location, DueDate, TaskStatus, MusicAlbum, MusicGenre, DocComments, DocKeywords, AudioAvgDataRate, DocComments FROM "MyIndex"..scope() WHERE CONTAINS(*,'"test*"',1033) AND CONTAINS(*,'"query*"',1033) ORDER BY PrimaryDate DESC

 

Here’s another example of the same query, “test query” but filtered to only show e-mails:

 

SELECT DocFormat, Url, HasAttach, Characterization, PerceivedType, IsDeleted, WorkID, IsAttachment, ConversationID, FileExt, Rank, DocAuthor, FromName, DocTitlePrefix, DocTitle, Importance, IsFlagged, IsFlaggedCompleted, FlagText, DueDate, AttachmentNames, BccName, CcName, ToName, People, DocCategory, ReceivedDate, PrimaryDate, FromAddress, ToAddress, CcAddress, BccAddress, Size, DisplayFolder FROM "MyIndex"..scope() WHERE CONTAINS(*,'"test*"',1033) AND CONTAINS(*,'"query*"',1033) AND (Contains(PerceivedType,'email') RANK BY COERCION(Absolute, 1000)) ORDER BY ReceivedDate DESC

 

These guys look a little overwhelming but they’re not too bad once you understand them.  If you look closely at these two expressions you’ll notice two differences; 1) some of the columns are different because we’ve figured out you’re querying for e-mails and we might want to show those specialized columns to you. And 2) The WHERE clause has changed to accommodate the added constraint need to only return e-mail’s.

 

So how do you know what to put in your SQL expression?  Well for the columns you just have to know which ones you want.  We don’t have them documented yet because they’re not stable enough to say “We’re going to support these forever!”  But check out the sample app and the examples above for a good starter list and I’ll also try to post some additional sample SQL queries in the near future that call out additional columns.  Just be aware that some of them most likely WILL CHANGE in the future. At any rate the columns is a fairly manageable portion of the expression but the WHERE clause is another story. 

 

The sample WHERE clauses above are relatively tame because the original query text didn’t contain any Advanced Query Syntax (AQS).  Once you start adding AQS to the mix your WHERE clauses can get messy in a hurry.  To help you out of this jam we added the ExecuteQuery() method.  Remember how I said that’s the only method you’ll ever want to call. :)

 

Calling SearchDesktopClass.ExecuteQuery()

The ExecuteQuery() method implements the proper logic for building a well formed SQL expression before calling the indexers OLE-DB provider via ADO.  ExecuteQuery() takes the following arguments:

  • pcwstrQuery:  This is the text of the users query which MAY contain AQS expressions.  The query will be parsed and converted into a WHERE clause to be passed as part of the SQL expression passed to the indexer.
  • lpcwstrColumn:  This is a list of 1 or more columns you want retrieved from the indexer.  This is the SELECT portion of the SQL expression.
  • lpcwstrSort:  This is the column you want the results ordered by.  This directly becomes the ORDER BY clause so you’ll need to pass it in as “RecievedDate DESC” or “ReceivedDate ASC”.  This value must be explicitly set to null if you don’t want the results sorted.  Due to a bug in 2.5 passing an empty string (“”) will cause the method to fail.
  • lpcwstrRestriction:  This is any optional constraint you want appended to the SQL expressions WHERE clause.  Why do you need this?  It let’s you filter the results to a particular type without have to append the AQS expression of “type:email” to the users query.  To constrain the results to email you’d pass in “Contains(PerceivedType,'email')”. This value must also be explicitly set to null if you don’t want any additional constraint applied.  Due to a bug in 2.5 passing an empty string (“”) will cause the method to fail.

 

As you can see, calling ExecuteQuery() is a lot simpler then calling ExecuteSQLQuery() even though it takes more arguments.  The only thing special you still need to know is the list of columns you want returned.  But watch this and other blogs for updates to the unofficial list of columns currently supported in 2.5.

 

Final tips…

In summary we can’t wait to see what cool tools you guys build using the new API’s.  Check out the sample and use that as a starting point.  And here are a couple of final tips for now:

 

  • Create a new SearchDesktopClass instance for every query.  There shouldn’t be any leaks when reusing an existing SearchDesktopClass but in the UI we create a new instance for every query so I’d recommend you do to as this will be the most tested usage pattern.
  • Don’t pass empty strings (“”) in any of the arguments to ExecuteQuery() at least for 2.5.  Pass null instead as there is a known bug related to passing empty strings.
  • The data binding stuff in the sample is super cool!  But when dealing with really large results sets it may not be super efficient as the entire result set has to be copied to a .NET DataTable object before use.  For ultimate speed you’ll have to stick with using the raw ADO recordset we return but you should at least give the data binding stuff a shot and see if you’re happy with the perf.
  • Give us feedback!  Lots and lots of feedback.  We are listening…

 

-Steve 

June 12

Customizing the Stock Previews

Ok so I thought I’d get the ball rolling by talking about how you can customize the item previews displayed in the large search results UI.  This is a big topic which isn’t easily covered by a single post so I thought I’d first talk broadly about how the previews work and how to perform basic modifications.  In future posts I can drill into more details if there’s interest.  But before any of that I need to throw out a disclaimer:

Modifying the previews shipped with Windows Desktop Search is not a supported feature.  Any new previews you build will not be guaranteed to work in future versions of the product.  The documentation that follows should be considered “unofficial” at best.

Preview Selection

The first topic I should touch on is how the appropriate preview gets chosen when a user selects an item.  When a new item is selected, the first thing the preview pane does is it identifies what type of previewer it’s going to display.  It currently only has two choices, the folder previewer or the content previewer.  The folder previewer is chosen if the item is a file system folder and is simply a shell view over the contents of the folder.  All other items get mapped to the content previewer which is an embedded WebBrowser control.  At this point we’re only interested in items mapped to the content previewer because they’re the only ones we can modify.

Once an item has been mapped to the content previewer it then goes through a second round of analysis to see what type of content should actually be displayed for the item.  This decision process is driven by the registry which means you can modify the registry to influence most of the preview selection decisions made.  Open RegEdit and look for the key:

 HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers

You’ll notice a few values and a set of 3 sub-keys Extension, PerceivedType, and Default.  Let’s start with the sub-keys.  The contents of those 3 keys are used to determine what preview an item gets and I’ve listed them in the order in which they get consulted.  The previewer first gets the file extension of the item being previewed (if any) and tries to find a sub-key under the Extensions key.  If a sub-key is found preview selection stops and the values of the found key are used to drive preview generation.  If no extension key is found then selection moves on to type based selection.  Every item in the index has, or at least should have, a value called its perceived type.  The perceived type of an item is the value used to tie it to the type filters displayed within the UI and it’s also the value used to tie the item a type wide preview.  So for type based selection we simply get the items perceived type and look for a sub-key of the same name under the PerceivedType key.  Once found preview selection stops and the values of the found key are used to drive preview generation.  If the item has no perceived type or the perceived type isn’t found then the value of the Default key is chosen and selection stops anyway.

Preview Generation

So the items been mapped to a key within the registry but what do the values mean?  We’ll those values bind the previewer to a COM based plug-in that knows how to generate preview content for the item.  The content generated by the plug-in can be essentially anything render-able by IE.  We currently ship with 3 preview plug-ins and the COM interface they implement isn’t public so you can’t add any new ones.  So why are we talking about customizing the previews then?  Because one of those plug-ins is pretty special…

If you were to look through all of the previewer keys you’d not two recurring patterns.  The keys either contain a single default value, one of two GUID’s.  Or they contain no default value but several named values.  The keys with one of the two GUID’s map the items preview to either the Native (“{015CA7C6-DECD-40dc-AAAC-73EA9940E0F9}”) or Office (“{7A35A3A8-3DEA-40e5-B2AA-21DEF91A219A}”) Previewer plug-ins.  Of those two the Native Previewer is the most interesting because it acts as a pass through for the items URL.  When an item is bound to this preview we simply navigate our embedded WebBrowser to the URL of the item which is useful for items that IE already does a good job of rendering like PDFs.  Even more interesting are the keys without a GUID because they get mapped to a special internal previewer called the Registry Based Previewer.

The Registry Based Previewer turns preview creation into an authoring process by generating preview content on the fly from HTML templates containing a set of ASP’ish (read NOT ASP) macros.  The remaining bulk of this post will talk about the structure of these templates but first let me break down the registry entries that control what template is used and how it’s rendered.  Looking at:

HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\contact

We see the values:

(Default):

ContentType:  0x00000001 (1)

ScriptOK:        0x00000001 (1)

TemplateUrl:  res://msnlExt.dll/contact.htm

The blank value for (Default) is what maps this items preview to the Registry Based Previewer.  The ContentType value controls the type of content generated by the previewer and can be either 0 for HTML or 1 for MSHTML.  I’d recommend you always use 1 as it’s the most flexible and most tested.  The ScriptOK value is used to override one of the security decisions we make based upon what store the item we’re previewing lives in.  If the item resides on your hard-drive it’s partially trusted so we let script execute by default.  But if the item lives anywhere else like say Outlook or OE we treat it as un-trusted and disable script execution by default.  The ScriptOK value lets us re-enable script execution on a per type basis.  Security is deep topic worthy of its own post but I should also point out at this time that we NEVER let ActiveX controls execute and there’s currently no way to override that decision.  Finally, the TemplateUrl value specifies the actual template to use when generating the preview content.  As you can see, all of our templates get pulled from a resource dll but you can change these values to point to a file on the users hard-drive which is the key to replacing the stock previews with custom ones!

Creating new Preview Templates

Probably the easiest way to figure out how to customize the previews is to dive in and start changing them.  You’ll notice that I’ve posted several listing which should act as a good starting point for your adventures.  Before you download and install any of the listings you need to be very sure that this is something you want to explore.  You’ll need to be comfortable working with HTML and modifying the registry.  Executing the UseDebugPreviews.reg command will replace all of you’re type based previews (meaning most of them) with a new Debug.htm preview that dumps out most of the available properties within a given view.  I’ve also provided a UseStockPreviews.rgs command which should roll everything back to stock but I can’t provide any warranties for any of this so USE AT YOUR OWN RISK!

After you’ve downloaded all 3 of the listings you’ll want to run the UseDebugPreviews.reg command to update the registry to use Debug.htm as the preview template for all type wide previews.  There’s an assumption made in UseDebugPreviews.reg that Debug.htm is located in the root of your C-Drive so you’ll need to modify that file if that’s not the case.  It’s very important that Debug.htm is saved as a UTF-8 encoded file.  The macro replacement done by the Registry Based Previewer will not work if you save the file as Unicode which is the default for Notepad.exe.  You’ll also need to either reboot your machine or kill/restart Explorer.exe to get the new previews to take effect.  It’s worth noting at this point that changes made to an already mapped preview template do not require a reboot.  In fact you can modify your template and simply select another item from the results list to see your changes take effect which is nice. 

So once you have Debug.htm installed you should try some queries out and notice what properties are available to your previews in what views.  I said above that Debug.htm will show you MOST of the properties available because some of the views like contacts have a ton of properties.  There are around 200 total but not all of the properties for an item are available in every view.  For instance when an item is displayed in the Contacts view it has a rich set of properties available.  But that same item displayed in the Communications or Everything view has only a subset of its total properties available.  The Debug.htm preview displays the availability of the properties that can be shown for an item no matter what view its in and is therefore quite useful.

Another thing you might notice while browsing around is that image previews still get mapped to the stock image previewer.  That’s because that mapping is done on a per extension basis and UseDebugPreviews.reg only updates the type wide previews.  To map an extension say .JPG you’ll need to modify the registry by hand.  Change the TemplateUrl value of HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\Extension\.jpg to “c:\debug.htm” and re-boot.  The previews of .JPG items should now map to the debug preview.

 

Understanding Preview Templates

Without getting into too much depth I’ll give you an overview of the templates and the macros we currently support.  The templates themselves are just UTF-8 encoded HTML files with some special macros added in the form of <%cmd:property%>.  At preview generation time the template along with the meta data of the currently selected item gets run through a transform engine to generate a valid HTML file for rendering within the Content Previewers embedded WebBrowser control.  Most of the macros are about inserting the dynamic values of the currently selected item but there are a few control commands as well.  Here’s a quick list of the major (but not all) commands:

  • <%if:{property}%>, <%else%>, and <%endif%> commands.  These commands let you include or exclude a block of code based upon the availability of a property for an item.  For instance contact have don’t have a MusicAlbum property so you can conditionally display HTML specific to music albums by placing it between “<%if:MusicAlbum%>Only shown if <b>MusicAlbum</b> exists.<%endif%>” commands.
  • <%ifValue:{property}%> command is an extension to the <%if:{property}%> command that also checks to make sure a property has an actually value before including a fragment of HTML.
  • <%value:{property}%> command injects that properties value into the output HTML.  Things like quotes & ampersands get escaped out so its safe to place quotes around this content for passing as an argument to a JavaScript routine.
  • <%title:{property}%> command injects the label of the property into the HTML.  This helps to make sure the column names used in your preview match those used in the results list view.
  • <%url:{property}%> command is an interesting one.  If you recall I recommended you always specify MSHTML as the content type generated by the Regsitry Based Previewer.  When you do that the output from the previewer is a dynamically built multi-part MIME file.  So why do we do that?  Well for items that live in a store like Outlook we sometimes need to render content that doesn’t physically exist as a file on the hard-drive, like say an image sent as an attachment.  Well we still want to be able to preview those items so what we do is embed them as part of the MSHTML file we’re building.  Now from an authoring perspective you want to be able to write an image preview that can preview images that exist either on your hard-drive or within Outlook and the <%url:url%> command lets you do just that.  Take a look at Debug.htm and notice that we use this command to populate the src attribute of an <img /> tag.  If the item is a physical file within the file system you’ll get back the URL for the item but if it’s an item within Outlook, the item gets embedded as a part of MSHTML file being built and you get back a relevant URL to the added part.  The <%url:characterization%> command is another useful one when passed as the src of an <iframe /> because it embeds the body of any Outlook item and returns a URL to the linked content.  For non Outlook items you get back the first 1k or so of text from the item.

There are a few other interesting commands but I won’t dive into them at this point.  This is more then enough to get you started.  Just use Debug.htm as a starting point and be sure to send me a link to any interesting previews you come up with.

Important Notes and Tips

  • There’s no way to map the preview for a file system folder to a content previewer.
  • All preview templates MUST be saved as UTF-8 encoded files.
  • You shouldn’t reference a property using <%url:{property}%> more than once per template.  This cause the property to get embedded multiple times and for e-mail bodies you may get some other weirdness.
  • Tip:  Use the View Source content menu option off the preview panes browser to see the HTML being generated by your template.
  • Tip: You can view the stock templates using IE.  You’ll need to first do a search so the WDS results view is displayed.  Then you can past the “res:” URL of the template you want to view into IE’s address bar.  IE should then display a malformed HTML page.  Do a View Source from within IE to view the template.
  • Tip: You can find the MSHTML file for the current preview under the “%temp%\msnl” directory.  If something isn’t rendering the way you think it should make sure IE renders the raw preview file correctly before blaming the preview pane.  Then let us know. J

 

Listing 1: Debug.htm

To create the Debug.htm file copy everything below the “—copy—“ line into an empty Notepad.exe document.  Then save the document AS UTF-8 to “c:\Debug.htm”The saved file MUST be UTF-8 encoded.   Use Notepads Save As feature to do this because the default for Notepad is to save as Unicode which won’t work.

 

—copy—

<html>

   <head>

      <!-- Template: debug.htm -->

      <meta http-equiv="content-type" content="text/html; charset=UTF-8">

      <style>

            .debug-group       { font-family: tahoma; font-size: 12pt; border: 1px solid #7EB6F0; background-color: #2765AB; color: #FFFFFF; }

            .debug-row-header  { font-family: tahoma; font-size: 10pt;background-color: #FFFFFF; color: green; }

        .debug-row-hidden  { font-family: tahoma; font-size: 8pt; background-color: #EEF4FB; color: black; }

        .debug-row         { font-family: tahoma; font-size: 8pt; background-color: #FFFFFF; color: black; }

            .debug-cell-header { border-bottom: 1px solid #7EB6F0; }

        .debug-cell        { border-bottom: 1px solid #7EB6F0; }

      </style>

      <script>

 

        function AddHiddenRow( prop, title, value )

        {

           document.write('<tr class="debug-row-hidden">');

           document.write('<td class="debug-cell">' + prop + '</td>');

           document.write('<td class="debug-cell">' + title + '&nbsp;</td>');

           document.write('<td class="debug-cell">' + value + '&nbsp;</td>');

           document.write('</tr>');

        }

 

        function AddRow( prop, title, value )

        {

           document.write('<tr class="debug-row">');

           document.write('<td class="debug-cell">' + prop + '</td>');

           document.write('<td class="debug-cell">' + title + '&nbsp;</td>');

           document.write('<td class="debug-cell">' + value + '&nbsp;</td>');

           document.write('</tr>');

        }

 

      </script>

   </head>

   <body leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">

      <!-- First we'll dump out the available properties -->

      <br>

      <div align="center" style="width: 100%; padding: 0px 15px 0px 15px;">

         <div class="debug-group" align="left" style="width: 100%; height=100%;">

            <div style="padding: 5px 5px 5px 5px;">Item properties</div>

            <div style="overflow: auto; width: 100%;">

               <table border="0" width="100%" cellspacing="0" cellpadding="5">

                  <tr class="debug-row-header">

                     <td class="debug-cell-header" width="100">Property</td>

                     <td class="debug-cell-header" width="100">Title</td>

                     <td class="debug-cell-header">Value</td>

                  </tr>

                  <script>

                     <%if:DocFormat%>

                        AddHiddenRow('DocFormat', '<%title:DocFormat%>', '<%value:DocFormat%>');

                     <%endif%>

                     <%if:Url%>

                        AddHiddenRow('Url', '<%title:Url%>', '<%value:Url%>');

                     <%endif%>

                     <%if:HasAttach%>

                        AddHiddenRow('HasAttach', '<%title:HasAttach%>', '<%value:HasAttach%>');

                     <%endif%>

                     <%if:PerceivedType%>

                        AddHiddenRow('PerceivedType', '<%title:PerceivedType%>', '<%value:PerceivedType%>');

                     <%endif%>

                     <%if:IsDeleted%>

                        AddHiddenRow('IsDeleted', '<%title:IsDeleted%>', '<%value:IsDeleted%>');

                     <%endif%>

                     <%if:WorkID%>

                        AddHiddenRow('WorkID', '<%title:WorkID%>', '<%value:WorkID%>');

                     <%endif%>

                     <%if:IsAttachment%>

                        AddHiddenRow('IsAttachment', '<%title:IsAttachment%>', '<%value:IsAttachment%>');

                     <%endif%>

                     <%if:ConversationID%>

                        AddHiddenRow('ConversationID', '<%title:ConversationID%>', '<%value:ConversationID%>');

                     <%endif%>

                     <%if:FileExt%>

                        AddHiddenRow('FileExt', '<%title:FileExt%>', '<%value:FileExt%>');

                     <%endif%>

                     <%if:Rank%>

                        AddRow('Rank', '<%title:Rank%>', '<%value:Rank%>');

                     <%endif%>

                     <%if:DocTitlePrefix%>

                        AddRow('DocTitlePrefix', '<%title:DocTitlePrefix%>', '<%value:DocTitlePrefix%>');

                     <%endif%>

                     <%if:DocTitle%>

                        AddRow('DocTitle', '<%title:DocTitle%>', '<%value:DocTitle%>');

                     <%endif%>

                     <%if:DocAuthor%>

                        AddRow('DocAuthor', '<%title:DocAuthor%>', '<%value:DocAuthor%>');

                     <%endif%>

                     <%if:PrimaryDate%>

                        AddRow('PrimaryDate', '<%title:PrimaryDate%>', '<%value:PrimaryDate%>');

                     <%endif%>

                     <%if:Size%>

                        AddRow('Size', '<%title:Size%>', '<%value:Size%>');

                     <%endif%>

                     <%if:FileExtDesc%>

                        AddRow('FileExtDesc', '<%title:FileExtDesc%>', '<%value:FileExtDesc%>');

                     <%endif%>

                     <%if:DisplayFolder%>

                        AddRow('DisplayFolder', '<%title:DisplayFolder%>', '<%value:DisplayFolder%>');

                     <%endif%>

                     <%if:FlagText%>

                        AddRow('FlagText', '<%title:FlagText%>', '<%value:FlagText%>');

                     <%endif%>

                     <%if:IsFlagged%>

                        AddRow('IsFlagged', '<%title:IsFlagged%>', '<%value:IsFlagged%>');

                     <%endif%>

                     <%if:Create%>

                        AddRow('Create', '<%title:Create%>', '<%value:Create%>');

                     <%endif%>

                     <%if:DueDate%>

                        AddRow('DueDate', '<%title:DueDate%>', '<%value:DueDate%>');

                     <%endif%>

                     <%if:Importance%>

                        AddRow('Importance', '<%title:Importance%>', '<%value:Importance%>');

                     <%endif%>

                     <%if:ToName%>

                        AddRow('ToName', '<%title:ToName%>', '<%value:ToName%>');

                     <%endif%>

                     <%if:CcName%>

                        AddRow('CcName', '<%title:CcName%>', '<%value:CcName%>');

                     <%endif%>

                     <%if:AttachmentNames%>

                        AddRow('AttachmentNames', '<%title:AttachmentNames%>', '<%value:AttachmentNames%>');

                     <%endif%>

                     <%if:DocCompany%>

                        AddRow('DocCompany', '<%title:DocCompany%>', '<%value:DocCompany%>');

                     <%endif%>

                     <%if:Location%>

                        AddRow('Location', '<%title:Location%>', '<%value:Location%>');

                     <%endif%>

                     <%if:DocCategory%>

                        AddRow('DocCategory', '<%title:DocCategory%>', '<%value:DocCategory%>');

                     <%endif%>

                     <%if:DocKeywords%>

                        AddRow('DocKeywords', '<%title:DocKeywords%>', '<%value:DocKeywords%>');

                     <%endif%>

                     <%if:MusicAlbum%>

                        AddRow('MusicAlbum', '<%title:MusicAlbum%>', '<%value:MusicAlbum%>');

                     <%endif%>

                     <%if:FileName%>

                        AddRow('FileName', '<%title:FileName%>', '<%value:FileName%>');

                     <%endif%>

                     <%if:MusicGenre%>

                        AddRow('MusicGenre', '<%title:MusicGenre%>', '<%value:MusicGenre%>');

                     <%endif%>

                  </script>

               </table>

            </div>

         </div>

      </div>

 

      <!-- Next we'll embed any characterization data -->

      <br>

      <div align="center" style="width: 100%; padding: 0px 15px 0px 15px;">

         <div class="debug-group" align="left" style="width: 100%; height=100%;">

            <div style="padding: 5px 5px 5px 5px;">Characterization</div>

            <iframe height="200" width="100%" src="<%url:characterization%>">

            </iframe>

         </div>

      </div>

 

      <!-- Finally we'll show the items image if it has one -->

      <br>

      <div align="center" style="width: 100%; padding: 0px 15px 0px 15px;">

         <div class="debug-group" align="left" style="width: 100%; height=100%;">

            <div style="padding: 5px 5px 5px 5px;">Image</div>

            <table border="0" width="100%" cellspacing="0" cellpadding="5">

               <tr class="debug-row-header">

                  <td><img src="<%url:url%>"></img></td>

               </tr>

            </table>

         </div>

      </div>

   </body>

</html>

Listing 2: UseDebugPreviews.rgs

To create the UseDebugPreviews.rgs file copy everything below the “—copy—“ line into an empty Notepad.exe document.  Then save the document to “c:\UseDebugPreviews.rgs”.

 

—copy—

Windows Registry Editor Version 5.00

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType]

@=""

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\communications]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\communications/calendar]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\communications/email]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\communications/im]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\contact]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document/note]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document/presentation]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document/spreadsheet]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document/text]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\favorite]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\folder]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\images]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\images/picture]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\images/video]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\music]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\program]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="c:\\debug.htm"

"ScriptOk"=dword:00000001

 

Listing 3: UseStockPreviews.rgs

To create the UseStockPreviews.rgs file copy everything below the “—copy—“ line into an empty Notepad.exe document.  Then save the document to “c:\UseStockPreviews.rgs”.

 

—copy—

Windows Registry Editor Version 5.00

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType]

@=""

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\communications]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/communications.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\communications/calendar]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/communications-calendar.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\communications/email]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/communications-email.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\communications/im]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/communications-im.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\contact]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/contact.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/document.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document/note]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/document-note.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document/presentation]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/document-presentation.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document/spreadsheet]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/document-spreadsheet.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\document/text]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/document-text.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\favorite]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/favorite.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\folder]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/folder.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\images]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/images.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\images/picture]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/images-picture.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\images/video]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/images-video.htm"

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\music]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/music.htm"

"ScriptOk"=dword:00000001

 

[HKEY_CURRENT_USER\Software\Microsoft\RSSearch\ContentIndexCommon\Previewers\PerceivedType\program]

@=""

"ContentType"=dword:00000001

"TemplateUrl"="res://msnlExt.dll/program.htm"

 

June 07

Hello Worl*

Hi there...

My name is Steve Ickman and I'm a developer on Windows Desktop Search. My goal for this blog is to facilitate Dev to Dev communication for all things WDS related.  This should be pretty easy considering we don’t actually have an API. :)  But even without an API there are still some pretty interesting ways you can enhance the power of WDS.  So let’s consider chapter one of this conversation more Dev to Power User and we’ll see where things go from there…

-Steve

 
My favoritec Sci-Fi movies
Stardust
Minority Report

Steven Ickman

Occupation
UI developer on Windows Desktop Search. The views expressed within my MSN Space are of my own and are not in any way that of the company I work for, Microsoft, or it's employees.