Modules/IdLE.Core/Private/Get-IdleStepRegistry.ps1
|
function Get-IdleStepRegistry { [CmdletBinding()] param( [Parameter()] [AllowNull()] [object] $Providers ) # Registry maps workflow Step.Type -> handler function name (string). # # Trust boundary: # - The registry is a host-provided extension point. It is not loaded from workflow configuration. # - Workflows are data-only and must not contain executable code. # # Security / secure defaults: # - Only string handlers (function names) are supported. # - ScriptBlock handlers are intentionally rejected to avoid arbitrary code execution. $registry = [hashtable]::new([System.StringComparer]::OrdinalIgnoreCase) # Helper: Resolve a step handler name without requiring global command exports. # # Resolution order: # 1) Global command discovery (host imported a module globally) -> "Invoke-IdleStepX" # 2) Module-scoped discovery (nested/hidden module loaded) -> "ModuleName\Invoke-IdleStepX" function Resolve-IdleStepHandlerName { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $CommandName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ModuleName ) # 1) Global discovery (optional; supports hosts that import step packs globally) $cmd = Get-Command -Name $CommandName -ErrorAction SilentlyContinue if ($null -ne $cmd) { return $cmd.Name } # 2) Module-scoped discovery (supports nested modules that are not globally exported) $module = Get-Module -Name $ModuleName -All | Select-Object -First 1 if ($null -eq $module) { return $null } if ($null -ne $module.ExportedCommands -and $module.ExportedCommands.ContainsKey($CommandName)) { # Use a module-qualified command name so the engine can invoke it without relying on # global session exports. This keeps built-in steps available "within IdLE" only. return "$($module.Name)\$CommandName" } return $null } # 1) Copy host-provided StepRegistry (optional) # We support two shapes for compatibility: # - Providers.StepRegistry (hashtable) # - Providers['StepRegistry'] (hashtable) $hostRegistry = $null if ($null -ne $Providers) { if ($Providers -is [hashtable] -and $Providers.ContainsKey('StepRegistry')) { $hostRegistry = $Providers['StepRegistry'] } elseif ($Providers.PSObject.Properties.Name -contains 'StepRegistry') { $hostRegistry = $Providers.StepRegistry } } if ($null -ne $hostRegistry) { if ($hostRegistry -isnot [hashtable]) { throw [System.ArgumentException]::new('Providers.StepRegistry must be a hashtable that maps Step.Type to a function name (string).', 'Providers') } foreach ($key in $hostRegistry.Keys) { if ($null -eq $key -or [string]::IsNullOrWhiteSpace([string]$key)) { throw [System.ArgumentException]::new('Providers.StepRegistry contains an empty step type key.', 'Providers') } $value = $hostRegistry[$key] if ($value -is [scriptblock]) { throw [System.ArgumentException]::new( "Providers.StepRegistry entry for step type '$key' is a ScriptBlock. ScriptBlock handlers are not allowed. Provide a function name (string) instead.", 'Providers' ) } if ($value -isnot [string] -or [string]::IsNullOrWhiteSpace([string]$value)) { throw [System.ArgumentException]::new( "Providers.StepRegistry entry for step type '$key' must be a non-empty string (function name).", 'Providers' ) } $registry[[string]$key] = ([string]$value).Trim() } } # 2) Register built-in steps if available. # # Built-in steps are first-party step packs (e.g. IdLE.Steps.Common). They may be loaded as nested # modules by the IdLE meta module. In that case, the step commands are not necessarily exported # globally. We therefore support module-qualified handler names. if (-not $registry.ContainsKey('IdLE.Step.EmitEvent')) { $handler = Resolve-IdleStepHandlerName -CommandName 'Invoke-IdleStepEmitEvent' -ModuleName 'IdLE.Steps.Common' if (-not [string]::IsNullOrWhiteSpace($handler)) { $registry['IdLE.Step.EmitEvent'] = $handler } } if (-not $registry.ContainsKey('IdLE.Step.EnsureAttribute')) { $handler = Resolve-IdleStepHandlerName -CommandName 'Invoke-IdleStepEnsureAttribute' -ModuleName 'IdLE.Steps.Common' if (-not [string]::IsNullOrWhiteSpace($handler)) { $registry['IdLE.Step.EnsureAttribute'] = $handler } } if (-not $registry.ContainsKey('IdLE.Step.EnsureEntitlement')) { $handler = Resolve-IdleStepHandlerName -CommandName 'Invoke-IdleStepEnsureEntitlement' -ModuleName 'IdLE.Steps.Common' if (-not [string]::IsNullOrWhiteSpace($handler)) { $registry['IdLE.Step.EnsureEntitlement'] = $handler } } return $registry } |