Microcode: Cleaning up Get-RecordedTV with Select-Object

In a previous post, I introduced Get-RecordedTV, which was built upon another function, Search-WindowsDesktop.

The old version of Get-RecordedTV directly returned the properties related to DVR from Windows Desktop Search, with incredibly long names like 'System.RecordedTV.IsRepeatBroadcast'.  In the interests of making future code shorter and explaining one of the free benefits you get with objects in PowerShell (Select-Object), I'm going to start by giving a new Get-RecordedTV.  Instead of giving you the properties directly from Desktop Search, this Get-RecordedTV reinterprets the results to make them easier to read.

function Get-RecordedTV() {
    Search-WindowsDesktop 'System.Title',
      'System.RecordedTV.EpisodeName',
      'System.RecordedTV.ChannelNumber',
      'System.RecordedTV.StationName',
      'System.RecordedTV.IsRepeatBroadcast',
      'System.RecordedTV.StationCallsign',
      'System.RecordedTV.ProgramDescription',
      'System.RecordedTV.IsSAP',
      'System.RecordedTV.IsHDContent',
      'System.RecordedTV.RecordingTime',
      'System.Video.FrameHeight',
      'System.Video.FrameWidth',
      'System.Video.EncodingBitrate',
      'System.Media.DateEncoded',
      'System.ParentalRating',
      'System.Size',
      'System.ItemPathDisplay' "WHERE System.RecordedTV.ChannelNumber IS NOT NULL" |
    Select-Object @{Name='Series';Expression={$_.'System.Title'}},
      @{Name='Episode';Expression={$_.'System.RecordedTV.EpisodeName'}},
      @{Name='Description';Expression={$_.'System.RecordedTV.ProgramDescription'}},
      @{Name='Channel';Expression={
          "$($_.'System.RecordedTV.ChannelNumber')-$($_.'System.RecordedTV.StationCallsign')-$($_.'System.RecordedTV.StationName')"
      }},
      @{Name='File';Expression={
          Get-Item $_.'System.ItemPathDisplay' -errorAction SilentlyContinue
      }},
      @{Name='Size';Expression={$_.'System.Size'}},
      @{Name='DateRecorded';Expression={$_.'System.RecordedTV.RecordingTime'}},
      @{Name='IsSAP';Expression={$_.'System.RecordedTV.IsSAP'}},
      @{Name='IsHD';Expression={$_.'System.RecordedTV.IsHDContent'}},
      @{Name='IsRepeatBroadcast';Expression={$_.'System.RecordedTV.IsRepeatBroadcast'}},
      @{Name='Width';Expression={$_.'System.Video.FrameWidth'}},
      @{Name='Height';Expression={$_.'System.Video.FrameHeight'}},
      @{Name='Rating';Expression={$_.'System.ParentalRating'}}
}

This entire function is still one pipeline.  It gets the results from Search-WindowsDesktop, and, as each item arrives, it pipes the results to Select-Object.

Select-Object is an incredibly useful PowerShell Cmdlet.  It is used to select one or more objects from a type, and return them in a bag of properties.  While it works great for selecting just a property or two (e.g. Get-Process | Select Name, ID), it can also be used in some interesting other ways.

The way I use it above is to rename a number of properties.  If you have an parameter in Select-Object that takes the format @{Name='String';Expression={expression}}, you will create a new property on the object with the value of the expression.  You can refer to something on the current object by using $_ (which, in PowerShell, is pretty commonly the current element in the pipeline).  You can also use this to run methods and return their values as properties.  And, finally, you can actually create a type that is only properties this way, by using a simple other PowerShell coolness, the range operator.

So, to create one hundred anonymous objects with two random numbers:

$random=New-Object Random
1..100 |
  Select-Object @{Name='Random1';Expression={$random.Next()}},
  @{Name='Random2';Expression={$random.Next()}}

People familiar with functional programming might know this sort of thing as a Tuple, but knowing the correct terminology doesn't help the usefulness sink in all that much to me.

In the case of Get-RecordedTV, the first use is pretty obvious: it reduces the amount of typing for each property name and makes it easier to identify.  The second advantage is more subtle.  By re-interpreting the return type from Search-WindowsDesktop to be a bunch of properties that the system decides to a bunch of properties that I choose, it means that I can make the dependency on Search-WindowsDesktop removable.  In doing so, I hurt anyone that has already built scripts around Get-RecordedTV, because they have to change their scripts to handle a new signature for the anonymous type, because the change subtracted types.

Subtractive changes break backwards compatibility, but in this case it frees me from the stack that I used to depend on.  This is a wonderful flexibility of writing in an interpreted language, and can be valuable because you can use it to make a flexible dependency instead of a fixed one.  With a flexible dependency, if I find out that they have turned Windows Desktop Search off, or I find a better mechanism down the line, I can replace my dependency on Search-WindowsDesktop with another technology.

I could write an interface to express the data I want to get, but that's overkill for my situation.  If I write an interface, then I have to have a real type already loaded, and I've swapped one dependency (on Search-WindowsDesktop) for two ( the type that implements the interface and the interface type ) and have essential hard-bolted a dependency on my interface to any application that wants to use the data.

Anonymous types allow you to get most of the benefits of interfaces with a flexible dependency.  As long as you keep the bag of properties the same, you can build upon scripts that return anonymous properties.  You can use this to coerce data into forms that are easier to remember, and to summarize data in simpler forms.  You will also find that a number of cmdlets within PowerShell (Select-Object, Sort-Object, and Group-Object) work amazingly well with bags of properties.

In a later posts, we'll use this base of code and these cmdlets to prune the collection, and demonstrate how the use of property bags and the exploration into Windows Desktop Search leads to a variety of wonderful side effects.

Hope this Helps,

James Brundage [MSFT]