Learning PowerShell: Random Album

With the inclusion of PowerShell in Windows 7, it has become much more accessible and widely used.  PowerShell has been on my list of technologies to learn for a few months now.  I’ve just been waiting for the right project to use as an excuse to try out PowerShell.

I found that project due to the lack of plug-in support in the Zune desktop application.  I listen to a lot of music at work, and I like to listen to entire albums.  What I wanted was a way to randomly select an album that I should listen to next.  I don’t want an album where I only have one or two songs, I want to select an entire album.

Enter PowerShell.

Step 1: The Integrated Scripting Environment

To get started, open up the PowerShell ISE by typing “ise” in the Windows 7 Start menu.  Right click the PowerShell ISE and select run as Administrator (this will save some headaches later).

image

We’ll see a three-paned window, so let’s step through the three panes one-by-one.

Top: Editor

The top pane is the script editor.  This is where we can enter a multi-line PowerShell script and save is as a .ps1 file.  It has syntax highlighting, as you would expect from an editor.

Middle: Output

This is where the output of your script will be displayed.  If you ran your script in a PowerShell console, the text that appears in this box would appear just below the line you entered.

Bottom: Prompt

The bottom pane is a PowerShell prompt, just like you would see in a PowerShell console.  You can execute single PowerShell commands here without including them in your script.

Step 2: Directory listing

image

Let’s start with a simple script to list out the subdirectories of a directory.  We’ve wrapped that in a function, because we’ll be using recursion later.

The second line calls two built-in PowerShell cmdlets, get-childitem and where-object.  Get-childitem, when called on a path, lists the contents of that directory.  The output is then piped into where-object, with a constraint saying we only want directories, not files.  The $_ variable is used similar to the way it is in Perl to refer to the output of the last line.  The end result of this line is an array of subdirectories of D:\Music. 

For me, this is a list of artist names and a Zune directory that contains all the music I’ve downloaded from Zune.  What I want is the albums (subdirectories of the artists) and the albums within the Zune directory (contained with artist directories).

Step 3: Getting Albums

image

These changes now pull subdirectories of the artist directories (which are albums) and limit those albums to ones that have at least 8 files in them.  Note that the last call to where-object looks for files, not directories (it negates the psiscontainer call).

One thing that always trips me up in PowerShell is the operators.  Sometimes it looks very similar to C#, but the operators are totally different – see the –gt for greater than.

Step 4: Handling the Zune Directory

image

Now we’re using recursion to handle the Zune directory.  We also need to ignore some of the Zune special directories that aren’t artist directories (note the –or operator, that tripped me up).  We also start using array variables and adding albums to the array using the += operator.

Step 5: Random Album

image

The last thing left to do is select a random album from the results.  We’ll do this by wrapping the Get-Album function with a new function that uses the Get-Random cmdlet to select an album from our master list.

Step 6: Running the Script

You can execute the script at any point by pressing F5, but after you save the script, you’ll need to change the permissions to allow execution of the script.  By default, PowerShell is fairly locked down in terms of permissions.  To allow execution of your own saved scripts, you’ll need to type this in the PowerShell prompt once:

 set-executionpolicy remotesigned

Here’s a listing of the entire script:

 function Get-Album ($topDir)
{
    $directories = get-childitem $topDir | 
                    where-object {($_.psiscontainer)}
    $returnvalues = @()
    foreach ($dir in $directories)
    {
        if($dir.Name -match "Channels" -or 
            $dir.Name -match "Books" -or
            $dir.Name -match "Playlists" -or
            $dir.Name -match "Podcasts" -or
            $dir.Name -match "Received Pictures" -or
            $dir.Name -match "Zune Temporary Music") 
            {
                continue
            }
        if($dir.Name -match "Zune" -or
            $dir.Name -match "Subscription")
            {
                $returnvalues += @(Get-Album $dir.FullName)
            }
        else
            {
                $subdirs = Get-Childitem $dir.FullName | 
                            where-object {($_.psiscontainer)}
                foreach ($subdir in $subdirs)
                {
                    $files = Get-Childitem $subdir.FullName | 
                                where-object {!($_.psiscontainer)}
                    if($files.count -gt 7)
                    {
                        $returnvalues += 
                            @($dir.Name + ' - ' + $subdir.Name)
                    }
                }
            }
    }
    return $returnvalues
}

function Get-RandomAlbum ($topDir)
{
    $albums = Get-Album $topDir
    $index = Get-Random -min 0 -max ($albums.count - 1)
    return $albums[$index]
}

Get-RandomAlbum D:\Music

The next step is to wrap this all into a Windows Gadget so it’s on my desktop all the time and I don’t need to launch PowerShell to get a random album suggestion.