src/public/Core/Get-AitherMember.ps1
|
#Requires -Version 7.0 # StrictMode-safe member access. # # Under `Set-StrictMode -Version Latest` (PS 7+), accessing a MISSING key of a # hashtable OR a missing property of an object via dot notation THROWS # ("property X cannot be found"). AitherZero config/manifest data are hashtables # with many optional sections (Automation, Services, Options, ...), so bare # `$config.Automation.Foo` is unsafe. Use this to read optional members safely: # returns the value if present, else $null. Chain it for nested access. # # WHY THIS LIVES IN public/ AND NOT private/ (D-844 - do not move it back). # Every automation script dot-sources `library/automation-scripts/_init.ps1`, # which runs `Import-Module <AitherZero.psd1> -Force`. `-Force` REMOVES and # re-imports the module - including while `Invoke-AitherPlaybook` is mid-run # executing a step. From that moment the still-executing runner belongs to a # session state that no longer exists, so its PRIVATE helpers are gone, while # exported functions still resolve through the global scope. That is why the # runner died with "The term 'Get-AitherMember' is not recognized" at the # START of the step AFTER the first step that ran a script - earlier steps # succeeded, then the loop's very first statement failed. # Exporting makes this helper survive the re-import. The `-Force` itself is # the underlying root cause and is tracked separately (D-848). function Get-AitherMember { [CmdletBinding()] param( [Parameter(Position = 0, ValueFromPipeline)]$Object, [Parameter(Position = 1, Mandatory)][string]$Name ) # The body MUST live in a `process` block. The parameter declares # ValueFromPipeline, but without one PowerShell runs the body once, in the # end block, with $Object holding only the LAST piped item - so # `$configs | Get-AitherMember -Name 'x'` silently returned a single value # instead of one per input, and every earlier item was dropped without a # word. Harmless while this was private and only ever called positionally; # it became a real trap the moment the function was exported (D-844). # (`$objs | Get-AitherMember 'x'` is still an error, correctly: piped, the # positional slot 0 is taken by the pipeline object, so a bare 'x' cannot # bind to -Name. Pass -Name explicitly when piping.) process { if ($null -eq $Object) { return $null } if ($Object -is [System.Collections.IDictionary]) { if ($Object.Contains($Name)) { return $Object[$Name] } return $null } $prop = $Object.PSObject.Properties[$Name] if ($prop) { return $prop.Value } return $null } } |