Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Archives
April 24, 2026

Solving The PowerShell Module Challenge

In this issue:

  • Module Folders
    • Performance Options
  • Module Totals
    • Using Grouping
  • Module Versions
    • Processing All Scopes
  • Summary

We're almost to the end of another month which means it is time to share my solution for last month's PowerShell scripting challenge. I hope you gave it try. Any opportunity to use PowerShell is an opportunity to improve your skills. You can't get better at PowerShell if you don't use it. As always, my solutions are not necessarily the only way, or maybe even the best way to solve the challenge. If you found a different technique, I'd love to hear about it. Share a link to your solution in the online comments for this newsletter issue.

The challenge involved analyzing folders for installed PowerShell modules. I gave you three goals. Let's compare notes.

Module Folders

> Get number of modules folders in each location that is part of %PSModulePath%. Include the scope, such as current user, and the size of each folder.

The first step is to identify the folders. When you install a module, you can specify the scope so that it installs for you or for the machine. These locations are hard-coded. PowerShell will automatically import modules from these locations and others as defined in the %PSModulePath% environment variable.

You can easily view this variable using the ENV: PSDrive.

PS C:\> $Env:PSModulePath
C:\Users\Jeff\Documents\PowerShell\Modules;C:\Program Files\PowerShell\Modules;c:\program files\powershell\7\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules;c:\Users\Jeff\.vscode\extensions\ms-vscode.powershell-2025.4.0\modules

This is an array of paths separated by a semi-colon on Windows. On other systems, there might be a different separator.

PS /home/jeff> $env:PSModulePath
/home/jeff/.local/share/powershell/Modules:/usr/local/share/powershell/Modules:/opt/microsoft/powershell/7/Modules

We can use .NET to discover the separator.

PS C:\> [System.IO.Path]::PathSeparator
;

Using this, it is easy to split the path variable into an array of locations.

PS C:\> $dirs = $env:PSModulePath -split [System.IO.Path]::PathSeparator
PS C:\> $dirs
C:\Users\Jeff\Documents\PowerShell\Modules
C:\Program Files\PowerShell\Modules
c:\program files\powershell\7\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
c:\Users\Jeff\.vscode\extensions\ms-vscode.powershell-2025.4.0\modules

The user scope will be locations under my profile. Anything under Program Files is the AllUsers scope. This scope should only apply to Windows machines. Anything else, such as under System32, we can classify as the System scope.

I should be able to detect the scope based on the path using a regular expression pattern.

$scope = switch -regex ($d) {
    "$([Environment]::UserName)" { 'User' }
    '(Program)|(usr)' { 'AllUsers' }
    default { 'System' }
}

You could also have used the -match operator in a series of If statements. I think using Switch is more efficient.

For each top-level directory I can easily get the number of modules based on the number of folders.

PS C:\> $dirs[0]
C:\Users\Jeff\Documents\PowerShell\Modules
PS C:\> (Get-ChildItem $dirs[0] -Directory).Count
5
PS C:\> Get-ChildItem $dirs[0] -Directory

    Directory: C:\Users\Jeff\Documents\PowerShell\Modules

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----            2/3/2025 10:25 AM                Microsoft.PowerShell.ConsoleGuiTools
d----           3/13/2026  3:56 PM                Microsoft.PowerShell.PSResourceGet
d----           3/27/2026 12:45 PM                Microsoft.PowerToys.Configure
d----            1/8/2026 10:41 AM                mySQLite
d----          10/23/2025  1:50 PM                PSReadLine

Likewise, I can easily get the total file size.

PS C:\> (Get-ChildItem -Path $dirs[0] -File -Recurse | Measure-Object -Property length -Sum).sum
53800920

I need to do this for each location and create a result object.

foreach ($d in $dirs) {
    $scope = switch -regex ($d) {
        "$([Environment]::UserName)" { 'User' }
        '(Program)|(usr)' { 'AllUsers' }
        default { 'System' }
    }

    [PSCustomObject]@{
        Path          = $d
        ModuleFolders = (Get-ChildItem -Path $d -Directory).count
        Size          = (Get-ChildItem -Path $d -File -Recurse | Measure-Object -Property length -Sum).sum
        Scope         = $scope
        Computername  = [System.Environment]::MachineName #Using this to support cross-platform
    }
}

This gives me results like this on my Windows desktop.

Path          : C:\Users\Jeff\Documents\PowerShell\Modules
ModuleFolders : 5
Size          : 53800920
Scope         : User
Computername  : PROSPERO

Path          : C:\Program Files\PowerShell\Modules
ModuleFolders : 41
Size          : 1628224096
Scope         : AllUsers
Computername  : PROSPERO

Path          : c:\program files\powershell\7\Modules
ModuleFolders : 16
Size          : 15281222
Scope         : AllUsers
Computername  : PROSPERO

Path          : C:\Program Files\WindowsPowerShell\Modules
ModuleFolders : 91
Size          : 1337800625
Scope         : AllUsers
Computername  : PROSPERO

Path          : C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules
ModuleFolders : 116
Size          : 133403225
Scope         : System
Computername  : PROSPERO

Path          : c:\Users\Jeff\.vscode\extensions\ms-vscode.powershell-2025.4.0\modules
ModuleFolders : 4
Size          : 312271725
Scope         : User
Computername  : PROSPERO

In PowerShell 7, my module path variable includes both Windows PowerShell and PowerShell 7 locations. I believe PowerShell automatically updates the variable so I can import Windows PowerShell modules. In Windows PowerShell, $env:PSModulePath doesn't show the PowerShell 7 locations.

My solution also works cross-platform.

Path          : /home/jeff/.local/share/powershell/Modules
ModuleFolders : 19
Size          : 211980897
Scope         : User
Computername  : BamBam

Path          : /usr/local/share/powershell/Modules
ModuleFolders : 2
Size          : 16868899
Scope         : AllUsers
Computername  : BamBam

Path          : /opt/microsoft/powershell/7/Modules
ModuleFolders : 10
Size          : 12655335
Scope         : System
Computername  : BamBam
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.