This PowerShell module is a toolbox to streamline the process of building and distributing PowerShell modules.
- Overview
- Getting Started
- Architecture
- Public Cmdlets
- Internal Domain Classes
- Supported Module Types
- Additional Features
- Dependencies
- CI/CD Integration
- Documentation Links
- Community Resources
- Previous Bug Fixes
PsCraft is a PowerShell module that handles the full lifecycle of a PowerShell module project:
- Scaffolding — Generate the project layout for a new module.
- Schema validation — Verify that an existing project conforms to the expected layout.
- Build orchestration — Compile, test, and package Script, Binary, CIM, and Manifest modules.
- Distribution — Publish to the PowerShell Gallery and create GitHub Releases.
The framework is class-based and split into two layers: thin public cmdlets in Public/ that delegate to domain classes in Private/. This separation keeps user-facing API stable while allowing the engine internals to evolve.
- Install and import the module:
Install-Module PsCraft
Import-Module PsCraft- Create your first module:
New-PsModule -Name MyModuleExample:
New-PsModule.mp4
PsCraft is organized around three logical layers that map directly to the knowledge modules maintained for this project:
┌─────────────────────────────────────────────────────────────┐
│ Public API Surface (Public/*.ps1) │
│ - thin cmdlets: Build-Module, New-PsModule, Test-Module, │
│ Publish-PsModule, Publish-GitHubRelease, │
│ Get-PSGalleryStatus, Move-ModulePath, Set-BuildVariables│
└────────────────────────┬────────────────────────────────────┘
│ delegates to
▼
┌─────────────────────────────────────────────────────────────┐
│ Build Engine & Module Scaffolding (Private/) │
│ - Orchestrator.psm1: PsModule, PsCraft, BuildOrchestrator,│
│ BuildContext, AliasVisitor, ParseResult │
│ - ModuleData.psm1: PsModuleSchema, PsModuleDefaults, │
│ PsModuleData, SchemaNode │
│ - BuildLog.psm1: BuildLog, BuildSummary, BuildLogEntry, │
│ BuildTaskResult, TestResult │
│ - Enums.psm1: SaveOptions, ModuleItemAttribute │
│ - defaults/{Script,Binary,Cim,Manifest}/ — templates │
└────────────────────────┬────────────────────────────────────┘
│ uses templates
▼
┌─────────────────────────────────────────────────────────────┐
│ Generated module projects │
│ (the PowerShell module that the consumer is authoring) │
└─────────────────────────────────────────────────────────────┘
Exposes high-level cmdlets for the full PowerShell module lifecycle — creation, building, testing, and publishing to repositories. Each public function is a thin wrapper that:
- Accepts and validates user input (
CmdletBinding,SupportsShouldProcess,ValidateScript,ValidateSet,ValidatePattern). - Returns objects to the pipeline (or uses
process/endblocks to maintain state across pipeline stages). - Instantiates and delegates to a core domain class in
Private/.
External integrations (GitHub Releases via REST, PowerShell Gallery, dotnet build) are reached through this layer.
Provides the core orchestration, schema validation, and automated scaffolding for Script, Binary, CIM, and Manifest modules.
- Core orchestrator —
Orchestrator.psm1defines theBuildOrchestratorclass (extendingPsCraft) which manages the build lifecycle (Clean,Compile,Test) and dispatches to type-specific compilers (CompileScriptModule,CompileBinaryModule, etc.). - Data & schema layer —
ModuleData.psm1implementsPsModuleDefaultsandPsModuleSchemato define directory structures and default content templates for each module type, whilePsModuleDataacts as a dictionary-based state container. - Logging & diagnostics —
BuildLog.psm1provides theBuildLogstatic class for structured console output (usingAnsiConsolewhen available) andBuildSummaryfor post-build reporting. - Type definitions —
Enums.psm1declares shared enumerations (SaveOptions,ModuleItemAttribute) used across the module's classes. - Validation —
cmdlets/Test-ProjectSchema.ps1exposesTest-PsModuleSchemato validate project structures against the definedPsModuleSchema.
The defaults/ folder contains per-type scaffolding templates that decouple generation logic from execution logic:
| Folder | Purpose | Key files |
|---|---|---|
defaults/Script/ |
Plain PowerShell script modules | Builder.ps1, Tester.ps1, ScriptAnalyzer.ps1, RootLoader.ps1, LocalData.ps1, FeatureTest.ps1, IntegrationTest.ps1, ModuleTest.ps1 |
defaults/Binary/ |
C#-based binary cmdlet modules | CmdletClass.cs, ProjectFile.csproj, ModuleTest.ps1 |
defaults/Cim/ |
CIM cmdlet definition modules | CimDefinition.cdxml, ModuleTest.ps1 |
defaults/Manifest/ |
Manifest-only modules | ModuleTest.ps1 |
The root composition (PsCraft.psm1) is the entry point that loads private classes and dot-sources public cmdlets to present a unified surface.
| Cmdlet | Purpose |
|---|---|
Build-Module |
Run the full build pipeline (clean, compile, test, package) for the current module project. |
New-PsModule |
Scaffold a new module project of a chosen type (Script, Binary, CIM, Manifest). |
Test-Module |
Execute the Pester test suite and analyzer checks. |
Publish-PsModule |
Publish the built module to the PowerShell Gallery. |
Publish-GitHubRelease |
Create / update a GitHub Release for a tag (uses GitHub REST API v3). |
Get-PSGalleryStatus |
Check publishing status of a module on the PowerShell Gallery. |
Move-ModulePath |
Move a built module artifact to a target path (commonly into Modules/ for consumption). |
Set-BuildVariables |
Initialize the build environment variables consumed by the orchestrator. |
Defined in Private/Orchestrator.psm1:
class AliasVisitor : System.Management.Automation.Language.AstVisitor— AST walker used for static analysis (e.g. detecting alias usage in code under test).class ParseResult— Holds the outcome of a script/AST parse.class BuildContext— Per-build state container passed to compile/test steps.class PsModule : IDisposable— Represents a single PowerShell module project; owns load/save lifecycle and resource cleanup. Note: PsModule derives fromSystem.Collections.Generic.Dictionary[string, Object], not fromIDictionary/IEnumerable— see Previous Bug Fixes and Dependencies → PowerShell Class System for the implications.class PsCraft : Microsoft.PowerShell.Commands.ModuleCmdletBase— Base domain class; inherits PowerShell cmdlet infrastructure so domain code can write to the standard streams.class BuildOrchestrator : PsCraft— The end-to-end build coordinator. Detects the module topology and dispatches to the appropriate compile pipeline.
Defined in Private/ModuleData.psm1:
class SchemaNode— Recursive tree node describing a directory or file in a module layout.class PsModuleSchema— Validates an existing project against an expectedSchemaNodetree.class PsModuleDefaults— Static factory / template provider that returns the appropriatePsModuleSchemafor a given module type.class PsModuleData : System.Collections.Generic.Dictionary[string, Object]— Dictionary-based state container for arbitrary key/value pairs about a module.
Defined in Private/BuildLog.psm1:
class BuildLogEntry— A single log line / structured event.class BuildTaskResult— Outcome of a single build task (e.g. compile, test, package).class TestResult— Pester-style test outcome (passed/failed/skipped, message, stack).static class BuildLog— Console output helper. HonorsAnsiConsoleavailability for color, falls back to plain text otherwise. All build steps route through this class for consistent formatting.class BuildSummary— AggregatesBuildTaskResultentries and renders a post-build report.
Defined in Private/Enums.psm1:
enum SaveOptions— Controls overwrite / conflict behavior when persisting module artifacts.enum ModuleItemAttribute— Flags describing the kind of file a schema node represents (script, manifest, test, docs, …).
| Type | Description | When to use |
|---|---|---|
| Script | Pure PowerShell functions organized in .ps1 files. Default. |
Most PowerShell modules. |
| Binary | C#-compiled cmdlet assemblies; includes a .csproj and starter CmdletClass.cs. |
Performance-critical cmdlets, low-level system access, or any case where C# is required. |
| CIM | CIM cmdlet definition modules (.cdxml). |
Modules that wrap CIM classes / WMI providers. |
| Manifest | Manifest-only modules (.psd1 only, no implementation files). |
Re-export modules, meta-modules, or modules that simply repackage other modules. |
Sign your PowerShell scripts for enhanced security:
Add-Signature -File MyNewScript.ps1Create graphical interfaces for your scripts (works on Windows and Linux):
Add-GUI -Script MyNewScript.ps1PsCraft relies on a small set of external and language-level dependencies. The most important ones are documented in this folder.
A thread-safe, in-memory and file-based logging module for PowerShell. Install from the PowerShell Gallery:
Install-Module cliHelper.loggerTypical usage inside a script or module:
#Requires -Modules cliHelper.logger
try {
$logger = [IO.Path]::Combine([IO.Path]::GetTempPath(), "MyAppLogs") | New-Logger
$logger | Add-JsonAppender
$logger.LogInfoLine("Application started.")
} finally {
$logger.ReadEntries(@{ type = "JSON" })
$logger.Dispose() # <-- ALWAYS dispose to flush and release file handles
}Full in-depth usage, custom LogEntry subclasses, and appender configuration are documented here: About_logger_module.
⚠️ Important: appenders (especially file-based ones) hold resources. You must call$logger.Dispose()when you are finished logging to ensure logs are flushed and files are closed properly. Use atry...finallyblock to ensure it is always called. Failure to do so can lead to:
- Log messages not being written to files (still stuck in buffers).
- File locks being held, preventing other processes (or even later runs of your script) from accessing the log files.
PsCraft's domain layer is implemented with PowerShell classes.
If you are new to PowerShell class development (constructors, methods, properties, inheritance, and interfaces), these about_* topics will greatly help you:
- About_Classes_Constructors — How to define constructors, static constructors, base-class invocation,
Init()chaining pattern (used heavily in PsCraft'sPsModulefamily — see Coding Conventions). - About_Classes_Methods — Method definitions, static methods, hidden methods,
Update-TypeDatapattern. - About_Classes_Properties — Property declarations, validation attributes, hidden/static properties,
Update-TypeDatafor calculated properties and aliases. - About_Classes_Inheritance — Single inheritance, interface implementation (
IEquatable,IComparable,IFormattable), generic-type inheritance, type accelerators (used by PsCraft to publish its types without requiring ausing modulestatement).
💡 Pitfall to be aware of: PowerShell classes do not implement
IDictionary/IEnumerable. AlthoughPsModuleDataandPsModuleinherit fromSystem.Collections.Generic.Dictionary[string, Object], that base class is not exposed through a PowerShell-defined class as an enumerator. As a result, calling.GetEnumerator()directly on a class instance fails. Iterate withforeach(which uses the language-level enumeration) or expose an explicit enumerator method, e.g.:foreach ($item in $psModuleData) { ... } # works $psModuleData.GetEnumerator() # throws "GetEnumerator() does not exist"
- Public cmdlets in
Public/are thin wrappers that instantiate and delegate to core domain classes defined inPrivate/. Domain logic is encapsulated in PowerShell classes (e.g.PsModule,PsCraft) withinPrivate/modules, promoting an object-oriented architecture over pure functional scripting. - Class construction uses a hidden
Initmethod called by constructors to centralize initialization logic and avoid code duplication across overloads. This is the workaround for PowerShell's lack of constructor chaining — see About_Classes_Constructors. - Static factory methods (e.g.
PsModule::Create,PsCraft::From) are preferred over direct constructor calls to handle path resolution and complex setup. - Build steps and status messages are routed through the
BuildLogstatic class to ensure consistent formatting and optional ANSI color support.
PsCraft ships with workflows for both GitHub Actions and Azure Pipelines:
- GitHub Actions (
.github/workflows/):build_module.yaml— Build & test on every push / PR.publish_module.yaml— Publish to the PowerShell Gallery and create a GitHub Release on tag.delete_old_workflow_runs.yaml— Periodic cleanup of old workflow runs.
- Azure Pipelines —
azure-pipelines.ymlmirrors the build/test pipeline. - Devcontainer —
.devcontainer/provides a reproducible containerized dev environment (Dockerfile +devcontainer.json).
- About_Modules — Microsoft's official module documentation.
- About_logger_module — Thread-safe logging dependency.
- About_Classes_Constructors — PowerShell class constructor patterns.
- About_Classes_Methods — PowerShell class method patterns.
- About_Classes_Properties — PowerShell class property patterns.
- About_Classes_Inheritance — Inheritance, interfaces, and type accelerators.
- The Monad Manifesto — Core PowerShell concepts.
- The SysAdmin Channel — Practical module development.
- Mike F Robbins' Blog — Module design patterns.
- PowerShell Modules and Encapsulation — Advanced module concepts.
-
Critical Bug —
Type must be a type provided by the runtime (Parameter 'types')Root cause (real one, after bisecting): The
PsModuleclass had two overloadedEqualsmethods where one accepted[PsModule]$other— the class itself as a parameter type. When PowerShell'sDefaultBinder.SelectMethod()tries to resolve which overload to compile during type definition,PsModuleis still an incomplete / not-yet-compiled type at that point, so the runtime throws the "type must be a type provided by the runtime" error.Fix: Merged both
Equalsoverloads into a single[bool] Equals([object]$other)that uses-as [PsModule]internally. TheIEquatable[PsModule]interface declaration was also removed (it was the first clue, but not the actual trigger on its own).
