Question

I want to get the last line of a specific function in a .ps1 file. I already accomplished this with powershell v3 code:

function GetEndLineNumber($ParseFile, $functionName))
{
    $AbstractSyntaxTree = $NewParser::ParseFile($ParseFile, [ref]$null,  [ref]$null)
    $functionsInFile = $AbstractSyntaxTree.FindAll({$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]}, $true)
    #endregion

    $initializeFunction = $null
    foreach($function in $functionsInFile)
    {
        if($function.Name -eq $functionName)
        {
            $initializeFunction = $function
            break
        }
    }

    if($initializeFunction -eq $null){ return 0 }

    $initializeFunctionBody = $initializeFunction.Body

    return $initializeFunctionBody.Extent.EndLineNumber
}

But this script should also run on v2, i tried with System.Management.Language.PSParser and with ScriptBlock. But without any success. Someone knows how I could get the last line ( as an int or string) of a specific function name in a .ps1 file?

EDIT:

I think here are some missunderstandings: I want to get the last line of a specific function in a .ps1 script. Not the last line of a specific function in a function. Because I have to add my own code to this function at the end Here a simple example how my script could look:

test.ps1

1function test321
2 {
3  Write-Host "test3142"
4 }
5
6function test1
7{
8 if($true)
9 {
10  Write-Host "hello"
11 }
12 #comment
13 
14 Write-host "end"
15 
16}
17
18function test2
19{
20 #bla
21}

I want a function called e.g. like GetEndLineNumber($scriptFile, $functionName) and it should be work like this:

test2.ps1

$path = "C:\Scripts\test.ps1"
$lastLine = GetEndLineNumber $path "test1"

$lastLine should be in this case 15 or 16.
Était-ce utile?

La solution

In PowerShell V2 we can use System.Management.Automation.PSParser and do some parsing ourselves using produced tokens:

function GetEndLineNumber {
    param(
        $scriptFile,
        $functionName
    )

    $code = Get-Content -LiteralPath $scriptFile
    $tokens = [System.Management.Automation.PSParser]::Tokenize($code, [ref]$null)

    $waitForFuncName = $false
    $funcFound = $false
    $level = 0

    foreach($t in $tokens) {
        switch($t.Type) {
            Keyword {
                if ($t.Content -eq 'function') {
                    $waitForFuncName = $true
                }
            }
            CommandArgument {
                if ($waitForFuncName) {
                    $waitForFuncName = $false
                    $funcFound = $t.Content -eq $functionName
                }
            }
            GroupStart {
                ++$level
            }
            GroupEnd {
                --$level
                if ($funcFound -and $level -eq 0 -and $t.Content -eq '}') {
                    return $t.StartLine
                }
            }
        }
    }
}

I did not test it thoroughly but I tried it for a few functions and scripts and it returned correct line numbers.

Autres conseils

In V3, there is another a simpler option for functions that already exist:

$cmd = Get-Command -Name $functionName -CommandType Function,Filter
if ($null -ne $cmd)
{
    return $cmd.ScriptBlock.Ast.Extent.EndLineNumber
}

This will work for functions defined with the function/filter keywords as well as with Set-Item, e.g.

Set-Item -Path function:foo -Value { return 42 }

The original code using the Parser api wouldn't work correctly in this case.

In V2, the PSParser api might be the best alternative - but it would require some slightly tricky parsing. After you find the 'function' token followed by the function name you want, you would skip the optional parameter list (matching '(' and ')') and then you would match '{' and '}' tokens, the last unnested '}' token being your last line.

This isn't complicated code, but it's a little tricky, e.g. if you didn't skip the optional parameter list, you could incorrectly find matched curly braces as a default argument, e.g.

function foo($a = {}) {}

Adding to @JPBlanc's answer, be careful with that split. It appears to count everything between the curly braces as part of the script block, including any newlines immediately before and after:

 function test {
 'line1'
 'line2'
 'line3'
 }

 $myFunction = Get-Item Function:\test
 ($myFunction.ScriptBlock ).ToString().split("`n").count

 5


 function test {'line1'
 'line2'
 'line3'}

 $myFunction = Get-Item Function:\test
 ($myFunction.ScriptBlock ).ToString().split("`n").count

 3

Taking the last element of the split from that first one returns a blank line. Just to be safe, I'd run it through .trim() before you do the split. You can also shorten it some by using $function:<function name> instead of Get-Item, and array slicing instead of -Select :

function test {
'line1'
'line2'
'line3'
}

$function:test.tostring().trim().split("`n")[-1]

'line3'

What about this way of getting last line :

First .source the PS1 file :

. .\Myscript.ps1

Then get the scriptblock of the function :

$myFunction = Get-Item Function:\MyFunction
($myFunction.ScriptBlock ).ToString().split("`n") | select -last 1

As you can read in @mjolinor's answer you should be carreful in that split.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top