Solving The PowerShell Module Challenge
In this issue:
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