Types/PSAdapter.Class/PSAdapter.class.ps1

<#
.SYNOPSIS
    This contains the PSAdapter class.
.DESCRIPTION
    This contains the definition of the PSAdapter class and other classes used to build a CmdletAdapter.
#>



<#
.SYNOPSIS
    The Query Builder for the PSAdapter class.
.DESCRIPTION
    The Query Builder for the PSAdapter class.

    Any CmdletAdapter that wants to work with instances needs to implement a QueryBuilder class.
    
    The QueryBuilder class is responsible for building the query that will be used to filter the instances.
#>
 
class PSAdapterQueryBuilder : Microsoft.PowerShell.Cmdletization.QueryBuilder {
    # The query options.
    [Collections.IDictionary] $QueryOption = [Ordered]@{}
    # The query filter list.
    [Collections.Generic.List[PSObject]] $QueryFilterList = [Collections.Generic.List[PSObject]]::new()
    # The adapter.
    [PSAdapter] $Adapter
    # PSAdapterQueryBuilder constructor.
    PSAdapterQueryBuilder(
        # The adapter.
        [PSAdapter]$adapter
    ) {        
        $this.Adapter = $adapter
    }
    AddQueryOption([string] $name, [object] $value) {
        $this.QueryOption[$name] = $value
        $this.Adapter.Cmdlet.WriteVerbose("Added query option '$name' with value '$value'")
    }
    ExcludeByProperty([string] $propertyName, [Collections.IEnumerable]$ExcludePropertyValue, [object] $propertyValue, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch] $behaviorOnNoMatch) {
        $This.QueryFilterList.Add([PSCustomObject]([Ordered]@{
            FilterType = "ExcludeByProperty"
        } + $PSBoundParameters))
        $this.Adapter.Cmdlet.WriteVerbose("Excluded by property '$propertyName' with value '$propertyValue'")
    }
    FilterByAssociation([object]$AssociatedInstance, [string]$AssociationName, [string]$SourceRole, [string]$ResultRole, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch]$behaviorOnNoMatch) {
        $this.Adapter.Cmdlet.WriteVerbose("Filtered by association '$AssociationName' with associated instance '$AssociatedInstance'")
    }
    FilterByProperty([string] $propertyName, [Collections.IEnumerable]$AllowedPropertyValue, [bool] $wildcardsEnabled, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch] $behaviorOnNoMatch) {
        $This.QueryFilterList.Add([PSCustomObject]([Ordered]@{
            FilterType = "FilterByProperty"
        } + $PSBoundParameters))
        $this.Adapter.Cmdlet.WriteVerbose("Filtered property value '$propertyName' with value '$AllowedPropertyValue'")
    }
    FilterByMinPropertyValue([string]$propertyName, [object]$MinValue, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch] $behaviorOnNoMatch) {
        $This.QueryFilterList.Add([PSCustomObject]([Ordered]@{
            FilterType = "FilterByMinPropertyValue"
        } + $PSBoundParameters))
        $this.Adapter.Cmdlet.WriteVerbose("Filtered by MinValue value '$propertyName' with value '$MinValue'")
    }
    FilterByMaxPropertyValue([string]$propertyName, [object]$MaxValue, [Microsoft.PowerShell.Cmdletization.BehaviorOnNoMatch] $behaviorOnNoMatch) {
        $This.QueryFilterList.Add([PSCustomObject]([Ordered]@{
            FilterType = "FilterByMaxPropertyValue"
        } + $PSBoundParameters))
        $this.Adapter.Cmdlet.WriteVerbose("Filtered by MaxValue '$propertyName' with value '$MaxValue'")
    }
    [bool] MatchesFilters([object]$Instance) {
        $QueryBuilder = $this
        :nextQueryFilter foreach ($queryFilterItem in $QueryBuilder.QueryFilterList) {            
            $InstancePropertyValue = $Instance.$($queryFilterItem.PropertyName)
            switch ($queryFilterItem.FilterType) {
                FilterByProperty {
                    if ($queryFilterItem.WildcardsEnabled) {
                        foreach ($wildcard in $queryFilterItem.AllowedPropertyValue) {
                            if ($InstancePropertyValue -like $wildcard) {                                
                                continue nextQueryFilter
                            }
                        }
                        return $false
                    } else {
                        if ($InstancePropertyValue -notin $queryFilterItem.AllowedPropertyValue) {
                            if ($queryFilterItem.BehaviorOnNoMatch -eq 'ReportErrors') {
                                $this.Adapter.Cmdlet.WriteError("Property value $($queryFilterItem.PropertyName) is not in the allowed list")                             
                            }
                            return $false
                        }
                    }
                    
                    
                }
                FilterByMinPropertyValue {
                    if ($InstancePropertyValue -lt $queryFilterItem.MinValue) {
                        if ($queryFilterItem.BehaviorOnNoMatch -eq 'ReportErrors') {
                            $this.Adapter.Cmdlet.WriteError("Property value $($queryFilterItem.PropertyName) is less than the minimum value")                                    
                        }
                        return $false
                    }
                }
                FilterByMaxPropertyValue {
                    if ($InstancePropertyValue -gt $queryFilterItem.MaxValue) {
                        if ($queryFilterItem.BehaviorOnNoMatch -eq 'ReportErrors') {
                            $this.Adapter.Cmdlet.WriteError("Property value $($queryFilterItem.PropertyName) is greater than the maximum value")                                    
                        }
                        return $false
                    }
                }
                ExcludeByProperty {
                    if ($queryFilterItem.WildcardsEnabled) {
                        foreach ($wildcard in $queryFilterItem.ExcludePropertyValue) {
                            if ($InstancePropertyValue -like $wildcard) {                                
                                return $false
                            }
                        }
                    } else {
                        if ($InstancePropertyValue -in $queryFilterItem.ExcludePropertyValue) {
                            if ($queryFilterItem.BehaviorOnNoMatch -eq 'ReportErrors') {
                                $this.Adapter.Cmdlet.WriteError("Property value $($queryFilterItem.PropertyName) is in the exclude list")                                    
                            }
                            return $false
                        }
                    }                    
                }
            }            
        }
        return $true
    }
}


