是否可以仅在不执行脚本的情况下包含函数?

And*_*ndi 5 powershell pester

说我有MyScript.ps1:

[cmdletbinding()]
param ( 
    [Parameter(Mandatory=$true)]
        [string] $MyInput
)

function Show-Input {
    param ([string] $Incoming)
    Write-Output $Incoming
}

function Save-TheWorld {
    #ToDo
}

Write-Host (Show-Input $MyInput)
Run Code Online (Sandbox Code Playgroud)

是否有可能以某种方式点源函数?问题是,如果上面的脚本是点源,它执行整个事情......

我是使用Get-Content和解析函数并使用Invoke-Expression ...的最佳选择吗?或者有没有办法以编程方式访问PowerShell的解析器?我发现这可能是PSv3使用的可能,但这[System.Management.Automation.Language.Parser]::ParseInput不是一个选项,因为它必须在PSv2上工作.

我之所以要问的是,我正在尝试Pester PowerShell单元测试框架,以及它在函数上运行测试的方式是通过使用测试夹具中的函数来源文件.测试夹具如下所示:

MyScript.Tests.ps1

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"

Describe "Show-Input" {

    It "Verifies input 'Hello' is equal to output 'Hello'" {
        $output = Show-Input "Hello"
        $output.should.be("Hello")
    }
}
Run Code Online (Sandbox Code Playgroud)

And*_*ndi 2

注意——如果您发现这个答案有帮助,请投票给 jonZ 的答案,因为如果没有他有用的答案,我就无法想出这个答案。

我根据链接到的脚本 @jonZ 创建了这个函数提取器函数。这用于[System.Management.Automation.PsParser]::Tokenize遍历输入脚本中的所有标记,并将函数解析为函数信息对象,并将所有函数信息对象作为数组返回。每个对象看起来像这样:

Start       : 99
Stop        : 182
StartLine   : 7
Name        : Show-Input
StopLine    : 10
StartColumn : 5
StopColumn  : 1
Text        : {function Show-Input {,     param ([string] $Incoming),     Write-Output $Incoming, }}
Run Code Online (Sandbox Code Playgroud)

text 属性是一个字符串数组,可以写入临时文件和点,使用换行符来源或组合到字符串中,并使用 导入Invoke-Expression

仅提取函数文本,因此如果一行有多个语句,例如:Get-Process ; function foo () {仅提取与函数相关的部分。

function Get-Functions {
    param (
        [Parameter(Mandatory=$true)]
        [System.IO.FileInfo] $File
    )

    try {
        $content = Get-Content $File
        $PSTokens = [System.Management.Automation.PsParser]::Tokenize($content, [ref] $null)

        $functions = @()

        #Traverse tokens.
        for ($i = 0; $i -lt $PSTokens.Count; $i++) {
            if($PSTokens[$i].Type -eq  'Keyword' -and $PSTokens[$i].Content -eq 'Function' ) {
                $fxStart = $PSTokens[$i].Start
                $fxStartLine = $PSTokens[$i].StartLine
                $fxStartCol = $PSTokens[$i].StartColumn

                #Skip to the function name.
                while (-not ($PSTokens[$i].Type -eq  'CommandArgument')) {$i++}
                $functionName = $PSTokens[$i].Content

                #Skip to the start of the function body.
                while (-not ($PSTokens[$i].Type -eq 'GroupStart') -and -not ($PSTokens[$i].Content -eq '{')) {$i++ }

                #Skip to the closing brace.
                $startCount = 1 
                while ($startCount -gt 0) { $i++ 
                    if ($PSTokens[$i].Type -eq 'GroupStart' -and $PSTokens[$i].Content -eq '{') {$startCount++}
                    if ($PSTokens[$i].Type -eq 'GroupEnd'   -and $PSTokens[$i].Content -eq '}') {$startCount--}
                }

                $fxStop = $PSTokens[$i].Start
                $fxStopLine = $PSTokens[$i].StartLine
                $fxStopCol = $PSTokens[$i].StartColumn

                #Extract function text. Handle 1 line functions.
                $fxText = $content[($fxStartLine -1)..($fxStopLine -1)]
                $origLine = $fxText[0]
                $fxText[0] = $fxText[0].Substring(($fxStartCol -1), $fxText[0].Length - ($fxStartCol -1))
                if ($fxText[0] -eq $fxText[-1]) {
                    $fxText[-1] = $fxText[-1].Substring(0, ($fxStopCol - ($origLine.Length - $fxText[0].Length)))
                } else {
                    $fxText[-1] = $fxText[-1].Substring(0, ($fxStopCol))
                }

                $fxInfo = New-Object -TypeName PsObject -Property @{
                    Name = $functionName
                    Start = $fxStart
                    StartLine = $fxStartLine
                    StartColumn = $fxStartCol
                    Stop = $fxStop
                    StopLine = $fxStopLine
                    StopColumn = $fxStopCol
                    Text = $fxText
                }
                $functions += $fxInfo
            }
        }
        return $functions
    } catch {
        throw "Failed in parse file '{0}'. The error was '{1}'." -f $File, $_
    }
}

# Dumping to file and dot sourcing:
Get-Functions -File C:\MyScript.ps1 | Select -ExpandProperty Text | Out-File C:\fxs.ps1
. C:\fxs.ps1
Show-Input "hi"

#Or import without dumping to file:

Get-Functions -File  C:\MyScript.ps1 | % { 
    $_.Text -join [Environment]::NewLine | Invoke-Expression
}
Show-Input "hi"
Run Code Online (Sandbox Code Playgroud)