Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Archives
February 4, 2025

More Module Management

Last time, we started looking at PowerShell techniques to manage PowerShell modules. Even though I know how to use the commands in the Microsoft.PowerShell.PSResourceGet module, I'm always looking for ways to be more economical and efficient. I also look for ways to leverage the pipeline even if a command lacks support. I have some examples of that today.

Removing Old Module Versions

Hopefully you downloaded the script file from last time with my custom functions for retrieving installed modules. I still need to clean up installed older versions. Of course, make sure you truly don't need an older version to meet a specific requirement.

I can use the Get-InstalledPSResource cmdlet to get a list of installed modules, but it is a little cumbersome to use because the parameters I need to use have limited pipeline capabilities.

PS C:\> help Get-InstalledPSResource -Parameter Name,Scope

-Name <string[]>

    Required?                    false
    Position?                    0
    Accept pipeline input?       true (ByValue)
    Parameter set name           (All)
    Aliases                      None
    Dynamic?                     false
    Accept wildcard characters?  true


-Scope <scopetype>

    Required?                    false
    Position?                    Named
    Accept pipeline input?       false
    Parameter set name           (All)
    Aliases                      None
    Dynamic?                     false
    Accept wildcard characters?  false

The same is true for the Uninstall-PSResource cmdlet which I know I'll eventually want to use.

PS C:\&gt; help Uninstall-PSResource -Parameter Name,Scope,Version

-Name <string[]>
    Name(s) of package(s) to uninstall.

    Required?                    true
    Position?                    0
    Accept pipeline input?       true (ByValue)
    Parameter set name           NameParameterSet
    Aliases                      None
    Dynamic?                     false
    Accept wildcard characters?  true


-Scope <scopetype>

    Required?                    false
    Position?                    Named
    Accept pipeline input?       false
    Parameter set name           (All)
    Aliases                      None
    Dynamic?                     false
    Accept wildcard characters?  false


-Version <string>

    Required?                    false
    Position?                    Named
    Accept pipeline input?       false
    Parameter set name           NameParameterSet
    Aliases                      None
    Dynamic?                     false
    Accept wildcard characters?  true

To use these parameters, I will need to use ForEach-Object to process each object.

The first task is to identify the old versions of the PowerShell 7 modules that I want to manage.

Get-PSModuleStatus -PipelineVariable pv | Foreach-Object {
    Get-InstalledPSResource -Name $_.name -Scope $_.Scope |
    Sort-Object Version -Descending |
    Select-Object -Skip 1
} | Select-Object Name,@{Name="Scope";Expression={$pv.Scope}},Version
Older module versions
figure 1

I need to pass the scope value down the pipeline so I use the -PipelineVariable parameter to capture the value. I then use a calculated property to add the scope value to the output. This is the output I want to eventually feed to Uninstall-PSResource.

When you look at this code, is this something you want to type repeatedly to remove old versions of modules? I don't. I am also being forced to use ForEach-Object because the Get-InstalledPSResource cmdlet doesn't support pipeline input by property value for the -Name and -Scope parameters. Here's a hint. Whenever you are forced to use ForEach-Object to process an object, that is an indication that you could write a function to do the same thing. ForEach-Object is a cmdlet alternative that does the same thing as a function. Think of it as creating an ad-hoc function. However, I'm going to create a more permanent function.

Function Get-PSModuleInstalled {
    [CmdletBinding()]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName,
            HelpMessage = 'Specify the module to check'
        )]
        [ValidateNotNullOrEmpty()]
        [String]$Name,

        [Parameter(
            ValueFromPipelineByPropertyName,
            HelpMessage = 'The scope of the module to check'
        )]
        [ValidateSet('CurrentUser', 'AllUsers')]
        [string]$Scope = 'CurrentUser',

        [Parameter(HelpMessage = 'Skip the most current version')]
        [Int]$Skip = 0
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Starting $($MyInvocation.MyCommand)"
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Running under PowerShell version $($PSVersionTable.PSVersion)"
        $hash = [ordered]@{
            PSTypeName = 'myPSResource'
            Name       = $Name
            Version    = $Null
            Path       = $Null
            Installed  = $Null
            Scope      = $Scope
            Available  = $false
            Update     = $false
        }
    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Checking $Name in the $Scope scope"
        #sorting on version just to be sure
        Get-InstalledPSResource -Name $Name -Scope $Scope |
        Sort-Object -Property Version -Descending |
        Select-Object -Skip $Skip |
        ForEach-Object {
            $hash.Name = $_.Name
            $hash.Scope = $Scope
            $hash.Version = $_.Version
            $hash.Path = $_.InstalledLocation
            $hash.Installed = $_.InstalledDate
            [PSCustomObject]$hash
        }
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Ending $($MyInvocation.MyCommand)"
    } #end
}
Want to read the full issue?
GitHub
Bluesky
LinkedIn
Mastodon
jdhitsolutions.github.io
Powered by Buttondown, the easiest way to start and grow your newsletter.