说我有 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)
是否有可能以某种方式点击功能?问题是如果上面的脚本是点源的,它会执行整个事情......
使用Get-Content
并解析函数并使用Invoke-Expression ...是我的最佳选择吗?或者有没有办法以编程方式访问PowerShell的解析器?我发现使用[System.Management.Automation.Language.Parser]::ParseInput
的PSv3可能会有这种情况,但这不是一个选项,因为它必须在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")
}
}
答案 0 :(得分:4)
使用Doug's Get-Function function您可以这样包含这些功能:
$script = get-item .\myscript.ps1
foreach ($function in (get-function $script))
{
$startline = $function.line - 1
$endline = $startline
$successful = $false
while (! $successful)
{
try {
$partialfunction = ((get-content $script)[$startline..$endline]) -join [environment]::newline
invoke-expression $partialfunction
$successful = $true
}
catch [Exception] { $endline++ }
}
}
编辑:[System.Management.Automation.IncompleteParseException]可用于代替Powershell V2中的[Exception]。
答案 1 :(得分:2)
注意 - 如果您觉得这个答案有用,请upvote 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, }}
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"