Saturday, July 14, 2018

Keeping PowerShell Modules Up To Date

At one time, many (most?) IT Pros did not worry to much about updating PowerShell modules,relying for the most part on whatever cmdlets come with the Application, PowerShell, and/or the OS. But times have changed. With the release of Windows PowerShell 5.1, work on modules both inside and outside Microsoft has not stopped. And a number of modules now have updates that might be useful.

Another big change in the PowerShell world is the movement towards holding these new versions on Repositories that you can access via built-in commands (i.e. Find-Module, Update-Module, etc). The question then becomes what modules need updating? Turns out there is a script or two for that!

The first question is whether you care enough about module updates to worry about dealing with updates, and the side effects of module changes.

As I was writing this article, I encountered an interesting example of this issue. The version of the PSReadline module has been updated  to Version2.0 This version has a breaking change in how you set UI token colours. it took a while to sort out but it drove home to me the need to test new versions and not just update because you can. Unless, I suppose, you like living on the edge.

So how do you tell what modules need updating. And as ever, there’s a script for that ™.

Here is a simple function that looks at all the modules you have on your system (well those pointed to via $env:PSModulePath), and checks to see if that module is available via the PSGallery. For every module the founction returns an object with three properties: the module name, the version on the local system and the version on PSGallery. For modules that do not exist on PSGallery, the script returns a PSGallery version of ‘zero’. Here’s the function:

Function Get-ModuleVersionInformation {

[Cmdletbinding()]
Param()

# Startup
$Start = Get-Date
Write-Verbose 'Get-ModuleVersionInformation'
Write-Verbose 'started at: [$start]'

# Get the modules on the local system
$Modules = Get-Module -ListAvailable -Verbose:$False
Write-Verbose ("{0} modules locally" -f $modules.count)

# For each module, see if it exists on PSGallery
# Create/emit an object for each module with the name,
# and the version number of local and remote versions
Foreach ($Module in $Modules) {
  Write-Verbose "Processing $($module.name)"
  $UpdateHt         = [ordered] @{}    # create the hash table
  $UpdateHt.Name    = $Module.Name     # Add name
  $UpdateHt.Version = $Module.Version  # And local version

try {
#  Find module, and add gallery version number to hash table
    $GalMod = Find-Module $Module.name -ErrorAction Stop
    $Updateht.GalVersion = $GalMod.Version
  }
  # here - find module could not find the module in the gallery
Catch {  
   # If module isn't in the gallery
  $Updateht.GalVersion = [System.Version]::new(0,0) 
  }

# now emit the object
New-Object -TypeName PSObject -Property $UpdateHt

} # End foreach

$End = Get-Date
Write-Verbose "Stopped at: [$End]"
Write-Verbose "Took $(($End-$Start).TotalSeconds) seconds"
  
} # End Function

On my system, the output looks like this:

Psh[Cookham24:C:\foo]> $mods = Get-ModuleVersionInformation
Psh[Cookham24:C:\foo]> $mods

Name                     Version     GalVersion
----                     -------     ----------
Buildlab                 1.0         0.0       
NetCmdlets               16.0.6446.0 16.0.6592.0
ScriptBrowser            1.3.2.0     1.3.1.0   
tfl                      1.0         0.0       
Azure.AnalysisServices   0.5.0       0.5.2     
Azure.Storage            4.1.0       4.3.1     
AzureRM                  5.2.0       6.4.0                      
AzureRM.RedisCache       4.1.0       0.0       
AzureRM.Relay            0.3.1       0.0
 

… Etc.

Loaded with this function you can then do this:

$zero = [System.Version]::new(0,0)
foreach ($mod in $mods) {
  If ($mod.galversion -eq $zero) {
  $msg = "Module [$($mod.name)] does not exist in PSGallery"
  $msg
  continue
  }
  If ($mod.galversion -gt $mod.version){
  $msg = "Module [$($mod.name)] should be updated from v$($mod.version) to v$($mod.galversion)"
  }
  Else {
  $msg = "Module [$($mod.name)] does not need to be updated - current version $($mod.version)"
  }

$msg
} # End foreach

The output, again in my main workstation looks like this:

Module [Buildlab] does not exist in PSGallery
Module [NetCmdlets] should be updated from v16.0.6446.0 to v16.0.6592.0
Module [ScriptBrowser] does not need to be updated - current version 1.3.2.0
Module [tfl] does not exist in PSGallery
Module [Azure.AnalysisServices] should be updated from v0.5.0 to v0.5.2
Module [Azure.Storage] should be updated from v4.1.0 to v4.3.1
Module [AzureRM] should be updated from v5.2.0 to v6.4.0
Module [AzureRM.AnalysisServices] should be updated from v0.6.2 to v0.6.9
Module [AzureRM.ApiManagement] should be updated from v5.1.0 to v6.1.1
Module [AzureRM.ApplicationInsights] should be updated from v0.1.1 to v0.1.4
Module [AzureRM.Automation] should be updated from v4.2.0 to v5.0.1
Module [AzureRM.Backup] should be updated from v4.0.2 to v4.0.6
Module [AzureRM.Batch] should be updated from v4.0.4 to v4.1.1
Module [AzureRM.Billing] should be updated from v0.14.0 to v0.14.3

This leaves me with a bit of work to do – updating PSReadLine, as I discovered did have some unfortunate side effects (and I suspect I am not the only one to have fallen over this issue!).

If I was brave, I could have updated that last script fragment to actually update each of the older modules. Not sure I’m ready for that, just yet…





No comments: