Private/GetParamVariable.ps1

Function GetParamVariable {
    [CmdletBinding()]
    param (
        [scriptblock]$ScriptBlock
    )     
    # Tokenize the script
    [array] $tokens = [Management.Automation.PSParser]::Tokenize($ScriptBlock, [ref]$null) | Where-Object {
        $_.Type -ne 'NewLine' -and  $_.Type -ne 'Comment'
    }
    # old code was buggy - it can grab internal param block for code like { $a = 1; invoke-command { param($b) } -argumentlist $a }
    # New code know that scriptblock param() can only be the first token or right after [attribute] tokens, any other - ignored.
    # It also get right variable names when param($a = $b + $c, $d) and other difficult cases (see tests)
    $state = 0
    $bracket = 0
    $awaitVariable = $false
    foreach ($token in $tokens)
    {
        # using state machine method
        switch ($state) {
            0 { # search for sttribute start or param
                if ($token.Type -eq 'Keyword' -and $token.Content -eq 'param') {
                    $state = 3 # collect variables start
                    $awaitVariable = $true # catch variable name after param(
                }
                elseif ($token.Type -eq 'Operator' -and $token.Content -eq '[') { #attribute start
                    $state = 1 # check for attribute token
                    $bracket++
                }
                else { # no param found, break
                    $state = -1
                }
            }
            1 { # Attribute token check. may be excessive?
                if ($token.Type -eq 'Attribute') {
                    $state = 2 # wait for close attribute block
                }
            }
            2 { # await attribte end
                if ($token.Type -eq 'Operator') {
                    if ($token.Content -eq '[') {
                        $bracket++
                    }
                    elseif  ($token.Content -eq ']') {
                        $bracket--
                        if ($bracket -eq 0) {
                            # catched attribute close bracket
                            $state = 0 # back to param() search
                        }
                    }
                }
            }
            3 { # inside params
                if ($token.Type -eq 'GroupStart' -and $token.Content -eq '(') {
                    $bracket++
                }
                elseif ($token.Type -eq 'GroupEnd' -and $token.Content -eq ')') {
                    $bracket--
                    if ($bracket -eq 0) {
                        # param() closed, exiting
                        $state = -1
                    }
                }
                elseif ($token.Type -eq 'Operator' -and $token.Content -eq '[') {
                    $bracket += 2 #count square brackets
                }
                elseif ($token.Type -eq 'Operator' -and $token.Content -eq ']') {
                    $bracket -= 2 #count square brackets
                }
                elseif ($token.Type -eq 'GroupStart' -and ($token.Content -eq '{' -or $token.Content -eq '@{')) {
                    $bracket += 2 #count curly brackets
                }
                elseif ($token.Type -eq 'GroupEnd' -and $token.Content -eq '}') {
                    $bracket -= 2 #count curly brackets
                }
                elseif ($token.Type -eq 'Operator' -and $token.Content -eq ',' -and ($bracket -eq 1)) {
                    $awaitVariable = $true # await variable name after comma without extra brackets
                }
                elseif ($token.Type -eq 'Variable' -and ($bracket -eq 1) -and $awaitVariable)
                {
                    $awaitVariable = $false
                    $token.Content
                }
            }
        }
        if ($state -eq -1) { break }
    }
}