PowerShell Module Manifest: Build a Publishable Module
─□✕

PowerShell Module Manifest: Build a Publishable Module

PowerShell Tips Editor 5 min read
PowerShell Module Manifest: Build a Publishable Module

What Separates a Script File from a Real Module

A .psm1 file with a handful of functions is not a publishable module — it is a script collection. A proper PowerShell module has a manifest: a .psd1 file that declares metadata, version, dependencies, exported symbols, and licensing information. Without a manifest, the PowerShell Gallery rejects your submission, other developers cannot pin a specific version as a dependency, and your private tooling has no reliable versioning story. The manifest is the artifact that transforms a script file into software.

Quick Answer

Run New-ModuleManifest to scaffold a .psd1 file, fill in the required Gallery fields (ModuleVersion, Author, Description, LicenseUri), explicitly list exported functions in FunctionsToExport, and publish with Publish-Module using your PSGallery API key.

Generating a Manifest Scaffold with New-ModuleManifest

New-ModuleManifest creates a fully commented .psd1 template with every supported key present. Generating from the cmdlet is always preferable to writing the file by hand — the output is syntactically correct and all optional keys are present as commented stubs. Pass the key fields you know up front to pre-populate the most important fields.

$manifestParams = @{
    Path              = ".\MyModule\MyModule.psd1"
    RootModule        = "MyModule.psm1"
    ModuleVersion     = "1.0.0"
    Author            = "Your Name"
    CompanyName       = "Your Company"
    Description       = "A brief description of what MyModule does"
    PowerShellVersion = "5.1"
    LicenseUri        = "https://github.com/yourname/MyModule/blob/main/LICENSE"
    ProjectUri        = "https://github.com/yourname/MyModule"
    Tags              = @("administration","utilities","automation")
}

New-ModuleManifest @manifestParams
Write-Host "Manifest created at: $(Resolve-Path '.\MyModule\MyModule.psd1')"

Required Fields for PSGallery Publishing

The PowerShell Gallery enforces a minimum set of manifest fields before accepting a submission. Missing or empty values for any of these causes Publish-Module to fail with a clear error message. At minimum you need: ModuleVersion (semantic version string), Author, Description (non-empty), and LicenseUri pointing to a reachable URL. ProjectUri and Tags are optional but strongly recommended for discoverability.

# Verify required fields before publishing
$manifest = Test-ModuleManifest -Path ".\MyModule\MyModule.psd1"

Write-Host "Module name    : $($manifest.Name)"
Write-Host "Version        : $($manifest.Version)"
Write-Host "Author         : $($manifest.Author)"
Write-Host "Description    : $($manifest.Description)"
Write-Host "LicenseUri     : $($manifest.LicenseUri)"

# Test-ModuleManifest returns $null and writes an error on failure
if ($null -eq $manifest) {
    Write-Error "Manifest validation failed. Correct errors before publishing."
}

FunctionsToExport, AliasesToExport, and CmdletsToExport

Setting FunctionsToExport = '*' is a convenience anti-pattern. It exports every function defined in the module, including private helper functions that were never intended to be part of the public API. Always enumerate your public functions explicitly. This makes the module’s contract clear and prevents internal implementation details from becoming part of the public surface.

# In MyModule.psd1 - explicitly list public functions
# Edit the manifest to set these values:

Update-ModuleManifest -Path ".\MyModule\MyModule.psd1" `
    -FunctionsToExport @(
        "Get-MyThing",
        "Set-MyThing",
        "Remove-MyThing",
        "New-MyThing"
    ) `
    -AliasesToExport @("gmt","smt") `
    -CmdletsToExport @()

# Private functions (e.g., Invoke-InternalHelper) are NOT listed
# and remain inaccessible outside the module
Write-Host "Exports configured."

RequiredModules and ModuleVersion Pinning

If your module depends on other modules, declare them in RequiredModules. Without this declaration, your module may load but fail at runtime on machines that lack the dependency. Pin the minimum required version using a hashtable with ModuleName and ModuleVersion keys. PowerShell will automatically install missing dependencies when the module is installed from the Gallery.

Update-ModuleManifest -Path ".\MyModule\MyModule.psd1" `
    -RequiredModules @(
        @{ ModuleName = "Microsoft.Graph.Users"; ModuleVersion = "2.0.0" },
        @{ ModuleName = "ImportExcel"; ModuleVersion = "7.8.0" }
    )

# Verify the manifest still passes validation after the update
$result = Test-ModuleManifest -Path ".\MyModule\MyModule.psd1" -ErrorAction Stop
Write-Host "Manifest valid. Required modules: $($result.RequiredModules.Count)"

Signing the Module with a Code Signing Certificate

Signing your module allows it to run under the AllSigned execution policy and provides integrity verification for consumers. Obtain a code signing certificate from an internal CA or a public certificate authority, then sign both the .psm1 and .psd1 files. The .cat catalog file covers all module files and is the preferred approach for Gallery-published modules.

# Get your code signing certificate from the personal store
$cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert |
    Where-Object { $_.NotAfter -gt (Get-Date) } |
    Select-Object -First 1

if ($null -eq $cert) {
    throw "No valid code signing certificate found in CurrentUser\My store."
}

# Sign the module files
Set-AuthenticodeSignature -FilePath ".\MyModule\MyModule.psm1" -Certificate $cert
Set-AuthenticodeSignature -FilePath ".\MyModule\MyModule.psd1" -Certificate $cert

Write-Host "Module signed with: $($cert.Subject)"

Testing Locally and Publishing with Publish-Module

Before publishing to the public Gallery, register a local NuGet feed as a test repository and publish there first. This catches packaging errors without creating a public version that cannot be deleted. When the local test passes, publish to the PSGallery using your API key. Remember: once published, a version is permanent — you cannot overwrite it, only publish a higher version number.

# Test publish to a local repository first
Register-PSRepository -Name LocalTest -SourceLocation "C:\LocalRepo" `
    -PublishLocation "C:\LocalRepo" -InstallationPolicy Trusted

Publish-Module -Path ".\MyModule" -Repository LocalTest
Install-Module MyModule -Repository LocalTest -Force
Import-Module MyModule
Get-Command -Module MyModule

# Publish to PSGallery (requires API key)
$apiKey = Read-Host "PSGallery API Key" -AsSecureString
$plainKey = [System.Net.NetworkCredential]::new("", $apiKey).Password
Publish-Module -Path ".\MyModule" -NuGetApiKey $plainKey -Repository PSGallery -WhatIf

Common Errors

  • FunctionsToExport = ‘*’ exposes private helpers. Any function defined in the .psm1 is exported when the wildcard is used. Internal functions prefixed with a verb like Invoke- become part of the public API unintentionally. Always enumerate the exact function names you want to expose.
  • Publishing fails with “version already exists”. The PSGallery permanently records every published version. Attempting to republish the same version number always fails, even if you deleted the module locally. Increment ModuleVersion for every new publication, including patches. Use semantic versioning: major.minor.patch.

Related Cmdlets / See Also

Wrapping Up

A complete module manifest turns a private script collection into a versioned, discoverable, dependency-aware package. Scaffold with New-ModuleManifest, enumerate exports explicitly, declare all dependencies, sign the files, and test against a local repository before touching the PSGallery. These steps take less than an hour and produce a module that other PowerShell developers can trust and pin.

Send-Item -To