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:\> 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

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
}