It’s again that time of the year, its MIX in Las Vegas. Lots and lots of sessions and every session is recorded on video. So even if you can’t be in Vegas get the whole thing. But I don’t like the idea of downloading all of those via the browser. So I wrote a script to download them via BitsTransfer from PowerShell loosely based on a script from poshcode I used to get videos from PDC. I can’t find the original author and his script again but thanks a lot.

Update: I polished the script a little bit like warnings if a file is already on disk, listing all missing sessions, sorting index, removing the dummy, some minor bugfixing with naming, etc. Still not perfect but better. Rename to to extract.

The script takes a session code as parameter. It then grabs the RSS-Feed to extract title, speaker, tags etc. These values will inserted into an index.xml file (maybe I write a corresponding XSLT for viewing but you can already view it in the browser). After that a BitsTransfer Job is started to download the video.

If the script is called without parameter it will list all missing sessions.

I use some small functions in my profile to control downloads:

# Downloads with BitsTransfer
function Get-Downloads {
  Import-Module BitsTransfer
  Get-BitsTransfer | % { "{0}: {2} {3:0.00} MB/{4:0.00} MB ( {1:0.00}% )" -f $_.DisplayName, $(100*$_.BytesTransferred/$_.BytesTotal), $_.JobState, ($_.BytesTransferred/1MB), ($_.BytesTotal/1MB) }
Set-Alias "dl" "Get-Downloads"

# Get a list of all downloads
function Complete-Downloads 
  Import-Module BitsTransfer
  Get-BitsTransfer | ? { $_.JobState -eq "Transferred" } | Complete-BitsTransfer 

# suspend all Downloads (optional all below a certain threshhold
function Suspend-Downloads( [int] $maxPercent = 100 )
  Get-BitsTransfer | ? { $_.JobState -eq "Transferring"  -and ($_.BytesTransferred/$_.BytesTotal) -lt ($maxPercent/100) } | Suspend-BitsTransfer 

#resume all Downloads (optional all above a certain threshhold
function Resume-Downloads( [int] $minPercent = 0 )
  Get-BitsTransfer | ? { $_.JobState -eq "Suspended" -and ($_.BytesTransferred/$_.BytesTotal) -gt ($minPercent/100) } | Resume-BitsTransfer -Async

# download Mix Session Videos. Call it like mix "EX21" "CL01" "FT05"
function Mix()
  complete-downloads; $args | % { & $scripthome\get-Mix10Video.ps1 $_ } ; sleep -Seconds 5; get-downloads

Use Get-Downloads to view the progress of the download and Complete-Downloads to finish it (The file will not show up until you complete it)

Use the mix function to call the script with multiple sessions e.g. mix “EX14” “EX06” will download the excellent talks of Laurent Bugnion about MVVM Pattern and Robby Ingebretsen about Design Principles and other things.

Extract the zip. Pay attention to not overwrite your index.xml if you already have it. Put the script at any place appropriate to you and the index.xml into the destination directory. I suggest putting your destination path into the script instead of my default value. Call it either with ? to get the list of available sessions or with a session code to download these video.

An Example:

.\Get-Mix10Video.ps1 CL01

will put the file CL01-Introduction to Windows Phone 7 Series.wmv into your destination directory.

#requires -version 2.0
   [Parameter(Position=1, Mandatory=$false)]

   [Parameter(Position=2, Mandatory=$false)]
   [ValidateSet("wmv","wmv-hq","pptx", "mp4")]
   [String]$MediaType ="wmv-hq",
   [string]$Destination = "F:\Videos\Mix10",
   [string]$rss = ""

Import-Module BitsTransfer

#$illegalChars = "[{0}]" -f ([Regex]::Escape([String] [System.IO.Path]::GetInvalidFileNameChars()))
$illegalChars = '[:*?\\\/\t\n<>|"]'

# Get Session RSS, parse Titel, Speaker, Tags, Description, ...
$wc = new-object System.Net.WebClient
$rssdata = [xml]$wc.DownloadString($rss)
$item = $ | ? { $$Video) }

Push-Location $Destination
$Extension = $(switch -wildcard($MediaType){"wmv*"{"wmv"} "mp4"{"mp4"} "pptx"{"pptx"}})
$SrcUrl = "{0}/{2}.{1}" -f  $MediaType, $Extension, $Video
$Destfile = ( "{0}-{1}.{2}" -f $Video, $Title, $Extension ) -replace $illegalChars, ""
$Destpath = $("{0}\{1}" -f $Destination, $Destfile)
$indexFile =  $("{0}\{1}" -f $Destination, "index.xml")

$Title = $item.title

# get index.xml
$index = new-object XML

# returns the list of the missing sessions if code is ? or item not found
# use filter like | ? { $_.tags -contains "WindowsPhone" } | % { .\ $_.code }
if ( $Video -eq "?" -or $item -eq $null )
    $ |  % {
         New-Object PsObject -Property @{ 
            code    = $$"/") +1 )
            title   = $_.title
            speaker = $
            tags    = @( $_.category ) 
    } | ? {
        $xpath = "//session"
       $index.SelectSingleNode($xpath) -eq $null
    } | sort -Property "code"

# does index already contains session?
$session = $index.SelectSingleNode("//session")
if ($session -eq $null) {
  $firstSession = @( $index.sessions.session )[0]
  $session = $firstSession.Clone()
  $session.code = $Video

$session.title = $Title
$session.speaker = $
$session.tags = $item.category  -join ","
$session.sessionref = $
$session.local = $Destpath
$session.href = $SrcUrl
$session.description = $item.description

# remove dummy as it is no longer needed
$dummy = $session = $index.SelectSingleNode("//session")
if ($dummy -ne $null) { $index.sessions.RemoveChild( $dummy ) }

# sort sessions by code
[void] ( $index.sessions.session | sort -Property "code" | % { $index.sessions.RemoveChild($_); $index.sessions.AppendChild($_) } )


# file downloaded in former session?
if (Test-Path "$Video*.$Extension") {
  $title = "File $Video*.$Extension already exists!"
  $message = "Do you want to delete the existing file and download it new?"

  $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
      "Delete $Destfile and start download"

  $continue = New-Object System.Management.Automation.Host.ChoiceDescription "&Continue", `
      "Continue without deleting file"
  $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
      "Hold file and skip download"

  $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $continue, $no)

  $result = $host.ui.PromptForChoice($title, $message, $options, 2) 

  switch ($result)
        0 { delete $Destpath }
        1 { "Continuing with download" }
        2 {             


#start download
[void] ( Start-BitsTransfer -Source $SrcUrl -Destination $Destpath -DisplayName ($Video +": "+$Title)  -Description $item.description -Async )


Write-Host "You may now use Get-BitsTransfer to check on the status of the downloads. By default, failed transfers will be retried every 10 minutes for two weeks."

The index.xml uses the following format:

<?xml version="1.0" encoding="utf-8"?>
<!-- Sessionlist unter brauchbar? -->
    <title>Changing our Game – an Introduction to Windows Phone 7 Series</title>
    <speaker>Joe Belfiore</speaker>
    <tags>Mobile, Windows Phone</tags>
    <local>CL01-Introduction to Windows Phone 7 Series.wmv</local>
      Major changes are coming to Windows Phone! This session goes in-depth on the design and 
      features of Windows Phone and gives a comprehensive picture of what’s 
      coming in this exciting new release.