Updating apps with PowerShell 5.0 and Chocolatey

An introduction to the PackageManagement module

 
If you are using Windows 10 or if you have installed the Windows Management Framework 5.0 production Preview (available here), you may have noticed a PowerShell module named PackageManagement.

This is the new name for what was called “OneGet” in previous versions of the WMF 5.0.
It contains 10 cmdlets :

Module PackageManagement

So, what is this ?
This is a package manager manager. No, you are not seeing double. It really is a manager of package managers, in other words, a framework to integrate multiple package managers.
These package managers are called “PackageProviders” in OneGet terminology :

PS C:\> Get-PackageProvider

Name                     Version          DynamicOptions
----                     -------          --------------
msi                      10.0.10514.6     {AdditionalArguments}
msu                      10.0.10514.6     {}
Programs                 10.0.10514.6     {IncludeWindowsInstaller, IncludeSystemComponent}
NuGet                    2.8.5.127        {Destination, SkipDependencies, ContinueOnFailure, Excl...
Chocolatey               2.8.5.130        {SkipDependencies, ContinueOnFailure, ExcludeVersion, F...
PSModule                 1.0.0.0          {PackageManagementProvider, Location, InstallUpdate, In...

 
One of these awesome package managers is Chocolatey. Chocolatey is very popular and using its provider for PackageManagement gives us access to a software catalog of almost 3000 packages !

In the Production Preview of WMF 5.0 (from August), the Chocolatey provider is not installed by default. Fear not : it can be installed by just answering “Y” to the following prompt :

PS C:\> Find-Package -ProviderName chocolatey

The provider 'chocolatey v2.8.5.130' is not installed.
chocolatey may be manually downloaded from https://oneget.org/ChocolateyPrototype-2.8.5.130.exe and installed.
Would you like PackageManagement to automatically download and install 'chocolatey' now?
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"):

 
Now, let’s install stuff !
We can search in the Chocolatey gallery using the cmdlet Find-Package , like so :

PS C:\> Find-Package -Source chocolatey -Name sublimetext2 -AllVersions

Name                           Version          Source           Summary
----                           -------          ------           -------
sublimetext2                   2.0.1            chocolatey       Sublime Text 2 is a sophisticate...
sublimetext2                   2.0.1.1          chocolatey       Sublime Text 2 is a sophisticate...
sublimetext2                   2.0.2.2221       chocolatey       Sublime Text 2 is a sophisticate...
sublimetext2                   2.0.2139         chocolatey       Sublime Text 2 (beta) is a sophi...
sublimetext2                   2.0.2165         chocolatey       Sublime Text 2 (beta) is a sophi...
sublimetext2                   2.0.2181         chocolatey       Sublime Text 2 (beta) is a sophi...
sublimetext2                   2.0.2210         chocolatey       Sublime Text 2 (beta) is a sophi...
sublimetext2                   2.0.22101        chocolatey       Sublime Text 2 is a sophisticate...

 
We see above that there are different versions for this package but we don’t have to worry about this, unless we want a specific version of a package. Without the parameter -AllVersions, this gets only the latest stable version of any given package.

Now, we install the latest stable version of Sublime Text :

Install-Package

So, we can manage the installation of one or multiple software packages right from PowerShell. This is nice, but how do we keep them up-to-date ?

As we have seen at the beginning of the post, there is a Install-Package cmdlet, a Uninstall-Package cmdlet, but there is no Update-Package cmdlet. According to this issue in the project page, this may be coming relatively soon.
In the meantime, let’s roll on own.

Updating Chocolatey packages with PowerShell

 
Let’s say we have a bunch of apps which were installed from Chocolatey with Install-Package, and if they are not up-to-date, we want to update them to the latest stable version available in the Chocolatey gallery.

Here is what we currently have :

PS C:\> Get-Package -ProviderName chocolatey

Name                           Version          Source           Summary
----                           -------          ------           -------
7zip                           9.22.01.20130618 C:\Chocolatey... 7-Zip is a file archiver with a...
7zip.install                   9.22.01.20130618 C:\Chocolatey... 7-Zip is a file archiver with a...
autohotkey.portable            1.1.22.09        C:\Chocolatey... AutoHotkey is a free, open sour...
DotNet4.5                      4.5.20120822     C:\Chocolatey... The Microsoft .NET Framework 4.5
filezilla                      3.14.1           C:\Chocolatey... FileZilla – The free FTP solution
foxitreader                    7.0.6.1126       C:\Chocolatey... Foxit Reader: The Best PDF Reader
github                         3.0.6.4          C:\Chocolatey... Git client for GitHub.com proje...
Recuva                         1.51.1063        C:\Chocolatey... Recuva recovers files deleted f...
SublimeText2                   2.0.2.2221       C:\Chocolatey... Sublime Text 2 is a sophisticat...
vlc                            2.2.0            C:\Chocolatey... VLC Media Player

 
The first thing we need to do is compare the currently installed version with the latest stable version in the Chocolatey gallery for each package :
Compare-Object Packages Version

As you can see above, Compare-Object is not going to help us very much. It is only telling us when a version number from one side is not found on the other side. What we want to know is : for each installed package, is the version number lower than the version number of the latest version in the Chocolatey gallery ?

To simplify our experimentations, we are going to work on a single package. When we are done, we’ll be able to use a foreach loop extend the logic to multiple packages, so it’s not a big deal. Let’s take VLC Media Player :

VLC Package Versions

Here, we see that our currently installed version of VLC is not the latest version available. Now, how can we programmatically and reliably determine that ?

We also see that on both sides the value of the Version property is a string. How do we compare strings ? With regular expressions, woohoo !!
Did you feel the irony in the “woohoo” ? I thought so.

Comparing version numbers reliably and taking into account all the possible versioning schemes using regex is feasible but very painful. As I said before, if you are parsing text in PowerShell, there is probably a better way.

The better way in this case, is to cast our miserable strings to powerful objects of the type Version :

PS C:\> [Version]$($CurrentPackage.Version)

Major  Minor  Build  Revision
-----  -----  -----  --------
2      2      0      -1


PS C:\> [Version]$($LatestPackage.Version)

Major  Minor  Build  Revision
-----  -----  -----  --------
2      2      1      20150630


PS C:\> [Version]$($CurrentPackage.Version) -lt [Version]$($LatestPackage.Version)
True

 
Thanks to the .NET Framework class System.Version, we can simply use a comparison operator (-lt, here) to reliably tell if the currently installed package has a lower version number than the latest version in the Chocolatey gallery.

If this is true (meaning if the installed package is not up-to-date), we update it from the Chocolatey gallery :

C:\> If ([Version]$($CurrentPackage.Version) -lt [Version]$($LatestPackage.Version)) {
    Install-Package -InputObject $LatestPackage } Else {
    Write-Output "$($CurrentPackage.Name) is up-to-date" }


Name                           Version          Source           Summary
----                           -------          ------           -------
vlc                            2.2.1.20150630   chocolatey       VLC Media Player

C:\> $CurrentPackage = Get-Package -ProviderName chocolatey -Name vlc
C:\> If ([Version]$($CurrentPackage.Version) -lt [Version]$($LatestPackage.Version)) {
    Install-Package -InputObject $LatestPackage } Else {
    Write-Output "$($CurrentPackage.Name) is up-to-date" }
vlc is up-to-date

 
The first if statement evaluates to True so it installs the latest version.
Following the update of the vlc package, we refresh the value of $CurrentPackage. Then, when we run the if statement again, it evaluates to False (as expected) so this doesn’t trigger a download and install.

That’s it.
I have encapsulated the same logic in a function called Update-ChocolateyPackage which can update all the Chocolatey packages, or only the package(s) specified with a -Name parameter. You can grab it here.

Also, it can be used to check the Chocolatey packages for updates, without actually updating anything. This can be done by adding the parameter -WhatIf.
The output “What if” information is only in the case where a package is not up-to-date, so no output means that all packages are up-to-date.
 

Download Podcasts with PowerShell

A while back, I saw this article from the Scripting Guy on parsing RSS feeds.
Being an avid RSS feeds consumer and podcast watcher/listener, this was a “aha” moment.
After all, podcasts are basically RSS feeds with a media file attached to each entry. So, my project was to build PowerShell cmdlets to extract the desired information from podcasts and podcast entries/episodes, and to download them.

The first version of my Podcast module was using Invoke-WebRequest for the download of media files. It was quite slow and if there was multiple media files to download, it was not downloading them in parallel.

So, this new version uses BITS to download the media files via the cmdlet Start-BitsTransfer. This has many advantages :

  • It is faster
  • It allows to start several downloads in parallel using the parameter -Asynchronous
  • It pauses the download(s) if connectivity is lost and automatically resumes them when the connectivity is back.
  • It allows to easily track the progress of download(s) with the property
    BytesTransferred of the BitsJob object(s).

I won’t explain the code in the whole module because it would take at least 4 blog articles alone. I will just demonstrate the usage of its 4 cmdlets.

Get-Podcast :

You can give it one or more podcast URL(s) to obtain information on the most recent episodes for the specified podcasts :

Get-Podcast

It is also possible to send it a bunch of URLs via the pipeline.

Most often, I don’t manually specify URLs, I give it a file listing all my favorite podcasts as input :

Get-Podcast_List

Notice the parameter -FromLastDays, this retrieves only the podcasts published after the specified number of days ago. This is a nice way to check your podcasts for updates. Here, I know I was up-to-date on all my favorite podcasts 7 days ago so I use -FromLastDays to check what is new since then.

By the way, if you want a list of cool tech podcasts, here is mine :

http://s.ch9.ms/Shows/Defrag-Tools/feed/mp4high
http://s.ch9.ms/Shows/The-Defrag-Show/feed/mp4high
http://veeam.podbean.com/feed/
http://feeds.feedburner.com/PowerScripting
http://s.ch9.ms/Shows/Edge/feed/mp4high
http://feeds.feedburner.com/RunasRadio
http://recordings.talkshoe.com/rss19367.xml
http://feeds.feedburner.com/linuxunplugged
http://intechwetrustpodcast.com/feed/
http://feeds.feedburner.com/Get-scripting
http://feeds.packetpushers.net/datanauts

Save-Podcast :

It takes the same parameters as Get-Podcast with the additional -Destination which allows to specify where the media file is saved.

Save-Podcast

The nice part is that you can pipe the output of Get-Podcast to Save-Podcast. So, you can check the output of Get-Podcast first, without committing to download and then, if you actually want to download the output podcast episodes, you can add | Save-Podcast to your previous command :

Save-Podcast_Pipeline

One limitation when you do it this way : Save-Podcast is getting one media file at a time through the pipeline, so the downloads are sequential, not in parallel. This also means that the count of files and the total size displayed by Write-Progress are incorrect. The count of files is always 1 when using pipeline input. If you have a solution for this, I am all hears.

Add-PodcastToList :

This allows to add podcast URLs to the file containing your favorite podcasts, without having to manually edit the file. It can also create a list of podcasts because if the file specified doesn’t exist, it creates it :

PS C:\> Add-PodcastToList -Url http://feeds.feedburner.com/PowerScripting -List $env:USERPROFILE\Desktop
\MyPodcastList.txt

 

You can also use this cmdlet in combination with Get-Podcast to create a new podcast list from an existing list :

PS C:\> Get-Podcast -List C:\GitHub\Powershell-Utility\Podcast\PodcastList.txt -FromLastDays 30 |
 Where-Object { $_.Summary -like "*scripting*" } |
 Add-PodcastToList -List $env:USERPROFILE\Desktop\ScriptingPodcasts.txt

 
As you can see above, I got podcasts from my main podcast list selecting only the episodes from the last 30 days because I don’t need a huge number of episodes. Then, I filter down to the episodes which have the word “scripting” in their Summary field and I add the PodcastUrl from these episodes to a new list.

Don’t worry if multiple episodes of the same podcast match the filter, Add-PodcastToList will not add the same URL more than once, because it checks if the destination list already contains the URL before adding it.

Remove-PodcastFromList :

This cmdlet allows to remove podcast URLs from a podcast list, without having to manually edit the file :

PS C:\> Remove-PodcastFromList "http://feeds.feedburner.com/linuxunplugged" "$env:USERPROFILE\Desktop\FavoritePodcasts.txt"

 
Here, we were using positional parameters, -Url is at position 0 and -List is at position 1.

Podcasts can come and go, or some podcasts can be a series on a specific topic and are not updated after the series is finished. You can use Remove-PodcastFromList to cleanup stale podcasts from a list, like so :

PS C:\> Get-Podcast "C:\GitHub\Powershell-Utility\Podcast\PodcastList.txt" -Last 1 |
 Where-Object { $_.PublishedDate -lt (Get-Date).AddMonths(-6) } |
 Remove-PodcastFromList -List "C:\GitHub\Powershell-Utility\Podcast\PodcastList.txt"

 
This obtains the last episode of each podcast in the list, then it filters down to the episodes older than 6 months ago. We assume that if the latest episode is older than 6 months, the podcast is probably finished or abandoned, so we remove it from the list.

That’s pretty much all there is to using the Podcast module. It is available for grabbing and/or improving on this GitHub repository.