datum.psm1

#Region './Classes/1.DatumProvider.ps1' 0
class DatumProvider
{
    hidden [bool]$IsDatumProvider = $true

    [hashtable]ToHashTable()
    {
        $result = ConvertTo-Datum -InputObject $this
        return $result
    }

    [System.Collections.Specialized.OrderedDictionary]ToOrderedHashTable()
    {
        $result = ConvertTo-Datum -InputObject $this
        return $result
    }
}
#EndRegion './Classes/1.DatumProvider.ps1' 17
#Region './Classes/FileProvider.ps1' 0
Class FileProvider : DatumProvider
{
    hidden $Path
    hidden [hashtable] $Store
    hidden [hashtable] $DatumHierarchyDefinition
    hidden [hashtable] $StoreOptions
    hidden [hashtable] $DatumHandlers
    hidden [string] $Encoding

    FileProvider ($Path, $Store, $DatumHierarchyDefinition, $Encoding)
    {
        $this.Store = $Store
        $this.DatumHierarchyDefinition = $DatumHierarchyDefinition
        $this.StoreOptions = $Store.StoreOptions
        $this.Path = Get-Item $Path -ErrorAction SilentlyContinue
        $this.DatumHandlers = $DatumHierarchyDefinition.DatumHandlers
        $this.Encoding = $Encoding

        $Result = Get-ChildItem $path | ForEach-Object {
            if ($_.PSisContainer)
            {
                $val = [scriptblock]::Create("New-DatumFileProvider -Path `"$($_.FullName)`" -Store `$this.DataOptions -DatumHierarchyDefinition `$this.DatumHierarchyDefinition -Encoding `$this.Encoding")
                $this | Add-Member -MemberType ScriptProperty -Name $_.BaseName -Value $val
            }
            else
            {
                $val = [scriptblock]::Create("Get-FileProviderData -Path `"$($_.FullName)`" -DatumHandlers `$this.DatumHandlers -Encoding `$this.Encoding")
                $this | Add-Member -MemberType ScriptProperty -Name $_.BaseName -Value $val
            }
        }
    }
}
#EndRegion './Classes/FileProvider.ps1' 33
#Region './Classes/Node.ps1' 0
Class Node : hashtable
{
    Node([hashtable]$NodeData)
    {
        $NodeData.keys | % {
            $This[$_] = $NodeData[$_]
        }

        $this | Add-Member -MemberType ScriptProperty -Name Roles -Value {
            $PathArray = $ExecutionContext.InvokeCommand.InvokeScript('Get-PSCallStack')[2].Position.text -split '\.'
            $PropertyPath = $PathArray[2..($PathArray.count - 1)] -join '\'
            Write-Warning "Resolve $PropertyPath"

            $obj = [PSCustomObject]@{}
            $currentNode = $obj
            if ($PathArray.Count -gt 3)
            {
                foreach ($property in $PathArray[2..($PathArray.count - 2)])
                {
                    Write-Debug "Adding $Property property"
                    $currentNode | Add-Member -MemberType NoteProperty -Name $property -Value ([PSCustomObject]@{})
                    $currentNode = $currentNode.$property
                }
            }
            Write-Debug "Adding Resolved property to last object's property $($PathArray[-1])"
            $currentNode | Add-Member -MemberType NoteProperty -Name $PathArray[-1] -Value ($PropertyPath)

            return $obj
        }
    }
    static ResolveDscProperty($Path)
    {
        "Resolve-DscProperty $Path"
    }
}
#EndRegion './Classes/Node.ps1' 36
#Region './Private/Compare-Hashtable.ps1' 0
function Compare-Hashtable
{
    [CmdletBinding()]
    Param(

        $ReferenceHashtable,

        $DifferenceHashtable,

        [string[]]
        $Property = ($ReferenceHashtable.Keys + $DifferenceHashtable.Keys | Select-Object -Unique)
    )

    Write-Debug "Compare-Hashtable -Ref @{$($ReferenceHashtable.keys -join ';')} -Diff @{$($DifferenceHashtable.keys -join ';')} -Property [$($Property -join ', ')]"
    #Write-Debug "REF:`r`n$($ReferenceHashtable|ConvertTo-JSON)"
    #Write-Debug "DIFF:`r`n$($DifferenceHashtable|ConvertTo-JSON)"

    foreach ($PropertyName in $Property)
    {
        Write-Debug " Testing <$PropertyName>'s value"
        if ( ($inRef = $ReferenceHashtable.Contains($PropertyName)) -and
            ($inDiff = $DifferenceHashtable.Contains($PropertyName))
        )
        {
            if ($ReferenceHashtable[$PropertyName] -as [hashtable[]] -or $DifferenceHashtable[$PropertyName] -as [hashtable[]] )
            {
                if ( (Compare-Hashtable -ReferenceHashtable $ReferenceHashtable[$PropertyName] -DifferenceHashtable $DifferenceHashtable[$PropertyName]) )
                {
                    Write-Debug " Skipping $PropertyName...."
                    # If Compae returns something, they're not the same
                    Continue
                }
            }
            else
            {
                Write-Debug "Comparing: $($ReferenceHashtable[$PropertyName]) With $($DifferenceHashtable[$PropertyName])"
                if ($ReferenceHashtable[$PropertyName] -ne $DifferenceHashtable[$PropertyName])
                {
                    [PSCustomObject]@{
                        SideIndicator = '<='
                        PropertyName  = $PropertyName
                        Value         = $ReferenceHashtable[$PropertyName]
                    }

                    [PSCustomObject]@{
                        SideIndicator = '=>'
                        PropertyName  = $PropertyName
                        Value         = $DifferenceHashtable[$PropertyName]
                    }
                }
            }
        }
        else
        {
            Write-Debug " Property $PropertyName Not in one Side: Ref: [$($ReferenceHashtable.Keys -join ',')] | [$($DifferenceHashtable.Keys -join ',')]"
            if ($inRef)
            {
                Write-Debug "$PropertyName found in Reference hashtable"
                [PSCustomObject]@{
                    SideIndicator = '<='
                    PropertyName  = $PropertyName
                    Value         = $ReferenceHashtable[$PropertyName]
                }
            }
            else
            {
                Write-Debug "$PropertyName found in Difference hashtable"
                [PSCustomObject]@{
                    SideIndicator = '=>'
                    PropertyName  = $PropertyName
                    Value         = $DifferenceHashtable[$PropertyName]
                }
            }
        }
    }

}
#EndRegion './Private/Compare-Hashtable.ps1' 78
#Region './Private/ConvertTo-Datum.ps1' 0
function ConvertTo-Datum
{
    param (
        [Parameter(ValueFromPipeline)]
        $InputObject,

        [AllowNull()]
        $DatumHandlers = @{}
    )

    begin
    {
        $HandlerNames = $DatumHandlers.Keys
    }

    process
    {
        if ($null -eq $InputObject)
        {
            return $null
        }

        if ($InputObject -is [System.Collections.IDictionary])
        {
            $hashKeys = [string[]]$InputObject.Keys
            foreach ($Key in $hashKeys)
            {
                $InputObject[$Key] = ConvertTo-Datum -InputObject $InputObject[$Key] -DatumHandlers $DatumHandlers
            }
            # Making the Ordered Dict Case Insensitive
            ([ordered]@{} + $InputObject)
        }
        elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
        {
            $collection = @(
                foreach ($object in $InputObject)
                {
                    ConvertTo-Datum -InputObject $object -DatumHandlers $DatumHandlers
                }
            )

            , $collection
        }
        elseif (($InputObject -is [psobject] -or $InputObject -is [DatumProvider]) -and $InputObject -isnot [pscredential])
        {
            $hash = [ordered]@{}

            foreach ($property in $InputObject.PSObject.Properties)
            {
                $hash[$property.Name] = ConvertTo-Datum -InputObject $property.Value -DatumHandlers $DatumHandlers
            }

            $hash
        }
        # if There's a matching filter, process associated command and return result
        elseif ($HandlerNames -and ($result = & {
                    foreach ($Handler in $HandlerNames)
                    {
                        $FilterModule, $FilterName = $Handler -split '::'
                        if (!(Get-Module $FilterModule))
                        {
                            Import-Module $FilterModule -Force -ErrorAction Stop
                        }
                        $FilterCommand = Get-Command -ErrorAction SilentlyContinue ('{0}\Test-{1}Filter' -f $FilterModule, $FilterName)
                        if ($FilterCommand -and ($InputObject | &$FilterCommand))
                        {
                            try
                            {
                                if ($ActionCommand = Get-Command -ErrorAction SilentlyContinue ('{0}\Invoke-{1}Action' -f $FilterModule, $FilterName))
                                {
                                    $ActionParams = @{}
                                    $CommandOptions = $Datumhandlers.$handler.CommandOptions.Keys
                                    # Populate the Command's params with what's in the Datum.yml, or from variables
                                    $Variables = Get-Variable
                                    foreach ( $ParamName in $ActionCommand.Parameters.keys )
                                    {
                                        if ( $ParamName -in $CommandOptions )
                                        {
                                            $ActionParams.add($ParamName, $Datumhandlers.$handler.CommandOptions[$ParamName])
                                        }
                                        elseif ($Var = $Variables.Where{ $_.Name -eq $ParamName })
                                        {
                                            $ActionParams.Add($ParamName, $Var.Value)
                                        }
                                    }
                                    $result = (&$ActionCommand @ActionParams)
                                    $result
                                }
                            }
                            catch
                            {
                                Write-Warning "Error using Datum Handler $Handler, returning Input Object. The error was: '$($_.Exception.Message)'."
                                $InputObject
                            }
                        }
                    }
                }))
        {
            $result
        }
        else
        {
            $InputObject
        }
    }
}
#EndRegion './Private/ConvertTo-Datum.ps1' 107
#Region './Private/Get-DatumType.ps1' 0
function Get-DatumType
{
    param (
        [object]
        $DatumObject
    )

    if ($DatumObject -is [hashtable] -or $DatumObject -is [System.Collections.Specialized.OrderedDictionary])
    {
        'hashtable'
    }
    elseif ($DatumObject -isnot [string] -and $DatumObject -is [System.Collections.IEnumerable])
    {
        if ($Datumobject -as [hashtable[]])
        {
            'hash_array'
        }
        else
        {
            'baseType_array'
        }

    }
    else
    {
        'baseType'
    }

}
#EndRegion './Private/Get-DatumType.ps1' 30
#Region './Private/Get-MergeStrategyFromString.ps1' 0
function Get-MergeStrategyFromString
{
    [CmdletBinding()]
    [OutputType([hashtable])]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $MergeStrategy
    )

    <#
    MergeStrategy: MostSpecific
            merge_hash: MostSpecific
            merge_baseType_array: MostSpecific
            merge_hash_array: MostSpecific

    MergeStrategy: hash
            merge_hash: hash
            merge_baseType_array: MostSpecific
            merge_hash_array: MostSpecific
            merge_options:
            knockout_prefix: --

    MergeStrategy: Deep
            merge_hash: deep
            merge_baseType_array: Unique
            merge_hash_array: DeepTuple
            merge_options:
            knockout_prefix: --
            Tuple_Keys:
                - Name
                - Version
    #>


    Write-Debug -Message "Get-MergeStrategyFromString -MergeStrategy <$MergeStrategy>"
    switch -regex ($MergeStrategy)
    {
        '^First$|^MostSpecific$'
        {
            @{
                merge_hash           = 'MostSpecific'
                merge_baseType_array = 'MostSpecific'
                merge_hash_array     = 'MostSpecific'
            }
        }

        '^hash$|^MergeTopKeys$'
        {
            @{
                merge_hash           = 'hash'
                merge_baseType_array = 'MostSpecific'
                merge_hash_array     = 'MostSpecific'
                merge_options        = @{
                    knockout_prefix = '--'
                }
            }
        }

        '^deep$|^MergeRecursively$'
        {
            @{
                merge_hash           = 'deep'
                merge_baseType_array = 'Unique'
                merge_hash_array     = 'DeepTuple'
                merge_options        = @{
                    knockout_prefix = '--'
                    tuple_keys      = @(
                        'Name',
                        'Version'
                    )
                }
            }
        }
        default
        {
            Write-Debug -Message "Couldn't Match the strategy $MergeStrategy"
            @{
                merge_hash           = 'MostSpecific'
                merge_baseType_array = 'MostSpecific'
                merge_hash_array     = 'MostSpecific'
            }
        }
    }

}
#EndRegion './Private/Get-MergeStrategyFromString.ps1' 86
#Region './Private/Merge-DatumArray.ps1' 0
function Merge-DatumArray
{
    [CmdletBinding()]
    Param(
        $ReferenceArray,

        $DifferenceArray,

        $Strategy = @{ },

        $ChildStrategies = @{'^.*' = $Strategy },

        $StartingPath
    )

    Write-Debug "`tMerge-DatumArray -StartingPath <$StartingPath>"
    $knockout_prefix = [regex]::Escape($Strategy.merge_options.knockout_prefix).insert(0, '^')
    $HashArrayStrategy = $Strategy.merge_hash_array
    Write-Debug "`t`tHash Array Strategy: $HashArrayStrategy"
    $MergeBasetypeArraysStrategy = $Strategy.merge_basetype_array
    $MergedArray = [System.Collections.ArrayList]::new()

    $SortParams = @{}
    if ($PropertyNames = [String[]]$Strategy.merge_options.tuple_keys)
    {
        $SortParams.Add('Property', $PropertyNames)
    }

    if ($ReferenceArray -as [hashtable[]])
    {
        Write-Debug "`t`tMERGING Array of Hashtables"
        if (!$HashArrayStrategy -or $HashArrayStrategy -match 'MostSpecific')
        {
            Write-Debug "`t`tMerge_hash_arrays Disabled. value: $HashArrayStrategy"
            $MergedArray = $ReferenceArray
            if ($Strategy.sort_merged_arrays)
            {
                $MergedArray = $MergedArray | Sort-Object @SortParams
            }
            return $MergedArray
        }

        switch -Regex ($HashArrayStrategy)
        {
            '^Sum|^Add'
            {
                (@($DifferenceArray) + @($ReferenceArray)) | ForEach-Object {
                    $null = $MergedArray.add(([ordered]@{} + $_))
                }
            }

            # MergeHashesByProperties
            '^Deep|^Merge'
            {
                Write-Debug "`t`t`tStrategy for Array Items: Merge Hash By tuple`r`n"
                # look at each $RefItems in $RefArray
                # if no PropertyNames defined, use all Properties of $RefItem
                # else use defined propertyNames
                # Search for DiffItem that has the same Property/Value pairs
                # if found, Merge-Datum (or MergeHashtable?)
                # if not found, add $DiffItem to $RefArray

                # look at each $RefItems in $RefArray
                $UsedDiffItems = [System.Collections.ArrayList]::new()
                foreach ($ReferenceItem in $ReferenceArray)
                {
                    $ReferenceItem = [ordered]@{} + $ReferenceItem
                    Write-Debug "`t`t`t .. Working on Merged Element $($MergedArray.Count)`r`n"
                    # if no PropertyNames defined, use all Properties of $RefItem
                    if (!$PropertyNames)
                    {
                        Write-Debug "`t`t`t ..No PropertyName defined: Use ReferenceItem Keys"
                        $PropertyNames = $ReferenceItem.Keys
                    }
                    $MergedItem = @{} + $ReferenceItem
                    $DiffItemsToMerge = $DifferenceArray.Where{
                        $DifferenceItem = [ordered]@{} + $_
                        # Search for DiffItem that has the same Property/Value pairs than RefItem
                        $CompareHashParams = @{
                            ReferenceHashtable  = [ordered]@{} + $ReferenceItem
                            DifferenceHashtable = $DifferenceItem
                            Property            = $PropertyNames
                        }
                        (!(Compare-Hashtable @CompareHashParams))
                    }
                    Write-Debug "`t`t`t ..Items to merge: $($DiffItemsToMerge.Count)"
                    $DiffItemsToMerge | ForEach-Object {
                        $MergeItemsParams = @{
                            ParentPath          = $StartingPath
                            Strategy            = $Strategy
                            ReferenceHashtable  = $MergedItem
                            DifferenceHashtable = $_
                            ChildStrategies     = $ChildStrategies
                        }
                        $MergedItem = Merge-Hashtable @MergeItemsParams
                    }
                    # If a diff Item has been used, save it to find the unused ones
                    $null = $UsedDiffItems.AddRange($DiffItemsToMerge)
                    $null = $MergedArray.Add($MergedItem)
                }
                $UnMergedItems = $DifferenceArray | ForEach-Object {
                    if (!$UsedDiffItems.Contains($_))
                    {
                        ([ordered]@{} + $_)
                    }
                }
                if ($null -ne $UnMergedItems)
                {
                    if ($UnMergedItems -is [System.Array])
                    {
                        $null = $MergedArray.AddRange($UnMergedItems)
                    }
                    else
                    {
                        $null = $MergedArray.Add($UnMergedItems)
                    }
                }
            }

            # UniqueByProperties
            '^Unique'
            {
                Write-Debug "`t`t`tSelecting Unique Hashes accross both arrays based on Property tuples"
                # look at each $DiffItems in $DiffArray
                # if no PropertyNames defined, use all Properties of $DiffItem
                # else use defined PropertyNames
                # Search for a RefItem that has the same Property/Value pairs
                # if Nothing is found
                # add current DiffItem to RefArray

                if (!$PropertyNames)
                {
                    Write-Debug "`t`t`t ..No PropertyName defined: Use ReferenceItem Keys"
                    $PropertyNames = $ReferenceItem.Keys
                }

                $MergedArray = [System.Collections.ArrayList]::new()
                $ReferenceArray | ForEach-Object {
                    $CurrentRefItem = $_
                    if (!( $MergedArray.Where{ !(Compare-Hashtable -Property $PropertyNames -ReferenceHashtable $CurrentRefItem -DifferenceHashtable $_ ) }))
                    {
                        $null = $MergedArray.Add(([ordered]@{} + $_))
                    }
                }

                $DifferenceArray | ForEach-Object {
                    $CurrentDiffItem = $_
                    if (!( $MergedArray.Where{ !(Compare-Hashtable -Property $PropertyNames -ReferenceHashtable $CurrentDiffItem -DifferenceHashtable $_ ) }))
                    {
                        $null = $MergedArray.Add(([ordered]@{} + $_))
                    }
                }
            }
        }
    }

    $MergedArray
}
#EndRegion './Private/Merge-DatumArray.ps1' 159
#Region './Private/Merge-Hashtable.ps1' 0
function Merge-Hashtable
{
    [outputType([hashtable])]
    [cmdletBinding()]
    Param(
        # [hashtable] These should stay ordered
        $ReferenceHashtable,

        # [hashtable] These should stay ordered
        $DifferenceHashtable,

        $Strategy = @{
            merge_hash           = 'hash'
            merge_baseType_array = 'MostSpecific'
            merge_hash_array     = 'MostSpecific'
            merge_options        = @{
                knockout_prefix = '--'
            }
        },

        $ChildStrategies = @{},

        [string]
        $ParentPath
    )

    Write-Debug "`tMerge-Hashtable -ParentPath <$ParentPath>"
    # Removing Case Sensitivity while keeping ordering
    $ReferenceHashtable = [ordered]@{} + $ReferenceHashtable
    $DifferenceHashtable = [ordered]@{} + $DifferenceHashtable
    $clonedReference = [ordered]@{} + $ReferenceHashtable

    if ($Strategy.merge_options.knockout_prefix)
    {
        $KnockoutPrefix = $Strategy.merge_options.knockout_prefix
        $KnockoutPrefixMatcher = [regex]::escape($KnockoutPrefix).insert(0, '^')
    }
    else
    {
        $KnockoutPrefixMatcher = [regex]::escape('--').insert(0, '^')
    }
    Write-Debug "`t Knockout Prefix Matcher: $knockoutPrefixMatcher"

    $knockedOutKeys = $ReferenceHashtable.keys.where{ $_ -match $KnockoutPrefixMatcher }.foreach{ $_ -replace $KnockoutPrefixMatcher }
    Write-Debug "`t Knockedout Keys: [$($knockedOutKeys -join ', ')] from reference Hashtable Keys [$($ReferenceHashtable.keys -join ', ')]"

    foreach ($currentKey in $DifferenceHashtable.keys)
    {
        Write-Debug "`t CurrentKey: $currentKey"
        if ($currentKey -in $knockedOutKeys)
        {
            Write-Debug "`t`tThe Key $currentkey is knocked out from the reference Hashtable."
        }
        elseif ($currentKey -match $KnockoutPrefixMatcher -and !$ReferenceHashtable.contains(($currentKey -replace $KnockoutPrefixMatcher)))
        {
            # it's a knockout coming from a lower level key, it should only apply down from here
            Write-Debug "`t`tKnockout prefix found for $currentKey in Difference hashtable, and key not set in Reference hashtable"
            if (!$ReferenceHashtable.contains($currentKey))
            {
                Write-Debug "`t`t..adding knockout prefixed key for $curretKey to block further merges"
                $clonedReference.add($currentKey, $null)
            }
        }
        elseif (!$ReferenceHashtable.contains($currentKey) )
        {
            #if the key does not exist in reference ht, create it using the DiffHt's value
            Write-Debug "`t Added Missing Key $currentKey of value: $($DifferenceHashtable[$currentKey]) from difference HT"
            $clonedReference.add($currentKey, $DifferenceHashtable[$currentKey])
        }
        else
        {
            #the key exists, and it's not a knockout entry
            $RefHashItemValueType = Get-DatumType $ReferenceHashtable[$currentKey]
            $DiffHashItemValueType = Get-DatumType $DifferenceHashtable[$currentKey]
            Write-Debug "for Key $currentKey REF:[$RefHashItemValueType] | DIFF:[$DiffHashItemValueType]"
            if ($ParentPath)
            {
                $ChildPath = (Join-Path $ParentPath $currentKey)
            }
            else
            {
                $ChildPath = $currentKey
            }

            switch ($RefHashItemValueType)
            {
                'hashtable'
                {
                    if ($Strategy.merge_hash -eq 'deep')
                    {
                        Write-Debug "`t`t .. Merging Datums at current path $ChildPath"
                        # if there's no Merge override for the subkey's path in the (not subkeys),
                        # merge HASHTABLE with same strategy
                        # otherwise, merge Datum
                        $ChildStrategy = Get-MergeStrategyFromPath -Strategies $ChildStrategies -PropertyPath $ChildPath

                        if ($ChildStrategy.Default)
                        {
                            Write-Debug "`t`t ..Merging using the current Deep Strategy, Bypassing default"
                            $MergePerDefault = @{
                                ParentPath          = $ChildPath
                                Strategy            = $Strategy
                                ReferenceHashtable  = $ReferenceHashtable[$currentKey]
                                DifferenceHashtable = $DifferenceHashtable[$currentKey]
                                ChildStrategies     = $ChildStrategies
                            }
                            $subMerge = Merge-Hashtable @MergePerDefault
                        }
                        else
                        {
                            Write-Debug "`t`t ..Merging using Override Strategy $($ChildStrategy|ConvertTo-Json)"
                            $MergeDatumParam = @{
                                StartingPath    = $ChildPath
                                ReferenceDatum  = $ReferenceHashtable[$currentKey]
                                DifferenceDatum = $DifferenceHashtable[$currentKey]
                                Strategies      = $ChildStrategies
                            }
                            $subMerge = Merge-Datum @MergeDatumParam
                        }
                        Write-Debug "`t # Submerge $($submerge|ConvertTo-Json)."
                        $clonedReference[$currentKey] = $subMerge
                    }
                }

                'baseType'
                {
                    #do nothing to use most specific value (quicker than default)
                }

                # Default used for hash_array, baseType_array
                Default
                {
                    Write-Debug "`t .. Merging Datums at current path $ChildPath`r`n$($Strategy|ConvertTo-Json)"
                    $MergeDatumParams = @{
                        StartingPath    = $ChildPath
                        Strategies      = $ChildStrategies
                        ReferenceDatum  = $ReferenceHashtable[$currentKey]
                        DifferenceDatum = $DifferenceHashtable[$currentKey]
                    }

                    if ($clonedReference.$currentKey -is [System.Array])
                    {
                        [System.Array]$clonedReference[$currentKey] = Merge-Datum @MergeDatumParams
                    }
                    else
                    {
                        $clonedReference[$currentKey] = Merge-Datum @MergeDatumParams
                    }
                    Write-Debug "`t .. Datum Merged for path $ChildPath"
                }
            }
        }
    }

    return $clonedReference
}
#EndRegion './Private/Merge-Hashtable.ps1' 157
#Region './Public/Get-DatumRSOP.ps1' 0
function Get-DatumRsop
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [hashtable]
        $Datum,

        [Parameter(Mandatory = $true)]
        [hashtable[]]
        $AllNodes,

        [Parameter()]
        [string]
        $CompositionKey = 'Configurations',

        [Parameter()]
        [scriptblock]
        $Filter = {}
    )

    if ($Filter.ToString() -ne ([System.Management.Automation.ScriptBlock]::Create( {})).ToString())
    {
        Write-Verbose -Message "Filter: $($Filter.ToString())"
        $AllNodes = [System.Collections.Hashtable[]]$allNodes.Where($Filter)
        Write-Verbose -Message "Node count after applying filter: $($AllNodes.Count)"
    }

    foreach ($node in $AllNodes)
    {
        $rsopNode = $node.clone()

        $configurations = Lookup $CompositionKey -Node $node -DatumTree $Datum -DefaultValue @()
        if ($rsopNode.contains($CompositionKey))
        {
            $rsopNode[$CompositionKey] = $configurations
        }
        else
        {
            $rsopNode.Add($CompositionKey, $configurations)
        }

        $configurations.Foreach{
            if (-not $rsopNode.Contains($_))
            {
                $rsopNode.Add($_, (Lookup -PropertyPath $_ -DefaultValue @{} -Node $node -DatumTree $Datum))
            }
            else
            {
                $rsopNode[$_] = Lookup -PropertyPath $_ -DefaultValue @{} -Node $node -DatumTree $Datum
            }
        }

        $rsopNode
    }
}
#EndRegion './Public/Get-DatumRSOP.ps1' 57
#Region './Public/Get-FileProviderData.ps1' 0
function Get-FileProviderData
{
    [OutputType([System.Array])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Path,

        [Parameter()]
        [AllowNull()]
        [hashtable]
        $DatumHandlers = @{},

        [Parameter()]
        [ValidateSet('Ascii', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')]
        [string]
        $Encoding = 'Default'
    )

    if (-not $script:FileProviderDataCache)
    {
        $script:FileProviderDataCache = @{}
    }

    $file = Get-Item -Path $Path
    if ($script:FileProviderDataCache.ContainsKey($file.FullName) -and
        $file.LastWriteTime -eq $script:FileProviderDataCache[$file.FullName].Metadata.LastWriteTime)
    {
        Write-Verbose -Message "Getting File Provider Cache for Path: $Path"
        , $script:FileProviderDataCache[$file.FullName].Value
    }
    else
    {
        Write-Verbose -Message "Getting File Provider Data for Path: $Path"
        $data = switch ($file.Extension)
        {
            '.psd1'
            {
                Import-PowerShellDataFile -Path $file | ConvertTo-Datum -DatumHandlers $DatumHandlers
            }
            '.json'
            {
                ConvertFrom-Json -InputObject (Get-Content -Path $Path -Encoding $Encoding -Raw) | ConvertTo-Datum -DatumHandlers $DatumHandlers
            }
            '.yml'
            {
                ConvertFrom-Yaml -Yaml (Get-Content -Path $Path -Encoding $Encoding -Raw) -Ordered | ConvertTo-Datum -DatumHandlers $DatumHandlers
            }
            '.yaml'
            {
                ConvertFrom-Yaml -Yaml (Get-Content -Path $Path -Encoding $Encoding -Raw) -Ordered | ConvertTo-Datum -DatumHandlers $DatumHandlers
            }
            Default
            {
                Write-Verbose -Message "File extension $($file.Extension) not supported. Defaulting on RAW."
                Get-Content -Path $Path -Encoding $Encoding -Raw
            }
        }

        $script:FileProviderDataCache[$file.FullName] = @{
            Metadata = $file
            Value    = $data
        }
        , $data
    }
}
#EndRegion './Public/Get-FileProviderData.ps1' 68
#Region './Public/Get-MergeStrategyFromPath.ps1' 0
function Get-MergeStrategyFromPath
{
    [CmdletBinding()]
    Param(
        $Strategies,

        $PropertyPath
    )
    Write-Debug "`tGet-MergeStrategyFromPath -PropertyPath <$PropertyPath> -Strategies [$($Strategies.keys -join ', ')], count $($Strategies.count)"
    # Select Relevant strategy
    # Use exact path match first
    # or try Regex in order
    if ($Strategies.($PropertyPath))
    {
        $StrategyKey = $PropertyPath
        Write-Debug "`t Strategy found for exact key $StrategyKey"
    }
    elseif ($Strategies.keys -and
        ($StrategyKey = [string]($Strategies.keys.where{ $_.StartsWith('^') -and $_ -as [regex] -and $PropertyPath -match $_ } | Select-Object -First 1))
    )
    {
        Write-Debug "`t Strategy matching regex $StrategyKey"
    }
    else
    {
        Write-Debug "`t No Strategy found"
        return
    }

    Write-Debug "`t StrategyKey: $StrategyKey"
    if ( $Strategies[$StrategyKey] -is [string])
    {
        Write-Debug "`t Returning Strategy $StrategyKey from String '$($Strategies[$StrategyKey])'"
        Get-MergeStrategyFromString $Strategies[$StrategyKey]
    }
    else
    {
        Write-Debug "`t Returning Strategy $StrategyKey of type '$($Strategies[$StrategyKey].Strategy)'"
        $Strategies[$StrategyKey]
    }
}
#EndRegion './Public/Get-MergeStrategyFromPath.ps1' 42
#Region './Public/Invoke-TestHandlerAction.ps1' 0
function Invoke-TestHandlerAction
{
    [OutputType([string])]
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $Password,

        [Parameter()]
        [object]
        $Test,

        [Parameter()]
        [object]
        $Datum
    )

    @"
Action: $handler
Node: $($Node|fl *|Out-String)
Params:
$($PSBoundParameters | ConvertTo-Json)
"@


}
#EndRegion './Public/Invoke-TestHandlerAction.ps1' 27
#Region './Public/Merge-Datum.ps1' 0
function Merge-Datum
{
    [OutputType([System.Array])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $StartingPath,

        [Parameter(Mandatory = $true)]
        [object]
        $ReferenceDatum,

        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [object]
        $DifferenceDatum,

        [Parameter()]
        [hashtable]
        $Strategies = @{
            '^.*' = 'MostSpecific'
        }
    )

    Write-Debug -Message "Merge-Datum -StartingPath <$StartingPath>"
    $strategy = Get-MergeStrategyFromPath -Strategies $Strategies -PropertyPath $startingPath -Verbose

    Write-Verbose -Message " Merge Strategy: @$($strategy | ConvertTo-Json)"

    $referenceDatumType = Get-DatumType -DatumObject $ReferenceDatum
    $differenceDatumType = Get-DatumType -DatumObject $DifferenceDatum

    if ($referenceDatumType -ne $differenceDatumType)
    {
        Write-Warning -Message "Cannot merge different types in path '$StartingPath' REF:[$referenceDatumType] | DIFF:[$differenceDatumType]$($DifferenceDatum.GetType()) , returning most specific Datum."
        return $ReferenceDatum
    }

    if ($strategy -is [string])
    {
        $strategy = Get-MergeStrategyFromString -MergeStrategy $strategy
    }

    switch ($referenceDatumType)
    {
        'BaseType'
        {
            return $ReferenceDatum
        }

        'hashtable'
        {
            $mergeParams = @{
                ReferenceHashtable  = $ReferenceDatum
                DifferenceHashtable = $DifferenceDatum
                Strategy            = $strategy
                ParentPath          = $StartingPath
                ChildStrategies     = $Strategies
            }

            if ($strategy.merge_hash -match '^MostSpecific$|^First')
            {
                return $ReferenceDatum
            }
            else
            {
                Merge-Hashtable @mergeParams
            }
        }

        'baseType_array'
        {
            switch -Regex ($strategy.merge_baseType_array)
            {
                '^MostSpecific$|^First'
                {
                    return $ReferenceDatum
                }

                '^Unique'
                {
                    if ($regexPattern = $strategy.merge_options.knockout_prefix)
                    {
                        $regexPattern = $regexPattern.insert(0, '^')
                        $result = @(($ReferenceDatum + $DifferenceDatum).Where{ $_ -notmatch $regexPattern } | Select-Object -Unique)
                        , $result
                    }
                    else
                    {
                        $result = @(($ReferenceDatum + $DifferenceDatum) | Select-Object -Unique)
                        , $result
                    }

                }

                '^Sum|^Add'
                {
                    #--> $ref + $diff -$kop
                    if ($regexPattern = $strategy.merge_options.knockout_prefix)
                    {
                        $regexPattern = $regexPattern.insert(0, '^')
                        , (($ReferenceDatum + $DifferenceDatum).Where{ $_ -notMatch $regexPattern })
                    }
                    else
                    {
                        , ($ReferenceDatum + $DifferenceDatum)
                    }
                }

                Default
                {
                    return (, $ReferenceDatum)
                }
            }
        }

        'hash_array'
        {
            $MergeDatumArrayParams = @{
                ReferenceArray  = $ReferenceDatum
                DifferenceArray = $DifferenceDatum
                Strategy        = $strategy
                ChildStrategies = $Strategies
                StartingPath    = $StartingPath
            }

            switch -Regex ($strategy.merge_hash_array)
            {
                '^MostSpecific|^First'
                {
                    return $ReferenceDatum
                }

                '^UniqueKeyValTuples'
                {
                    #--> $ref + $diff | ? % key in Tuple_Keys -> $ref[Key] -eq $diff[key] is not already int output
                    , (Merge-DatumArray @MergeDatumArrayParams)
                }

                '^DeepTuple|^DeepItemMergeByTuples'
                {
                    #--> $ref + $diff | ? % key in Tuple_Keys -> $ref[Key] -eq $diff[key] is merged up
                    , (Merge-DatumArray @MergeDatumArrayParams)
                }

                '^Sum'
                {
                    #--> $ref + $diff
                    (@($DifferenceArray) + @($ReferenceArray)).Foreach{
                        $null = $MergedArray.Add(([ordered]@{} + $_))
                    }
                    , $MergedArray
                }

                Default
                {
                    return , $ReferenceDatum
                }
            }
        }
    }
}
#EndRegion './Public/Merge-Datum.ps1' 164
#Region './Public/New-DatumFileProvider.ps1' 0
function New-DatumFileProvider
{
    [CmdletBinding()]
    param (
        [Parameter()]
        [Alias('DataOptions')]
        [AllowNull()]
        [object]
        $Store,

        [Parameter()]
        [AllowNull()]
        [hashtable]
        $DatumHierarchyDefinition = @{},

        [Parameter()]
        [string]
        $Path = $Store.StoreOptions.Path,

        [Parameter()]
        [ValidateSet('Ascii', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')]
        [string]
        $Encoding = 'Default'
    )

    if (-not $DatumHierarchyDefinition)
    {
        $DatumHierarchyDefinition = @{}
    }

    [FileProvider]::new($Path, $Store, $DatumHierarchyDefinition, $Encoding)
}
#EndRegion './Public/New-DatumFileProvider.ps1' 33
#Region './Public/New-DatumStructure.ps1' 0
function New-DatumStructure
{
    [OutputType([hashtable])]
    [CmdletBinding(DefaultParameterSetName = 'FromConfigFile')]

    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'DatumHierarchyDefinition')]
        [Alias('Structure')]
        [hashtable]
        $DatumHierarchyDefinition,

        [Parameter(Mandatory = $true, ParameterSetName = 'FromConfigFile')]
        [System.IO.FileInfo]
        $DefinitionFile,

        [Parameter()]
        [ValidateSet('Ascii', 'BigEndianUnicode', 'Default', 'Unicode', 'UTF32', 'UTF7', 'UTF8')]
        [string]
        $Encoding = 'Default'
    )

    switch ($PSCmdlet.ParameterSetName)
    {
        'DatumHierarchyDefinition'
        {
            if ($DatumHierarchyDefinition.Contains('DatumStructure'))
            {
                Write-Debug -Message 'Loading Datum from Parameter'
            }
            elseif ($DatumHierarchyDefinition.Path)
            {
                $datumHierarchyFolder = $DatumHierarchyDefinition.Path
                Write-Debug -Message "Loading default Datum from given path $datumHierarchyFolder"
            }
            else
            {
                Write-Warning -Message 'Desperate attempt to load Datum from Invocation origin...'
                $callStack = Get-PSCallStack
                $datumHierarchyFolder = $callStack[-1].PSScriptRoot
                Write-Warning -Message " ---> $datumHierarchyFolder"
            }
        }

        'FromConfigFile'
        {
            if ((Test-Path -Path $DefinitionFile))
            {
                $DefinitionFile = (Get-Item -Path $DefinitionFile -ErrorAction Stop)
                Write-Debug -Message "File $DefinitionFile found. Loading..."
                $DatumHierarchyDefinition = Get-FileProviderData -Path $DefinitionFile.FullName -Encoding $Encoding
                if (-not $DatumHierarchyDefinition.Contains('ResolutionPrecedence'))
                {
                    throw 'Invalid Datum Hierarchy Definition'
                }
                $datumHierarchyFolder = $DefinitionFile.Directory.FullName
                Write-Debug -Message "Datum Hierachy Parent folder: $datumHierarchyFolder"
            }
            else
            {
                throw 'Datum Hierarchy Configuration not found'
            }
        }
    }

    $root = @{}
    if ($datumHierarchyFolder -and -not $DatumHierarchyDefinition.DatumStructure)
    {
        $structures = foreach ($store in (Get-ChildItem -Directory -Path $datumHierarchyFolder))
        {
            @{
                StoreName     = $store.BaseName
                StoreProvider = 'Datum::File'
                StoreOptions  = @{
                    Path = $store.FullName
                }
            }
        }

        if ($DatumHierarchyDefinition.Contains('DatumStructure'))
        {
            $DatumHierarchyDefinition['DatumStructure'] = $structures
        }
        else
        {
            $DatumHierarchyDefinition.Add('DatumStructure', $structures)
        }
    }

    # Define the default hierachy to be the StoreNames, when nothing is specified
    if ($datumHierarchyFolder -and -not $DatumHierarchyDefinition.ResolutionPrecedence)
    {
        if ($DatumHierarchyDefinition.Contains('ResolutionPrecedence'))
        {
            $DatumHierarchyDefinition['ResolutionPrecedence'] = $structures.StoreName
        }
        else
        {
            $DatumHierarchyDefinition.Add('ResolutionPrecedence', $structures.StoreName)
        }
    }
    # Adding the Datum Definition to Root object
    $root.Add('__Definition', $DatumHierarchyDefinition)

    foreach ($store in $DatumHierarchyDefinition.DatumStructure)
    {
        $storeParams = @{
            Store    = (ConvertTo-Datum ([hashtable]$store).clone())
            Path     = $store.StoreOptions.Path
            Encoding = $Encoding
        }

        # Accept Module Specification for Store Provider as String (unversioned) or Hashtable
        if ($store.StoreProvider -is [string])
        {
            $storeProviderModule, $storeProviderName = $store.StoreProvider -split '::'
        }
        else
        {
            $storeProviderModule = $store.StoreProvider.ModuleName
            $storeProviderName = $store.StoreProvider.ProviderName
            if ($store.StoreProvider.ModuleVersion)
            {
                $storeProviderModule = @{
                    ModuleName    = $storeProviderModule
                    ModuleVersion = $store.StoreProvider.ModuleVersion
                }
            }
        }

        if (-not ($module = Get-Module -Name $storeProviderModule -ErrorAction SilentlyContinue))
        {
            $module = Import-Module $storeProviderModule -Force -ErrorAction Stop -PassThru
        }
        $moduleName = ($module | Select-Object -First 1).Name

        $newProviderCmd = Get-Command ('{0}\New-Datum{1}Provider' -f $moduleName, $storeProviderName)

        if ($storeParams.Path -and -not [System.IO.Path]::IsPathRooted($storeParams.Path) -and $datumHierarchyFolder)
        {
            Write-Debug -Message 'Replacing Store Path with AbsolutePath'
            $storePath = Join-Path -Path $datumHierarchyFolder -ChildPath $storeParams.Path -Resolve -ErrorAction Stop
            $storeParams['Path'] = $storePath
        }

        if ($newProviderCmd.Parameters.Keys -contains 'DatumHierarchyDefinition')
        {
            Write-Debug -Message 'Adding DatumHierarchyDefinition to Store Params'
            $storeParams.Add('DatumHierarchyDefinition', $DatumHierarchyDefinition)
        }

        $storeObject = &$newProviderCmd @storeParams
        Write-Debug -Message "Adding key $($store.StoreName) to Datum root object"
        $root.Add($store.StoreName, $storeObject)
    }

    #return the Root Datum hashtable
    $root
}
#EndRegion './Public/New-DatumStructure.ps1' 159
#Region './Public/Resolve-Datum.ps1' 0
function Resolve-Datum
{
    [OutputType([System.Array])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $PropertyPath,

        [Parameter(Position = 1)]
        [Alias('Node')]
        [object]
        $Variable = $ExecutionContext.InvokeCommand.InvokeScript('$Node'),

        [Parameter()]
        [string]
        $VariableName = 'Node',

        [Parameter()]
        [Alias('DatumStructure')]
        [object]
        $DatumTree = $ExecutionContext.InvokeCommand.InvokeScript('$ConfigurationData.Datum'),

        [Parameter(ParameterSetName = 'UseMergeOptions')]
        [Alias('SearchBehavior')]
        [hashtable]
        $Options,

        [Parameter()]
        [Alias('SearchPaths')]
        [string[]]
        $PathPrefixes = $DatumTree.__Definition.ResolutionPrecedence,

        [Parameter()]
        [int]
        $MaxDepth = $(
            if ($mxdDpth = $DatumTree.__Definition.default_lookup_options.MaxDepth)
            {
                $mxdDpth
            }
            else
            {
                -1
            })
    )

    # Manage lookup options:
    <#
    default_lookup_options Lookup_options options (argument) Behaviour
                MostSpecific for ^.*
    Present default_lookup_options + most Specific if not ^.*
        Present lookup_options + Default to most Specific if not ^.*
            Present options + Default to Most Specific if not ^.*
    Present Present Lookup_options + Default for ^.* if !Exists
    Present Present options + Default for ^.* if !Exists
        Present Present options override lookup options + Most Specific if !Exists
    Present Present Present options override lookup options + default for ^.*


    +========================+================+====================+============================================================+
    | default_lookup_options | Lookup_options | options (argument) | Behaviour |
    +========================+================+====================+============================================================+
    | | | | MostSpecific for ^.* |
    +------------------------+----------------+--------------------+------------------------------------------------------------+
    | Present | | | default_lookup_options + most Specific if not ^.* |
    +------------------------+----------------+--------------------+------------------------------------------------------------+
    | | Present | | lookup_options + Default to most Specific if not ^.* |
    +------------------------+----------------+--------------------+------------------------------------------------------------+
    | | | Present | options + Default to Most Specific if not ^.* |
    +------------------------+----------------+--------------------+------------------------------------------------------------+
    | Present | Present | | Lookup_options + Default for ^.* if !Exists |
    +------------------------+----------------+--------------------+------------------------------------------------------------+
    | Present | | Present | options + Default for ^.* if !Exists |
    +------------------------+----------------+--------------------+------------------------------------------------------------+
    | | Present | Present | options override lookup options + Most Specific if !Exists |
    +------------------------+----------------+--------------------+------------------------------------------------------------+
    | Present | Present | Present | options override lookup options + default for ^.* |
    +------------------------+----------------+--------------------+------------------------------------------------------------+

    If there's no default options, auto-add default options of mostSpecific merge, and tag as 'default'
    if there's a default options, use that strategy and tag as 'default'
    if the options implements ^.*, do not add Default_options, and do not tag

    1. Defaults to Most Specific
    2. Allow setting your own default, with precedence for non-default options
    3. Overriding ^.* without tagging it as default (always match unless)

    #>


    Write-Debug -Message "Resolve-Datum -PropertyPath <$PropertyPath> -Node $($Node.Name)"
    # Make options an ordered case insensitive variable
    if ($Options)
    {
        $Options = [ordered]@{} + $Options
    }

    if (-not $DatumTree.__Definition.default_lookup_options)
    {
        $default_options = Get-MergeStrategyFromString
        Write-Verbose -Message ' Default option not found in Datum Tree'
    }
    else
    {
        if ($DatumTree.__Definition.default_lookup_options -is [string])
        {
            $default_options = Get-MergeStrategyFromString -MergeStrategy $DatumTree.__Definition.default_lookup_options
        }
        else
        {
            $default_options = $DatumTree.__Definition.default_lookup_options
        }
        #TODO: Add default_option input validation
        Write-Verbose -Message " Found default options in Datum Tree of type $($default_options.Strategy)."
    }

    if ($DatumTree.__Definition.lookup_options)
    {
        Write-Debug -Message ' Lookup options found.'
        $lookup_options = @{} + $DatumTree.__Definition.lookup_options
    }
    else
    {
        $lookup_options = @{}
    }

    # Transform options from string to strategy hashtable
    foreach ($optKey in ([string[]]$lookup_options.Keys))
    {
        if ($lookup_options[$optKey] -is [string])
        {
            $lookup_options[$optKey] = Get-MergeStrategyFromString -MergeStrategy $lookup_options[$optKey]
        }
    }

    foreach ($optKey in ([string[]]$Options.Keys))
    {
        if ($Options[$optKey] -is [string])
        {
            $Options[$optKey] = Get-MergeStrategyFromString -MergeStrategy $Options[$optKey]
        }
    }

    # using options if specified or lookup_options otherwise
    if (-not $Options)
    {
        $Options = $lookup_options
    }

    # Add default strategy for ^.* if not present, at the end
    if (([string[]]$Options.Keys) -notcontains '^.*')
    {
        # Adding Default flag
        $default_options['Default'] = $true
        $Options.Add('^.*', $default_options)
    }

    # Create the variable to be used as Pivot in prefix path
    if ($Variable -and $VariableName)
    {
        Set-Variable -Name $VariableName -Value $Variable -Force
    }

    # Scriptblock in path detection patterns
    $pattern = '(?<opening><%=)(?<sb>.*?)(?<closure>%>)'
    $propertySeparator = [System.IO.Path]::DirectorySeparatorChar
    $splitPattern = [regex]::Escape($propertySeparator)

    $depth = 0
    $mergeResult = $null

    # Get the strategy for this path, to be used for merging
    $startingMergeStrategy = Get-MergeStrategyFromPath -PropertyPath $PropertyPath -Strategies $Options

    # Walk every search path in listed order, and return datum when found at end of path
    foreach ($searchPrefix in $PathPrefixes)
    {
        #through the hierarchy

        $arraySb = [System.Collections.ArrayList]@()
        $currentSearch = Join-Path -Path $searchPrefix -ChildPath $PropertyPath
        Write-Verbose -Message ''
        Write-Verbose -Message " Lookup <$currentSearch> $($Node.Name)"
        #extract script block for execution into array, replace by substition strings {0},{1}...
        $newSearch = [regex]::Replace($currentSearch, $pattern, {
                param($match)
                $expr = $match.groups['sb'].value
                $index = $arraySb.Add($expr)
                "`$({$index})"
            }, @('IgnoreCase', 'SingleLine', 'MultiLine'))

        $pathStack = $newSearch -split $splitPattern
        # Get value for this property path
        $datumFound = Resolve-DatumPath -Node $Node -DatumTree $DatumTree -PathStack $pathStack -PathVariables $arraySb

        if ($datumFound -is [DatumProvider])
        {
            $datumFound = $datumFound.ToOrderedHashTable()
        }

        Write-Debug -Message " Depth: $depth; Merge options = $($Options.count)"

        #Stop processing further path at first value in 'MostSpecific' mode (called 'first' in Puppet hiera)
        if ($null -ne $datumFound -and ($startingMergeStrategy.Strategy -match '^MostSpecific|^First'))
        {
            return $datumFound
        }
        elseif ($null -ne $datumFound)
        {

            if ($null -eq $mergeResult)
            {
                $mergeResult = $datumFound
            }
            else
            {
                $MergeParams = @{
                    StartingPath    = $PropertyPath
                    ReferenceDatum  = $mergeResult
                    DifferenceDatum = $datumFound
                    Strategies      = $Options
                }
                $mergeResult = Merge-Datum @MergeParams
            }
        }

        #if we've reached the Maximum Depth allowed, return current result and stop further execution
        if ($depth -eq $MaxDepth)
        {
            Write-Debug " Max depth of $MaxDepth reached. Stopping."
            , $mergeResult
            return
        }
    }
    , $mergeResult
}
#EndRegion './Public/Resolve-Datum.ps1' 236
#Region './Public/Resolve-DatumPath.ps1' 0
function Resolve-DatumPath
{
    [OutputType([System.Array])]
    [CmdletBinding()]
    param (
        [Parameter()]
        [Alias('Variable')]
        $Node,

        [Parameter()]
        [Alias('DatumStructure')]
        [object]
        $DatumTree,

        [Parameter()]
        [string[]]
        $PathStack,

        [Parameter()]
        [System.Collections.ArrayList]
        $PathVariables
    )

    $currentNode = $DatumTree
    $propertySeparator = '.' #[System.IO.Path]::DirectorySeparatorChar
    $index = -1
    Write-Debug -Message "`t`t`t"

    foreach ($stackItem in $PathStack)
    {
        $index++
        $relativePath = $PathStack[0..$index]
        Write-Debug -Message "`t`t`tCurrent Path: `$Datum$propertySeparator$($relativePath -join $propertySeparator)"
        $remainingStack = $PathStack[$index..($PathStack.Count - 1)]
        Write-Debug -Message "`t`t`t`tbranch of path Left to walk: $propertySeparator$($remainingStack[1..$remainingStack.Length] -join $propertySeparator)"

        if ($stackItem -match '\{\d+\}')
        {
            Write-Debug -Message "`t`t`t`t`tReplacing expression $stackItem"
            $stackItem = [scriptblock]::Create(($stackItem -f ([string[]]$PathVariables)) ).Invoke()
            Write-Debug -Message ($stackItem | Format-List * | Out-String)
            $pathItem = $stackItem
        }
        else
        {
            $pathItem = $currentNode.($ExecutionContext.InvokeCommand.ExpandString($stackItem))
        }

        # if $pathItem is $null, it won't have subkeys, stop execution for this Prefix
        if ($null -eq $pathItem)
        {
            Write-Verbose -Message " NULL FOUND at `$Datum.$($ExecutionContext.InvokeCommand.ExpandString(($relativePath -join $propertySeparator) -f [string[]]$PathVariables))`t`t <`$Datum$propertySeparator$(($relativePath -join $propertySeparator) -f [string[]]$PathVariables)>"
            if ($remainingStack.Count -gt 1)
            {
                Write-Verbose -Message "`t`t----> before: $propertySeparator$($ExecutionContext.InvokeCommand.ExpandString(($remainingStack[1..($remainingStack.Count-1)] -join $propertySeparator)))`t`t <$(($remainingStack[1..($remainingStack.Count-1)] -join $propertySeparator) -f [string[]]$PathVariables)>"
            }
            return $null
        }
        else
        {
            $currentNode = $pathItem
        }


        if ($remainingStack.Count -eq 1)
        {
            Write-Verbose -Message " VALUE found at `$Datum$propertySeparator$($ExecutionContext.InvokeCommand.ExpandString(($relativePath -join $propertySeparator) -f [string[]]$PathVariables))"
            , $currentNode
        }

    }
}
#EndRegion './Public/Resolve-DatumPath.ps1' 73
#Region './Public/Test-TestHandlerFilter.ps1' 0
function Test-TestHandlerFilter
{
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        [Parameter(ValueFromPipeline = $true)]
        [object]$InputObject
    )

    $InputObject -is [string] -and $InputObject -match '^\[TEST=[\w\W]*\]$'
}
#EndRegion './Public/Test-TestHandlerFilter.ps1' 12