Published in Articles on by Michiel van Oosterhout ~ 6 min read

About a year ago we looked at how to publish a PowerShell script package using the PowerShellGet package provider. This package provider can also publish modules, which are more suitable for distributing a large set of features (for example, a set of cmdlets for a cloud service like Azure App Service or AWS S3).

Wallpaper Flare

Creating a PowerShell module

A PowerShell module is a set of files (typically PowerShell scripts and/or .NET assemblies) in a directory, with a manifest describing those files, packaged into a NuGet package, distributed via a repository, and installed into one of the directories in $Env:PSModulePath.

Module types

There are three types of modules:

The name of the module's directory is the module's base name, and the primary script or assembly, as well as the manifest file should have the same base name. The manifest is a data file that describes the module using many settings. Although the manifest is optional for script and binary modules, it becomes required when you want to distribute the module via a repository.

Using a local package source

For testing purposes we can publish a module to a temporary local repository. The Windows PowerShell script below creates and registers one:

# Create a temporary directory
$packageSourcePath = "$Env:USERPROFILE\Desktop\Temp"
$_ = New-Item -ItemType Directory -Path $packageSourcePath

# Register a temporary repository
$_ = Register-PackageSource -Trusted -Provider PowerShellGet -Name Temp -Location $packageSourcePath

The Temp directory on the desktop will contain a new NuGet package file (.nupkg) after a module is published to the Temp repository.

The Windows PowerShell script below completely removes the temporary repository:

# Unregister the temporary repository
Unregister-PackageSource -ProviderNam PowerShellGet -Source Temp

# Delete the temporary directory and its contents
$packageSourcePath = "$Env:USERPROFILE\Desktop\Temp"
Remove-Item -Force -Path $packageSourcePath -Recurse

Creating the module

The steps to create a module are similar to the steps to create a script package:

  • Create the directory and files
  • Add the manifest (.psd1 file) with ModuleVersion, Author, and Description set
  • Validate the manifest using Test-ModuleManifest
  • Update the version in the manifest using Update-ModuleManifest
    Note: This will set GUID, CompanyName, Copyright, FunctionsToExport, CmdletsToExport, and AliasesToExport if not already set.
  • Publish the module using Publish-Module

A PowerShell module is created and published by invoking Publish-Module. This will validate the manifest, create a NuGet package (.nupkg) file, and upload the NuGet package to the repository referred to by the -Repository parameter.

# Publish the Test module to the Temp repository
Publish-Module -Path "$Env:USERPROFILE\Desktop\Test" -Repository Temp

When using the -Path parameter, its value should be the path of the module's directory.

Using NuGet Package Explorer to inspect the module

Publishing the module to a local package source gives us an opportunity to inspect the package file (.nupkg). The easiest way to do that is by installing NuGet Package Explorer:

# Install NuGet Package Explorer from the Microsoft Store
winget install "NuGet Package Explorer" --accept-source-agreements --accept-package-agreements

Now you can open the NuGet package (e.g. Test.1.0.0.nupkg) that you can find in the temporary package source's directory, and check its contents and metadata.

A PowerShell module opened in NuGet Package Explorer
A PowerShell module opened in NuGet Package Explorer

All files from the module's directory should be included, and some of the settings in the module's manifest will have been mapped to the corresponding NuGet package metadata. In addition, the following rules apply:

  • CompanyName maps to Owners;
  • RequiredModules maps to Dependencies;
  • RequiredModules is an array of module specifications 1;
  • RequiredModules entries must be in the global session state at the time of publishing;
  • RequiredModules entries must be available in the repository;
  • PrivateData.PSData.ExternalModuleDependencies is an array of strings;
  • PrivateData.PSData.ExternalModuleDependencies should contain the names of those RequiredModules that are not available in the package source;
  • PrivateData.PSData.ExternalModuleDependencies entries are excluded from Dependencies;
  • PrivateData.PSData.Prerelease can contain a prerelease label since PowerShellGet 1.6;
  • PrivateData.PSData.Prerelease prerelease labels are not semver 1.0.0 compatible 2;
  • the PSModule tag is added automatically to Tags;
  • the PSIncludes_Cmdlet tag is added automatically to Tags if the module exports a cmdlet;
  • the PSIncludes_Function tag is added automatically to Tags if the module exports a function;
  • a PSCommand_ tag is added automatically to Tags for each exported alias, cmdlet, or function;
  • a PSCmdlet_ tag is added automatically to Tags for each exported cmdlet;
  • a PSFunction_ tag is added automatically to Tags for each exported function;

What is exported by a module can be controlled by RootModule, which designates the primary file, whose cmdlets and functions will be exported by default. CmdletsToExport and FunctionsToExport can be used to override the default exports from the primary file, as well as to export from other files included in the module.

NestedModules can be used to import additional script (.psm1) or binary (.dll) modules into the root module's session state. Those modules are in control of their own exports. The exports from nested modules are only available to the root module. When a script file (.ps1) is specified it simply runs in the root module's session state on import of the root module. (Use ScriptsToProcess to run scripts in the caller's session.)

Consuming a PowerShell module

The lifecycle of a distributed module on some system is managed by this list of cmdlets:

Summary

A PowerShell module is suitable to distribute a larger set of features, and can have a dependency on other modules. The package format is based on NuGet with limited support for semantic versioning. Consuming PowerShell modules distributed this way is enabled via a set of cmdlets using the Module noun.


  1. A module specification specifies ModuleName (required), GUID (optional), and one (required) of ModuleVersion (minimum required version), RequiredVersion (exact required version), or MaximumVersion (maximum required version). ↩︎

  2. The label is validated using $validCharacters = "^[a-zA-Z0-9]+$"↩︎