PSPublishModule already generates production module bootstrappers during staging and packaging. Development bootstrappers are different: they live in the source checkout and make local F5/import workflows easier while the final packaged module remains production-shaped.
Use this feature when a source checkout needs to import a freshly built local .NET binary before the module is packaged. It is intentionally a development-time convenience, not a replacement for the packaged bootstrapper.
A scan across the Evotec repository root found three useful families:
- Script-only modules that do not need binary development loading.
- Simple binary modules with older always-on development
.psm1loaders. - A small set of newer or conflict-prone binary modules, such as PSWriteOffice, that need environment-gated local binary loading and sometimes AssemblyLoadContext support on PowerShell Core.
That does not justify many per-module templates. The shared scope is intentionally small:
Off: do not generate development binary loading logic.Environment: generate development binary loading logic, but only activate it when the module-specific environment variable istrue.Auto: generate development binary loading logic and use it automatically when a matching local build output exists.
Environment is the default when New-ConfigurationBuild -NETDevelopmentBinaries is used. This keeps local source bin outputs from taking over a module import unless the maintainer opts in for that shell.
Use development bootstrappers for:
- A binary-backed module where maintainers run
dotnet buildor F5 from an IDE and then import the source module without packaging it first. - A module with dependency conflicts that should use
NETAssemblyLoadContexton PowerShell Core during local development, matching the packaged module behavior more closely. - A source tree with checked-in
Public,Private,Classes,Enums, orLiblayout where PowerForge can regenerate the source.psm1safely. - A team repository where the source
.psm1should include the loader, but local binaries should only win when a maintainer explicitly sets an environment variable.
Avoid development bootstrappers for:
- Pure script modules with no local binary output.
- Hand-authored single-file
.psm1modules unless you explicitly chooseReplaceSingleFile. - Source layouts that depend on custom
Information.IncludePS1folders not represented by the standard source loader. PowerForge preserves those files by default so custom dot-sourcing is not lost. - CI/package validation. The packaged/staged bootstrapper remains the proof for release behavior.
The main entrypoint is still New-ConfigurationBuild / Invoke-ModuleBuild.
New-ConfigurationBuild `
-NETProjectPath "$PSScriptRoot\..\Sources\Example\Example.csproj" `
-NETProjectName 'Example' `
-NETFramework 'net8.0', 'net472' `
-NETAssemblyLoadContext `
-NETDevelopmentBinariesOptional overrides exist for unusual layouts:
-NETDevelopmentBinariesMode Environment|Auto|Off-NETDevelopmentBinariesPath <path-to-bin-root>-NETDevelopmentBinariesEnvironmentVariable <name>-NETDevelopmentConfigurationEnvironmentVariable <name>-NETDevelopmentSourceBootstrapperMode PreserveSingleFile|ReplaceSingleFile
When no environment names are supplied, PowerForge derives:
<MODULE>_USE_DEVELOPMENT_BINARIES<MODULE>_DEVELOPMENT_CONFIGURATION
The generated source .psm1 probes bin\<Configuration>\<TFM>\<Module>.dll, preferring Core-compatible TFMs in PowerShell Core and desktop-compatible TFMs in Windows PowerShell. PowerShell Core uses the generated development ALC loader when NETAssemblyLoadContext is enabled; Windows PowerShell keeps the direct-import compatibility path.
With the default Environment mode, a maintainer opts into local binaries per shell:
$env:EXAMPLE_USE_DEVELOPMENT_BINARIES = 'true'
$env:EXAMPLE_DEVELOPMENT_CONFIGURATION = 'Debug'
Import-Module .\Example.psd1 -ForceUnset the variable, or set it to anything other than true, to go back to the packaged/source fallback:
Remove-Item Env:\EXAMPLE_USE_DEVELOPMENT_BINARIES -ErrorAction SilentlyContinue
Import-Module .\Example.psd1 -ForceUse Auto only when a repository's normal source import should always prefer a matching local build output when one exists:
New-ConfigurationBuild `
-NETProjectPath "$PSScriptRoot\..\Sources\Example\Example.csproj" `
-NETProjectName 'Example' `
-NETDevelopmentBinaries `
-NETDevelopmentBinariesMode AutoPowerForge only overwrites source .psm1 files when the layout is generated-safe:
- Standard script folders:
Classes,Enums,Private,Public. - Binary source layout: checked-in
Lib\<layout>folders. - Existing PowerForge-generated development bootstrapper files.
Hand-authored single-file source modules are preserved by default. To replace one intentionally:
New-ConfigurationBuild `
-NETProjectPath "$PSScriptRoot\..\Sources\Example\Example.csproj" `
-NETDevelopmentBinaries `
-NETDevelopmentSourceBootstrapperMode ReplaceSingleFileWhen DevelopmentBinariesMode is Off, PowerForge removes development loading logic from known generated source bootstrappers. For Lib-based source layouts it regenerates the normal source binary loader instead of deleting the module entrypoint.
Build-level JSON uses unprefixed Build properties:
{
"Build": {
"Name": "Example",
"SourcePath": "Module",
"CsprojPath": "Sources/Example/Example.csproj",
"DevelopmentBinariesMode": "Environment",
"DevelopmentBinariesPath": "Sources/Example/bin",
"DevelopmentBinariesEnvironmentVariable": "EXAMPLE_USE_DEVELOPMENT_BINARIES",
"DevelopmentConfigurationEnvironmentVariable": "EXAMPLE_DEVELOPMENT_CONFIGURATION",
"DevelopmentSourceBootstrapperMode": "PreserveSingleFile"
}
}BuildLibraries segments may use either the unprefixed names or the cmdlet-style NET* aliases:
{
"Type": "BuildLibraries",
"BuildLibraries": {
"NETProjectPath": "Sources/Example/Example.csproj",
"NETAssemblyLoadContext": true,
"NETDevelopmentBinaries": true,
"NETDevelopmentBinariesMode": "Environment",
"NETDevelopmentBinariesPath": "Sources/Example/bin"
}
}For build-level DevelopmentBinariesPath, relative paths are resolved from the config file directory, like CsprojPath. For segment-level DevelopmentBinariesPath, relative paths stay source-root relative, matching the rest of the BuildLibraries segment behavior.
Prefer fixing PowerForge/PSPublishModule when several repositories need the same development import behavior. Repository-local .psm1 loaders should be kept for truly unusual source layouts only. Common patterns that belong in PowerForge include:
- Environment-gated local binary loading.
- AssemblyLoadContext source imports on PowerShell Core.
- Direct-import reuse when a development assembly is already loaded.
- Runtime/native probing for development outputs.
- Safe source
.psm1regeneration rules for standard script folders andLiblayouts.