class PSAdapterBase : Microsoft.PowerShell.Cmdletization.CmdletAdapter[PSObject]
{
    [Microsoft.PowerShell.Cmdletization.QueryBuilder] GetQueryBuilder() {
        $this.Cmdlet.WriteVerbose("Getting query builder")
        $queryBuilder = [PSAdapterQueryBuilder]::new($this)        
        return $queryBuilder
    }
    [object] $ResolvedClass = $null
    [Collections.Generic.List[Threading.Tasks.Task]] $Tasks = [Collections.Generic.List[Threading.Tasks.Task]]::new()
    static [Ordered] $ResolvedClasses = [Ordered]@{}
    ResolveClass() {
        if ($This::ResolvedClasses[$this.ClassName]) {
            $this.ResolvedClass = $This::ResolvedClasses[$this.ClassName]
            return
        }
        if ($this.ClassName -as [type]) {
            $this.ResolvedClass = $this.ClassName -as [type]
        }
        elseif ( $(
            $typeExists = [type]::GetType($this.ClassName, $false, $true)
            $typeExists
        )) {
            $this.ResolvedClass = $typeExists
        }
        elseif ($(
            $foundCommand = $this.Cmdlet.SessionState.InvokeCommand.GetCommand($this.ClassName, 'Cmdlet,Function,Alias')
            $foundCommand
        )) {
            $this.ResolvedClass = $foundCommand
        } elseif ($(
            $foundVariable = $this.Cmdlet.SessionState.PSVariable.Get($this.ClassName)
            $foundVariable
        )) {
            $this.ResolvedClass = $foundVariable.Value
        }
        if ($this.ResolvedClass) {
            $This::ResolvedClasses[$this.ClassName] = $this.ResolvedClass
        }
        $this.Cmdlet.WriteVerbose("Class resolved to $($this.ResolvedClass)")
    }

