DESCRIPT.ION support in Monad — Part 3

Last time, we wrote code to refresh our description data while we navigate the file system.  We hadn't yet integrated it to our directory listings, so let's do that now.

Step 3: Customize the format.mshxml for directory listings

Our final step is to customize the formatting information that Monad uses to output a directory listing.  This is a fairly small change that mimics the way we output the “LastWriteTime” column.

First, open “FileSystem.format.mshxml” from your Monad installation directory.  This file tells Monad how convert the raw .Net File System objects into a form more suitable for humans.  The tweaking we are about to do illustrates one of the fundamental powers of Monad.  Data in the pipeline retains its full object-oriented fidelity until it reaches your screen.  This final stage of the pipeline is called “Formatting and Output.”

Now, before we customize our directory listing, let’s go over the different parts of the formatting file to see what it does.

The formatting files are XML.  The root <Configuration> node contains the following high-level configuration nodes: SelectionSets, Controls, and ViewDefinitions.

<Configuration>
    <SelectionSets>
        <SelectionSet>
           ...
        </SelectionSet>
    </SelectionSets>

    <!-- # GLOBAL CONTROL DEFINITIONS # -->
    <Controls>
        <Control>
           ...
        </Control>
    </Controls>
   
    <!-- # VIEW DEFINITIONS # -->
    <ViewDefinitions>
       <View>
          ...
       </View>
       <View>
          ...
       </View>
    </ViewDefinitions>
</Configuration>

The SelectionSets node lists a set of .Net types.  Later, the custom views from this file use this SelectionSet to help Monad determine the object types to which Monad should apply this formatting information.

The Controls node defines a custom header.  Later, custom views from this file use this header on groups of file system objects.

The Views form the real core of this file:

<ViewDefinitions>
   <View>
      <Name>children</Name>
      <ViewSelectedBy>
         ...
      </ViewSelectedBy>
      <GroupBy>
         ...
      </GroupBy>
      <TableControl>
         <TableHeaders>
            ...
         </TableHeaders>
         <TableRowEntries>
            ...
         </TableRowEntries>
      </TableControl>
   </View>
   <View>
      ...
      <ListControl>
         ...
      </ListControl>
   </View>
   <View>
      ...
      <WideControl>
         ...
      </WideControl>
   </View>
</ViewDefinitions>

We start by naming the view, and then use the ViewSelectedBy node to reference the list of .Net objects to which this view applies.  This reference is the name of one of the SelectionSets nodes earlier in the file.  Next, the GroupBy node tells Monad how to group the objects.  For example, a directory listing is grouped by its parent.  Each grouping gets a header, as defined by one of the Controls earlier in the file.  This is most evident when you do a recursive directory listing, as in “dir –rec.”

Then, we define three views: a TableControl, a ListControl, and a WideControl.  These correspond to the following statements, respectively:

 dir | format-table
 dir | format-list
 dir | format-wide

We’ll focus on (and customize) only the TableControl.

A TableControl has a header row, followed by data rows.  We define the header format with a sequence of TableColumnHeader nodes, then the row format with a sequence of TableRowEntry nodes.

Headers have a label, width, and alignment.  In this file, row entries reference either a direct property of the underlying object, or a ScriptBlock.  As illustrated by the LastWriteTime entry, ScriptBlocks reference the current pipeline object using the “$_” automatic variable.

When Monad loads its formatting data, the first file to define a view wins.  Ours will define the TableControl for DirectoryInfo objects first, so it will override the view that the default FileSystem.format.mshxml defines.

To make Monad look at our file before the default, add the following lines to your profile.msh:

## Update the formatting XML, overriding one of the filesystem
## views with the one we define in FileSystem.Description.Format.mshxml
$formatFile = "$(parse-path $profile)\FileSystem.Description.Format.mshxml"
update-FormatData -prependpath $formatFile

Now, copy the FileSystem.format.mshxml into the same directory as your profile, but call it FileSystem.Description.format.mshxml.  After you copy, change its read-only bit so that you can edit it.  Open the file, now at “My Documents”\msh\FileSystem.Description.format.mshxml.  We’ll place our customizations in this file, rather than hacking on the one that ships with Monad. 

For a test, go to a temporary directory and create a file with a very long name:

MSH:7 C:\Temp >"hi" > ("a"*200)
MSH:8 C:\Temp >dir

    Directory: FileSystem::C:\Temp

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         10/9/2005   3:16 PM          4 aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaa

In your custom formatting file, delete the <wrap /> node, reload MSH, and then do another listing in that directory:

MSH:2 C:\Temp >dir

    Directory: FileSystem::C:\Temp

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         10/9/2005   3:16 PM          4 aaaaaaaaaaa...

You can delete the long file now, as we’re done with it.

Let’s finally get our description column into directory listings.  In your custom formatting file, do the following:

  1. Delete the SelectionSets node, and all of its children.  The defaults will serve us just fine.
  2. Delete the Controls node, and all of its children.  The defaults will serve us just fine.
  3. Delete the View node that contains the ListControl.
  4. Delete the View node that contains the WideControl.
  5. Change the width of the LastWriteTime header to 20.
  6. After the last Table Column Header (labeled “<TableColumnHeader/>”,) create a new TableColumnHeader node:

    <TableColumnHeader>
       <Label>Description</Label>
       <Alignment>left</Alignment>
       <Width>30</Width>
    </TableColumnHeader>

  7. After the last Table Column Item (the one for “Name,”) create a new TableColumnItem node:

    <TableColumnItem>
       <ScriptBlock>
          ${lee.holmes.descriptions}[$_.Name]
       </ScriptBlock>
    </TableColumnItem>

  8. Your file should now look like this [FileSystem.Description.Format.mshxml]
  9. Create a file called “DESCRIPT.ION” in your profile directory.  Place the following in it:

    DESCRIPT.ION Descriptions file
    FileSystem.Description.Format.mshxml Formatting customizations
    profile.msh MSH custom profile

  10. Restart your MSH shell, navigate to your profile directory, and get a directory listing:

And there you have it.  DESCRIPT.ION support in Monad.

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

3 Responses to “DESCRIPT.ION support in Monad — Part 3”

  1. ac writes:

    Couldn’t there be a common way to plug in some managed code to both Monad and Explorer listings or possibly only the other by user choice.

    I wonder what would be the performance implications of plugging in some event on the OnDirListingBegin:

    pseudo c#ish code:

    public Descriptions descs;
    public void OnDirListingBegin(Directory d) {

    if (d.Files.exists("DESCRIPT.ION")) {
    descs = new Descriptions("DESCRIPT.ION");
    d.Fields.Add("Description",
    delegate (File f) { return descs.GetForFile(f); }
    );
    }

    }

    What do you think?

  2. Lee writes:

    I don’t think that performance would be the problem in a system like this, as Explorer already supports right-click shell extensions, and numerous other extension points.

    I think the problem right now would come from the intermingling of the native and managed code worlds. The explorer shell is written in native code, and so are its extensions. You can only host one version of the CLR per process, so the team would have a significant challenge loading plugins written to different versions of .Net.

    I like the idea, though.

  3. Keith J. Farmer writes:

    Hmm..

    Now you should modify it to include an indicator for .msh files which pass signature verification. 😉

Leave a Reply