Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing PowerShell script with AST

I'm trying to parse through a Pester script and extract values from the -Tag parameter. Anyone know how to do this using [System.Management.Automation.PSParser]?. I'm was thinking I'd have to loop through the tokens returned from [System.Management.Automation.PSParser]::Tokenize() but that seems pretty kludgy and given that the values for -Tag could be given in many different formats, not very practical.

At the end of the day, I'm hoping to return a collection with the Describe block name, and the list of tags (if any) for that block.

Name     Tags        
----     ----        
Section1 {tag1, tag2}
Section2 {foo, bar}  
Section3 {asdf}      
Section4 {}      

Here are the sample Pester tests that I'm working with.

describe 'Section1' -Tag @('tag1', 'tag2') {
    it 'blah1' {
        $true | should be $true
    }
}
describe 'Section2' -Tag 'foo', 'bar' {
    it 'blah2' {
        $true | should be $true
    }    
}
describe 'Section3' -Tag 'asdf'{
    it 'blah3' {
        $true | should be $true
    }
}
describe 'Section4' {
   it 'blah4' {
        $true | should be $true
   }
}

Anyone have any ideas on how to solve this? Is [System.Management.Automation.PSParser] the right way to go or is there a better way?

Cheers

like image 733
Brandon Olin Avatar asked Oct 07 '16 03:10

Brandon Olin


1 Answers

Using PS3.0+ Language namespace AST parser:

$text = Get-Content 'pester-script.ps1' -Raw # text is a multiline string, not an array!

$tokens = $null
$errors = $null
[Management.Automation.Language.Parser]::ParseInput($text, [ref]$tokens, [ref]$errors).
    FindAll([Func[Management.Automation.Language.Ast,bool]]{
        param ($ast)
        $ast.CommandElements -and
        $ast.CommandElements[0].Value -eq 'describe'
    }, $true) |
    ForEach {
        $CE = $_.CommandElements
        $secondString = ($CE | Where { $_.StaticType.name -eq 'string' })[1]
        $tagIdx = $CE.IndexOf(($CE | Where ParameterName -eq 'Tag')) + 1
        $tags = if ($tagIdx -and $tagIdx -lt $CE.Count) {
            $CE[$tagIdx].Extent
        }
        New-Object PSCustomObject -Property @{
            Name = $secondString
            Tags = $tags
        }
    }
Name       Tags             
----       ----             
'Section1' @('tag1', 'tag2')
'Section2' 'foo', 'bar'     
'Section3' 'asdf'           
'Section4' 

The code doesn't interpret the tags as a list of strings, but simply uses the original text extent.
Use the debugger in PowerShell ISE / Visual Studio / VSCode to inspect the various data type cases.

like image 62
wOxxOm Avatar answered Sep 29 '22 07:09

wOxxOm