Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
May 27, 2025

A Module Measurement Scripting Solution

At the end of last month I left you with a PowerShell scripting challenge. This is a great way to flex your scripting muscles and hopefully learn something new. If nothing else, the challenges re-enforces your understanding of PowerShell syntax, language, and concepts that you can apply to anything.

Let me share my solutions for the most recent challenge. My solution is not the only way to achieve the end result, and don't consider this a competition to see how close you can get to my solution. Compare your work against mine and try to understand the differences.

The Basic Challenge

I offered two challenges last month. For the basic challenge I wanted you to find how many modules are installed in each location defined in %PSMODULEPATH%. This environment variable contains the paths where PowerShell searches for modules. Some of the paths are hard-coded. You'll see different locations depending on your PowerShell version.

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

You might have additional locations. Some modules might add locations. And this is a operating system variable so you can easily modify it outside of PowerShell to add other locations.

The challenge is to check each location in the path. Each location is separated by a semi-colon so we can split the string.

PS C:\> $env:PSModulePath -split ";"
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

However, the path separator on non-Windows platforms may be something else. If you wanted to write code that would work cross-platform, you need to discover the path separator character. Fortunately, this is a property you can find in the System.IO.Path .NET class.

$pathSep = [System.IO.Path]::PathSeparator

Using this gives me the same results.

PS C:\> $pathSep = [System.IO.Path]::PathSeparator
PS C:\> $env:PSModulePath -split  $pathSep
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

And it works cross-platform.

PS /home/jeff> $pathSep = [System.IO.Path]::PathSeparator
PS /home/jeff> $env:PSModulePath -split  $pathSep
/home/jeff/.local/share/powershell/Modules
/usr/local/share/powershell/Modules
/opt/microsoft/powershell/Modules

I'll save the locations to a variable.

$locations = $env:PSModulePath -split $pathSep

For this challenge, all you needed to do was count the modules in each location. Each module is stored as a folder in the location.

PS C:\> dir $locations[1] -Directory | Select -first 3

    Directory: C:\Program Files\PowerShell\Modules

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           3/20/2025  8:47 AM                Microsoft.PowerShell.ConsoleGuiTools
d----           3/20/2025  8:47 AM                Microsoft.PowerShell.Crescendo
d----           3/20/2025  8:47 AM                Microsoft.PowerShell.PSResourceGet

For each location, I can count the number of top-level folders and that will give me the number of modules installed in each location.

$out = Foreach ($Location in $locations) {
    #count top-level directories
    $count = (Get-ChildItem -Path $Location -Directory -ErrorAction SilentlyContinue).Count
    #create custom object output
    [PSCustomObject]@{
        Location     = $Location
        Count        = $count
        PSVersion    = $PSVersionTable.PSVersion
        Computername = [System.Environment]::MachineName
        UserName     = [System.Environment]::UserName
    }
}

I am setting the ErrorAction preference on Get-ChildItem to SilentlyContinue. This will suppress any errors for locations with no top-level directories. For each location, I am creating a custom object. Note that I am using .NET references for the user and computer names so that this code will run cross-platform.

Here's my Windows result.

PS C:\> $out | Format-Table

Location                                           Count PSVersion Computername UserName
--------                                           ----- --------- ------------ --------
C:\Users\jeff\Documents\PowerShell\Modules             1 7.5.1     CADENZA      jeff
C:\Program Files\PowerShell\Modules                   27 7.5.1     CADENZA      jeff
c:\program files\powershell\7\Modules                 14 7.5.1     CADENZA      jeff
C:\Program Files\WindowsPowerShell\Modules            40 7.5.1     CADENZA      jeff
C:\Windows\system32\WindowsPowerShell\v1.0\Modules    86 7.5.1     CADENZA      jeff

And here's an Ubuntu result.

PS /home/jeff> $out | Format-Table

Location                                   Count PSVersion Computername UserName
--------                                   ----- --------- ------------ --------
/home/jeff/.local/share/powershell/Modules     3 7.5.0     Cadenza      jeff
/usr/local/share/powershell/Modules            0 7.5.0     Cadenza      jeff
/opt/microsoft/powershell/Modules             10 7.5.0     Cadenza      jeff

This challenge was only interested in the number of different modules. You might have multiple versions of the same module installed, but they will share the same top-level folder. Finding module by version is part of the advanced challenge.

Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.