internal/tepp/scripts/input.ps1
Register-PSFTeppScriptblock -Name PSFramework-Input-ObjectProperty -ScriptBlock { #region Utility Functions function Get-Property { [CmdletBinding()] param ( $InputObject ) if (-not $InputObject) { return @{ } } $properties = @{ } switch ($InputObject.GetType().FullName) { #region Variables or static input 'System.Management.Automation.Language.CommandExpressionAst' { switch ($InputObject.Expression.GetType().Name) { 'BinaryExpressionAst' { # Return an empty array. A binary expression ast means pure numbers as input, no properties return @{ } } 'VariableExpressionAst' { $members = Get-Variable -Name $InputObject.Expression.VariablePath.UserPath -ValueOnly -ErrorAction Ignore | Select-Object -First 1 | Get-Member -MemberType Properties foreach ($member in $members) { try { $typeString = $member.Definition.Split(" ")[0] $memberType = [type]$typeString $typeKnown = $true } catch { $memberType = $null $typeKnown = $false } $properties[$member.Name] = [pscustomobject]@{ Name = $member.Name Type = $memberType TypeKnown = $typeKnown } } return $properties } 'MemberExpressionAst' { try { $members = Get-Variable -Name $InputObject.Expression.Expression.VariablePath.UserPath -ValueOnly -ErrorAction Ignore | Where-Object $InputObject.Expression.Member.Value -ne $null | Select-Object -First 1 -ExpandProperty $InputObject.Expression.Member.Value -ErrorAction Ignore | Get-Member -MemberType Properties } catch { return $properties } foreach ($member in $members) { try { $typeString = $member.Definition.Split(" ")[0] $memberType = [type]$typeString $typeKnown = $true } catch { $memberType = $null $typeKnown = $false } $properties[$member.Name] = [pscustomobject]@{ Name = $member.Name Type = $memberType TypeKnown = $typeKnown } } return $properties } 'ArrayLiteralAst' { # Not yet supported return @{ } } } #region Input from Variable if ($pipelineAst.PipelineElements[$inputIndex].Expression -and $pipelineAst.PipelineElements[0].Expression[0].VariablePath) { $properties += ((Get-Variable -Name $pipelineAst.PipelineElements[0].Expression[0].VariablePath.UserPath -ValueOnly) | Select-Object -First 1 | Get-Member -MemberType Properties).Name } #endregion Input from Variable } #endregion Variables or static input #region Input from Command 'System.Management.Automation.Language.CommandAst' { $command = Get-Command $InputObject.CommandElements[0].Value -ErrorAction Ignore if ($command -is [System.Management.Automation.AliasInfo]) { $command = $command.ResolvedCommand } if (-not $command) { return $properties } foreach ($type in $command.OutputType.Type) { foreach ($member in $type.GetMembers("Instance, Public")) { # Skip all members except Fields (4) or Properties (16) if (-not ($member.MemberType -band 20)) { continue } $properties[$member.Name] = [pscustomobject]@{ Name = $member.Name Type = $null TypeKnown = $true } if ($member.PropertyType) { $properties[$member.Name].Type = $member.PropertyType } else { $properties[$member.Name].Type = $member.FieldType } } foreach ($propertyExtensionItem in ([PSFramework.TabExpansion.TabExpansionHost]::InputCompletionTypeData[$type.FullName])) { $properties[$propertyExtensionItem.Name] = $propertyExtensionItem } } #region Command Specific Inserts foreach ($propertyExtensionItem in ([PSFramework.TabExpansion.TabExpansionHost]::InputCompletionCommandData[$command.Name])) { $properties[$propertyExtensionItem.Name] = $propertyExtensionItem } #endregion Command Specific Inserts return $properties } #endregion Input from Command # Unknown / Unexpected input default { return @{ } } } } function Update-Property { [CmdletBinding()] param ( [Hashtable] $Property, $Step ) $properties = @{ } #region Expand Property if ($Step.ExpandProperty) { if (-not ($Property[$Step.ExpandProperty])) { return $properties } $expanded = $Property[$Step.ExpandProperty] if (-not $expanded.TypeKnown) { return $properties } foreach ($member in $expanded.Type.GetMembers("Instance, Public")) { # Skip all members except Fields (4) or Properties (16) if (-not ($member.MemberType -band 20)) { continue } $properties[$member.Name] = [pscustomobject]@{ Name = $member.Name Type = $null TypeKnown = $true } if ($member.PropertyType) { $properties[$member.Name].Type = $member.PropertyType } else { $properties[$member.Name].Type = $member.FieldType } } foreach ($propertyExtensionItem in ([PSFramework.TabExpansion.TabExpansionHost]::InputCompletionTypeData[$expanded.Type.FullName])) { $properties[$propertyExtensionItem.Name] = $propertyExtensionItem } return $properties } #endregion Expand Property # In keep input mode, the original properties will not be affected in any way if ($Step.KeepInputObject) { $properties = $Property.Clone() } $filterProperties = $Step.Properties | Where-Object Kind -eq "Property" #region Select What to keep if (-not $Step.KeepInputObject) { :main foreach ($propertyItem in $Property.Values) { #region Excluded Properties foreach ($exclusion in $Step.Excluded) { if ($propertyItem.Name -like $exclusion) { continue main } } #endregion Excluded Properties foreach ($stepProperty in $filterProperties) { if ($propertyItem.Name -like $stepProperty.Name) { $properties[$propertyItem.Name] = $propertyItem continue main } } } } #endregion Select What to keep #region Adding Content :main foreach ($stepProperty in $Step.Properties) { switch ($stepProperty.Kind) { 'Property' { if ($stepProperty.Filter) { continue main } if ($properties[$stepProperty.Name]) { continue main } foreach ($exclusion in $Step.Excluded) { if ($stepProperty.Name -like $exclusion) { continue main } } $properties[$stepProperty.Name] = [PSCustomObject]@{ Name = $stepProperty.Name Type = $null TypeKnown = $false } continue main } 'CalculatedProperty' { if ($properties[$stepProperty.Name]) { continue main } $properties[$stepProperty.Name] = [PSCustomObject]@{ Name = $stepProperty.Name Type = $null TypeKnown = $false } continue main } 'ScriptProperty' { if ($properties[$stepProperty.Name]) { continue main } $properties[$stepProperty.Name] = [PSCustomObject]@{ Name = $stepProperty.Name Type = $null TypeKnown = $false } continue main } 'AliasProperty' { if ($properties[$stepProperty.Name]) { continue main } $properties[$stepProperty.Name] = [PSCustomObject]@{ Name = $stepProperty.Name Type = $null TypeKnown = $false } if ($properties[$stepProperty.Target].TypeKnown) { $properties[$stepProperty.Name].Type = $properties[$stepProperty.Target].Type $properties[$stepProperty.Name].TypeKnown = $properties[$stepProperty.Target].TypeKnown } continue main } } } #endregion Adding Content $properties } function Read-SelectObject { [CmdletBinding()] param ( [System.Management.Automation.Language.CommandAst] $Ast, [string] $CommandName = 'Select-Object' ) $results = [pscustomobject]@{ Ast = $Ast BoundParameters = @() Property = @() ExcludeProperty = @() ExpandProperty = '' ScriptProperty = @() AliasProperty = @() KeepInputObject = $false } #region Process Ast if ($Ast.CommandElements.Count -gt 1) { $index = 1 $parameterName = '' $position = 0 while ($index -lt $Ast.CommandElements.Count) { $element = $Ast.CommandElements[$index] switch ($element.GetType().FullName) { 'System.Management.Automation.Language.CommandParameterAst' { $parameterName = $element.ParameterName if ($parameterName -like "k*") { $results.KeepInputObject = $true } $results.BoundParameters += $element.ParameterName break } 'System.Management.Automation.Language.StringConstantExpressionAst' { if (-not $parameterName) { switch ($position) { 0 { $results.Property = $element } 1 { $results.AliasProperty = $element } 2 { $results.ScriptProperty = $element } } $position = $position + 1 } if ($parameterName -like "pr*") { $results.Property = $element } if ($parameterName -like "exp*") { $results.ExpandProperty = $element.Value } if ($parameterName -like "exc*") { $results.ExcludeProperty = $element.Value } if ($parameterName -like "a*") { $results.AliasProperty = $element } if ($parameterName -like "scriptp*") { $results.ScriptProperty = $element } $parameterName = '' break } 'System.Management.Automation.Language.ArrayLiteralAst' { if (-not $parameterName) { switch ($position) { 0 { $results.Property = $element.Elements } 1 { $results.AliasProperty = $element.Elements } 2 { $results.ScriptProperty = $element.Elements } } $position = $position + 1 } if ($parameterName -like "pr*") { $results.Property = $element.Elements } if ($parameterName -like "exp*") { $results.ExpandProperty = $element.Elements.Value } if ($parameterName -like "exc*") { $results.ExcludeProperty = $element.Elements.Value } if ($parameterName -like "a*") { $results.AliasProperty = $element.Elements } if ($parameterName -like "scriptp*") { $results.ScriptProperty = $element.Elements } $parameterName = '' break } 'System.Management.Automation.Language.ConstantExpressionAst' { if (-not $parameterName) { switch ($position) { 0 { $results.Property = $element } 1 { $results.AliasProperty = $element } 2 { $results.ScriptProperty = $element } } $position = $position + 1 } if ($parameterName -like "pr*") { $results.Property = $element } if ($parameterName -like "exp*") { $results.ExpandProperty = $element.Value.ToString() } if ($parameterName -like "exc*") { $results.ExcludeProperty = $element.Value.ToString() } if ($parameterName -like "a*") { $results.AliasProperty = $element } if ($parameterName -like "scriptp*") { $results.ScriptProperty = $element } $parameterName = '' break } 'System.Management.Automation.Language.HashtableAst' { if (-not $parameterName) { switch ($position) { 0 { $results.Property = $element } 1 { $results.AliasProperty = $element } 2 { $results.ScriptProperty = $element } } $position = $position + 1 } if ($parameterName -like "pr*") { $results.Property = $element } if ($parameterName -like "a*") { $results.AliasProperty = $element } if ($parameterName -like "scriptp*") { $results.ScriptProperty = $element } $parameterName = '' break } default { $parameterName = '' } } $index = $index + 1 } } #endregion Process Ast #region Convert Results $resultsProcessed = [pscustomobject]@{ HasIncludeFilter = $false RawResult = $results Properties = @() Excluded = $results.ExcludeProperty ExpandProperty = $results.ExpandProperty KeepInputObject = $results.KeepInputObject } switch ($CommandName) { #region Select-Object 'Select-Object' { #region Properties foreach ($element in $results.Property) { switch ($element.GetType().FullName) { 'System.Management.Automation.Language.HashtableAst' { try { $resultsProcessed.Properties += [pscustomobject]@{ Name = ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$|^L$|^Label$' | Select-Object -First 1).Item2.PipelineElements[0].Expression.Value Kind = "CalculatedProperty" Type = "Unknown" Filter = $false } } catch { } } default { if ($element.Value -match "\*") { $resultsProcessed.HasIncludeFilter = $true } $resultsProcessed.Properties += [pscustomobject]@{ Name = $element.Value.ToString() Kind = "Property" Type = "Inherited" Filter = $element.Value -match "\*" } } } } #endregion Properties } #endregion Select-Object #region Select-PSFObject 'Select-PSFObject' { #region Properties foreach ($element in $results.Property) { switch ($element.GetType().FullName) { 'System.Management.Automation.Language.HashtableAst' { try { $resultsProcessed.Properties += [pscustomobject]@{ Name = ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$|^L$|^Label$' | Select-Object -First 1).Item2.PipelineElements[0].Expression.Value Kind = "CalculatedProperty" Type = "Unknown" Filter = $false } } catch { } } default { try { $parameterItem = ([PSFramework.Parameter.SelectParameter]$element.Value).Value } catch { continue } if ($parameterItem -is [System.String]) { if ($parameterItem -match "\*") { $resultsProcessed.HasIncludeFilter = $true } $resultsProcessed.Properties += [pscustomobject]@{ Name = $parameterItem Kind = "Property" Type = "Inherited" Filter = $parameterItem -match "\*" } } else { $resultsProcessed.Properties += [pscustomobject]@{ Name = $parameterItem Kind = "CalculatedProperty" Type = "Unknown" Filter = $false } } } } } #endregion Properties #region Script Properties foreach ($scriptProperty in $results.ScriptProperty) { switch ($scriptProperty.GetType().FullName) { 'System.Management.Automation.Language.HashtableAst' { foreach ($name in $scriptProperty.KeyValuePairs.Item1.Value) { $resultsProcessed.Properties += [pscustomobject]@{ Name = $name Kind = "ScriptProperty" Type = "Unknown" Filter = $false } } } default { try { $propertyValue = [PSFramework.Parameter.SelectScriptPropertyParameter]$scriptProperty.Value } catch { continue } $resultsProcessed.Properties += [pscustomobject]@{ Name = $propertyValue.Value.Name Kind = "ScriptProperty" Type = "Unknown" Filter = $false } } } } #endregion Script Properties #region Alias Properties foreach ($scriptProperty in $results.AliasProperty) { switch ($scriptProperty.GetType().FullName) { 'System.Management.Automation.Language.HashtableAst' { foreach ($aliasPair in $scriptProperty.KeyValuePairs) { $resultsProcessed.Properties += [pscustomobject]@{ Name = $aliasPair.Item1.Value Kind = "AliasProperty" Type = "Alias" Filter = $false Target = $aliasPair.Item2.PipelineElements.Expression.Value } } } default { try { $propertyValue = [PSFramework.Parameter.SelectAliasParameter]$scriptProperty.Value } catch { continue } $resultsProcessed.Properties += [pscustomobject]@{ Name = $propertyValue.Aliases[0].Name Kind = "AliasProperty" Type = "Alias" Filter = $false Target = $propertyValue.Aliases[0].ReferencedMemberName } } } } #endregion Alias Properties } #endregion Select-PSFObject } #endregion Convert Results $resultsProcessed } #endregion Utility Functions # Grab Pipeline and find starting index [System.Management.Automation.Language.PipelineAst]$pipelineAst = $commandAst.parent $index = $pipelineAst.PipelineElements.IndexOf($commandAst) # If it's the first item: Skip, no input to parse if ($index -lt 1) { return } $inputIndex = $index - 1 $steps = @{ } #region Step backwards through the pipeline until the definitive object giver is found :outmain while ($true) { if ($pipelineAst.PipelineElements[$inputIndex].CommandElements) { # Resolve command and fail if it breaks $command = $null # Work around the ? alias for Where-Object being a wildcard if ($pipelineAst.PipelineElements[$inputIndex].CommandElements[0].Value -eq "?") { $command = Get-Alias -Name "?" | Where-Object Name -eq "?" } else { $command = Get-Command $pipelineAst.PipelineElements[$inputIndex].CommandElements[0].Value -ErrorAction Ignore } if ($command -is [System.Management.Automation.AliasInfo]) { $command = $command.ResolvedCommand } if (-not $command) { return } switch ($command.Name) { 'Where-Object' { $steps[$inputIndex] = [pscustomobject]@{ Index = $inputIndex Skip = $true Type = 'Where' } $inputIndex = $inputIndex - 1 continue outmain } 'Tee-Object' { $steps[$inputIndex] = [pscustomobject]@{ Index = $inputIndex Skip = $true Type = 'Tee' } $inputIndex = $inputIndex - 1 continue outmain } 'Sort-Object' { $steps[$inputIndex] = [pscustomobject]@{ Index = $inputIndex Skip = $true Type = 'Sort' } $inputIndex = $inputIndex - 1 continue outmain } #region Select-Object 'Select-Object' { $selectObject = Read-SelectObject -Ast $pipelineAst.PipelineElements[$inputIndex] -CommandName 'Select-Object' $steps[$inputIndex] = [pscustomobject]@{ Index = $inputIndex Skip = $false Type = 'Select' Data = $selectObject } if ($selectObject.HasIncludeFilter -or ($selectObject.Properties.Type -eq "Inherited") -or $selectObject.ExpandProperty) { $inputIndex = $inputIndex - 1 continue outmain } break outmain } #endregion Select-Object #region Select-PSFObject 'Select-PSFObject' { $selectObject = Read-SelectObject -Ast $pipelineAst.PipelineElements[$inputIndex] -CommandName 'Select-PSFObject' $steps[$inputIndex] = [pscustomobject]@{ Index = $inputIndex Skip = $false Type = 'PSFSelect' Data = $selectObject } if ($selectObject.HasIncludeFilter -or ($selectObject.Properties.Type -eq "Inherited") -or $selectObject.ExpandProperty) { $inputIndex = $inputIndex - 1 continue outmain } break outmain } #endregion Select-PSFObject default { break outmain } } } else { break } } #endregion Step backwards through the pipeline until the definitive object giver is found # Catch moving through _all_ options in the pipeline if ($inputIndex -lt 0) { return } #region Process resulting / reaching properties $properties = Get-Property -InputObject $pipelineAst.PipelineElements[$inputIndex] $inputIndex = $inputIndex + 1 while ($inputIndex -lt $index) { # Eliminate preliminary follies if (-not $steps[$inputIndex]) { $inputIndex = $inputIndex + 1; continue } if ($steps[$inputIndex].Skip) { $inputIndex = $inputIndex + 1; continue } # Process the current step, then move on unless done $properties = Update-Property -Property $properties -Step $steps[$inputIndex].Data $inputIndex = $inputIndex + 1 } #endregion Process resulting / reaching properties $properties.Keys | Sort-Object } |