Module and Package Management Tools
When I started my IT career, we didn't have to worry much about the pace of change. Operating systems were released on a 3 to 5-year cycle, and software updates were released at a leisurely pace. However, in the DevOps-oriented world today, the pace of change is much faster. This is especially true in the PowerShell world, where new modules and updates are released frequently. I thought I would share some tooling I've created to help me keep up with module and package updates. Don't assume these solutions will work for you; take them as inspiration to create your own solutions.
Module Management Tools
My PowerShell world is primarily PowerShell 7. I have some modules that I use in both Windows PowerShell and PowerShell 7 that are installed in Windows PowerShell. But they are not my focus. I want to manage newer modules installed in PowerShell 7. I am also using the Microsoft.PowerShell.PSResourceGet
module to help me manage modules. This module is available in the PowerShell Gallery and is the replacement for the now legacy, PowerShellGet
module. The commands in the PSResourceGet
module are similar to those in PowerShellGet
and query the same repository, but the commands are faster.
One List to Rule Them All
I have a list of modules that I use in my PowerShell 7 environment. These are modules that I want to keep up-to-date and ensure that they are installed on my desktop and laptop. I keep the file in my Scripts folder which has a symbolic link to my OneDrive folder. This way, I can access the file from any of my computers.
# My PowerShell 7 modules
# C:\scripts\MyPS7Modules.txt
# These modules were, or should be, installed using PSResourceGet
Microsoft.PowerShell.ConsoleGuiTools
Microsoft.PowerShell.Crescendo
Microsoft.Powershell.PSResourceGet
Microsoft.WinGet.Client
Microsoft.PowerShell.WhatsNew
PSFunctionTools
PSWorkItem
PSReminderLite
PSReadLine
psedit
PSBluesky
PowerShellRun
SecretManagement.1Password
PwshSpectreConsole
PSDupes
Terminal-Icons
TerminalGuiDesigner
ThreadJob
I can use this list to install or update the modules on my desktop or laptop. I can also use it to check the status of the modules.
Get-PSModuleStatus
I wrote a PowerShell tool based on commands in the PSResourceGet
module to help me manage my modules. The tool is called Get-PSModuleStatus
.
Function Get-PSModuleStatus {
[CmdletBinding(DefaultParameterSetName = 'File')]
[OutputType('myPSResource')]
Param (
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName,
ParameterSetName = 'Name',
HelpMessage = 'The name of the module to check. The module should already be installed.'
)]
[ValidateNotNullOrEmpty()]
[Alias('Name')]
[string[]]$ModuleName,
[Parameter(
ValueFromPipelineByPropertyName,
ParameterSetName = 'Name',
HelpMessage = 'The module installation scope'
)]
[ValidateSet('CurrentUser', 'AllUsers')]
[string]$Scope = 'CurrentUser',
[Parameter(
ParameterSetName = 'File',
HelpMessage = 'The path to the file containing the module names'
)]
[ValidateScript({ Test-Path $_ }, ErrorMessage = 'Cannot find the file {0}')]
[string]$Path = 'C:\scripts\MyPS7Modules.txt'
)
Begin {
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)"
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Running under PowerShell version $($PSVersionTable.PSVersion)"
} #begin
Process {
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Detected parameter set $($PSCmdlet.ParameterSetName)"
if ($PSCmdlet.ParameterSetName -eq 'File') {
#filter out commented and blank lines
$ModuleName = Get-Content -Path $path | Where-Object { $_ -NotMatch '^#' -AND $_ -match '\w+' }
}
If ($ModuleName) {
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Processing $($ModuleName.Count) modules"
$ModuleName | ForEach-Object {
$Name = $_
$hash = [ordered]@{
PSTypeName = 'myPSResource'
Name = $Name
Version = $Null
Path = $Null
Installed = $Null
Scope = $Scope
Available = $false
Update = $false
}
$get = Try {
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Getting PSResource $Name"
Get-InstalledPSResource -Name $Name -Scope $Scope -ErrorAction Stop -Verbose:$False
} #Try
Catch {
#Search for module if using a list
if ($PSCmdlet.ParameterSetName -eq "File") {
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Testing $Name in AllUsers scope"
Try {
#suppressing Verbose output from the PSResourceGet commands
Get-InstalledPSResource -Name $Name -Scope AllUsers -ErrorAction Stop -Verbose:$False
$hash.Scope = 'AllUsers'
}
Catch {
Write-Warning "Cannot find $Name in either scope"
#write a placeholder to the pipeline
@{
Version = $null
InstalledDate = $null
InstalledLocation = $null
}
$hash.update = $False
}
}
} #outer Catch
if ($get) {
#get the most recent version
$current = $get | Sort-Object -Property Version | Select-Object -Last 1
#update the hash table
$hash.Version = $current.Version
$hash.Path = $Current.InstalledLocation
$hash.Installed = $current.InstalledDate
#get online version
$online = Find-PSResource -Name $Name -Type Module -Verbose:$False
$hash.Available = $online.Version
if ($hash.Version) {
$hash.Update = $online.Version -gt $hash.Version
}
#write a custom object to the pipeline
[PSCustomObject]$hash
} #if $get
} #foreach
} #if module name
} #process
End {
Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)"
} #end
} #end function
The PSResourceGet
module manages both modules and installed scripts. They are referred to as resources
. Since my function is targeted at modules, I've given it a more targeted name.
The function will accept input from the pipeline or a file. You can see that I have set the module list as the default and made the File
parameter set the default. I am bending best practices a bit here. The function should accept pipeline input like this:
Get-Content C:\scripts\MyPS7Modules.txt | Get-PSModuleStatus
But that is too much typing for me when I know I will always be using the same source file.

Or I can get a single module status like this:
PS C:\> Get-PSModuleStatus PSBluesky -Scope AllUsers
Name Scope Version Available Update
---- ----- ------- --------- ------
PSBluesky AllUsers 2.1.0 2.4.0 True