Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
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 About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.