# Understanding PowerShell Modules

Almost all PowerShell commands live in modules, and by adding new modules to PowerShell, you can easily add more commands. Here is all you need to know about PowerShell modules.

PowerShell is a scripting engine. Commands are strictly separated from this engine and reside in modules. That’s why you can easily extend the PowerShell command set. Even the specialized PowerShell consoles shipping with Exchange or SQL Server are plain regular PowerShell consoles that simply load more commands from external modules.

You can use whatever PowerShell host you like (console, ISE editor, VSCode editor, etc.) to manage Active Directory, Exchange, Sharepoint, or whatever it may be. Just make sure the PowerShell modules are loaded that provide the commands you require.

This is the first part of an article series deeply looking into PowerShell modules. It applies to both Windows PowerShell and the new PowerShell 7 aka PowerShell Core. The first part explains how commands are loaded from modules, and how PowerShell automatically loads modules on-demand.

As an experienced PowerShell user, you may safely skip this article. It provides you with a basic overview of the module architecture, and I’ll reference this material in other articles where I explain how to author your own modules.

Before PowerShell modules were invented in PowerShell 2, commands were added using Snap-Ins. The snap-in technology is obsolete and was removed from PowerShell 7. In Windows PowerShell, you can still use Add-PSSnapin to load snap-ins. Snap-ins are not covered in this article, and you should try and avoid them in favor of modules.

## Commands On Demand

When you launch a new PowerShell it comes with only very few commands loaded into memory:

(Get-Command -ListImported).Count

213


Still, there are thousands of commands potentially available:

(Get-Command).Count

3716


The reported numbers can vary greatly, too, so they may be different for you. That’s because PowerShell loads commands on demand which saves resources and speeds up the launch process, and commands can be extended by adding PowerShell modules. The more modules you have, the more commands are available.

When you launch a fresh PowerShell, only a few Modules are pre-loaded in memory:

# load a new fresh PowerShell instance w/o running any profile scripts:
powershell.exe -NoProfile
# dump all modules loaded in memory:
Get-Module

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------


These pre-loaded Modules provide a basic set of PowerShell commands:

(Get-Module).ExportedCommands | Format-Wide -Column 6

Add-Computer       Add-Content        Checkpoint-Com... Clear-Content     Clear-EventLog    Clear-Item
Clear-ItemProperty Clear-RecycleBin   Complete-Trans... Convert-Path      Copy-Item         Copy-ItemProperty
Debug-Process      Disable-Compute... Enable-Compute... Get-ChildItem     Get-Clipboard     Get-ComputerInfo
...


# dump source code from url:
Invoke-RestMethod -Uri powershell.fun -UseBasicParsing


You are not restricted to the pre-loaded commands, though. PowerShell automatically loads modules on demand when you use a command that hasn’t been loaded into memory yet.

## Implicitly Importing New Modules

There is no need for you to manually import modules or even know about them:

• Cache: Details of all commands from all modules are kept in a cache file. Because of this cache, PowerShell always suggests all available commands to you even if their modules haven’t been loaded into memory yet.
• Auto-Loading: Once you actually use a command, PowerShell determines whether the module that defines the command has been loaded into memory. If it wasn’t loaded yet, PowerShell automatically calls Import-Module for you. Imported modules typically stay in memory until you close PowerShell.

### Discovering Commands and Modules

This command lists all PowerShell commands with the verb Get from all modules, including the ones that haven’t been loaded into memory yet:

Get-Command -Verb Get


As a beginner, it’s a good idea to focus on PowerShell commands with verb Get because these commands only read information and are safe to play with. There is no risk of accidentally changing settings or deleting things.

The column Source reveals the name of the Module that defines the command:

CommandType Name                         Version   Source
----------- ----                         -------   ------
...
Cmdlet      Get-Event                    3.1.0.0   Microsoft.PowerShell.Utility
Cmdlet      Get-EventLog                 3.1.0.0   Microsoft.PowerShell.Management
Cmdlet      Get-EventSubscriber          3.1.0.0   Microsoft.PowerShell.Utility
Cmdlet      Get-ExecutionPolicy          3.0.0.0   Microsoft.PowerShell.Security
Cmdlet      Get-FormatData               3.1.0.0   Microsoft.PowerShell.Utility
Cmdlet      Get-GPInheritance            1.0.0.0   GroupPolicy
Cmdlet      Get-GPO                      1.0.0.0   GroupPolicy
Cmdlet      Get-GPOReport                1.0.0.0   GroupPolicy
Cmdlet      Get-GPPermission             1.0.0.0   GroupPolicy
Cmdlet      Get-GPPrefRegistryValue      1.0.0.0   GroupPolicy
Cmdlet      Get-GPRegistryValue          1.0.0.0   GroupPolicy
Cmdlet      Get-GPResultantSetOfPolicy   1.0.0.0   GroupPolicy
Cmdlet      Get-GPStarterGPO             1.0.0.0   GroupPolicy
Cmdlet      Get-Help                     3.0.0.0   Microsoft.PowerShell.Core
...


If a module name starts with Microsoft.PowerShell. then it is shipping as part of PowerShell, and you can safely assume the module (and its commands) are available to anyone using the same PowerShell version that you use.

Any module with a different name is not part of PowerShell. It may be part of the operating system, a software installation, or installed from the PowerShell Gallery. If your scripts use commands from these modules, it is your job to identify these dependencies and make sure the modules (and commands inside of it) are available wherever you want to run your script.

When you want to use any one of the listed commands, there is no need to manually import modules. Just use them. PowerShell does all the rest for you.

Let’s dump the local Administrator accounts and show them in a dialog: Get-LocalGroupMember can read the members of a local user group, and Out-GridView shows data in a dialog, so here is a one-liner that shows all accounts in the local Administrators group:

Get-LocalGroupMember -SID S-1-5-32-544 | Out-GridView


The command immediately runs. No need for you to care much about modules.

### Modules Ship Commands

To better understand the mechanisms behind the scenes, let’s manually identify the modules that ship these two commands. Start a new PowerShell, then run this code:

# get all commands (do NOT use parameter -Name)
Get-Command |
# pick the commands we want to investigate

$env:PSModulePath -split ';'  Make sure the listing contains the module you intended to use, and lists the command in the column ExportedCommands. If your module isn’t included in the list, make sure it exists and is present in one of the default folders. If the module is listed but the command isn’t showing in ExportedCommands, make sure the ExecutionPolicy allows scripts to run, and the module isn’t corrupted. If the module is listed, and the command shows in ExportedCommands, make sure there is no other module exporting a conflicting command with the same name as the one you intended to run. #### Module Auto-Import It then checks whether the module is already loaded into memory: $loaded = (Get-Module -Name Microsoft.PowerShell.LocalAccounts) -ne $null$loaded


If the module isn’t yet loaded, it figures out whether the module is available:

# find the module and its file location:
Get-Module -Name Microsoft.PowerShell.LocalAccounts -ListAvailable | Select-Object -ExpandProperty ModuleBase


If it is available, it then imports it explicitly into memory:

Import-Module -Name Microsoft.PowerShell.LocalAccounts


You can turn off module auto-loading by setting the variable $PSModuleAutoLoadingPreference to None: # turn module autoloading off:$PSModuleAutoLoadingPreference = 'None'
# command will not run (unless its module was loaded previously)
Get-LocalGroup
$PSModuleAutoLoadingPreference = 'All' # command runs (PowerShell identifies and imports required module) Get-LocalGroup  This preference variable was added for security reasons. Auto-loading PowerShell modules can be a security risk. After all, when a module is imported it can execute arbitrary PowerShell code. With module auto-loading disabled, it is your responsibility to import all required modules via Import-Module. PowerShell will no longer import any module automatically. You can also set the variable to ModuleQualified: modules are auto-loaded only when you run a module-qualified command (prepend the command name with the module name that defines the command): # launch a fresh powershell (replace with pwsh.exe for PowerShell 7) powershell.exe -noprofile # enable module-qualified auto-import only:$PSModuleAutoLoadingPreference = 'ModuleQualified'
# plain commands will not auto-import modules:
Get-LocalGroup
# module-qualified commands will auto-import:
Microsoft.PowerShell.LocalAccounts\Get-LocalGroup


## Explicitly Importing Modules

Most of the time, PowerShell is importing modules automatically. There are five scenarios, though, where running Import-Module manually can be helpful or even required:

• Refresh Module: If you are authoring your own PowerShell modules, you may want to freshly re-import an already imported module, i.e. when you changed code inside of it. Use the parameter -Force to force a fresh import:

# freshly re-import an already imported module because you changed its code:
Import-Module -Name MyLittleModule -Force

• Load From Custom Location: PowerShell auto-imports modules only from known module folders. If the module isn’t located in one of the folders listed in $env:PSModulePath, use Import-Module and provide the path to the module instead of its name: # import a module from a non-default file location, i.e. from a USB stick: Import-Module -Name 'd:\mytools\MyLittleModule'  • Ensure Loaded: Module auto-import is triggered by command usage. PowerShell modules can contain more than commands, though. Some PowerShell modules include PowerShell providers. A provider implements drives. For example, the optional Microsoft module ActiveDirectory ships with a provider that implements the new PowerShell drive AD: to traverse the Active Directory structure. Before you can use the drive, you need to import the module. Either use one of its commands to trigger implicit loading, or use Import-Module: # import module to load contained providers: Import-Module -Name ActiveDirectory # (make sure you installed the module, it is optional) # drive AD: now available: Get-ChildItem -Path 'AD:\'  • Diagnose Correct Module Loading: If you suspect there is something wrong with a module, import it manually and enable verbose and debug output: # manually load module and enable verbose and debug output: # (this invokes the command in a scriptblock so$DebugPreference won't change globally)
& { $DebugPreference = 'Continue'; Import-Module -Name Microsoft.PowerShell.Utility -Force -Verbose }  The output lists all commands imported by the module, plus potentially other information that may be useful to investigate issues: VERBOSE: Loading module from path 'C:\Windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utili ty.psd1'. VERBOSE: Importing cmdlet 'New-Object'. VERBOSE: Importing cmdlet 'Measure-Object'. ...  • Auto-Import Disabled: If you have disabled module auto-import for security reasons (by setting the variable $PSModuleAutoLoadingPreference to None) you must use Import-Module and import all modules manually that you intend to use.

## Module Location

Let’s take a look at all PowerShell commands available for your PowerShell environment, and add the information about the module that defines these commands:

# calculated property:
# get the base path for a module
$basePath = @{ Name = 'Path' Expression = {$_.Module.ModuleBase }
}

# get all PowerShell commands...
Get-Command -CommandType Function, Cmdlet |
# ...that are defined by a module...
Where-Object ModuleName |
# ...and output command name, type, module name, and module location:
Select-Object -Property Name, CommandType, ModuleName, $basePath  The output reports the commands, command types, and the module from which they will be loaded when you use the commands: Name CommandType ModuleName Path ---- ----------- ---------- ---- ... Revoke-SPOTenantServicePrincipalPermission Cmdlet Microsoft.Online.SharePoint.PowerShell C:\Users\tobia\OneDrive\Dokumente\WindowsPo... Revoke-SPOUserSession Cmdlet Microsoft.Online.SharePoint.PowerShell C:\Users\tobia\OneDrive\Dokumente\WindowsPo... Save-CauDebugTrace Cmdlet ClusterAwareUpdating C:\Windows\system32\WindowsPowerShell\v1.0\... Save-Help Cmdlet Microsoft.PowerShell.Core Save-Package Cmdlet PackageManagement C:\Program Files\WindowsPowerShell\Modules\... Save-ShieldedVMRecoveryKey Cmdlet ShieldedVMDataFile C:\Windows\system32\WindowsPowerShell\v1.0\... Save-VolumeSignatureCatalog Cmdlet ShieldedVMDataFile C:\Windows\system32\WindowsPowerShell\v1.0\... Save-WindowsImage Cmdlet Dism C:\Windows\system32\WindowsPowerShell\v1.0\... Search-ADAccount Cmdlet ActiveDirectory C:\Windows\system32\WindowsPowerShell\v1.0\... Select-Object Cmdlet Microsoft.PowerShell.Utility C:\Windows\System32\WindowsPowerShell\v1.0 Select-String Cmdlet Microsoft.PowerShell.Utility C:\Windows\System32\WindowsPowerShell\v1.0 Select-Xml Cmdlet Microsoft.PowerShell.Utility C:\Windows\System32\WindowsPowerShell\v1.0 Send-AppvClientReport Cmdlet AppvClient C:\Windows\system32\WindowsPowerShell\v1.0\... ...  The module Microsoft.PowerShell.Core is the only module in this list that does not report a file location. This module is hardcoded into PowerShell and always loaded when PowerShell launches. The commands in this module are vital to PowerShell: Get-Command -Module Microsoft.Powershell.Core  ### Default Module Locations PowerShell is monitoring all folders listed in this environment variable: $env:PSModulePath -split ';'


By default, this variable contains three folders:

C:\Users\tobia\OneDrive\Dokumente\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules


The first path holds all modules installed per user and is located in your user profile. The second path stores all modules installed for all users and is located inside the default location for installed 3rd party software. The last folder keeps all internal Microsoft modules and is off limits for anyone else.

### Custom Module Locations

Even though you can edit the folder list in $PSModulePath and add more locations, this is strongly discouraged: • Per User, All User: Sometimes, 3rd party software decide to install PowerShell modules in completely separate custom locations. Only why? Typically, PowerShell modules are installed either per user or for all users, just like any other type of software. The default module folders enable you to do just that. There is really no reason for adding additional module locations. • Damages To Default Folders: Occasionally, software publishers damage the default locations while trying to add their custom folders. Typically, this occurs when PowerShell modules are shipped as MSI packages. For example, when an MSI package accidentally replaced the path to your user profile with a custom location (rather than adding that custom location), any module installed per user becomes suddenly unavailable. • Slow Network, Slow PowerShell: Network shares and UNC paths are frequently abused as a convenient central module store. While technically possible, this slows down PowerShell command discovery and command completion, and can add hefty network traffic: PowerShell visits the folders listed in $env:PSModulePath very frequently, i.e. whenever you request command completion, and unless you have a very fast network and file server, you start to see strange progress bars while waiting for command completion results.

PowerShell modules should strictly be stored in local folders: push them to one of the local default module folders in $env:PSModulePath using classic software distribution, or pull them from central repositories using Install-Module. If you see more or less (or different) folders than the default folders in $env:PSModulePath, you should rethink and try and make sure all PowerShell modules are stored in the default folders.

If you must import modules from non-default places, use Import-Module and submit the path to the module folder. This call can then be added to your profile script in \$profile to automatically load such modules.

### What’s Next

Now that you know the basics about PowerShell modules, let’s take a look at some of the best sources for new free PowerShell modules, and how to install, update, and remove additional PowerShell modules.