Module Builder
Build-Module
-SourceFolder <String>
-ModulePath <String>
[-Depth <Int32> = 1]
[<CommonParameters>]Build a new module (.psm1) file from a folder containing PowerShell scripts (.ps1 files) and other resources.
This module builder doesn't take care of the module manifest (.psd1 file), but it simply builds the module file
from the scripts and resources in the specified folder while taking of the following:
- Merging the statements (e.g.
#Requiresandusingstatements) - Preventing duplicates and collisions (e.g. duplicate function names)
- Ordering the statements based on their dependencies (e.g. classes inheritance)
- Formatting the output.
- Automatically exporting variables, functions, cmdlets, aliases and types.
It doesn't touch any module settings defined in the module manifest (.psd1 file), such as the module Version,
NestedModules and ScripsToProcess. The only requirement is that the following settings are not defined
(or commented out):
FunctionsToExport = @()VariablesToExport = @()AliasesToExport = @()
These particular settings are automatically generated based on the cmdlets, variables and aliases defined in the
source scripts and eventually handled by the module (.psm1) file.
The general consensus behind this module builder is that the module author defines the items that should be
loaded (imported) by putting them in the module source folder and shouldn't be concerned with invoking
(dot-sourcing) any scripts knowing that this could lead to similar concerns as using the [Invoke-Expression]
cmdlet especially when working in a team. See also https://github.com/PowerShell/PowerShell/issues/18740.
This means that this module builder will only accept specific statements (blocks) and reject (with a warning) on statements that require any invocation which potentially could lead to conflicts with other functions and types.
Anything that concerns a dynamic preparation of the module should be done by a specific module manifest setting
or scripted in the ScriptsToProcess setting of the module manifest.
The accepted statements might be divided into different files and (sub)folders using any file name or folder name with the exception of functions that need to be exported as cmdlets.
The accepted statement types are categorized and loaded in the following order:
- Requirements
- using statements
- enum types
- Classes
- Variables assignments
- [(private) Functions]
- [(public) Cmdlets]
- Aliases
- Format files
The module builder will merge the #Requires statements from the source scripts and will add them to the top of
the module file.
If multiple #required -version statements are found, the highest version will be used.
If conflicting #required -PSEdition statements are found, a merge conflict exception is thrown.
If multiple #required -Modules statements are found, the module names will be merged and the highest version
If set in any script, the module builder will add the #Requires -RunAsAdministrator statement to the top of the
module file.
Tip
Consider to make your function self-elevating.
In general using statements are merged and added to the module file except for the using module with will be
rejected and a warning will be shown.
The module builder will use the full namespace name and added or merged them accordingly.
The module builder will reject this statement and will suggest to use the module manifest instead.
The module builder will reformat the assembly path and merge the assembly names and add them to module file.
Enum and Flags types are reformatted using the explicit item value and added or merged them accordingly.
Note
All types are automatically added to the TypeAccelerators list to make sure they are publicly available.
Class definitions are sorted based on any derived (custom) class dependency and added or accordingly.
If conflicting there are multiple classes with the same name a merge conflict exception is thrown unless the
content of the class is exactly the same.
Note
All types are automatically added to the TypeAccelerators list to make sure they are publicly available.
Warning
PowerShell classes do have some known limitation and know issues that might cause problems when using a module builder. For example, when dividing classes that are depended of each other over multiple files would lead to "Unable to find type []" in the "PROBLEMS" tab. The only solution is to put these classes in the same file or neglect the specific problem.
The module builder will merge the variable assignments and add export them when the module is loaded.
Tip
For variables that are dynamically assigned during module load time, consider to use the ScriptsToProcess
setting in the module manifest instead or define the variable during the concerned function or class execution.
Any function that is defined in the source scripts will be added to the module file as a private function.
Meaning the function will not be exported by the module builder and will not be available to the user
when the module is loaded. The function will only be available to other functions in the module file.
To export a function as a cmdlet, the function needs to be defined in a script file with the .ps1 extension,
see: [(public) cmdlets].
Any (public) function that needs to be exported by the module builder is called a cmdlets in this design.
The module builder will recognize any PowerShell script file (.ps1) that contains a param block and will treat
it as a cmdlet. The name of the script file will be used as the cmdlet name. Any Required or Using statement
will be merged and added to the module file.
This module builder design enforces the use of advanced functions and prevents coincidentally interfering with
other cmdlets or other items in the module framework. See also Add ScriptsToInclude to the Module Manifest.
This concept facilitates troubleshooting and testing prototypes without (re)building a new module:
#Requires -Modules @{ModuleName="Pester"; ModuleVersion="5.5.0"}
using module MyModule
param([alias("Path")]$PrototypePath)
Describe 'Test-Object' {
BeforeAll {
if ($PrototypePath) {
$Content = Get-Content -Raw -LiteralPath $PrototypePath
$CommandName = [io.path]::GetFileNameWithoutExtension($PSCommandPath) -replace '\.Tests$'
Mock $CommandName ([ScriptBlock]::Create($Content))
}
}
...Testing a prototype:
. Tests\MyCmdlet.Tests.ps1 Source\Cmdlets\MyCmdlet.ps1This module builder only supports aliases for (public) cmdlets (exported functions). A cmdlet alias might be set using the Alias Attribute Declaration, this will export the alias when the module is loaded.
Note
The Set-Alias command statement is rejected as aliases should be avoided for private functions as they can
make code difficult to read, understand and impact availability (see: AvoidUsingCmdletAliases).
The module builder will accept PowerShell format files (.ps1xml) and will merge the view definitions.
For more details on formatting views, see: about Types.ps1xml.
In general, any statement that requires any invocation (dot-sourcing) is rejected by the module builder. This includes any native cmdlet (and is not limited to) the following specific statements:
The Install-Module cmdlet is rejected along with other cmdlet commands, to specify scripts that run in the
module's session state, use the NestedModules manifest setting.
The New-Type cmdlet is rejected along with other cmdlet commands, to load any assembly or type definitions
written in a different language than PowerShell, use the RequiredAssemblies manifest setting or the
ScriptsToProcess manifest setting for any dynamic or conditional loading. Or consider to load the required
type just-in-time while executing the depended class or cmdlet.
Build a new module file from the scripts in the .\Scripts folder and save it to .\MyModule.psm1.
Build-Module -SourceFolder .\Scripts -ModulePath .\MyModule.psm1.PARAMETERS SourceFolder
The source folder containing the PowerShell scripts and resources to build the module from.
.PARAMETERS ModulePath
The path to the module file to create.
Warning
The module file will be overwritten if it already exists.
.PARAMETERS Depth
The depth of the source folder structure to search for scripts and resources. Default is 1.
Name: -SourceFolder
Aliases: # None
Type: [String]
Value (default): # Undefined
Parameter sets: # All
Mandatory: True
Position: # Named
Accept pipeline input: False
Accept wildcard characters: FalseName: -ModulePath
Aliases: # None
Type: [String]
Value (default): # Undefined
Parameter sets: # All
Mandatory: True
Position: # Named
Accept pipeline input: False
Accept wildcard characters: FalseName: -Depth
Aliases: # None
Type: [Int32]
Value (default): 1
Parameter sets: # All
Mandatory: False
Position: # Named
Accept pipeline input: False
Accept wildcard characters: False