Wednesday, November 02, 2016

Speed-updating powershell modules from the gallery

Hi all.

TL;DR: Update all your PoSH modules from PowershellGallery.com within your ISE profile - and with lightning speed!

I recently received a IseSteroids License due to being an MVP - many thanks to Tobias Weltner for this valuable gift!

I then started to think about "how do I keep IseSteroids" up to date? It receives minor updates quite frequently, so I tried this simple command in my personal ISE profile:

Update-Module -Name IseSteroids

This was a "no brainer" - update-module is a slow running thing. So ok, lets go - google/bing/whatever. I finally came up with a solution that has "history":



ScriptingFee had the idea of parsing the IseSteroids release history page to grab the latest version number. Then she compares this to the local IseSteroids version and in case of mismatch updates the module.
This idea was picked up by Tobias and driven a bit farther. He detects the most up to date version from the powershell gallery URL for a given module.

I had the idea of putting this into a pipeline aware function and wrap it in a second function that will update any module - including support for pipelining, whatIf/Confirm and Force. Here's what I came up with - comments welcome :-)

And if someone suggests to publish it to the Powershell Gallery - ok, then I will do, I promise!


function Get-PublishedModuleVersion
{
   <#
    .SYNOPSIS
    Takes a module name and searches the Powershell gallery for its current version number. It accepts pipeline input for the module name.

    .DESCRIPTION
    When using Get-InstalledModule | Update-Module, this takes a long time. So some smart people on the web thought about how to improve this process.
    The result is impressing - fetching the version number from the Powershell gallery URL for a module is a huge improvement over relying on Update-Module to detect the version numbers on its own.

    .PARAMETER ModuleName
    Specifies a module name to search the current version for

    .EXAMPLE
    Get-PublishedModuleVersion -ModuleName IseSteroids
    Searches for the IseSteroids version in the Powershell gallery and returns its version number.

    .LINK
    http://www.powertheshell.com/findmoduleversion/
    http://scriptingfee.de/isesteroids-auf-aktuellem-stand-halten/

    .INPUTS
    System.String

    .OUTPUTS
    System.Version
  #>


   [ CmdletBinding() ]
   param
   (
     [ Parameter( Position = 0, HelpMessage='A module name must be specified to search for its current version. Please enter the name of a module.', Mandatory = $True, ValueFromPipeline = $True ) ] [string] $ModuleName
   )
   begin {
     $baseurl = 'https://www.powershellgallery.com/packages'
   }
   process {
     # access the main module page, and add a random number to trick proxies
     $url = ( '{0}/{1}/?dummy={2}' -f $baseurl, $ModuleName, ( Get-Random ) )
     $request = [System.Net.WebRequest]::Create( $url )
     # do not allow to redirect. The result is a "MovedPermanently"
     $request.AllowAutoRedirect = $false
     try
     {
       # send the request
       $response = $request.GetResponse()
       # get back the URL of the true destination page, and split off the version
       $response.GetResponseHeader( 'Location' ).Split( '/' )[-1] -as [Version]
       # make sure to clean up
       $response.Close()
       $response.Dispose()
     }
     catch
     {
       Write-Warning -Message $_.Exception.Message
     }
   }
}

function Update-OutdatedModule {
   <#
      .SYNOPSIS
      This function updates one or more modules that were installed from the PowerShell gallery if newer versions are available. It accepts pipeline input for the module name.

      .DESCRIPTION
      Updating modules from the Powershell Gallery can be done via calling Update-Module. This can even be used in a pipeline with Get-InstalledModule | Update-Module. But that's a quite slow command - so this workaround was created.
      Update-OutdatedModule checks the module's download URL for the most current version number, and it updates only if the local version is older.
      For modules that are installed for all users, it obviously requires an elevated session to work.

      .PARAMETER ModuleName
      The name of the module to update.

      .PARAMETER Force
      Updates the module even if the currently installed version is up to date.

      .EXAMPLE
      Update-OutdatedModule -ModuleName ScriptCop -Force
      Updates the ScriptCop module regardless of its current version

      .EXAMPLE
      ( Get-InstalledModules ).Name | Update-OutdatedModules -WhatIf
      Checks if newer versions for any module are available, but does not update.

      .NOTES
      Place additional notes here.

      .LINK
      https://evilgpo.blogspot.com
      http://powertheshell.com

      .INPUTS
      System.String

      .OUTPUTS
      None
  #>


   [ CmdletBinding( SupportsShouldProcess = $True ) ]
   param(
     [ Parameter( Position = 0, HelpMessage='One or more module names to be updated must be specified. Please enter the name of a module.', ValueFromPipeline = $True, Mandatory = $True ) ] [String] $ModuleName,
     [Switch] $Force
   )

   process {

     # first, get the version of the currently installed module. If it is not installed, Get-InstalledModule will throw a non-terminating error
     Write-Verbose -Message ( 'Trying to check module version for {0}...' -f $ModuleName )
     $CurrentModuleVersion = ( Get-InstalledModule -Name $ModuleName ).Version
     If ( $CurrentModuleVersion -eq $null ) { Return }
     Write-Verbose -Message ( 'Found installed version : {0}' -f $CurrentModuleVersion.ToString() )

     # Then get the version that is available in the powershell gallery. If it is not in the gallery, Get-PublishedModuleVersion will throw a warning
     Write-Host ( 'Checking current {0} version {1} for updates...' -f $ModuleName, $CurrentModuleVersion )
     $AvailableModuleVersion = Get-PublishedModuleVersion -ModuleName $ModuleName
     If ( $AvailableModuleVersion -eq $null ) { Return }
     Write-Verbose -Message ( 'Latest available version: {0}' -f $AvailableModuleVersion.ToString() )

     # Check if versions already match. Since we use [version] types, we do not need to take care if the version has 2, 3 or 4 parts.
     If ( $CurrentModuleVersion -ge $AvailableModuleVersion )
     {
       Write-Host ( '{0} version is already up to date.' -f $ModuleName ) -ForegroundColor Green
       If ( $Force )
       {
         Write-Verbose -Message '"Force" specified, updating anyway...'
         # fooling the second check if -force was specified - better ideas welcome about how to pass -force down to Update-Module...
         $CurrentModuleVersion = [Version]0.0.0.0
       }
     }

     # check if versions do not match (will always be true if -force was specified) and do what has to be done
     If ( $CurrentModuleVersion -lt $AvailableModuleVersion )
     { 
       Write-Host ( 'Updating {0} to new version {1}...' -f $ModuleName, $AvailableModuleVersion ) -ForegroundColor Yellow
       If ( $PSCmdlet.ShouldProcess( $ModuleName ) )
       {
         # We always use the -force switch here because we already caught if the module does not need to be updated.
         # If we reach this line, the module is either outdated or the -force switch was specified by the user
         Update-Module -Name $ModuleName -Force -Verbose
       }
     }
   }
}

# Update-OutdatedModule -ModuleName IseSteroids

No comments:

Post a Comment