Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Forked from romero126/Expand-String
Created December 9, 2020 06:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jaykul/4a6bad4f73ed518dfefc37ded36f511d to your computer and use it in GitHub Desktop.
Save Jaykul/4a6bad4f73ed518dfefc37ded36f511d to your computer and use it in GitHub Desktop.
function Expand-AstVariables {
param (
[Parameter(Mandatory, ValueFromPipeline)]
[System.Management.Automation.Language.Ast] $Statement,
[Parameter(Mandatory)]
[ref] $result,
[Parameter(Mandatory)]
[PSObject] $Properties
)
begin {
}
process {
if (-not $result.Value)
{
return
}
switch ($Statement.GetType().FullName)
{
"System.Management.Automation.Language.ExpandableStringExpressionAst" {
$string = $Statement.Extent.Text
$nestedExpressions = $Statement.NestedExpressions
# iterate nested expression backwards - this way we won't need to re-calculate offsets
for ($i = $nestedExpressions.Count - 1; $i -ge 0; $i--){
$subExpression = $nestedExpressions[$i]
$resolvedVariable = $subExpression
# Get start/end positions of sub expression
$startPos = $subExpression.Extent.StartScriptPosition
$endPos = $subExpression.Extent.EndScriptPosition
# Resolve Here
$resolvedVariable = Expand-AstVariables -Statement $subExpression -Result $result -Properties $Properties
if (-not $result.Value) {
# if ast Walking is not valid, REVERT to Default
return $Statement.Extent.Text
}
$resolvedValue = "{0}" -f ($resolvedVariable -join " ")
# Remove the variable expression
$string = $string.Remove(($startPos.Offset-$statement.Extent.StartOffset), $endPos.Offset - $startPos.Offset)
# Insert the resolved value
$string = $string.Insert(($startPos.Offset-$statement.Extent.StartOffset), $resolvedValue)
}
# Trim start and end as AST Adds additional Escaping
$string = $string.Substring(1, $string.Length - 2)
return $string
}
"System.Management.Automation.Language.SubExpressionAst" {
$item = $Statement.SubExpression
}
"System.Management.Automation.Language.StatementBlockAst" {
$item = $Statement.Statements
}
"System.Management.Automation.Language.PipelineAst" {
$item = $Statement.PipeLineElements
}
"System.Management.Automation.Language.CommandExpressionAst" {
$item = $Statement.Expression
}
"System.Management.Automation.Language.MemberExpressionAst" {
# Get Member
$value = Expand-AstVariables -Statement $Statement.Expression -Result $result -Properties $Properties
$member = Expand-AstVariables -Statement $Statement.Member -Result $result -Properties $Properties
if ($result -eq $false) {
return
}
return $value.$($member)
}
"System.Management.Automation.Language.VariableExpressionAst" {
return $Properties.$($Statement.VariablePath.UserPath)
}
"System.Management.Automation.Language.StringConstantExpressionAst" {
return $Statement.Value
}
"System.Management.Automation.Language.InvokeMemberExpressionAst" {
$expression = Expand-AstVariables -Statement $Statement.Expression -Result $result -Properties $Properties
$arguments = $Statement.Arguments | Expand-AstVariables -Result $result -Properties $Properties
$member = Expand-AstVariables -Statement $Statement.Member -Result $result -Properties $Properties
# Before we process we should try to fail fast, and only enable the functions we want to use
# Theres no sense trying to resolve a function if its not allowed
$isAllowListedAction = "SelectSingleNode", "SelectNodes" -contains $Statement.Member.Value
if (-not $isAllowListedAction) {
$result.Value = $false
return
}
# Validate allowed types for our AllowList
$isAllowedValueType = "System.Xml.XmlElement" -contains $expression.GetType().FullName
if (-not $isAllowedValueType) {
$result.Value = $false
return
}
if ($result -eq $false) {
return
}
# Cleanup arguments a little bit
$arguments = $arguments | ForEach-Object {
$_.TrimStart('"').TrimEnd('"')
}
try {
($expression.$Member).Invoke($arguments)
}
catch {
$result.value = $false
}
return
}
default {
$result.Value = $false
}
}
if ($false -eq $result.Value) {
return
}
$item | ForEach-Object { Expand-AstVariables -Statement $_ -Result $result -Properties $Properties }
}
end {
}
}
function Expand-StringVariables
{
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string]$String,
[Parameter(Mandatory)]
[PSObject] $Properties
)
begin {
$scanStringMethod = [System.Management.Automation.Language.Parser].GetMethod('ScanString', ('Static','NonPublic'))
if($scanStringMethod -isnot [System.Reflection.MethodInfo]) {
throw 'Unable to hook parser'
}
}
process {
$parsedString = $scanStringMethod.Invoke($null, $String) -as [System.Management.Automation.Language.ExpandableStringExpressionAst]
if (($null -eq $parsedString) -or (-not $parsedString)){
return $String
}
$result = $true
$value = Expand-AstVariables -Statement $parsedString -Result ([ref]$result) -Properties $Properties
if ($result)
{
return $value
}
return $string
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment