skip to content

In PowerShell, what is the difference between a .psm1 script module file and a .psd1 module manifest, and what does adding a manifest give you?

level: juniorimportance: must knowfreq 68%

answer

  1. one file is code, one is data
  2. hashtable, no executable logic
  3. RootModule points back at the code
  4. version, GUID, dependencies, export list
  5. New-ModuleManifest writes the template

basics

~20 s

A .psm1 file holds the module's actual code — its function definitions. A .psd1 manifest holds metadata about the module as a PowerShell hashtable: version, GUID, author, dependencies, and the exact list of commands the module exports.

solid answer

~50 s

A script module is a `.psm1` file: ordinary PowerShell code, mostly function definitions, that gets executed when the module is imported. That alone is a working module — put `Tools.psm1` in a folder called `Tools` on `PSModulePath` and `Import-Module Tools` loads it. A manifest is a separate `.psd1` file containing one PowerShell hashtable of metadata. Its `RootModule` key points at the code file (the `.psm1` or a binary `.dll`); the rest describes the module — `ModuleVersion`, `GUID`, `Author`, `Description`, `PowerShellVersion`, `RequiredModules`, and the export lists `FunctionsToExport`, `CmdletsToExport`, `AliasesToExport`, `VariablesToExport`. You generate one with `New-ModuleManifest` and validate it with `Test-ModuleManifest`. The manifest is what makes a module shippable: it gives you a version to pin against, declared dependencies, and an explicit public surface. The PowerShell Gallery requires one, since `PrivateData.PSData` carries the tags, license and project URIs shown on the package page.

code

powershell · 8 lines
powershell
New-ModuleManifest -Path .\Tools\Tools.psd1 `
    -RootModule 'Tools.psm1' `
    -ModuleVersion '1.4.0' `
    -Author 'Platform Team' `
    -Description 'Small disk and inventory helpers.' `
    -FunctionsToExport @('Get-DiskFree')

Test-ModuleManifest -Path .\Tools\Tools.psd1 | Format-List Name, Version, ExportedFunctions

go deeper

for a junior

Be able to say plainly that the .psm1 holds the code and the .psd1 holds metadata, and name a few manifest keys such as ModuleVersion, RootModule and FunctionsToExport.

for a middle

Explain the mechanics: RootModule wiring the manifest to the code, module scope, and the fact that exports are the intersection of Export-ModuleMember and the manifest's export lists. Know New-ModuleManifest and Test-ModuleManifest.

for a senior

Show that you treat the export lists as a public API contract, enumerate them rather than using '*', and use RequiredModules and PowerShellVersion to fail imports early on machines that cannot satisfy them.

for a principal

Own the packaging convention for the team: what belongs in a shared module versus a script, how the manifest's version becomes the contract downstream consumers pin against, and when a binary module is worth the build pipeline it drags in.

## What a "module" is A PowerShell module is a unit of packaging: a folder whose name matches the module name, placed somewhere on `PSModulePath`, containing the code and (usually) the metadata that describes it. Importing a module adds its functions, cmdlets, aliases and variables into your session. There are three kinds you meet in practice — script modules, binary modules, and manifest modules — and the last of those is the source of most of the confusion in this question. ## The script module: .psm1 A script module is a file with a `.psm1` extension. Its contents are ordinary PowerShell — the extension is what tells PowerShell to treat it as a module rather than a script to run once. When the module is imported, the whole file is executed in its own module scope, which is why the functions it defines become available while its internal helper variables do not leak into your session. ```powershell # Tools/Tools.psm1 function Get-DiskFree { Get-PSDrive -PSProvider FileSystem | Select-Object Name, Used, Free } Export-ModuleMember -Function Get-DiskFree ``` By default a `.psm1` exports all of its functions. The moment you call `Export-ModuleMember` even once, only what you name is exported — that is how you keep private helpers private. A `.psm1` on its own is a complete, importable module. Nothing about a manifest is mandatory for it to work. ## The manifest: .psd1 A manifest is a file with a `.psd1` extension whose entire content is a single PowerShell hashtable literal. It contains no executable logic — PowerShell reads it in a restricted way, precisely so that discovering a module does not mean running its code. ```powershell @{ RootModule = 'Tools.psm1' ModuleVersion = '1.4.0' GUID = 'd0e9a1b2-3c4d-5e6f-7a8b-9c0d1e2f3a4b' Author = 'Platform Team' Description = 'Small disk and inventory helpers.' PowerShellVersion = '5.1' RequiredModules = @('Microsoft.PowerShell.Management') FunctionsToExport = @('Get-DiskFree') CmdletsToExport = @() AliasesToExport = @() VariablesToExport = @() PrivateData = @{ PSData = @{ Tags = @('disk','inventory') } } } ``` You do not have to type that by hand: `New-ModuleManifest -Path .\Tools\Tools.psd1 -RootModule 'Tools.psm1' -ModuleVersion '1.4.0'` writes a fully commented template, and `Test-ModuleManifest .\Tools\Tools.psd1` parses it and reports what it would export. `RootModule` is the link between the two files: it names the code that is loaded when the module is imported. A manifest with no `RootModule` at all is legal — that is a **manifest module**, typically used as an umbrella that pulls in several nested modules via `NestedModules`. ## What the manifest actually buys you - **A version.** `ModuleVersion` is what lets several versions of the same module sit side by side and lets a script ask for one specifically. - **Identity.** `GUID` identifies the module across renames and across repositories. - **Declared dependencies.** `RequiredModules` and `PowerShellVersion` fail the import early and loudly instead of failing deep inside a function. - **An explicit public surface.** The export lists are your API contract; everything not listed is an implementation detail you are free to change. - **Publishability.** `Publish-Module` requires a manifest, and the gallery page is rendered from `PrivateData.PSData` (`Tags`, `LicenseUri`, `ProjectUri`, `ReleaseNotes`). ## Exports are an intersection, not an override A classic beginner bug: the `.psm1` exports `Get-DiskFree`, the manifest lists something else or an empty array, and the command "disappears". The effective export set is the intersection of what the code exports and what the manifest lists. Listing a function in `FunctionsToExport` cannot resurrect one the `.psm1` withheld, and exporting from the `.psm1` cannot bypass a manifest that does not list it. The other habit worth naming is leaving `FunctionsToExport = '*'`. It works, but PowerShell then cannot know the module's commands without loading it, which slows down command discovery in every session. Enumerate them explicitly. ## Binary modules, briefly A binary module is a compiled .NET assembly (`.dll`) whose classes derive from `Cmdlet` or `PSCmdlet`. You choose one when you need real .NET performance, tighter parameter binding, or code you would rather not ship as readable text. Its `RootModule` points at the `.dll`, and it uses `CmdletsToExport` rather than `FunctionsToExport`. Most team automation is a script module; binary modules are the exception, not the norm. ## What an interviewer is listening for That you know the manifest is data and the `.psm1` is code; that a manifest is optional to import but effectively mandatory to distribute; and that you can name three or four manifest keys and say what each one is for.

  • If the .psm1 already works on its own, when would you not bother writing a manifest?
    For a throwaway helper you dot-source or import by path on one machine, the manifest is ceremony. The moment anyone else installs it, or a second version has to exist, or you want `Publish-Module` to work, you need one. In practice: personal scratch code no, anything with a consumer yes.
  • What is the effective export set if the manifest lists FunctionsToExport = @('Get-Foo','Get-Bar') but the .psm1 calls Export-ModuleMember -Function Get-Foo?
    Only `Get-Foo`. The two lists intersect — the manifest can narrow what the code exports but cannot widen it. `Get-Bar` stays private, and there is no error, which is exactly what makes this bug hard to spot. `Test-ModuleManifest` shows the manifest's claim, so confirm with `Get-Command -Module` after importing.
  • Why does PowerShell parse a .psd1 in a restricted mode instead of just executing it?
    Because module discovery reads manifests constantly — every `Get-Module -ListAvailable` and every auto-load probe. If a manifest could run arbitrary code, merely listing available modules would execute untrusted code. Restricting it to literal data (hashtables, strings, numbers, arrays) makes reading metadata safe and cheap.

saying these in an interview costs you the question

  • Thinks the .psd1 contains the module's functions
  • Says a module cannot be imported without a manifest
  • Believes listing a function in FunctionsToExport exports it regardless of the code
  • Leaves FunctionsToExport as '*' and calls it equivalent
  • Confuses ModuleVersion with the required PowerShell version

context