Auditing Expressions Part 2

I needed expression expansion to work reliably!

Background

In this post, I shared a cool proof of concept that would allow you to view the code used to create results, along with the basic components broken down that were used. But it turns out that only very simple expressions could be operated against.

If I had any sub-expressions like this: ($one + 2) * 3, things would break because of the parenthesis. E.g. if it splits by operators for math (+-*/), then it would try to execute the expression ($one and 2) and 3. That’s no good! I could try to counteract by also stripping those parenthesis, but it just seemed like a messy hole that I should avoid. What other methods, constructors, types, etc. was I going to start fighting against?

Instead, I opted to redo the function. I added in some other stuff like pipeline support to improve QoL. Without further ado, here is version 2!

The New and Improved Add-MemberProperties

Instead of trying to split on arithmetic operators (+-/*), I will choose instead to only expand variables that are non-objects and non-hashtables.

function Add-MemberProperties {
    param(
        [Parameter(Position = 0)]
        [string]$Property,
        [parameter(ValueFromPipeline)]
        [scriptblock]$InputObject,
        $Object,
        [switch]$Force
    )

    $errors = $null
    $tokens = [System.Management.Automation.PSParser]::Tokenize(
        $InputObject, [ref]$errors
    ) | ForEach-Object { $_ }

    $values = $tokens |
    Where-Object Type -In "Variable" |
    ForEach-Object {
        if (($var = Get-Variable -ValueOnly $_.Content) -and $var.GetType().Name -notmatch "Object|Hashtable") {
            "{0}={1}" -f $_.Content, $var
        }
    }

    $Object | Add-Member -NotePropertyName $Property -NotePropertyValue $InputObject.Invoke()[0] -Force:$Force

    # Replace any combination of tab + spaces or any single tab with nothing.
    $script = ($InputObject.ToString() -replace "[`t ]{2,}|`t").Trim()
    $Object | Add-Member -NotePropertyName "${Property}Formula" -NotePropertyValue $script

    # If there isn't any value to add, we should skip the property entirely.
    if ($values.count) {
        $Object | Add-Member -NotePropertyName "${Property}Values" -NotePropertyValue ($Values -join "; ")
    }
}

Demonstration

Just like originally shown in the other blog post about auditing expressions, here is a simple object with a couple extra variables to interact with:

$loan = [PSCustomObject]@{
    "Loan Amount"=100
    Rate=6.25
    Term=60
    Fico=725
}

$holding = 3
$termRates = @{
    12=6
    24=5
    36=4
    48=3
    60=2
}

This time, I’ll do a more complex scriptblock with the sub-expression. The new method that only expands variables instead of trying to split by operators means that the values will be a lot shorter too.

{($loan.'Loan Amount' * $loan.Rate) + $holding } |
Add-MemberProperties -Object $loan -Property Held

Output of $loan:

Loan Amount : 100
Rate        : 6.25
Term        : 60
Fico        : 725
Held        : 628
HeldFormula : ($loan.'Loan Amount' * $loan.Rate) + $holding
HeldValues  : holding=3

Now it is much easier to figure out how to the Held was created and it is also much more performant, which is nice.