    [object] GetDynamicParameters() {        
        
        $this.Cmdlet.WriteVerbose("Getting dynamic parameters")
        [Management.Automation.RuntimeDefinedParameterDictionary] $runtimeParameters = 
            [Management.Automation.RuntimeDefinedParameterDictionary]::new()
        $this.ResolveClass()
        if ($this.ResolvedClass) {
            if ($this.ResolvedClass -is [Management.Automation.CommandInfo]) {
                foreach ($parameter in $this.ResolvedClass.Parameters.Values) {
                    if ($this.Cmdlet.Parameters.ContainsKey($parameter.Name)) {
                        continue
                    }
                    [Management.Automation.RuntimeDefinedParameter]::new($parameter.Name, $parameter.ParameterType, $parameter.Attributes)                    
                }
                
            }
        }
        return $null
    }
    
    [object[]] GetInstances() {
        return @(
            $hashCodeList = [Collections.Generic.List[int]]::new()
            foreach ($var in Get-Variable) {
                $varHashCode = 
                    if ($var.Value.GetHashCode) { $var.Value.GetHashCode() }
                    else { continue }                
                if ($hashCodeList.Contains($varHashCode)) { continue }
                if ($this.ResolvedClass -is [type] -and 
                    $var.Value -is $this.ResolvedClass) {
                    $var.Value
                    $hashCodeList.Add($varHashCode)
                }
                elseif ($var.pstypenames -match $(
                    if ($this.ClassName -match '^/' -and $this.ClassName -match '/$') {
                        [Regex]::new($this.ClassName -replace '^/' -replace '/$', 'IgnoreCase,IgnorePatternWhitespace','00:00:01')
                    } else {
                        [Regex]::Escape($this.ClassName)
                    }                    
                )) {
                    $var.Value
                }
            }
        )        
    }
    [psobject] GetMethodSplat([Microsoft.PowerShell.Cmdletization.MethodInvocationInfo] $MethodInvocationInfo) {
        $methodName = $MethodInvocationInfo.MethodName
        $myInvocationNamePattern = "^$([Regex]::Escape($this.Cmdlet.MyInvocation.InvocationName))"
        $myScriptPrivateDataKeys = $this.PrivateData.Keys -match $myInvocationNamePattern -match "[\.\:]{1,2}$([Regex]::Escape($methodName))"
        $methodScriptBlock = if ($myScriptPrivateDataKeys) {
            [ScriptBlock]::Create(($this.PrivateData[$myScriptPrivateDataKeys] -join [Environment]::NewLine))
        } elseif ($MethodName -match '^\s{0,}\{' -and $methodName -match '\}\s{0,}$') {
            $methodName = $methodName -replace '^\s{0,}\{' -replace '\}\s{0,}$'
            [scriptblock]::Create($methodName)
        }
        if (-not $methodScriptBlock) {            
            return $null
        }
            
        $methodScriptParameters = 
            $methodScriptBlock.Ast.FindAll({
                param($ast) 
                $ast -is [Management.Automation.Language.ParameterAst] -or 
                ($ast -is [System.Management.Automation.Language.AttributeAst] -and $ast.TypeName.Name -eq 'Alias')
            }, $false)
        $methodScriptParameterNames = 
            @(foreach ($ast in $methodScriptParameters) {
                if ($ast -is [Management.Automation.Language.ParameterAst]) {
                    $ast.Name.VariablePath.UserPath
                } else {
                    $ast.PostionalArguments.Value
                }
            })
        $methodSplat = [Ordered]@{}
        foreach ($parameter in $MethodInvocationInfo.Parameters) {
            if ($methodScriptParameterNames -contains $parameter.Name -and $null -ne $parameter.Value) {
                $methodSplat[$parameter.Name] = $parameter.Value
            }
        }
        $methodSplat.psobject.properties.add([psnoteproperty]::new('Command', $methodScriptBlock))
        return $methodSplat
    
    }
    BeginProcessing() {
        $this.Cmdlet.WriteVerbose("Beginning processing") 
    }
    StopProcessing() {
        $this.Cmdlet.WriteVerbose("Stopping processing")
    }    
    ProcessRecord([Microsoft.PowerShell.Cmdletization.QueryBuilder]$QueryBuilder) {
        $this.Cmdlet.WriteVerbose("Processing query builder")
        foreach ($instance in $this.GetInstances()) {
            if ($QueryBuilder.MatchesFilters($instance)) {
                $this.Cmdlet.WriteObject($instance)
            }
        }
        
    }
    ProcessRecord([Microsoft.PowerShell.Cmdletization.QueryBuilder]$QueryBuilder, 
        [Microsoft.PowerShell.Cmdletization.MethodInvocationInfo] $MethodInvocationInfo, 
        [bool]$PassThru) {
        $this.Cmdlet.WriteVerbose("Processing query and method")
        foreach ($instance in $this.GetInstances()) {
            if ($QueryBuilder.MatchesFilters($instance)) {
                $this.ProcessRecord($instance, $MethodInvocationInfo, $PassThru)
            }
        }        
    }
    ProcessRecord(        
        [psobject]$Instance, 
        [Microsoft.PowerShell.Cmdletization.MethodInvocationInfo]$MethodInvocationInfo, 
        [bool]$PassThru
    ) {
        $methodSplat = $this.GetMethodSplat($MethodInvocationInfo)
        $instanceMember = $instance.psobject.Members[$MethodInvocationInfo.MethodName]
        $methodOutput = 
            if ($methodSplat) {
                $this.Cmdlet.SessionState.PSVariable.Set('this', $this)
                $this.Cmdlet.SessionState.PSVariable.Set('_', $Instance)
                . $methodSplat.Command @methodSplat
            }
            elseif ($instanceMember) {
                
                if ($instanceMember -isnot [Management.Automation.PSEvent]) {
                    $invokeArgs = @(foreach ($methodParameter in $MethodInvocationInfo.Parameters) {
                        $methodParameter.Value
                    })
                    $instanceMember.Invoke($invokeArgs)
                } else {
                    $registerObjectEvent = $this.Cmdlet.SessionState.InvokeCommand.GetCommand('Register-ObjectEvent','Cmdlet')
                    $registerObjectEventSplat = [Ordered]@{} + $this.Cmdlet.MyInvocation.BoundParameters
                    foreach ($parameterKey in @($registerObjectEventSplat.Keys)) {
                        if (-not $registerObjectEvent.Parameters[$parameterKey]) {
                            $registerObjectEventSplat.Remove($parameterKey)
                        }                    
                    }
                    Register-ObjectEvent @registerObjectEventSplat
                }            
            }
        if ($methodOutput -is [Threading.Tasks.Task]) {
            $this.Tasks.Add($methodOutput)
        }
        if ($PassThru) {
            if ($methodOutput) {
                if ($methodOutput -isnot [Threading.Tasks.Task]) {                
                    $this.Cmdlet.WriteObject($methodOutput)
                }                
            } else {
                $this.Cmdlet.WriteObject($instance)
            }            
        }
    }
    ProcessRecord([Microsoft.PowerShell.Cmdletization.MethodInvocationInfo] $methodInvocationInfo) {
        $this.Cmdlet.WriteVerbose("Processing static method invocation: $($methodInvocationInfo.MethodName)")
        $methodSplat = $this.GetMethodSplat($MethodInvocationInfo)
        if ($methodSplat) {
            $methodCommandOutput = . $methodSplat.Command @methodSplat *>&1
            
            $this.Cmdlet.WriteObject($methodCommandOutput, $true)
            
            return
        }
        if ($this.ResolvedClass -is [type]) {
            $myMember = $this.ResolvedClass::($methodInvocationInfo.MethodName)            
            $myMemberResult = 
                if ($myMember -is [Management.Automation.PSMethod]) {
                    $invokeArgs = @(foreach ($methodParameter in $MethodInvocationInfo.Parameters) {
                        $methodParameter.Value
                    })
                    $myMember.Invoke($invokeArgs)
                } elseif ($null -ne $myMember) {
                    $myMember
                }
            
            $this.Cmdlet.WriteObject($myMemberResult)
        } else {
            $typeData = Get-TypeData -TypeName $this.ClassName
            if (-not $typeData) { return }
        }
    }    
    EndProcessing() {
        $this.Cmdlet.WriteVerbose("Ending processing")
        if ($this.Tasks.Count) {
            foreach ($task in $this.Tasks) {
                $this.Cmdlet.WriteObject($task.Result)
            }
        }        
    }    
}

class PSAdapter : PSAdapterBase, Management.Automation.IDynamicParameters {}