Last active
September 17, 2025 18:19
-
-
Save romero126/ea56740b4e81ba0d82d133b1f17450c2 to your computer and use it in GitHub Desktop.
ScriptBlockAstLdapFilter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using namespace System.Collections.Generic | |
| using namespace System.Management.Automation.Language | |
| param ( | |
| [Parameter(Position = 0, ValueFromPipeline)] | |
| $InputObject = { $_.SamAccountName -eq 'Clark*' } | |
| ) | |
| class ScriptBlockLdapFilter : AstVisitor2 { | |
| [System.Text.StringBuilder] $LdapFilter = [System.Text.StringBuilder]::new() | |
| [ScriptBlockAst]$scriptBlockAst | |
| [string] ToString() { | |
| return '('+$this.LdapFilter.ToString()+')' | |
| } | |
| ScriptBlockLdapFilter([ScriptBlockAst]$scriptBlockAst) { | |
| # Constructor that takes a ScriptBlockAst | |
| # It initializes the scriptBlockAst property and starts visiting the AST | |
| $this.scriptBlockAst = $scriptBlockAst | |
| $scriptBlockAst.Visit($this) | |
| } | |
| [AstVisitAction] VisitParenExpression([ParenExpressionAst]$ast) { | |
| # ParenExpressionAst often looks like { ( ... ) } | |
| # We expand this to LDAP filter syntax by adding surrounding parentheses | |
| $null = $this.LdapFilter.Append('(') | |
| $ast.Pipeline.Visit($this) | |
| $null = $this.LdapFilter.Append(')') | |
| return [AstVisitAction]::SkipChildren | |
| } | |
| [AstVisitAction] VisitVariableExpression([VariableExpressionAst]$ast) { | |
| # VariableExpressionAst often looks like { $_ } | |
| # Throws if we try to expand beyond { $_ } | |
| # We can probably do this in MemberExpressionAst but this way grabs more edge cases | |
| if ($ast.VariablePath.UserPath -ne '_') { | |
| throw "Cannot evaluate { $($ast.VariablePath.UserPath) } only `$_ is a supported variable." | |
| return [AstVisitAction]::StopVisit | |
| } | |
| return [AstVisitAction]::Continue | |
| } | |
| [AstVisitAction] VisitMemberExpression([MemberExpressionAst]$ast) { | |
| # MemberExpressionAst often looks like { $_.Property } | |
| # Member.Extent should always be a StringConstantExpressionAst | |
| if ($ast.Member -isnot [StringConstantExpressionAst]) { | |
| throw "Cannot evaluate { $($ast.Member.Extent.Text) } only property access like `$_.Property is supported." | |
| return [AstVisitAction]::StopVisit | |
| } | |
| $null = $this.LdapFilter.Append($ast.Member.Extent.Text) | |
| return [AstVisitAction]::Continue | |
| } | |
| [AstVisitAction] VisitConstantExpression([ConstantExpressionAst]$ast) { | |
| # ConstantExpressionAst often looks like { 15 } | |
| if ($ast.Parent -is [MemberExpressionAst]) { | |
| return [AstVisitAction]::SkipChildren | |
| } | |
| $null = $this.LdapFilter.Append($ast.Value) | |
| return [AstVisitAction]::SkipChildren | |
| } | |
| [AstVisitAction] VisitStringConstantExpression([StringConstantExpressionAst]$ast) { | |
| # StringConstantExpressionAst often looks like { 'value' } or { "value" } | |
| # We only want to process string constants that are not part of a MemberExpressionAst | |
| if ($ast.Parent -is [MemberExpressionAst]) { | |
| return [AstVisitAction]::SkipChildren | |
| } | |
| $null = $this.LdapFilter.Append($ast.Value) | |
| return [AstVisitAction]::Continue | |
| } | |
| [AstVisitAction] VisitBinaryExpression([BinaryExpressionAst]$ast) { | |
| # We need to throw for certain edge cases here. | |
| # Constants cannot be evaluated against a nested expression | |
| if ( | |
| $ast.Left -is [BinaryExpressionAst] -and | |
| ( | |
| $ast.Right -is [StringConstantExpressionAst] -or | |
| $ast.Right -is [ConstantExpressionAst] | |
| ) | |
| ) { | |
| throw "Cannot evaluate { $($ast.Right.Extent.Text) } against a nested expression." | |
| return [AstVisitAction]::StopVisit | |
| } | |
| if ($ast.Left -is [StringConstantExpressionAst] -and $ast.Right -is [BinaryExpressionAst]) { | |
| throw "Cannot evaluate { $($ast.Left.Extent.Text) } against a nested expression." | |
| return [AstVisitAction]::StopVisit | |
| } | |
| # Right side should always be a string constant | |
| if ( | |
| $ast.Right -isnot [ConstantExpressionAst] -and | |
| $ast.Right -isnot [StringConstantExpressionAst] -and | |
| $ast.Right -isnot [BinaryExpressionAst] -and | |
| $ast.Right -isnot [ParenExpressionAst] | |
| ) { | |
| throw "Cannot evaluate { $($ast.Right.Extent.Text) } Right side must be of type constant or nested expression" | |
| return [AstVisitAction]::StopVisit | |
| } | |
| # Apply the And and Or Symbols before processing children | |
| # This ensures nested expressions are properly formatted | |
| if ($ast.Operator -eq 'and') { | |
| $null = $this.LdapFilter.Append('&') | |
| } elseif ($ast.Operator -like 'or') { | |
| $null = $this.LdapFilter.Append('|') | |
| } | |
| # Process Left and Right sides of the binary expression | |
| $null = $this.LdapFilter.Append('(') | |
| $ast.Left.Visit($this) | |
| # Apply the LDAP operator between left and right | |
| switch ($ast.Operator) { | |
| 'And' { } | |
| 'Or' { } | |
| 'ILike' { $null = $this.LdapFilter.Append('=') } | |
| 'Like' { $null = $this.LdapFilter.Append('=') } | |
| 'Ieq' { $null = $this.LdapFilter.Append('=') } | |
| 'Eq' { $null = $this.LdapFilter.Append('=') } | |
| 'Ine' { $null = $this.LdapFilter.Append('!=') } | |
| 'Ne' { $null = $this.LdapFilter.Append('!=') } | |
| 'INotLike' { $null = $this.LdapFilter.Append('!=') } | |
| 'NotLike' { $null = $this.LdapFilter.Append('!=') } | |
| 'Match' { $null = $this.LdapFilter.Append('~=') } | |
| 'IMatch' { $null = $this.LdapFilter.Append('~=') } | |
| 'NotMatch' { $null = $this.LdapFilter.Append('!~=') } | |
| 'INotMatch'{ $null = $this.LdapFilter.Append('!~=') } | |
| default { | |
| throw "Unsupported operator: $($ast.Operator)" | |
| return [AstVisitAction]::StopVisit | |
| } | |
| } | |
| $ast.Right.Visit($this) | |
| $null = $this.LdapFilter.Append(')') | |
| return [AstVisitAction]::SkipChildren | |
| } | |
| } | |
| $scriptBlockAst = $InputObject.Ast | |
| $visitor = [ScriptBlockLdapFilter]::new($scriptBlockAst) | |
| $visitor.ToString() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment