Last updated: 2026-09-22
PowerForge can turn a .ps1, .psm1, .psd1, or conventional module directory into three different artifact shapes:
- a packaged executable that preserves dynamic PowerShell semantics, or a genuinely typed executable for an eligible top-level script;
- an importable binary or hybrid module whose eligible functions are compiled to typed CLR methods;
- a plain CLR library containing eligible typed methods for use from .NET.
These are deliberately separate claims. Packaging makes a script easier to distribute, but does not make its body faster. Typed compilation removes PowerShell's dynamic execution path for a conservative function subset and can improve CPU-bound code. Every build writes a JSON manifest that says which path was used.
| Kind | Default mode | Result | PowerShell required at runtime | Typed speedup expected |
|---|---|---|---|---|
Executable / exe |
Package |
Single-file host with embedded script and PowerShell SDK | Embedded in the application | No |
Executable / exe |
Strict |
PowerShell-free typed .NET executable | No | Yes, for eligible CPU-bound work and process startup |
BinaryModule / dll |
Strict (explicit) |
Importable DLL when every function compiles | Yes, as the cmdlet host | Only inside sufficiently coarse compiled work |
BinaryModule / dll |
Hybrid (default) |
Module folder with a typed DLL and .psm1 fallback |
Yes | For eligible functions; unsupported functions remain scripts |
Library / library |
Hybrid |
CLR DLL with eligible public static methods | No | Yes, when called as CLR code |
Supported target frameworks are:
- executable:
net10.0; - CLR library or binary module:
net472,net10.0.
The net472 binary-module lane is tested by importing and invoking the generated DLL in Windows PowerShell 5.1. The modern lane runs in PowerShell 7.6 on .NET 10. PowerForge no longer accepts net8.0 as a PowerShell compilation target. Microsoft ends .NET 8 support on 10 November 2026; carrying a third compiler target through the remaining correctness milestones would add a short-lived compatibility lane without extending the supported product lifetime. This compiler policy does not remove net8.0 from unrelated PowerForge build, packaging, or website features.
Dated validation and architecture-history paragraphs may still name net8.0 or PowerShell 7.4. They record the framework and host used for that evidence; they do not restore those versions to the active compiler support matrix.
The target framework and publication profile determine what must already exist on the destination computer. An installed powershell.exe or pwsh is not used as the runtime for a generated executable.
| Artifact | Target | PowerShell engine used | Destination requirement |
|---|---|---|---|
| Package EXE | net10.0 |
Embedded PowerShell SDK 7.6 | .NET 10 for a framework-dependent build; nothing separately installed for a self-contained build |
| Strict EXE | net10.0 |
None | .NET 10 for a framework-dependent build; nothing separately installed for a self-contained or NativeAOT build |
| Binary module | net472 |
Windows PowerShell 5.1 Desktop host | Windows PowerShell 5.1 and its .NET Framework runtime |
| Binary module | net10.0 |
PowerShell 7.6 Core host | A compatible PowerShell 7.6 host, which supplies .NET 10 |
| CLR library | net472 or net10.0 |
None | A consuming process on the matching CLR family |
Framework-dependent executables are the smallest normal build, but require the matching .NET runtime. Self-contained builds carry that runtime and are therefore larger and platform-specific. Single-file publication still targets one runtime identifier such as win-x64 or linux-x64; it does not make one binary portable across operating systems. NativeAOT removes both the PowerShell and installed-.NET requirements, but is available only to Strict typed executables and must be built for each target platform and architecture.
Target support is narrower than build availability. PowerForge currently marks only Strict net10.0 framework-dependent and NativeAOT executables for win-x64 and linux-x64 as Supported, after executing both profiles on their target hosts. Portable managed artifacts retain the PortableManaged support level. Self-contained, trimmed, Package/Hybrid, macOS, and Arm64 profiles remain Experimental until that exact framework, deployment model, RID, and host behavior pass the same closure and execution gate. ReadyToRun is benchmark-only and cannot be selected as a public target.
PowerShell 5.1 compatibility currently means a net472 generated binary module loaded by Windows PowerShell. PowerForge does not currently produce a Windows PowerShell 5.1 packaged EXE. A Strict EXE is also not a hidden choice between PowerShell 5.1, 7.4, and 7.6: no PowerShell engine runs after a successful Strict compilation.
| Model | Best fit | Compatibility boundary | Distribution and security tradeoff |
|---|---|---|---|
| Package EXE | Existing scripts that need broad dynamic PowerShell behavior | Runs the embedded script through the bundled PowerShell SDK | Largest artifact and attack surface; embedded source is inspectable; rebuild when bundled PowerShell or .NET dependencies need security updates; generated-host similarity can contribute to antivirus reputation or heuristic detections |
| Strict EXE | Deliberately typed utilities whose complete reachable program fits the supported compiler contract | Every reachable entrypoint statement and local function must compile | No PowerShell runtime or embedded script; smaller framework-dependent and NativeAOT options; still ordinary analyzable code and not immune to antivirus false positives |
| Hybrid binary module | Real modules with a mixture of compiler-friendly and dynamic functions | Eligible bodies compile as generated cmdlets or native-bound functions backed by CLR methods; unsupported bodies remain PowerShell source | Preserves broad module behavior, but requires a matching PowerShell host and carries the combined maintenance surface of generated code plus retained scripts |
| Strict binary module | Modules intentionally constrained to the typed subset | Every exported implementation must compile, while PowerShell remains the cmdlet host | No script fallback, but still depends on the target PowerShell/.NET host contract |
| CLR library | Typed functions intended for direct .NET consumption | Only eligible methods are emitted; no PowerShell fallback is carried | Normal managed-library deployment and analysis rules apply |
Code signing establishes publisher identity and artifact integrity; it does not make arbitrary generated programs inherently trustworthy to antivirus products. Use a certificate only for code owned and distributed by that certificate's publisher. Do not submit private packaged executables to public malware-analysis services unless sharing the embedded source and dependencies with that service is acceptable.
For executable development, see the source-first run/watch guide, including arguments, cancellation, incremental rebuilds, and the boundary between development edits and reviewed distribution artifacts.
For a repeatable artifact matrix, create one portable project manifest instead of repeating command switches in a repository-specific build script:
powerforge powershell project init .\src\Sample.psd1 `
--project .\powerforge.psproject.json `
--name Sample
powerforge powershell project analyze .\powerforge.psproject.json
powerforge powershell project explain .\powerforge.psproject.json
powerforge powershell project recommend .\powerforge.psproject.json
powerforge powershell project lock .\powerforge.psproject.json
powerforge powershell project restore .\powerforge.psproject.json
powerforge powershell project restore .\powerforge.psproject.json --offline
powerforge powershell project build .\powerforge.psproject.json
powerforge powershell project test .\powerforge.psproject.json
powerforge powershell project diagnose .\powerforge.psproject.json
powerforge powershell project pack .\powerforge.psproject.json
powerforge powershell project install .\powerforge.psproject.jsonThe manifest maps source/resource policy, one named semantic profile, provider packages and trust, an exact artifact matrix, dependency/provider lock paths, an optional ABI baseline, and diagnostic/IR policy onto the existing compiler contracts. Each target must have a unique kind/mode/TFM/RID/architecture/deployment identity. No source or module identity changes compiler behavior. SemanticProfileId is an effective compiler input: it participates in target hashing, binding/lowering, compatible #requires policy, provider resolution, caches, package variants, artifacts, and diagnostics. When compatibility fields omit it, net472 selects the Windows PowerShell 5.1 profile and modern targets select the PowerShell 7.6 profile; explicit profiles and target contracts remain authoritative. Unknown profiles fail closed, and behaviorally different profiles do not share compilation identity.
Provider-package manifest schema 3 requires an explicit redistribution disposition and an exact RID allow-list in addition to canonical package, publisher, license, signer, archive, manifest, assembly, dependency, ABI, and semantic-profile identity. RequireRedistributable makes redistribution permission an analysis/lock trust-policy gate, and artifact delivery independently rejects every package that is not approved for redistribution. A selected RID outside the reviewed provider allow-list fails before compilation; a RID-restricted package also fails for every RID-less library, binary-module, or portable executable target because portability cannot be inferred. Project analyze, explain, recommend, lock, build, and receipt validation resolve the package against each selected target. RID-specific native assets are declared separately from managed assemblies, rejected if they contain managed metadata, inspected as PE/ELF/Mach-O without loading, matched to the declared RID architecture, and required to close every native import against another exact asset or the explicit target OS ABI. Their format, architecture, imports, and hash are locked, selected only for the target RID, collision-checked against the complete provider runtime closure, copied beside the artifact when required, and recorded as provider-native runtime files in provenance and CycloneDX. Platform loader-name probing is modeled explicitly and does not accept arbitrary dotted suffixes. The exact redistribution and target restrictions participate in provider-lock schema 3 and are repeated in build provenance and the SBOM. An explicitly present empty RID list means the reviewed package is portable, while an omitted field is invalid. Rebuild older provider packages with the current SDK and regenerate/review older provider locks because their earlier schemas did not authenticate these fields.
restore acquires exact NuGet identities into .powerforge/environment/packages, records the reviewed dependency locks plus a target-specific complete packages.lock.json closure, and verifies NuGet's canonical signed/unsigned content identity, the downloaded archive bytes, the resolved assets graph, and the extracted files consumed by MSBuild. restore --offline clears package sources and proves the already acquired environment can satisfy those same locks. build injects any reviewed direct package reference absent from the generated template, runs the generated compilation project in locked mode, reconciles its complete actual assets graph with that target lock, and records the closure-lock SHA-256 in the durable compiler manifest. It rejects project drift, environment-evidence drift, missing or changed closure locks, modified archives or extracted package payloads, extra or missing actual packages, provider-lock drift, or any dependency change. Tool-owned .powerforge state and declared artifact roots are supplied as generated-output roots, so restore packages and previous outputs cannot become authored resource input.
test first revalidates the current project, target, locks, complete build inventory, and every artifact hash before executing the declared surface. pack requires matching passed test evidence and produces a deterministic qualified ZIP whose authenticated descriptor includes the exact target, semantic profile, dependency/provider locks, ABI, SBOM, provenance, test identity, complete file inventory, and artifact hash. install validates that descriptor, extracts into an immutable project-local root, compares every installed byte with the archive, verifies the primary artifact identity, and repeats the declared EXE, clean module import, or CLR metadata test. Existing matching installations are reusable and tampered content fails closed. The installation directory identity is a complete 256-bit Base64URL SHA-256 derived from both the full target-contract hash and full authenticated artifact-set hash, so different manifests, resources, or sidecars cannot collide merely because their primary binary matches.
recommend is advisory only. Without a supplied boundary profile it reports static eligibility and suggests the next measurement. With --boundary-profile <profile.json>, it can recommend coarsening an expensive typed/hosted boundary, retaining hosted execution, or evaluating a Strict candidate. It never edits source, changes the project target, or describes eligible units as PowerShell language coverage.
Use powerforge powershell support --output json for the canonical qualified support matrix. The current toolchain channel is Preview: portable managed outputs and the target-host-qualified Windows/Linux x64 Strict profiles are advertised, while macOS, Arm64, self-contained, and trimmed exact profiles remain experimental until their own semantic, lock, install, target-host, and performance packet passes.
Project, target-contract, dependency-lock, provider-lock, compiler-manifest, explanation, diagnostic, cache, and ABI evidence carry explicit schema or semantic-profile identities. Unknown schema versions fail instead of being guessed. During preview, an intentional incompatible change requires a new schema or semantic profile plus migration guidance. The planned stable policy accepts an older schema through at least two minor release trains before removal unless retaining it would violate a security or correctness invariant. Additive diagnostic fields do not change a semantic or ABI identity; changed behavior does. Semantic profiles already participate in compilation identity; full profile promotion additionally requires the exact-host oracle evidence described in the roadmap. Public package publication, upgrade, and rollback proof remain a separate explicitly authorized release lane and are never inferred from a source checkout.
For the common case, point PowerForge at the module directory. It selects the matching top-level manifest and root module, infers a hybrid binary-module build, and writes to the module's artifacts directory:
powerforge powershell build .\MyModule --allow-unreviewed-dependencies --emit-sourceArtifact builds require a separately reviewed dependency graph by default. Capture the dependencyGraph from powerforge powershell analyze <path> --output json, review and store that graph, then pass the raw graph JSON with --dependency-lock <graph.json> or the equivalent -DependencyLock cmdlet object. For a local development build only, --allow-unreviewed-dependencies / -AllowUnreviewedDependencies is the explicit opt-out; manifest schema 13 records dependencyLockReviewed: false so that result cannot be mistaken for a reviewed build.
The accepted input shapes are:
.ps1: defaults to a packaged executable;- several loose
.ps1files: default to a Hybrid typed library that emits eligible functions and reports omissions; choose Strict explicitly when every function must compile; .psm1: defaults to a hybrid binary module and uses a same-name sibling.psd1when present;.psd1: resolves a literal.psm1RootModule;- directory: prefers a manifest matching the directory name, otherwise accepts one unambiguous top-level manifest or script module.
Directory discovery does not recurse into samples, tests, or nested modules looking for an entrypoint. Multiple plausible top-level entries fail with their candidate names. A manifest root currently needs to be a .psm1 beside the .psd1; an existing binary RootModule is rejected because it is already compiled input. A same-name sibling manifest is accepted only when its RootModule points back to the selected .psm1. Use --kind and --mode only when overriding the inferred artifact shape or fallback policy.
Unconditional top-level literal $PSScriptRoot dot-sourced files share the root module's compilation scope. The resolver also recognizes conventional top-level Get-ChildItem $PSScriptRoot\Public\*.ps1 -Recurse / Private loader declarations and their $Import.FullName dot-source loop without executing module code. Eligible functions in discovered Public or Private files can therefore become binary cmdlets, while unsupported functions remain in their staged script files. Conditional and function-local dot-sources, ScriptsToProcess, and script-based nested modules stay runtime content because PowerShell gives them different scope and loading semantics.
Module inputs cannot be overridden to Executable. Use a standalone .ps1 as the executable entrypoint or build the module as BinaryModule; PowerForge does not invent module-to-application startup semantics.
Compile several standalone files without creating a manifest or build configuration:
powerforge powershell build .\Public\Get-One.ps1 `
--path .\Public\Get-Two.ps1 `
--kind dll `
--out .\artifacts `
--allow-unreviewed-dependencies `
--emit-source
Build-PowerShellArtifact `
-Path .\Public\Get-One.ps1, .\Public\Get-Two.ps1 `
-Kind BinaryModule `
-AllowUnreviewedDependencies `
-EmitSourceLoose binary-module file sets are Strict by default because there is no .psm1 entrypoint in which unsupported functions could remain as fallback. All files must be contained by the first file's directory.
A multi-file executable has one explicit root script. The root param() block remains the process argument contract, and PowerForge follows reachable unconditional literal dot-sources recursively from that entrypoint. Every supplied --path must belong to that contained dependency closure; unrelated files are rejected instead of being bundled speculatively:
powerforge powershell build .\Tool.ps1 `
--path .\Private\Helpers.ps1 `
--entry-point .\Tool.ps1 `
--kind exe `
--mode Package `
--allow-unreviewed-dependencies `
--out .\artifacts
Build-PowerShellArtifact `
-Path .\Tool.ps1, .\Private\Helpers.ps1 `
-EntryPoint .\Tool.ps1 `
-Kind Executable `
-Mode Package `
-AllowUnreviewedDependenciesPackage and Hybrid executables extract the retained dependency tree with its relative layout before the embedded script runs, so $PSScriptRoot, $PSCommandPath, and nested literal dot-sources retain file-backed behavior. Strict executables instead compile the explicit entrypoint and its reachable contained dot-source closure into one runtime-free program. Every reachable unit must lower successfully; dynamic, escaping, missing, linked, or unrelated dependencies fail closed.
analyze and every successful build manifest include a detailed dependency/resource plan, while census aggregates the same evidence for each product. Detailed items record their stable kind, discovery source, selection reason, relative path, byte size, existence, and artifact disposition. The resource summary reports included, excluded, required, inferred, and unclassified file counts and sizes, so a successful-looking binary cannot silently lose files its source expected.
| Input dependency | BinaryModule output | Package EXE | Strict EXE | CLR Library |
|---|---|---|---|---|
Root source and literal .ps1 closure |
Eligible functions compile; Hybrid script remains beside the DLL | Root is embedded; reachable dot-sources are embedded and extracted with their relative layout | Complete reachable graph must compile | Eligible functions compile; unsupported functions are omitted in Hybrid |
FormatsToProcess, TypesToProcess, ScriptsToProcess, local RequiredAssemblies, local NestedModules, and FileList |
Contained manifest closure is copied with its relative layout; Strict rejects script runtime hooks | Module inputs are not executable entrypoints | Module inputs are not executable entrypoints | Non-script required files are copied beside the DLL |
Explicit IncludeResource / --include-resource |
Copied with its source-root-relative path | Embedded and extracted into a contained source-root-relative runtime layout | Copied beside the EXE | Copied beside the DLL |
High-confidence literal $PSScriptRoot file path |
Inferred and copied in Declared mode |
Inferred, embedded, and extracted in Declared mode |
Inferred and copied in Declared mode |
Inferred and copied in Declared mode |
| Optional module-root payload | Included only in CompleteModule mode or by an explicit declaration |
A single script never sweeps sibling content | A single script never sweeps sibling content | Included only in CompleteModule mode or by an explicit declaration |
RequiredModules and named external RequiredAssemblies |
Preserved as external host requirements; not embedded | Not resolved or bundled automatically | Not resolved or bundled automatically | Remain consumer requirements |
A generated artifact with selected payload is an artifact set, not necessarily one physical file. Binary modules, Strict EXEs, and CLR libraries copy selected CSS, JavaScript, images, managed assemblies, native libraries, templates, data, manifests, and type/format data beside the primary artifact with their relative paths intact. Package EXEs instead embed selected resources and extract them with the reachable dot-source closure into a private contained runtime layout. Exact inferred resource references are rewritten to that layout. An explicit include or CompleteModule selection gives the packaged script body an extracted $PSScriptRoot and $PSCommandPath, so declared dynamic paths such as Join-Path $PSScriptRoot $name resolve without leaving sidecars beside the durable EXE. Extraction uses a per-user build-identity cache that remains available after the parent process exits, so asynchronous children can continue consuming embedded scripts and resources. Parameter defaults retain EXE-backed path metadata even when the script body uses the extracted entry path. PowerForge signs only build-owned generated files; adjacent vendor assemblies retain their publisher identity. SingleFile includes Package-mode selected resources, while adjacent Strict/DLL payload remains a multi-file artifact set.
Resource selection is policy-driven:
Declaredis the default. It includes manifest-required files, explicit includes, and high-confidence contained file literals such asGet-Content "$PSScriptRoot/Templates/report.html".CompleteModuleincludes all contained module-root payload except explicit exclusions. Use it for a staged module directory, not an unchecked repository root.Nonedisables inference and broad optional selection; manifest-required files and explicit includes still apply.FileListis authoritative. A missing entry or an exclusion that matches a manifest-required file fails closed.IncludeResourceandExcludeResourceaccept contained paths, directories, and*,?, or**globs. An unmatched pattern, include/exclude collision, link, root escape, case collision, selected output overlap, or inaccessible explicitly selected directory fails closed with a diagnostic.CompleteModulelikewise requires every contained directory to be enumerable; only undeclared optional inventory is best effort.Resources,Resource,Lib,Libraries, andruntimesare classification hints only.Vendor,Templates,Web,Data, or any other folder works the same way.- Dynamic resource paths are left unclassified and require an explicit include. In a Package EXE, that declaration also selects extracted-root semantics for the script body; undeclared dynamic paths continue to refer to the durable artifact directory. A single
.ps1build never sweeps neighboring folders automatically.
For example:
powerforge powershell analyze .\MyModule `
--include-resource 'Templates/**' `
--include-resource 'Vendor' `
--exclude-resource 'Vendor/**/*.pdb'
Build-PowerShellArtifact -Path .\MyModule `
-ResourceMode Declared `
-IncludeResource 'Templates/**', 'Vendor' `
-ExcludeResource 'Vendor/**/*.pdb' `
-AllowUnreviewedDependenciesPowerForge does not turn a module into an EXE or infer an application entrypoint from exported functions. A standalone .ps1 that imports another module does not cause that module and its complete resource tree to be bundled; module dependency acquisition remains an explicit deployment concern.
Analyze a file or a complete source tree before building:
powerforge powershell analyze .\MyModule --mode HybridUse explain when you need a stable reason rather than the full build plan. Human output lists file-level blockers, missing-dependency causes, and typed, runtime-fallback, or rejected unit decisions with their causal diagnostics. JSON output uses relocation-safe unit identities and redacts machine-specific absolute paths:
powerforge powershell explain .\MyModule --mode Strict
powerforge powershell explain .\MyModule --mode Hybrid --output jsonThe explanation schema includes a compatibility version and semantic fingerprint. Explanation schema 6 uses semantic compatibility version 5 and records native function binding alongside directional parent-module state and region dependencies. Final artifact and CLI explanations use the immutable post-shaping ledger as their sole disposition authority. Ledger schema 5 identifies a native-bound CLR body with UsesNativeFunctionBinding: it requires the PowerShell host, but its authored body is not retained source and its export is a function rather than a generated cmdlet. The older public overload that accepts only an artifact kind and shaped compilation is obsolete, remains on schema 2/semantic compatibility 1, and rejects module-state or native-function methods whose evidence it cannot represent. The fingerprint excludes relocation coordinates and traversal order while preserving semantic order such as parameter position. Final artifact explanations include inferred types and value states, provider/dependency resolution, lowering choices, final fallback/rejection causes, and artifact disposition.
Within that Hybrid native-function contract, a computed member name may be read from an instance or static CLR target, and an instance computed-member target may be assigned. The bound and lowered nodes keep the receiver, name expression, static target, effects, and host requirements explicit; generated code evaluates receiver, then name, exactly once and delegates adapter, ETS, type-table, strict-mode, and error behavior to PowerShell's native dynamic member binder. Nested member/index shapes reuse the same native index owner. The assignment path compiles only the authored target AST while the RHS remains ordinary compiled IR, preserving PowerShell target evaluation and alias-visible mutation. This support does not make dynamic objects transferable or runtime-free: Strict artifacts, typed regions, null-conditional member access, computed method names, and unsupported target expressions continue to fail closed.
A complete multi-stage pipeline inside an eligible Hybrid native-function body uses one native command region even when every command stage is otherwise known to the compiler. The generated method continues before and after that region, while the active PowerShell invocation owns the exact authored pipeline: command and alias resolution, parameter binding, script-block scope, success/error/warning/information ordering, begin/process/end/clean scheduling, downstream stopping, cancellation, disposal, and mutation of referenced collections. A terminal explicit success-stream sink such as Out-Null remains retained under its existing fail-closed contract. This avoids partially reimplementing pipeline behavior in generated C# and keeps mutations visible to later generated statements. It does not make the pipeline runtime-free or transferable into a typed region; Strict analysis continues to reject the unsupported multi-stage pipeline.
Successful builds carry a portable statement-level failure map and a deterministic diagnostic audit trail. The audit records build-cache reasons, dependency-lock state, public-ABI state, fallback crossings, and selected provider contracts. To map a captured runtime failure back to authored source, provide the artifact manifest and local failure log:
powerforge powershell diagnose .\artifacts\Tool.powerforge-compilation.json `
--failure .\runtime-failure.logThe result is available as human output or --output json. It reports the compiler/runtime stage, stable reason, authored relative path, unit identity, source line/column, diagnostic code, and typed/hosted boundary. Absolute paths, authored source text, parser objects, environment state, and common secret assignments are removed from portable diagnostics.
Package a script as an executable:
powerforge powershell build .\Invoke-Report.ps1 `
--out .\artifacts `
--allow-unreviewed-dependencies `
--name Invoke-Report
.\artifacts\Invoke-Report.exe --Path C:\Reports --Format HtmlBuild a managed Hybrid executable when complete local functions are eligible but the entry script or dependencies still need hosted PowerShell semantics:
powerforge powershell build .\Invoke-MixedReport.ps1 `
--kind exe `
--mode Hybrid `
--allow-unreviewed-dependencies `
--out .\artifacts `
--name Invoke-MixedReportEligible functions become registered generated cmdlets in the packaged host. The entry script and unsupported units remain embedded source, and the manifest reports the typed, hosted, fallback, and static crossing-site counts. This is a managed/current-host delivery foundation, not a claim that Hybrid is NativeAOT-ready or that any cross-published RID is supported.
Compile an eligible top-level script into a PowerShell-free executable:
powerforge powershell build .\Measure-Threshold.ps1 `
--kind exe `
--mode Strict `
--allow-unreviewed-dependencies `
--out .\artifacts `
--name Measure-ThresholdStrict typed executables accept process-bindable scalar, enum, nullable, URI/version, date/time, GUID, switch, and one-dimensional array parameters. Their generated CLI preserves required parameters, aliases, explicit or source-order positions, AllowNull, AllowEmptyString, AllowEmptyCollection, and the supported ValidateNotNull, ValidateNotNullOrEmpty, ValidateSet, ValidateRange, and ValidatePattern metadata. It supports exact names, aliases, unambiguous abbreviations, positional values, repeated array options, --Name value, --Name=value, switches, and --. Missing, duplicate, ambiguous, unknown, or validation-failing parameters are rejected before invoking compiled code. PowerShell parameter sets, pipeline binding, host-only types, and discovery-only metadata are rejected on this runtime-independent surface rather than silently ignored.
Generated binary cmdlets apply PowerShell script-function invariant-culture conversion to numeric, date/time, and duration parameters before CLR property binding. This keeps accepted and rejected input independent of the caller's current culture, matching the authored advanced function rather than compiled-cmdlet culture defaults.
The generated host accepts positional arguments, --Name value, --Name=value, switches and aliases such as --Force, common switches on advanced scripts, and -- to stop named-argument parsing. A non-switch named parameter must have a value; use --Name=-value when that value begins with -. Duplicate aliases and aliases that collide with an authored or automatic parameter name are rejected before host generation, matching PowerShell's metadata boundary. Pipeline objects use PowerShell's normal formatting system before going to stdout; information and warning records also go to stdout, while errors go to stderr. Nonterminating error records do not by themselves change a successful process exit code; a top-level explicit exit <code> becomes the process exit code, and a terminating exception fails the process. $PSCommandPath normally resolves to the running artifact path and $PSScriptRoot to its durable directory. A Package build with explicit or complete-module resources instead resolves the script body's $PSCommandPath and $PSScriptRoot to the private extracted entry and root; parameter-binding path metadata remains artifact-backed. Packaging rejects exit inside a function, nested script block, trap, or caught region because exception instrumentation would change PowerShell behavior. It also rejects using module and using assembly because those directives are resolved before an embedded script can receive file-backed path metadata.
Compile a strict binary module:
powerforge powershell build .\MathTools.psm1 `
--kind dll `
--mode Strict `
--framework net10.0 `
--allow-unreviewed-dependencies `
--out .\artifacts
Import-Module .\artifacts\MathTools.dllWhen MathTools.psd1 exists beside MathTools.psm1, the primary artifact is a rewritten manifest in a module directory. RootModule, FunctionsToExport, and CmdletsToExport are remapped so a function that became a binary cmdlet keeps the same public name. Literal top-level Export-ModuleMember declarations are preserved across typed and fallback commands; dynamic export expressions are rejected. An omitted AliasesToExport entry stays omitted so aliases created by retained module source continue to follow PowerShell's default manifest policy.
Build a hybrid module when only part of the source is eligible. The kind and mode are inferred from the module input:
powerforge powershell build .\Operations `
--allow-unreviewed-dependencies `
--out .\artifacts
Import-Module .\artifacts\Operations\Operations.psm1Build a runtime-independent CLR library containing every eligible function:
powerforge powershell build .\Calculations.psm1 `
--kind library `
--mode Hybrid `
--allow-unreviewed-dependencies `
--out .\artifactsAdd --output json to either analyze or build for a stable machine-readable envelope. Analyzer diagnostics include a stable featureId, while dependencies explains what the selected artifact shape will compile, preserve, copy, embed, leave external, or reject.
Analyze, explain, and build consume the same normalized target contract. Compatibility options such as --framework, --rid, --self-contained, --optimization, and --no-single-file construct it; --target-contract .\target.json instead supplies an explicit integrity-checked contract to all three commands. A stored schema-v1 or schema-v2 contract is first verified against its declared support level and original hash rules, then migrated to schema v2, reclassified against the current support policy, and rehashed; a support-policy promotion or demotion therefore does not make an otherwise authentic stored request unreadable. NativeAOT planning resolves the runtime-pack version owned by the selected SDK and rejects a missing exact pack instead of silently choosing a newer ambient same-major pack. Successful artifacts emit target-contract, toolchain, dependency-lock, SBOM/provenance, and file-hash evidence. The CLI and cmdlet use a verified content-addressed build cache by default; use --cache-directory <path> / -BuildCacheDirectory to select its owner or --no-build-cache / -UseBuildCache:$false for a deliberately uncached build. Cache keys include the normalized restore graph, actual resolved package bytes, selected SDK/reference-pack bytes, target, reviewed graph lock, compiler identity, build host, and generated inputs. Restore verifies a copied payload before atomic promotion; unsafe reparse-point roots/ancestors and malformed or changed entries are misses, never trusted hits.
The cmdlet is a thin PowerShell surface over the same artifact builder:
Build-PowerShellArtifact `
-Path .\Operations `
-AllowUnreviewedDependencies `
-EmitSourceIt supports -WhatIf, returns PowerShellCompilationBuildResult, and uses the same discovery, defaults, overrides, and manifests as the CLI. -Kind, -Mode, -Name, and -OutputDirectory remain available when the inferred values are not the desired artifact.
Build-Module can compile any staged script module into its delivered binary-module shape. The source tree remains script-first; compilation happens after merge, manifest, formatting, and resource preparation, then the normal signing, documentation, validation, test, package, publish, and install phases consume the generated module.
Build-Module -ModuleName 'Any.Module' -Path $repositoryRoot -Settings {
New-ConfigurationBuild `
-Enable `
-CompilePowerShell `
-PowerShellCompilationMode Hybrid `
-PowerShellCompilationAllowUnreviewedDependencies
}Hybrid is the migration default: eligible functions become binary cmdlets and unsupported behavior remains explicit script fallback. Strict fails unless every executable unit can be emitted without fallback. ModulePipelineResult.PowerShellCompilationResult reports analyzed and emitted units, semantic and shaping fallback, runtime-routed units, omissions, exact coverage percentage, and the staged assembly path. These counts come from one final unit-disposition ledger, so a typed CLR unit may also be runtime-routed when it contains a bounded hosted command region. A generated cmdlet that hosts an advanced-function lifecycle is recorded as a binary surface with retained hosted semantics, not as typed CLR emission.
Release builds should pass a separately reviewed dependency graph through -PowerShellCompilationDependencyLock. -PowerShellCompilationAllowUnreviewedDependencies is the explicit local/development opt-out shown above. Resource inclusion remains declarative through -PowerShellCompilationResourceMode, -PowerShellCompilationIncludeResource, and -PowerShellCompilationExcludeResource; no module name or folder convention changes compiler behavior.
Use -PowerShellCompilationEmitIrSnapshots when a reviewed build needs a diffable semantic-only bound/lowered IR file. Use -PowerShellCompilationExpectedPublicAbiSha256 <sha256> with Strict compilation to fail closed when the generated public ABI differs from a reviewed baseline. The same controls are -EmitIrSnapshots / -ExpectedPublicAbiSha256 on Build-PowerShellArtifact and --emit-ir / --expected-abi-sha256 on the CLI.
Build-Module produces a module, so this opt-in deliberately produces a DLL-backed binary module. A standalone application has a different entrypoint and deployment contract; use Build-PowerShellArtifact -Kind Executable (or the equivalent CLI command) with an explicit .ps1 entry point when several scripts participate.
Analyze parses source and reports one decision per top-level script body or function. It produces no artifact.
Package preserves dynamic PowerShell behavior. The current executable lane embeds the source script and PowerShell SDK in a generated .NET host. It is a distribution feature, not typed compilation.
Hybrid compiles complete eligible functions and retains diagnostics for everything else. A hybrid binary module removes compiled function definitions from its generated .psm1, imports the typed DLL, and keeps unsupported functions on the script path. Literal $PSScriptRoot dot-source dependencies are staged recursively with their relative layout, including dependencies reached from manifest runtime hooks. Dynamic, missing, wildcard, working-directory-relative, source-root-escaping, or symbolic-link/junction paths fail before publication. A hybrid CLR library extracts eligible methods without carrying script fallback because it is intended for direct .NET consumption.
Binary modules can also compile typed control flow around deliberately bounded PowerShell command regions. Direct success, verbose, debug, warning, information, host, and nonterminating-error calls use generated PSCmdlet stream APIs with their real command-specific value parameter names. Write-Host remains a distinct host sink and emits a HostInformationMessage in an information record tagged PSHOST; it is not flattened into ordinary Write-Information. Their parameter contract preserves parameter sets, mandatory/position flags, pipeline and property-name binding, remaining arguments, literal help, hidden parameters, empty-value markers, wildcard discovery, and a conservative set of PowerShell-host types. An implicit end body with pipeline-bound parameters is emitted through EndProcessing, so it runs once with the final bound value just as the authored advanced function does. PowerShell 7 Hybrid modules can additionally host canonical begin, per-record process, end, and PowerShell 7.3+ clean lifecycle blocks through one steppable pipeline; the original record is preserved and StopProcessing() stays prompt. Authored cleanup is idempotent across normal, failure, stop, and disposal paths while the owning runspace remains usable. A terminal closed or broken runspace instead receives idempotent pipeline disposal without claiming that the authored clean block executed. Adjacent top-level command pipelines, parameter-only conditional command blocks, explicit discard assignments such as $null = Invoke-Operation, and a safe terminal command-result tail are grouped into one PowerShell invocation so binding and dispatch are amortized across the region. Parameters and typed locals created before the region are passed explicitly. An explicit typed assignment such as [string[]] $items = Invoke-Operation can capture success output from one bounded region and resume typed execution: zero outputs become null, one output becomes a scalar, multiple outputs become an array, and PowerShell's public conversion primitive applies the declared target type. Untyped targets, redirections, unresolved state, and nested local-function dispatch remain fallback rather than guessing at write-back semantics. Stream, command-region, and capture-host requirements propagate through eligible local function graphs, so typed callers do not lose a callee's PowerShell host contract. A terminal tail cannot write back to a CLR parameter or local, and a nested script block that captures unresolved module or dynamic scope is not eligible. The complete function stays on the Hybrid script path when either boundary cannot be preserved. The module-scoped dispatcher is cleared when the hybrid module is removed. Runtime-free CLR libraries and Strict typed EXEs never enable these PowerShell-backed regions.
Within a retained Hybrid function, more than one statement-aligned typed region can run around hosted PowerShell when canonical inputs, outputs, effects, errors, ordering, stopping, and source identity are closed. Every boundary transfer carries a schema-versioned contract for value shape, element kind, direction, ownership/provenance, output enumeration, and mutation. The closed policy admits stable scalars, exact atomic System.Collections.Hashtable and System.Collections.Specialized.OrderedDictionary references, one-dimensional stable-scalar arrays, and read-only exact System.Array, System.Collections.ArrayList, and System.Collections.Generic.List<T> references. A fresh region-owned collection can survive retained mutation and later enter another read-only region; authored comma output is preserved as no-enumerate output. The rewriter and runtime recheck origin, name, exact CLR type, and local storage before invoking each helper; otherwise the authored statements execute. If the target runspace cannot reconstruct an optimized retained function, the authored declaration remains installed. Direct System.Object and ETS/dynamic transfers, arbitrary enumerators, statically concrete multidimensional or object-element arrays, compiled collection mutation, and a second state owner remain hosted or fail closed. Exact System.Array and List<object> references are opaque to compiled code, with their elements observed only by the retained PowerShell boundary.
Only unconditional top-level literal dot-sourced files participate in the root module's typed source set. Conditional and function-local dot-sources plus manifest runtime hooks are still discovered and staged, but are counted as runtime fallback rather than being flattened into a different scope. Nested script modules and nested manifests keep their relative layout, manifest closure, and export policy.
Strict fails the build when any executable unit needs fallback. For an executable it compiles the entry script and its reachable literal dot-source closure into a .NET entrypoint plus direct static helper methods, with no PowerShell SDK dependency. Multi-file input requires an explicit entrypoint; dynamic calls, external commands, uncontracted recursion, ambiguous binding, and unreachable requested files fail instead of silently selecting a runtime path. For a DLL, Strict guarantees that the artifact contains only behavior covered by the typed compiler contract.
Eligibility is whole-function and intentionally conservative. One unsupported construct keeps the complete function on the PowerShell path.
The current subset supports:
- capability-classified CLR, PowerShell-host, process-bindable,
SwitchParameter, nullable, enum, and one-dimensional typed-array parameters, including target-typed literal defaults and exact static enum-member defaults with optional expression parentheses, plus untyped parameters preserved asSystem.Objectonly by generated binary-module hosts with the explicitUntypedObjectParameterscapability; - preserved function and parameter aliases plus parameter-set, position, pipeline, remaining-argument, literal-help, empty-value, wildcard, and validation metadata on hosts that implement those contracts;
- bounded
$PSBoundParameters.ContainsKey('CanonicalParameterName')queries, including metadata propagation across typed local calls and runtime-free Strict executable argument binding; - target-backed
$PSEdition,$PSVersionTable.PSVersion.Major, and PowerShell Core$IsCoreCLR,$IsWindows,$IsLinux, and$IsMacOSfacts in runtime-free artifacts; the selected semantic profile fixes the version-major value, while the complete live$PSVersionTable.PSVersionobject remains host-bound; read-only$env:NAMEaccess lowers to CLR environment lookup; generated binary cmdlets can additionally inject the live$PSVersionTable.PSVersion, exact read-only$ExecutionContext.SessionState.LanguageMode,$WhatIfPreference, supported action/confirm preferences, one read-only$Errorsnapshot, and one- or two-argument$PSCmdlet.ShouldProcess(...)contracts, including explicitly bound common-parameter overrides and the host-specific Windows PowerShell 5.1 versus PowerShell 7-Debugpreference behavior; - typed or safely inferred local variables, including a
$nullseed followed only by one exact target-compatible reference type; optional branches, loops, switch/try paths, and lifecycleprocessblocks merge null state conservatively, while mixed, unknown, value-type, and type-varying nullLengthflows remain hosted or rejected; - explicit
returnvalues and one terminal implicit-output expression; if/elseif/else, conservative scalarswitch,for, pre-testwhile, post-testdo/whileanddo/until, unlabeledbreak/continue,foreachover typed arrays or an explicitly typed scalar string, plus generated-binary-moduleforeachover an authored[array]/System.Arraywith object-valued items; Hybrid native functions also support directforeachover an untyped or[object]parameter or a map's.Keyscollection through the active PowerShell enumerator and invocation-owned loop variable; ordered CLR exception catches with boundedfinallyblocks, typed CLR exception throws, and bare rethrow inside a supported catch; labeled loop control remains hosted or rejected;- Boolean logic and scalar comparisons with known compatible types, including exact left-directed widening of a right integral operand to the left integral type; nullable integral and decimal equality use lifted CLR equality, while nullable integral and decimal relational comparisons use PowerShell's sign-sensitive null ordering with single evaluation of each operand; nullable floating equality and ordering, precision-changing promotion, narrowing, nullable-right widening, and otherwise dynamic coercion remain rejected;
- string equality with PowerShell case-sensitive or case-insensitive behavior;
- scalar type tests, regex match/replace, wildcard match, membership, and inclusive range operators; generated binary modules use the loaded PowerShell host for regex matching and range construction so
$Matches, scalar-versus-collection results, case/negation, endpoint conversion/overflow, character behavior, errors, and stopping retain their host semantics; - scalar string
-splitand string-array-join; - integral bitwise and shift operators with PowerShell-compatible small-integer promotion, plus target-compatible explicit conversions that use a compile-time literal when possible and PowerShell's public conversion primitive only in a PowerShell-backed host; a standalone single-element
[void] expressionstatement evaluates one otherwise supported typed operand exactly once and suppresses its success output through the same backend discard owner as$null = expression; - expandable strings containing statically typed string variables, with null strings rendered as empty text;
- case-insensitive homogeneous string dictionaries created from ordinary or
[ordered]string hashtable literals, including lookup and simple index assignment; generated binary modules additionally preserve bounded heterogeneousHashtable/OrderedDictionaryvalues plus adaptedIDictionarymember lookup and assignment. Qualified Hybrid-native map literals use the selected host's key comparer and can produce atomic success records while native storage preserves borrowed map identity; this does not admit general runtime-free dictionary escape or arbitrary nested collection output; - conservative
CmdletBindingpositional/default-set/ShouldProcess metadata and parameter binding metadata, with host-only behavior enabled only for generated binary cmdlets; exactSystem.Diagnostics.CodeAnalysis.SuppressMessageAttributedeclarations with two literal constructor strings and literal known named properties are validated once as compile-time-only metadata and omitted from the runtime ABI; - floating-point and decimal arithmetic with compatible operands;
- explicitly typed integral accumulators and loop counters with checked assignment semantics;
- unlabeled
breakandcontinueinside supported loops; - untyped array literals and nonempty
@(...)expressions that preserve PowerShell's observableobject[]type, explicitly typed one-dimensional array assignments including context-typed empty@(), typed-array concatenation with scalar/array and null operands, and typed-array,IList/ArrayList, or string indexing with PowerShell-compatible negative and missing-index behavior; - simple indexed assignment to one-dimensional typed arrays, lists, and dictionaries, including negative index normalization, plus simple
=assignment to one public, unambiguous, target-compatible writable CLR property or field on a typed local, parameter, or static type; - statically resolved CLR constructors, static fields/properties, instance fields/properties, and exact method overloads for supported typed arguments, including defined enum names supplied as string literals and standalone target-compatible type literals emitted as
System.Type/typeof(...); unresolved or target-missing literals fail closed. A Hybrid native function may resolve an authored target-incompatible conversion type in the active PowerShell host and apply PowerShell's conversion binder, but Strict/runtime-free targets remain closed. Direct or hoisted authored type literals consumed by an invocation inside an observed catch remain hosted until method-invocation wrapper identity is closed; - genuine binary-module
PSObjectvalues constructed from bounded[pscustomobject]@{ Name = Value }literals with one statically known note-property shape, direct known-property reads/writes, exactPSObject.Properties['Name'].Valueaccess, and exactAdd-Member -NotePropertyName/-NotePropertyValuemutation; arbitrary ETS and identity-observing methods remain fallback; - direct local function graphs across Strict executables and generated binary modules, including implicit source-order positions; explicit
[Parameter(Position = n)]positions in ascending numeric order; named, alias, unique-abbreviation, switch, mandatory, omitted-default, and supported validation-metadata binding. When any explicit position exists, parameters without one are named-only, sparse position numbers do not create argument gaps, and explicit positions remain active underCmdletBinding(PositionalBinding = $false). Named parameter sets and ambiguous or invalid metadata remain fallback or rejection. A direct self-recursive function additionally requires one target-compatible[OutputType]contract that matches its inferred body type, while mutual or otherwise uncontracted cycles stay on fallback. Calls into a local function that invokesShouldProcessalso stay on the PowerShell command path so the inner command identity andConfirmImpactare not replaced by the outer generated cmdlet; - PowerShell stream calls and bounded top-level command/pipeline regions that can capture parameters and typed locals, return success output into an explicit typed assignment, or own a safe terminal dynamic tail when generating a binary module.
Generated binary modules also have a bounded hosted-Boolean command-expression contract. The first registered shape is scalar Test-Path in an advanced function: one -LiteralPath/-PSPath value whose expression is proven to begin with FileSystem::, mandatory literal -ErrorAction Ignore, optional literal -PathType Any, Container, or Leaf, and optional -IsValid. Binding produces a typed Boolean node, lowering remains independent of the PowerShell AST, and the backend routes constant command text plus bound values through the existing command-capture delegate. The canonical Microsoft.PowerShell.Management\Test-Path command retains the current host's FileSystem-provider, literal-path, path-type, and version behavior inside that deliberately error-neutralized slice. Unqualified paths, -Path wildcard semantics, other provider drives, and local provider state stay hosted because compiled CLR locals are not PowerShell session state and the capture delegate does not reproduce every nested error-stream observation. The PowerShell 7-only -LP alias, basic functions, omitted or wider error policy, string arrays, coercive or untyped paths, dynamic path types, redirection, wider parameters, and runtime-free Strict artifacts likewise remain fallback or rejection. Generic expression traversal now propagates nested hosted providers, boundaries, runtime-state arguments, stream-message arguments, local calls, and $PSBoundParameters requirements to generated method signatures and manifests. This is a hosted Hybrid capability, not a translation to System.IO and not a claim of native filesystem independence.
The compiler-owned runtime-state command family now includes exactly no-argument Get-Date, including its canonical Microsoft.PowerShell.Utility qualification. The command binds to the existing runtime-state IR as a scalar System.DateTime, lowers to System.DateTime.Now, requires no PowerShell host capability, and records its provider contract in artifact manifests and dependency identity. Parameters, positional arguments, formatting, redirection, splatting, aliases, and invocation operators remain hosted or rejected; external provider packages and direct provider extensions cannot register this compiler-owned family. Strict libraries execute the contract on net472 and net10, a win-x64 Strict NativeAOT executable publishes and runs without PowerShell, and generated net472/Windows PowerShell 5.1 and net10/PowerShell 7.6 modules preserve the System.DateTime/Local result contract. The fixed external packet no longer reports its former seven command.get-date occurrences across three units, but independent blockers keep emission at 5/196 units and 5/183 functions with zero regressions. This is a measured semantic gain, not an emitted-unit or broad command-coverage claim.
Command identity has one compiler owner. The provider registry is only the deterministic catalog; the semantic resolver applies local-function precedence and target-host disposition once, and inference, binding, command islands, analysis/explain output, lowering evidence, Hybrid, and Strict consume that decision. In a generated PowerShell host, a provider rewrite requires the source to use the provider's canonical Module\Command identity. An unqualified name continues through normal PowerShell command resolution so local functions, required/imported modules, aliases, and caller session state can retain their authored precedence; Hybrid keeps the required hosted or source fallback, while Strict rejects a required fallback. Module-qualified aliases also remain runtime-resolved. Closed runtime-free library and executable targets may bind an unqualified registered provider after proving that no authored local function shadows it because those artifacts have no PowerShell command session to preserve. External providers intended for binary-module emission therefore need a canonical module name in their reviewed contract. The built-in catalog records Where-Object and ForEach-Object under Microsoft.PowerShell.Core, and Select-Object, Sort-Object, stream commands, Get-Date, New-Object, and Add-Member under Microsoft.PowerShell.Utility.
Applying that precedence rule deliberately rebaselined the fixed external assessment from 9 to 5 emitted units and functions: Locksmith2 moved from four emitted functions to one, and the Scoop installer moved from two to one. The removed credit came from unqualified host commands that the earlier registry-first path could rewrite without proving their resolved command identity. The lower 5/196-unit and 5/183-function baseline is therefore the safe expansion floor, not a language regression or a target to recover by relaxing resolution. The separate execution qualification still proves 4/4 selected emitted commands across four unrelated workloads, while the public acceptance packet remains 10/10 Hybrid modules and 4/4 Strict programs.
Member compilation is intentionally exact. The semantic binder resolves a single CLR member or overload, applies only supported assignable/numeric/one-character conversions, and falls back when resolution is missing or ambiguous. Both the type and selected constructor, method, property, or field must exist in the requested target framework's reference assemblies; analyzer-host-only APIs and general constructed generic types are rejected before lowering. Static assignment accepts only simple = against one public static property with a public getter and setter or one public non-literal, non-readonly static field. Compound assignment, dynamic or nonlocal receivers, ambiguous targets, and property assignment whose failure would be observed by a RuntimeException catch remain hosted or rejected. Dictionary literals use BCL representations in runtime-free artifacts: homogeneous string indexing retains a scalar string contract and heterogeneous values remain object-valued. A statically typed IDictionary dot-member read stays object-valued and performs dynamic key-first lookup through that dictionary's comparer, followed by a statically resolved CLR-member fallback. This is not general Extended Type System support or a promise that a mutable dictionary value keeps its literal type. Generated binary modules additionally model adapted dictionary writes through their hosted PowerShell capability. Null typed arrays preserve PowerShell's zero-length .Length behavior, and a nullable inferred string's property access uses PowerShell's empty-string property semantics while method invocation retains CLR null failure behavior.
A CLR callback member can receive a capture-free constant-Boolean script block through the same member-assignment pipeline. The target must be one exact, closed, target-compatible delegate returning Boolean. The script block may omit parameters or declare exactly the delegate arity with simple untyped, unqualified, writable names, and its entire undecorated body must be $true, $false, return $true, or return $false. The generated lambda remains target-typed so framework nullable annotations survive warning-as-error trim and NativeAOT builds. Computed results, captures, typed/defaulted/scoped/read-only parameters, extra statements, redirection, background execution, non-Boolean delegates, by-reference signatures, and lifecycle blocks remain Hybrid fallback or Strict rejection. Member verification uses structural generic signatures and proves target-framework write access separately from read access. This supports generic callback properties such as HttpClientHandler.ServerCertificateCustomValidationCallback without treating general script blocks or closures as compiled delegates.
Exact System.Diagnostics.CodeAnalysis.SuppressMessageAttribute declarations are compile-time-only compiler input. One shared policy accepts the real framework attribute only on a script or function parameter block, requires exactly two literal string constructor arguments, permits only literal Justification, MessageId, Scope, and Target properties, and then deliberately omits the attribute from the runtime ABI. Dynamic or malformed arguments, unknown or repeated properties, parameter-level placement, and unresolved lookalikes remain hosted or rejected. The fixed external packet drops from 45 to 34 parameter.metadata occurrences and from 38 to 31 affected units while remaining at 5/196 emitted units and 5/183 emitted functions with zero regressions; this is semantic cleanup, not a coverage increase.
Standalone [void] expression now has one statement-discard contract instead of being treated as a general conversion to System.Void. It is accepted only as a single-element statement pipeline whose operand already binds to the typed IR; the binder removes the conversion while preserving operand evaluation, lowering records that the result is discarded, and the backend uses one collision-free generic helper shared with value-returning $null = expression. Return, assignment, expression-container, multi-stage pipeline, and unsupported-operand forms remain hosted or rejected. Real net472/net10 libraries prove bare-value suppression and side-effecting member invocation, while the augmented conversion oracle passes Windows PowerShell 5.1, the pinned PowerShell 7.6 host, and the complete 31-case/93-observation matrix. The fixed external packet removes one of six expression.conversion occurrences and one of six affected units while staying at 5/196 emitted units and 5/183 emitted functions with zero regressions.
Parameter defaults now accept an exact static enum member when the authored static type is the parameter's enum type, including a nullable enum target and otherwise transparent expression parentheses. Resolution is case-insensitive like PowerShell but must identify exactly one public literal member in the selected target framework's reference metadata, with the target member's invariant numeric value becoming the portable literal ABI. A member that exists only in the compiler's newer host framework therefore fails closed for an older target. Mismatched enum types, missing members, non-enum static properties, method calls, and wider runtime expressions remain hosted or rejected. Real generated binary-cmdlet defaults preserve omitted-versus-explicitly-bound behavior, net472/net10 Strict libraries preserve the same literal ABI without PowerShell, and the augmented parameter-default oracle passes Windows PowerShell 5.1, pinned PowerShell 7.6, and the complete 31-case/93-observation matrix. The fixed external packet drops from 8 to 6 parameter.default occurrences, from 7 to 5 affected units, and from 4 to 3 affected workloads while remaining at 5/196 emitted units and 5/183 emitted functions with zero regressions.
An authored [array]/System.Array parameter may also use a compile-time-stable scalar default such as 'All'. The compiler records the resulting one-element object[] in the same portable literal contract used by other defaults and preserves omitted versus explicitly bound values. As in PowerShell, validation attributes apply to explicitly supplied elements, not to the default expression selected because the argument was omitted. Authored collection expressions, dynamic values, nested arrays, non-encodable elements, or authored conversions that would retain a different runtime type remain hosted or rejected. General [array] conversion expressions also remain outside this bounded parameter-default contract. Numeric spellings known to be unavailable to the selected semantic profile are a file-level parse failure in Package, Hybrid, and Strict: the compiler never packages source fallback that its target host cannot parse. This target-syntax gate currently covers the versioned numeric forms proved by this slice; it is not a claim that the modern parser emulates every historical host grammar difference. The fixed packet remains at 5/196 emitted units and 5/183 functions, and its post-emission function frontier still reports two unrelated parameter.default occurrences; no corpus emission gain is claimed for this slice.
Generated binary cmdlets now bind the exact read-only $ExecutionContext.SessionState.LanguageMode chain through the existing runtime-state IR and host dictionary. The value is captured at cmdlet invocation time as the live PSLanguageMode; it is not inferred from a semantic-profile label or embedded as build-time state. Assignment, a locally defined $ExecutionContext, other execution-context/session-state members, and Strict runtime-free targets remain hosted or rejected. In the earlier host review, real net472/Windows PowerShell 5.1, net8/then-pinned PowerShell 7.4.19, and net10/then-pinned PowerShell 7.6.5 modules matched the native host, while the expanded interpreted oracle contains 31 cases and 93 exact-host observations. In the fixed external packet this clears one runtime.scope occurrence and affected unit, reducing visible sole blockers from 5 to 4, but exposes a separate enum/string comparison blocker; emission therefore remains 5/196 units and 5/183 functions with zero regressions.
Hybrid native-bound functions support -as through the active PowerShell host, including expressions such as $size / 1GB -as [int] and 'Example.Type' -as [type]. Both operands execute once in authored order. Failed value conversions return null; invalid destination types and operand failures retain PowerShell error behavior. Native scalar/vector locals keep their PowerShell type constraints. These operations require the host and remain outside Strict compilation. The assessment records the qualified workflows and host versions.
The analyzer rejects dynamic behavior rather than guessing. Current blockers include:
- commands and pipelines outside the bounded binary-module region contract, including nested closures over unresolved runtime variables;
- dynamic member names, unbounded PowerShell-adapted properties, ambiguous overloads, and general object-property semantics;
- script blocks, unproven closures, mutable script/global/private/variable-provider scope, environment-provider mutation, and untyped parameters outside the explicit binary-module object-host contract;
- automatic or preference variables outside the explicit read-only intrinsic set, arbitrary
$PSVersionTablekeys, and$PSCmdletinteractions other than the boundedShouldProcessoverloads; - dynamic or host-incompatible parameter attributes, PowerShell default expressions,
dynamicparam, and lifecycle blocks outside the explicitly supported PowerShell 7 Hybrid/version matrix; - dynamic
$PSBoundParametersaccess, noncanonical or computed keys, dynamic/string throw operands, and[pscustomobject]construction outside generated binary modules; - nonterminal implicit pipeline output and nested output that is not proven to be the final result of its enclosing branch;
- PowerShell truthiness outside a hosted target, element-wise array comparison, and coercion between incompatible CLR types without an explicit target capability;
- string relational operators whose culture-aware ordering has not yet been translated;
- conversion expressions whose target is unavailable or whose dynamic semantics require a host capability not present in the artifact, heterogeneous branch return types, and integral division whose PowerShell result type depends on the quotient;
- untyped integral arithmetic that can change CLR type after overflow;
- array concatenation and compound-assignment operand pairs that have no exact static CLR operator;
- source
#requiresdirectives and runtime-bearingusing module/using assemblystatements, which keep the complete source file on the PowerShell runtime path rather than being silently omitted; - control flow for which binding and analysis cannot prove declaration, output, or return behavior.
This boundary is expected to expand through semantic proof, not syntax count. New constructs need differential tests against PowerShell before they become eligible.
Strict executable compilation is deliberately all-or-nothing. The root script is Main, its top-level param() block is the application argument contract, and every reachable statement and local function in its contained literal dot-source closure must have an equivalent typed lowering. One unsupported command, dynamic lookup, closure, coercion, or control-flow shape rejects the build rather than quietly placing PowerShell back into a supposedly runtime-free executable.
That means an arbitrary existing automation script is unlikely to qualify for Strict today. The refreshed exact-pinned real-product matrix later in this document currently emits between 4.37% and 21.63% of whole functions in Hybrid modules. A small purpose-built CLI can qualify completely because its entrypoint and helper graph can be designed around the supported subset; a command-heavy administration product usually cannot. Coverage should therefore be read as three separate outcomes:
- Strict program coverage: the complete reachable application graph is eligible, so a PowerShell-free EXE can be produced;
- Hybrid function coverage: complete eligible functions become generated cmdlets while other functions remain scripts;
- Hybrid region coverage: typed code can surround bounded PowerShell command regions so fewer, coarser runtime dispatches are needed.
PowerForge does not aim to reimplement the complete PowerShell language and runtime. Dynamic scope, providers, remoting, arbitrary command discovery, ETS adaptation, host interaction, and every coercion rule would effectively require another PowerShell engine. The useful goal is a well-specified typed subset that grows according to real-product impact, while unsupported behavior remains explicit and correct.
A managed Hybrid executable is now implemented as that bridge: it packages the hosted runtime/source closure, registers eligible local functions as generated cmdlets from the typed assembly, and routes the retained entry script through the same package host. Its manifest records that runtime evaluation is allowed, identifies embedded source and dependency closure, and exposes static boundary counts. The benchmark profiler measures actual typed/hosted crossings, reports nanoseconds per crossing and the estimated share of fine-boundary runtime attributable to crossing overhead, and emits a coarsening advisory. NativeAOT-hosted Hybrid delivery and named-RID promotion remain open; the artifact must never be presented as Strict compilation.
The implementation sequence, ownership rules, migration gates, and active checklists are maintained in the PowerShell Compilation Architecture Roadmap.
The current implementation is a semantic pipeline rather than a text replacement engine:
- input discovery resolves the root script or module and its contained authored dependency closure without executing it;
- the PowerShell parser produces syntax plus neutral source documents and spans;
- the semantic binder creates immutable symbols, scopes, functions, statements, expressions, type facts, value state, effects, capabilities, and fail-closed diagnostics;
- deterministic analysis passes compute definite assignment, output type/cardinality, call graphs, recursion, effects, capabilities, and fallback through fixed points;
- lowering selects typed CLR operations, generated-cmdlet operations, bounded hosted regions, and target-specific runtime primitives;
- the C# backend renders lowered nodes only; it has no PowerShell AST reference and performs no semantic inference;
- graph and binary-cmdlet shaping consume the same semantic result before the artifact builder compiles, optionally signs, records hashes/source maps/ABI evidence, and atomically publishes the artifact set.
Every bound node carries:
- its resolved CLR type and PowerShell-specific conversion rule;
- its source extent for diagnostics and generated-source mapping;
- observable effects such as pipeline output, stream use, mutation, exception flow, or PowerShell runtime dispatch;
- required target capabilities, for example pure CLR, cmdlet host, bound-parameter state, PowerShell objects, or a command region;
- an explicit fallback reason when the semantic contract cannot be proven.
With that boundary, a feature is bound once and backends consume the same proven semantic model. Strict EXEs accept only pure-CLR nodes; binary modules may admit cmdlet-host nodes; Hybrid artifacts may additionally admit bounded runtime regions. The former direct AST-to-C# emitter and its partial implementations were deleted after the existing behavior migrated; there is no compatibility switch that can silently bypass the IR.
Canonical command and pipeline semantics have one implementation route within the built-in bounded provider contract. Milestones 14–18 are complete within their documented profiles. Provider ABI 5 loads independently built, exact-lock executable adapters through the same registry, binder, IR, lowering, and generated-host contracts in Strict managed, Strict NativeAOT, Hybrid, and binary-module builds, and reconciles publisher/license/signature claims with canonical package metadata and signer policy. ABI 5 also adds deadline-bound process isolation for closed scalar string operations in generated Strict executables through one-use inherited pipe capabilities and entrypoint-reachable dispatch. Useful directory, management, native/process, and COM provider ecosystems remain separate Milestone 19 qualifications. Named semantic profiles govern target identity, binding, lowering, compatible #requires, provider selection, cache identity, package variants, artifacts, and diagnostics. Oracle schema 3 records the exact executable hash/version/length, runtime/build/release identity, OS, architecture, culture, feature switches, bounded ordered typed/null/cardinality output, streams/errors, encoding, filesystem effects, final LASTEXITCODE state, and directly observed ordered child-process launch/exit effects; replay and promotion validate structural/profile invariants against an immutable reviewed catalog for Windows PowerShell 5.1.26100.9168, PowerShell 7.4.20, and PowerShell 7.6.6, while a read-only scheduled lane proposes affected reviews for newer patch tags without advancing pins. The compiler-owned Windows boundary attaches the isolated host to a Job Object completion port, waits for its ready signal within the request deadline, reconciles all launch packets with authoritative cumulative Job Object process accounting, timestamps each launch against the authored-source gate, and only then releases authored source. Queue barriers replace timing delays; missing, surplus, recycled, late, or unbounded launch evidence fails closed. Direct-child launches and exits use stable per-observation invocation ordinals without persisting volatile process IDs, and Job containment terminates any tree still active when the observation boundary closes. Final LASTEXITCODE remains separate state, never a substitute for history. Thirty-one native minimized cases cover every currently promoted family. The main-branch servicing review executed all 31 cases on the newly pinned PowerShell 7.6.6 host; the Windows PowerShell 5.1 pin and its earlier case evidence are unchanged. A 24-case promoted runtime-free subset covers compatible #requires, profile-fixed $PSVersionTable.PSVersion.Major, bounded compiler-owned dictionary flow, compile-time-safe literal conversion, stable-string interpolation, local function graphs, typed/defaulted parameters, bounded parameter-validation metadata, exact/alias/abbreviated parameter binding, index/member assignment targets, exact constructed-generic List<T> construction/member invocation with PowerShell-compatible null Count, ordered typed catch filters, bounded scalar regex-switch matching, bounded literal or variable one-dimensional stable-scalar typed-array ForEach-Object enumeration with compiler-owned $_/$PSItem, pinned null-record evidence, and cross-target null-versus-empty artifact evidence, bounded typed-executable begin/process/end lifecycle invocation, bounded local Get-Help Name/Synopsis metadata, typed integral compound arithmetic, comparison operators including PowerShell-sign-aware nullable integral and decimal ordering, logical operators including bounded short-circuit non-null refinement, bounded literal New-Object CLR construction through the canonical constructor IR, and post-test loop control flow through the artifact-hash/inventory-bound Strict-executable observer in the earlier PowerShell 7.6.5 review: all 24 cases (100% of that subset) carried runtime-free differential evidence for that historical pin. The newly pinned PowerShell 7.6.6 host passed those 24 interpreted cases; a new Strict differential run was not part of this release review. Direct null predicates emit reference identity rather than user-defined equality; right-operand refinement is path-local, collection-left and unsealed/dynamic scalar uncertainty fails closed, and nullable enumerable value types are classified by their boxed underlying runtime type. The generic-list slice accepts only recursively target-compatible type arguments and exact target-reference members; unrelated generic definitions, generic methods, dynamic receivers, and non-exact overloads remain hosted or rejected. The pipeline-enumeration slice remains assignment-only and rejects process output/control flow, scalar input, and object arrays. The lifecycle slice accepts one explicitly typed stable-scalar ValueFromPipeline parameter, a compiler-allocated typed input array, and explicit begin/process/end blocks. A local lifecycle call may now supply an exact one-dimensional stable-scalar array literal, local, or parameter. In both bounded pipeline slices, a null typed array contributes one by-value record converted to the exact parameter type, while an empty array contributes none; types whose null conversion raises a PowerShell parameter-binding error fail closed. Begin/end each run once per lifecycle invocation. The output-producing slice accepts homogeneous stable-scalar process expressions at top level or inside nested if/elseif/else branches plus exactly one terminal end expression of the same declared type, preserving selected process output and terminal end order in a compiler-owned typed collection; begin output, loop/switch/try-nested output, heterogeneous or array-valued process output, and process return/break/continue control flow remain hosted or rejected. Scalar or mismatched input, dynamic/provider collections, redirection, extra command arguments, clean/dynamicparam, wider signatures, and binary-module lifecycle commands also remain hosted or rejected. The runtime-free help slice accepts one statically named compiled local function and exposes immutable Name/Synopsis strings from the canonical comment-help binder; help discovery, formatting, options, and other properties remain hosted. The host oracle snapshots explicitly selected AutomationNull properties through CLR identity before PowerShell binding can collapse them, and ordinary null stays distinct. Runtime-free Strict observation uses the observer-activated PowerForge.StrictObservation/1 base64 framing protocol, which preserves nullable, multiline string, and culture-sensitive values without changing normal executable output. Other nullable array member and enumeration shapes remain hosted pending separate language/runtime contracts. These percentages describe the promoted runtime-free oracle subset, not the complete oracle catalog or the PowerShell language. New command families must still add one deterministic binder/registry owner, typed stage/cardinality contracts, and lowering support rather than coordinated special cases in analysis, emission, shaping, and census.
The executable-provider matrix now uses the same locked package route to prove scalar and collection success values across the closed string, Int32, Int64, Double, and Boolean ABI; verbose/debug/warning/information/host/error sinks; terminating adapter errors; escaped CLR entry-point identifiers; fail-closed null results; cooperative cancellation propagated through a local-function call graph; adapter-owned exclusive-file cleanup after normal return, failure, and cancellation; and an exact two-assembly managed dependency closure in Strict and Hybrid. Generated binary cmdlets route StopProcessing() to the same cooperative token and deterministically dispose their cancellation source after invocation. Provider ABI 4 added separately named external operations plus the Strict-executable stream/cooperative-cancellation host surface. ABI 5 adds manifest-bounded process isolation through one-use inherited pipe capabilities, entrypoint-reachable provider-ID dispatch, length-prefixed frames, and child-tree termination; ABI 4 and earlier fail exact negotiation rather than silently acquiring the stronger meaning. Declared result types and the required framework CancellationToken parameter are verified from callable, non-abstract PE metadata without loading provider code; same-named local or non-framework types fail the trust boundary. Contracts flow through deterministic package and registry copies and participate in conformance and public ABI hashes. Package replacement validates the temporary archive before atomically replacing a previously valid destination. The compiler reader invokes the same canonical contract validator as the SDK packer, and it reconciles exact dependency IDs and versions with the package's NuGet metadata. Reviewed dependency content identities are independently checked against NuGet's resolved lock and acquired bytes during isolated project restore. A locked external file-read operation executes from both a framework-dependent Strict executable and a net10.0 win-x64 Strict NativeAOT PE; incompatible AOT declarations fail before publication. These are runtime observations of the packaged implementation, not metadata credit. Cleanup remains the adapter's responsibility, and the representative proof does not imply arbitrary filesystem, HTTP, directory, management, native/process, or COM coverage.
The hosted compatibility lane also executes a separately built, Authenticode-signed binary module and signed transitive managed dependency from an otherwise isolated target. It verifies module version/GUID, process architecture, assembly load context, help/type/format data, success and diagnostic streams, terminating and nonterminating errors, cooperative cancellation, exclusive-resource cleanup, and signer preservation after artifact staging. That proves the declared Hybrid module boundary; it does not translate an arbitrary binary cmdlet or remove its PowerShell runtime dependency.
Direct command-provider inputs may define compile-time contracts but cannot carry executable entry points or external-operation declarations; executable adapters must resolve through an exact reviewed package lock. SDK conformance, package reading, and compiler registration share one executable-shape validator. The Strict observation protocol preserves globally ordered success, information/host, warning, verbose, debug, and nonterminating error evidence, while lowered IR records RuntimeFreeProviderOperations separately from hosted PowerShellStreams.
Every newly eligible language feature should satisfy the same acceptance packet:
- define the supported PowerShell semantics and the deliberate rejection boundary;
- bind static types, effects, required capabilities, and source locations before emission;
- compare results and failure behavior with Windows PowerShell 5.1 and the supported PowerShell 7 lanes where the source feature applies;
- cover Strict rejection, Hybrid fallback, and each target framework that can observe different CLR behavior;
- inspect the emitted C# and preserve source-map evidence;
- rerun the real-product census and record which complete functions or regions became eligible;
- benchmark only workloads large enough to distinguish compiled work from host or cmdlet dispatch overhead.
This makes coverage growth reviewable and maintainable. Success is not the number of AST node types recognized; it is more useful real-product work crossing a proven semantic boundary without changing PowerShell-visible behavior.
Each successful build writes <name>.powerforge-compilation.json. The manifest records:
- artifact kind, mode, target framework, and runtime identifier;
- the resolved root and all authored files in the shared compilation scope;
- whether PowerShell is required and whether script fallback is used;
- compiled method count, runtime-fallback count, omitted-unit count, and coverage percentage;
- SHA-256 for the primary artifact, portable PDBs, and every distributed runtime or hybrid-module file;
- exact source diagnostics and locations for unsupported units;
- the target-specific NuGet closure-lock SHA-256 consumed by project builds;
- byte sizes for the primary artifact and every durable file;
- the complete discovered dependency/resource plan and each item's delivery disposition;
- executable optimization mode and Authenticode signing evidence when requested;
- a semantic explanation fingerprint, portable statement/boundary failure map, deterministic cache/dependency/ABI/fallback/provider audit trail, and the local-only retention/redaction policy;
- optional semantic-only bound/lowered IR evidence and its integrity hash when explicitly requested.
The manifest includes the immutable final unit-disposition ledger used by coverage, explain output, census, reproduction hashes, and boundary profiling. Dependency causes are attached only to the affected source unit; manifest-wide or otherwise non-unit runtime requirements remain delivery causes. Diagnostic hashes include portable file identity as well as code, feature, location, and message. For module-pipeline delivery, the same canonical finalizer runs after the last mutation in staging, packed ZIP, unpacked folder, managed repository package, and installed-module roots. Producer-local paths are replaced with portable relative identities, file hashes are recomputed against the delivered root, and machine-local checkpoint authority is excluded. Authenticode counts are carried into a copied delivery only for byte-identical files from the verified signed source root, so an installation version rewrite or unpacked post-copy replacement cannot retain stale positive signing evidence.
A packaged EXE therefore reports requiresPowerShellRuntime: true and usesPowerShellRuntimeFallback: true. A strict CLR library reports both values as false. A strict binary module requires PowerShell as its cmdlet host but reports no script fallback.
PowerForge stages the complete owned artifact shape and manifest before publication. Rebuilding under the same artifact name replaces prior EXE, DLL, PDB, module-directory, generated-source directory, manifest, and exactly the resource files recorded by the previous manifest. Removed resources are deleted, unrelated neighboring files are preserved, unowned collisions fail, same-name publication is serialized across threads and processes, and a failed durable commit rolls back to the previous set instead of leaving a new binary beside stale integrity evidence.
Disposable compiler projects live under a dedicated PowerForge/powershell-compilation temporary root, carry an ownership marker and active lock, and are deleted after normal builds. Stale cleanup removes only marked, unlocked, non-retained compiler workspaces older than the cleanup threshold. KeepBuildWorkspace adds an explicit retention marker; unrelated or legacy ps-* directories are never scavenged by name alone.
Add --emit-source or -EmitSource to publish <name>.generated as part of the same atomic artifact set. It contains the exact generated .cs files, .csproj, and a source-map.json that maps each generated method to its authored file and line; packaged executables also include the rewritten embedded Source.ps1. Generated PowerShell-SDK projects pin the serviced System.Security.Cryptography.Xml 10.0 line so an independently restored inspection build does not fall back to a vulnerable transitive version carried by the SDK. The project can be inspected or rebuilt directly:
dotnet build .\artifacts\MyModule.generated\MyModule.csproj -c ReleaseEvery emitted source file is listed with its role, SHA-256, and size in the compilation manifest. The emitted project includes local Directory.Build.*, Directory.Packages.props, and global.json isolation files so an ancestor repository's MSBuild, central-package, or SDK policy cannot silently change the inspection rebuild. Rebuilding the artifact without source emission removes a prior generated-source directory so stale C# cannot be mistaken for the current binary.
Add --emit-ir, -EmitIrSnapshots, or the Build-Module configuration equivalent to publish <name>.powerforge-ir.json. This file contains stable symbol/document identities, resolved types, output cardinality/value states, capabilities, effects, disposition, and bound/lowered node kinds. It never contains authored source text, parser AST objects, literal values, absolute paths, or hosted executable source. Its hash, the failure map, audit trail, redaction policy, decision trace, diagnostics, source map, ABI, dependency lock, target, providers, compiler, and SDK are all bound into reproduction evidence.
Diagnostics are local-only and are never uploaded automatically. Manifest, trace, audit, map, and optional IR evidence follow the artifact lifetime. Failed-build or crash bundles remain user-managed and should normally be removed after seven days when no longer needed. Generated source is intentionally a separate explicit opt-in because it contains reconstructable implementation details and, for packaged artifacts, may include authored source.
The checked-in benchmark suite validates every result outside the timed operation and compares typed CLR, generated cmdlet, PowerShell function, typed EXE, packaged EXE, and hand-written C# lanes. It also includes a dispatch-amortization workload that performs equivalent arithmetic through many fine cmdlet calls or one coarse generated command.
- the original PowerShell function;
- the generated binary cmdlet called through PowerShell;
- the generated typed CLR method called inside a C# loop;
- equivalent hand-written C#.
The current Windows computation and startup reference packet used PowerShell 7.6.4, Windows x64, and an AMD64 32-logical-core machine. The optimized ReadyToRun lane pinned stable same-major .NET SDK 10.0.303, and the startup metadata now records that SDK alongside every optimized artifact hash and size. Duration rows are medians after three warmups, 12 measured samples, and minimum/maximum exclusion; startup used two warmups and 10 measured samples. Every row has zero validation failures and pins clean candidate 009901b0bbf56285a1ab291e2a1bff760e05a4c9 plus generated artifact hashes.
Windows run IDs are 20260829-000416-818e0b53 and 20260829-000530-2e8dee77 (real functions), 20260829-000611-1abe7ff0 (synthetic loop), 20260829-000614-2f8f557b (indexed array), 20260829-000616-799275f9 (dispatch and boundary profile), 20260829-000618-1a782491 (startup), and 20260829-000630-abc01afb (local calls).
| Workload | Calls | PowerShell | Typed CLR | Hand-written C# | Typed vs PowerShell | Typed vs C# |
|---|---|---|---|---|---|---|
Real Get-AllowedAverageMs, absolute-cap branch |
50,000 | 203.11 ms | 6.67 ms | 3.43 ms | 30.4x faster | 1.94x slower |
Real Get-AllowedAverageMs, relative-cap branch |
50,000 | 212.24 ms | 6.42 ms | 3.39 ms | 33.1x faster | 1.89x slower |
| IPv4-to-PTR conversion helper | 50,000 | 512.14 ms | 14.86 ms | 8.22 ms | 34.5x faster | 1.81x slower |
| Synthetic triangular-number loop, 1,000 x 1,000 iterations | 1,000 | 37.89 ms | 4.78 ms | 3.24 ms | 7.9x faster | 1.47x slower |
| Indexed sum over 1,000-element typed array | 1,000 | 40.48 ms | 5.64 ms | 3.68 ms | 7.2x faster | 1.53x slower |
These results prove a benefit only for eligible computation executed as CLR code. They do not promise that an arbitrary script or a generated cmdlet call is faster.
The repeatable powershell-compilation-typed-local-calls lane exercises one entry script and one dot-sourced helper through both pwsh -File and a Strict executable. The clean run used 20,000 helper calls and recorded 354.83 ms for PowerShell versus 35.45 ms for the typed executable, or 10.0x.
The binary-cmdlet lane includes PowerShell command lookup, parameter binding, pipeline setup, and WriteObject for every call. It took 2,138.31 ms and 2,095.53 ms in the two 50,000-call threshold scenarios, versus 203.11 ms and 212.24 ms for the original functions. The dispatch-amortization workload then performed equivalent work through 1,000 fine cmdlet calls or one coarse command: 46.86 ms versus 4.81 ms, a 9.7x improvement. The useful product shape is a coarse cmdlet that performs substantial compiled work per invocation, not a tiny arithmetic cmdlet called in a PowerShell loop.
Executable startup proves that typed compilation changes the product result rather than merely its extension. The PowerShell-free typed EXE took 44.82 ms, pwsh -File took 186.41 ms, and the runtime-packaged EXE took 664.84 ms. The typed executable is 4.2x faster than pwsh -File and 14.8x faster than packaging in this one-shot workload. Packaging remains valuable for broad script compatibility and delivery ergonomics, not startup speed.
The optimization and footprint matrix below was rebuilt and executed with the same clean candidate and the win-x64 runtime identifier. ReadyToRun remains a measured experiment rather than a selectable public target.
| Windows x64 artifact | Bytes | Runtime model |
|---|---|---|
| Typed framework-dependent EXE | 190,158 | installed .NET |
| Typed ReadyToRun EXE | 57,344 | installed .NET; benchmark-only |
| Typed self-contained trimmed EXE | 13,444,295 | bundled trimmed .NET runtime |
| Typed NativeAOT EXE | 2,880,000 | native, no .NET or PowerShell runtime required |
| Packaged PowerShell EXE | 54,709,527 | embedded PowerShell runtime assets |
The Hybrid boundary profile measured 1,750 crossings at 14,682.9 ns per crossing with a 0.9785 estimated boundary-overhead ratio; the corresponding non-boundary work share was approximately 0.0215. It correctly advised coarsening the boundary or retaining hosted execution; this is workload evidence, not a universal cutoff.
The 2026-08-30 M22 quick qualification packet ran twice from clean implementation commit 99c1ad0fc9a25aae5b71eb6f308cebf339fa2fbe; every lane reported zero validation failures. The first clean build-cost row was 3,437.79 ms and the warm repeat was 2,064.29 ms, exposing the expected variance of a two-sample smoke run. The repeat allocated 44,160,528 managed bytes, changed process working set by 2,799,616 bytes, and produced a 7,168-byte primary artifact (20260830-120136-38ce8123). Binary-module import was 38.90 ms with 139,339 managed bytes allocated (20260830-120141-ab5d8a68). These quick rows verify instrumentation, budgets, and lack of a persistent regression; the full clean reference packet above remains the promotion evidence.
Target-host certification separately executed the same Strict net10.0 framework-dependent and NativeAOT workload on Windows x64 and Ubuntu 24.04 x64. Both hosts produced exact Unicode output (Zażółć-東京|résource-Łódź-東京|10), rejected invalid arguments with exit code 1, preserved resources, and passed executable-format, architecture, import, permission, and dependency-closure inspection. Windows cancellation used process termination and Linux cancellation produced SIGTERM exit 143. Windows PowerShell 5.1 independently revalidated the Windows artifacts. macOS remains experimental because the approved EvoMini authentication path did not establish a session; no macOS support claim is inferred from cross-publishing.
Run the same matrix locally:
Invoke-BenchmarkSuite `
-Path .\Benchmarks\PowerShellCompilation\powershell-compilation.benchmark.ps1 `
-Variable @{ IncludeOptimizedExecutables = $true } `
-RunMode localSee the benchmark README for the quick smoke command and lane definitions.
The 2026-09-06 corrective assessment records source fixes for Single precision/result types, constrained numeric promotion and catch selection, artifact-aware census, and per-input discovery failures. It also adds same-type integral remainder and constrained remainder assignment with complete number-theory and calendar workloads. The bounded compiler gate and all six Strict programs pass on Windows/Linux x64. Exact PowerShell error wrappers, broad script coverage, PR settlement, and public-package qualification remain separate limits.
The compiler is ready for targeted semantic expansion through its canonical parser → binder → bound IR → lowering → backend route. It is not yet ready for a broad “compile arbitrary PowerShell natively” promise or a public stable-channel push. The baseline-gated public Hybrid packet builds, imports, and invokes 10/10 modules. Of 735 authored units, two are emitted as complete compiled cmdlets, 733 remain retained/runtime-routed, and 13 terminal typed regions are promoted inside retained Pester functions. A control run at the pre-promotion head confirmed that the two full PSFramework cmdlets were already emitted. The separate seven-workload assessment emits 6/196 units (3.06%) and 6/183 functions (3.28%), records two promoted regions inside retained Locksmith2 functions, and still performs 0/7 complete-workload executions. Promoted regions never increase complete-unit/function totals. A separate opt-in qualification proves original/generated invocation parity for 4/4 selected emitted commands across three unrelated scenario families, but it does not yet exercise or benchmark these new partial-function regions. The four-file Strict application also consumes an exact delivered resource, proves exact success streams plus a bounded failure contract, and records six counterbalanced fresh-process samples after excluded warmups on both advertised targets. These are bounded product proofs, not broad native-compilation coverage.
The prerequisite ownership work is now in place: semantic profiles are effective inputs, the provider SDK has a locked ABI 5 executable route and canonical package/signer policy, bounded provider conformance includes a representative NativeAOT filesystem operation and deadline-bound process isolation, both corpus lanes share bounded acquisition and enforce their baselines, and the active backend/bootstrapper/closure-verifier owners were split below their structure ceilings. Coverage may widen only through these owners. Runtime-free read-only state includes process identity, user home, and current culture/UI culture in addition to the previously supported environment and target facts. Hybrid binary modules may also directly read or assign one bounded class of live parent module state and may read a member/index only when the authored expression explicitly converts that live value to a supported CLR receiver type, as described below; wider mutable or dynamic scopes still remain hosted or fail closed. Every already-emitted method now also carries one coarse graph derived from lowered IR, so boundary accounting, explainability, and future region selection share the same facts. Nullable CLR value-member reads, stable scalar interpolation in nonempty stream messages, and final expression output inside terminal try/catch branches flow through the same semantic IR. The bounded typed-executable lifecycle contract also materializes homogeneous stable-scalar process output from top-level expressions or nested conditional branches followed by its terminal end output as one ordered typed collection; output-producing begin, heterogeneous output, and loop/switch/try-nested or return/break/continue process control flow remain fallback. Earlier semantic waves briefly produced higher external emission counts before canonical command resolution correctly withdrew credit from unqualified host-command rewrites; the reviewed 5/196 floor and the current 6/196 result supersede those historical totals. The remaining maturity work is the rest of Milestone 23's module-scale state/region contract, a controlled disposable-target reboot/reconnect management exercise, additional physical target profiles, and an explicitly authorized public release.
A Hybrid binary module may compile a function that directly reads $script:<safe-identifier> or assigns it with simple = from a typed expression. It may also read a member or index when the authored expression provides the exact supported CLR receiver type directly over that state read, for example ([string]$script:Name).Length or ([string[]]$script:Items)[0]. This is authored proof, not type inference from a top-level declaration or retained initializer: fallback code may replace the value at any time. The runtime-state binder owns the live read; the ordinary conversion, CLR member/index, bound IR, lowering, and backend owners handle the operation. A canonical bound-value origin follows state-derived conversions, locals, success output, and local-call results. Dynamic/object member access, nested conversions, typed-local or call-result member/index access, state-bearing call arguments, method invocation, and loop/pipeline/lifecycle transport fail closed instead of opening an ETS or second eligibility path. Hosted command regions and captures check that origin before argument projection and retain the whole enclosing function when a state-derived input would cross the hosted boundary; hosted capture and retained lifecycle calls therefore cannot erase provenance. Index targets and keys evaluate exactly once in authored order. Indexed mutation uses separate collision-free RHS, target, and key temporaries in PowerShell's RHS-first order, including before null or out-of-range failures. Hosted array and list failures preserve the effective CLR exception, FQID, and category used by PowerShell typed catches; null arrays preserve NullArray.
The retained parent script module remains the only state owner. At import, PowerForge registers scope-bound readers and writers for the current runspace; generated cmdlets cross that boundary on each access. Fallback and compiled functions therefore observe the same live value. The assignment value uses the same canonical semantic traversal as every other typed expression, including bound-parameter presence, runtime state, hosted commands, provider cancellation, streams, and nested local calls. Null, scalar, array, and object assignment, case-insensitive names, missing-variable reads, and Constant/ReadOnly write failures preserve the tested Windows PowerShell 5.1 and PowerShell 7 behavior. The bridge rethrows the original state exception inside the compiled body so an authored compatible catch can handle it; only an unhandled tracked state error is restored to its original ErrorRecord at the cmdlet boundary. Read and write authority propagate separately through compiled local calls. Cleanup is installed before retained initialization: failed initialization rolls back reader, writer, and command-dispatch registrations, an early module return keeps the removal hook, and ordinary module removal clears the registrations. If retained initialization replaces OnRemove, PowerForge composes the authored handler with generated cleanup and runs that cleanup from finally, including when authored cleanup throws.
This is a hosted boundary, not native state compilation. Ledger schema 2 introduced the method's emitted/runtime-routed distinction, exact read/write variable names, and directional site crossings; current ledger schema 4 preserves those fields and adds the lowered-region graph plus promoted-region evidence. It labels the artifact disposition HostedModuleState and includes only the required reader and/or writer in the generated CLR ABI. IR snapshot schema 3 carries the directional capabilities and promoted semantic units, while explanation schema 5 and semantic compatibility 4 carry direction and region semantics into fingerprints. The prior public ledger constructors remain available, schema-2 JSON without a graph remains readable, and current ledger JSON round trips retain the graph and promoted helper identities. Compound assignment, increment/decrement, typed assignment targets, unsafe/dynamic names, member/index mutation, nested or compound typed receivers, effectful indexes, and member/index access on an untyped module-state value remain fallback even when the receiver is parenthesized. Strict binary modules reject this capability because they have no retained parent script scope. Future wider state access and Strict stateful modules must use the same semantic IR while keeping Hybrid and Strict state ownership separate and explicit.
For each method the semantic pipeline already accepts, lowering creates one deterministic ordered region graph before C# emission. Consecutive top-level statements with no hosted boundary form a typed region. A direct hosted command or capture forms a hosted region. Typed control or value flow that contains a hosted command or live-module-state crossing forms a mixed region. Each region records its relocation-safe authored source identity and span, case-normalized PowerShell symbol/state inputs, downstream-observed outputs, conservative possible receiver mutations, PowerShell streams, modeled error routes, single-evaluation ordering contract, exact static boundary sites, and a deterministic structural cost. That cost is only a ranking signal for profiling; it is not elapsed time, allocation, a complete effect proof, or permission by itself to move or fuse a boundary.
The C# backend uses this graph for the same command and module-state site counts exposed by generated-method metadata. Strict executable and module/library shaping carry it into ledger schema 4; powerforge powershell explain emits it in JSON and summarizes it in human output; explanation semantic compatibility 4 fingerprints the semantic graph and promoted helper identities while excluding relocation-dependent coordinates and region IDs. Methods created solely as hosted lifecycle wrappers have no fabricated lowered graph. Older ledger JSON remains readable with a missing graph.
Hybrid now uses that foundation for deliberately narrow partial-function shapes. When a non-lifecycle function has a binder-rejected top-level statement followed by a fully bound terminal suffix, the binder may retain that suffix as a candidate. The candidate passes through the same optimizer, analyzer, lowerer, region graph, and C# backend as a complete method. It is promoted only when its values, transfers, output, mutation, errors, and continuation close through canonical contracts. Local calls remain excluded except for the exact source-proved collection factory described below. Named dynamicparam, begin, process, end, and clean lifecycle shapes stay on their existing whole-function route because the first region contract has no lifecycle live-out model. A dedicated Hybrid-region target capability remains separate from live-module-state authority. The generated module keeps the original function and replaces only the integrity-checked region with a call to a compiler-owned CLR helper. The composer verifies both the selected-text hash and the complete authored-document hash that supplied the parameter/header ABI; any contract, header, source, hash, or span mismatch keeps the region in PowerShell.
powerforge powershell census --output json also exposes every terminal candidate considered by this slice as regionCandidates. The record carries immutable source/document hashes and span identity, the lowered graph when available, promoted status, and the stable decision code and explanation produced by the canonical analyzer or promotion policy. Human census output groups the leading retained reasons. Final CLR-name shaping reconciles these records, so a generated-helper collision is retained and reported rather than being silently dropped while evidence still calls it promoted. Census uses the same complete-module Hybrid source-discovery policy as the artifact path, so modules with dynamic loaders can be assessed while their undiscoverable source remains runtime-retained; this does not relax Strict resolution. Candidate totals are diagnostic opportunities only: they do not increase emitted-function or emitted-unit coverage.
This does not make the function a compiled cmdlet or a runtime-free unit. The ledger, explanation, artifact/module summary, IR snapshot, failure map, boundary evidence, and census report each promoted region separately while the containing function remains runtime-routed and excluded from emitted-unit/function coverage. Strict never consumes this Hybrid promotion path. Hybrid can promote one guarded non-terminal initialization prefix and transfer proven stable scalars or closed collection references into the retained continuation; existing, read-only, or validated invocation storage keeps the authored prefix native. It can also promote later statement-aligned regions when every input and output has an established transfer and ownership contract. The prefix guard remains the only fresh-local owner, so later regions update established invocation storage and cannot silently introduce a new local. A separately eligible terminal suffix may still compile.
One closed initializer may assign a fresh continuation local in exactly two branches when one authored constraint is a supported stable scalar and the other is the matching one-dimensional stable-scalar array. The ClosedValueAlternative contract introduced in transfer schema 4 records both alternatives and their individual output/enumeration contracts; current serialized transfer evidence uses schema 5. The helper returns a compiler-owned envelope as one atomic CLR value; the retained rewrite inspects its discriminator and reapplies the selected authored constraint before later PowerShell code can mutate or enumerate it. The condition may use a native-bound switch parameter, but it cannot read the target local. Missing or extra branches, extra branch statements or output, mismatched scalar/element types, object or ETS storage, object-element or multidimensional arrays, prior local ownership, and compiled mutation remain outside this contract.
One schema-5 local-call contract admits an exact zero-parameter function whose complete body creates a fresh System.Collections.ArrayList and returns that same reference through one comma/no-enumerate record. The canonical binder records an empty CLR parameter list, exact ArrayList lowered and projected result types, callee-fresh ownership, retained-only mutation, and no compiler-owned enumeration or enumerator lifetime. A consuming region lowers the proved call to the equivalent constructor; it does not call or reinterpret the retained helper's public success-stream method ABI. A complete Hybrid native-function consumer may use the same projection: the binder constructs the proved list through ordinary bound/lowered IR and passes it to the existing native assignment owner, which writes the exact authored variable in invocation-owned PowerShell storage. The call-graph fallback bypasses the helper's native parameter binder only for this explicit projection. Dynamic invocation, aliases, module-qualified or unresolved commands, parameters, recursion, captured state, extra statements, different return shaping, arbitrary enumeration, and other collection types remain hosted. This is one reusable source-shape contract, not a name-based intrinsic or a general local-call ABI.
A conditional return may compile through a structured return-or-fallthrough envelope when every branch returns the same supported parameter-borrowed shape and the alternate path continues in retained PowerShell. The generated helper does not enumerate or mutate the returned value. The retained authored return remains responsible for atomic versus one-level versus comma/no-enumerate output, partial records, error-action continuation, downstream stopping, and enumerator cleanup. The admitted return families are stable scalars, exact atomic Hashtable/OrderedDictionary, one-dimensional stable-scalar arrays, and read-only System.Array/ArrayList/List<T> references. Direct System.Object/ETS values, arbitrary enumerators, unproved local collection ownership, general or value-stream local-call collection closure, compiled collection mutation, streams beyond Success, and exception/trap interaction remain unsupported until their contracts exist in canonical IR. The composer and backend do not rediscover eligibility from syntax or generated C#.
The current checked-in baselines protect 13 such regions in the public packet and two in the external assessment. The public regions are all one-line terminal returns in retained Pester functions; the external regions are one-line returns in retained Locksmith2 functions. The packet runners now project the same candidate records to contained relative paths, preserve a null graph for pre-lowering rejection, publish per-workload decisions and a cross-workload retained frontier, and never copy compiler eligibility into the harness. The public runner additionally requires census and delivered-ledger promoted counts to match.
The full public frontier contains 30 terminal candidates: 13 promoted and 17 retained. Five retained PSD1001 candidates occur across Pester and Microsoft.PowerShell.Archive, but each is only a one-line return of a local computed by the hosted prefix. Twelve one-to-three-line Pester candidates construct [Pester.ShouldResult], which canonical binding currently represents as System.Object; compiling those tiny suffixes would require a PowerShell-class/runtime object-transfer contract. The external frontier contains two additional retained candidates in Locksmith2: one one-line live-local return and one five-line hosted verbose/return tail with structural cost five. Module-level import/invocation proves that accepted rewrites do not break the fixed probes, while focused fixtures prove direct source/generated parity and evidence integrity. No terminal candidate contains enough isolated work to justify widening object, local, or hosted-command transfer, and neither packet isolates a promoted command or measures saved work against crossing cost. Partial-function promotion therefore remains a preview coverage mechanism rather than a performance claim.
The census now separately reports regionOpportunities for maximal statement-aligned typed work found inside binder-rejected Hybrid functions. These records traverse the same optimizer, semantic passes, lowerer, and region-graph builder, but never enter the C# backend and never receive promoted or emitted credit. They include exact source/span identity, statement count, continuation, separate live-input-source and live-output-consumer completeness, typed transfers, streams, errors, ordering, local calls, and state boundaries. Fixed-point rebinding replaces earlier evidence for the same function, continuation stops at the first reachable terminating statement, and optimizer-folded work is reported only when every lowered region overlapping the authored statement is typed. A statement containing a mixed boundary is omitted rather than assigning one authored identity to partial subregions. When the following authored code is unbound, every mutation is conservatively listed as a possible live output and completeness stays false. The current public packet reports 367 opportunities, including 337 outside the terminal window; the external packet reports 59, including 55 outside it. Larger individual regions exist, but only two two-statement shapes repeat across unrelated families; both have incomplete consumers and non-stable dictionary or object-array transfer. No complete stable-scalar multi-statement opportunity exists outside the terminal window. This first lane covers binder rejection only; functions retained by later semantic, cmdlet-shaping, or artifact decisions require the next discovery slice before a zero count can mean no typed work was present.
WMI, CIM, CDXML, directory-style binary modules, native/process calls, and COM are present with deliberately different guarantees. Hybrid fixtures prove that PowerShell-hosted routes can be retained safely. Strict CIM/MI uses the separately built PowerForge.PowerShell.Provider.Management.Runtime package: query, enumerate, exact-key get, create, modify, delete, method invocation, association traversal, and bounded indication subscription execute from compiler-generated artifacts without PowerShell. Its release project emits the same nine-contract executable provider package used by acceptance tests, with three exact managed assemblies and one inspected win-x64 native bridge. Strict LDAP uses the separately built PowerForge.PowerShell.Provider.Directory.Runtime package with seven contracts for search, exact-DN read, add, modify, delete, rename/move, and compare. Its qualified security profile is deliberately narrow: win-x64, LDAP on port 389, and Negotiate authentication. LDAPS, StartTLS, Kerberos-only, NTLM-only, Basic, anonymous, explicit-credential, referral-chasing, and alternate-port modes fail closed until each receives its own target-host qualification. The package contains the exact adapter and LDAP protocol assemblies and closes the Windows LDAP native ABI through the same reviewed target catalog. Read-only integrated access, bounded paged-search state cleanup, provider-created session reuse, add/modify, true and false compare, search, rename, read, delete, post-delete absence, and final cleanup have executed against caller-declared directory targets. Early paged-search termination sends the RFC 2696 size-zero cleanup request; if a server rejects it, the connection is closed so its server-side paging state cannot remain live. Reusable sessions are opaque provider-created values bound to the qualified profile; arbitrary caller-created LDAP connections cannot enter the request contract. The executable package's seven contracts declare ABI-5 ProcessIsolated cancellation with a 45-second deadline. A generated Strict executable creates one-use inherited anonymous-pipe capabilities, emits only entrypoint-reachable provider IDs, exchanges length-prefixed bounded scalar string frames, keeps the direct worker alive until the response is consumed, and then kills the complete tree on success, caller cancellation, or deadline. Direct command-line worker entry and unreferenced package operations fail closed; former frame-marker text round-trips as ordinary payload; and a deliberately non-cooperative provider proves bounded termination and exclusive-handle release. Library, binary-module, and Hybrid hosts reject this contract. The ordinary reusable in-process adapter remains explicitly PostInitializationCooperative because the Windows Negotiate bind is synchronous through System.DirectoryServices.Protocols; Microsoft documents that boundary and its platform-owned bind-response timeout in the ldap_bind_s contract. These providers are distinct from their ordinary multi-target adapter library packages. Their closures are RID-selected, exact-hash locked, architecture/import-checked, closure-certified, and recorded in provenance/SBOM. This does not mean arbitrary PowerShell WMI/CIM/CDXML or directory cmdlets are automatically translated; hosted/profile boundaries remain unless registered provider contracts map a supported invocation. Certificate authentication remains intentionally absent from the CIM contract rather than silently mapped. A destructive management reboot/reconnect case still requires an explicitly disposable target.
PowerForge carries a self-contained, product-neutral compiler corpus under Benchmarks/PowerShellCompilation/Corpus. The Hybrid module exercises parameter metadata and defaults, operators, typed recursion, runtime-state injection, command-result capture, read-only environment access, known object shapes, and mutable list flows. Its committed net10 census baseline records a portable source fingerprint and 8/8 post-emission functions (100%) with no retained-source fallback or eligible-function loss. One emitted function also records its bounded hosted command region as runtime-routed; typed coverage and runtime routing are intentionally independent measures. The fixed Strict packet contains six programs totaling 24/24 emitted units. The number-theory and calendar programs extend the original four-program packet with generic remainder and constrained loop arithmetic. Its four-file application contributes 11 compiled methods and matches direct execution on both win-x64 and linux-x64 under the enforced baseline.
This is the stable acceptance surface for generic compiler contracts. It runs from any checkout without neighboring repositories:
powerforge powershell census `
.\Benchmarks\PowerShellCompilation\Corpus\HybridModule\Generic.Compiler.Corpus.psd1 `
--framework net10.0 `
--baseline .\Benchmarks\PowerShellCompilation\Corpus\census-baseline.net10.jsonSee the corpus README for the contract split. External repositories can be added, replaced, or removed as scale workloads without changing the compiler design. The fixed public packet remains the acceptance gate; a separate exact-hash external assessment packet records low-coverage frontier inputs without silently redefining that gate.
The committed acceptance surface is the product-neutral corpus under Benchmarks/PowerShellCompilation/Corpus. It contains generic module, multi-file, resource, lifecycle, command-provider, and executable contracts. Compiler behavior is derived from PowerShell syntax, semantic IR, dependency graphs, target contracts, and explicit resource policy—never from a repository name, module name, neighboring checkout, or conventional product-specific path.
The census command accepts any caller-supplied script, manifest, or module roots. Inputs may be added, replaced, randomized, or removed without changing compiler behavior:
powerforge powershell census `
.\path\to\first-module `
--path .\path\to\another-script.ps1 `
--framework net10.0 `
--write-baseline .\artifacts\powershell-compilation-census.json `
--output jsonThe census records discovery, typed/fallback coverage, parse errors, analyzer duration, dependency summaries, stable missing-feature impact, frequent co-blocker pairs, terminal-region candidate decisions, analysis-only region opportunities, and the final-ledger identity/disposition of every authored function. Candidate decisions and opportunities are serialized and round-trip with a written census, but neither becomes new baseline emission credit. A non-empty function baseline must contain exactly one unique, non-empty disposition identity per function; an older identity-less baseline fails closed and must be regenerated with --write-baseline and reviewed. Per-function comparison rejects lost semantic eligibility or emission, newly gained runtime routing, and new shaping fallback for a previously eligible function, so equal aggregate counts cannot hide replacement. Input disappearance, fallback growth, parser regressions, and normalized source-identity drift remain independent failures. External source trees are private, replaceable regression workloads; they are not committed compiler configuration and cannot authorize a special-case intrinsic.
Low coverage is not hidden by Hybrid mode. Every fallback is reported with a diagnostic. The machine-readable functionFrontier separates occurrences, affected units, visible sole blockers, and complete-input candidates so roadmap priority comes from repeated generic semantic shapes rather than a named module. Strict mode remains the proof boundary for a module with no authored runtime fallback.
The checked-in external assessment runner adds reproducible acquisition around the same census. Each workload supplies only evidence metadata: HTTPS payload, immutable revision or package version, SHA-256, license status, entry point, and scenario family. The assessment runner never imports or executes the payload. Its baseline requires the same source fingerprint and discovered surface, rejects new parser errors or fallback regressions, and allows reviewed emission gains. A separate opt-in qualification command requires explicit external-execution consent, builds one exact acquired module, asserts a selected unit was emitted rather than runtime-routed, invokes original and generated commands in separate child processes, and records only output hashes and parity. Neither lane certifies an unexecuted complete workload.
The current seven-workload assessment covers certificate-service administration, CIM/device registration, WMI/CIM/remoting/report generation, cross-platform installation, Windows package bootstrap, and API-client utilities. It discovers 155 source files, 196 executable units, and 183 authored functions with zero parser-error files. Current post-emission results are 6/196 units (3.06%) and 6/183 functions (3.28%), with zero complete-workload executions because this is a census-only lane. Its terminal-region frontier reports four candidates: two promoted, one retained by definite-assignment analysis, and one retained at a hosted command boundary. Its 59 analysis-only opportunities do not change those totals; all occur in the two related certificate-service workloads, so they are not external proof of an unrelated-family execution target. The live module-state slice newly emits Locksmith2/Public/Get-LS2Stores.ps1, while correctly retaining its runtime-routed classification. The separate qualification proves 4/4 selected emitted commands from four unrelated workloads across three scenario families, without claiming any complete workload. These ratios, candidate counts, and opportunity counts describe only this pinned packet; they are not PowerShell-language coverage and do not predict arbitrary-script success.
Generated binary modules also support one bounded command-discovery shape: Get-Command with exactly one name plus -ErrorAction Ignore or SilentlyContinue, consumed only as a Boolean availability test. The bound provider sends a constant, argument-bound script through the canonical hosted-command capture delegate, preserving exact-name and module-qualified autoload, wildcard lookup, and the distinct $Error effects of both actions in PowerShell 5.1 and 7. It records its hosted provider contract and never imports or executes source during compilation. The emitted CLR method therefore counts as one runtime-routed boundary and does not reduce fallback dependence. It does not expose CommandInfo, general Get-Command parameters, or runtime-free behavior; Strict libraries and executables continue to reject the command.
Packaging and typed compilation are not obfuscation or source protection. A packaged executable contains an embedded script and runtime assets that a determined user can inspect. A typed EXE or DLL is normal managed/native code and remains analyzable.
Build-PowerShellArtifact -SignArtifact and CLI --sign sign only build-owned Windows artifacts: the generated executable or library, typed assembly, hybrid module host, and generated primary module manifest. Bundled runtime files and nested/module dependencies keep their original publisher identity. Signing happens before SHA-256 and byte-size evidence is recorded and runs in an isolated Windows PowerShell process with a bounded timeout. A missing certificate, provider timeout, or non-valid signature aborts the atomic publication; no unsigned replacement or stale manifest is committed. Concurrent replacements serialize through a durable per-artifact lock file whose exclusive handle defines ownership across Windows and Unix. The broader PowerForge release pipeline remains the owner for packaging, release attestations, NuGet/GitHub publication, and policy-level signing configuration.
The fail-closed signing and atomic-publication contract is covered by automated tests. On 2026-08-23, before the target retirement decision, an internal acceptance run produced a valid Authenticode-signed typed EXE plus net8 and net472 binary modules. That result is historical signing evidence, not current net8 support. Each staged hash matched the final manifest and each artifact executed successfully in its target host. These were local internal proof artifacts only; nothing was published to PSGallery, NuGet, GitHub Releases, or another feed.
The generated EXE carries the PowerShell SDK, so it is much larger than the input script and may start more slowly than an installed pwsh. Self-contained publication adds the .NET runtime as well. With SingleFile = $false, PowerForge preserves the complete nested publish tree instead of copying only top-level files. Runtime-packaged artifacts must be rebuilt when their embedded PowerShell or .NET dependencies need security updates.
Strict typed executable compilation accepts one .ps1 entrypoint and its contained literal dot-source dependency closure. Top-level param() remains the process argument contract, while source functions become direct static CLR methods. Local function calls enforce the supported validation metadata and deliberately reject mutual or uncontracted recursion, splatting, redirection, external commands, dynamic command names, and incompatible argument conversion; direct self-recursion needs a single verified non-void [OutputType] contract. Authored [OutputType([void])] is accepted as advisory command metadata and does not suppress success output actually produced by the function body. It is not used as a local-call, lifecycle-output, or recursive value contract; body inference remains the semantic owner. A generated binary cmdlet can also preserve one statically resolved authored output type by name when that type is valid advisory PowerShell metadata but cannot participate in the generated CLR method signature. The binder carries the metadata name separately from the optional semantic return contract, and the generator uses PowerShell's string-form OutputTypeAttribute; real net472 and net10 modules retain the same command metadata without adding an assembly reference for the advisory name. Strict runtime-free targets still require a target-compatible semantic type, while unresolved names, dynamic expressions, and multiple output-type declarations remain rejected. A managed Hybrid executable uses the same explicit entrypoint boundary but registers eligible local functions as generated cmdlets and retains the entry script plus unsupported dependencies for hosted execution. It is runtime-preserving delivery, not a Strict or NativeAOT artifact. Source #requires directives are accepted only when their version and PSEdition requirements are compatible with the selected semantic profile; module, assembly, host/elevation, snap-in, unknown, or higher-version requirements reject Strict and remain explicitly hosted in Hybrid. Runtime-bearing using statements are never erased. Hybrid module composition preserves namespace using and module param prologues for mixed .ps1 or .psm1 source. Generated typed export shaping requires literal unconditional exports, including colon-attached literal forms such as -Function:Get-Value, and contained relative file references. Conditional export logic remains in the Hybrid script fallback and executes unchanged; Strict binary modules reject it because the export contract requires PowerShell execution. Strict modules also reject ScriptsToProcess and script-based NestedModules; Hybrid records those hooks as runtime fallback. Required contained assemblies, format files, type files, and scripts must exist; named external assemblies remain manifest references rather than local files. Every staged manifest or dot-source path must remain inside the source root without symbolic-link or junction traversal. Binary-module generation routes non-Verb-Noun or otherwise unrepresentable wrappers to Hybrid script fallback and excludes their methods from the generated CLR assembly; Strict mode rejects them. Generated cmdlet output uses PowerShell's normal collection-enumeration contract rather than treating only arrays as pipelines; OutputType advertises an array's element type and uses object when an enumerable's element type cannot be proven. Expandable strings accept closed scalar variables and the qualified Int32/Double promotion contract, with invariant formatting and parser-preserved escaped, braced, and repeated references. Runtime-free expressions still reject subexpressions and opaque stringification callbacks because direct conversion alone cannot preserve their observations or caller-local mutation. A native-bound Hybrid method may delegate a non-variable subexpression and its stringification to the live invocation owner when the binder proves that hosted boundary; it does not claim runtime-free interpolation. Enum arguments accept only defined names from literal strings. Null-to-reference overload binding remains fallback because PowerShell may convert null to an empty string where direct CLR would preserve null. A plain CLR library contains only eligible methods and no automatic PowerShell fallback host.
The typed boundary also preserves several less-visible PowerShell contracts. Indexing a null IDictionary yields null. Generated cmdlets for simple functions consume surplus positional arguments, while advanced functions retain advanced binding behavior. An array-returning local function stays on the Hybrid script path when a direct consumer would observe PowerShell pipeline scalarization. Observable SwitchParameter members or CLR identity likewise stay on the script path even though safe boolean control flow compiles. Hybrid composition keeps cross-file declaration timing conservative and removes its private dispatcher state before authored wildcard variable exports are evaluated.
Strict typed executables may request Trimmed or NativeAot optimization. Both require a RID-specific, self-contained, single-artifact build; NativeAOT already emits the native executable directly and does not enable MSBuild's separate single-file bundler. Packaged PowerShell executables are rejected because trimming a dynamic PowerShell runtime is not a safe default. Native AOT is therefore a deployment option only for the proven typed subset, not a promise that arbitrary PowerShell can be converted to native code.