是否可以在Powershell中点源或重复使用某些脚本函数而不执行它?我正在尝试重用脚本的功能,而不执行脚本本身。我可以将函数分解为仅函数文件,但我试图避免这样做。
示例点源文件:
function doA
{
Write-Host "DoAMethod"
}
Write-Host "reuseme.ps1 main."
消费档案示例:
. ".\reuseme.ps1"
Write-Host "consume.ps1 main."
doA
执行结果:
reuseme.ps1 main.
consume.ps1 main.
DoAMethod
期望的结果:
consume.ps1 main.
DoAMethod
答案 0 :(得分:7)
您 执行功能定义以使其可用。没有办法解决它。
你可以尝试在文件中抛出PowerShell解析器,只执行函数定义而不执行任何其他操作,但我想更简单的方法是将可重用部分构建为模块,或者仅仅作为除了声明之外不执行任何操作的脚本功能
对于记录来说,这是一个粗略的测试脚本,可以做到这一点:
$file = 'foo.ps1'
$tokens = @()
$errors = @()
$result = [System.Management.Automation.Language.Parser]::ParseFile($file, [ref]$tokens, [ref]$errors)
$tokens | %{$s=''; $braces = 0}{
if ($_.TokenFlags -eq 'Keyword' -and $_.Kind -eq 'Function') {
$inFunction = $true
}
if ($inFunction) { $s += $_.Text + ' ' }
if ($_.TokenFlags -eq 'ParseModeInvariant' -and $_.Kind -eq 'LCurly') {
$braces++
}
if ($_.TokenFlags -eq 'ParseModeInvariant' -and $_.Kind -eq 'RCurly') {
$braces--
if ($braces -eq 0) {
$inFunction = $false;
}
}
if (!$inFunction -and $s -ne '') {
$s
$s = ''
}
} | iex
如果在脚本引用脚本参数中声明的函数(不包括脚本的参数块),则会出现问题。并且可能会发生许多其他问题,我现在无法想到这些问题。我最好的建议仍然是区分可重用的库脚本和要调用的脚本。
答案 1 :(得分:3)
重用代码的最佳方法是将您的函数放在PowerShell模块中。只需创建包含所有功能的文件,并为其指定.psm1
扩展名。然后导入模块以使所有功能可用。例如,reuseme.psm1
:
function doA
{
Write-Host "DoAMethod"
}
Write-Host "reuseme.ps1 main."
然后,在任何你想要使用你的函数模块的脚本中,
# If you're using PowerShell 2, you have to set $PSScriptRoot yourself:
# $PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
Import-Module -Name (Join-Path $PSScriptRoot reuseme.psm1 -Resolve)
doA
答案 2 :(得分:0)
函数执行后,写主机“ reuseme.ps1 main”行。被称为“过程代码”(即,它不在函数内)。通过将其包装在评估 $ MyInvocation.InvocationName -ne“。”
的IF语句中,可以告诉脚本不要运行此过程代码。$ MyInvocation.InvocationName查看脚本的调用方式,如果您使用点(。)对脚本进行点源处理,它将忽略过程代码。如果运行/调用不带点(。)的脚本,则它将执行过程代码。下面的示例:
function doA
{
Write-Host "DoAMethod"
}
If ($MyInvocation.InvocationName -ne ".")
{
Write-Host "reuseme.ps1 main."
}
因此,当您正常运行脚本时,将看到输出。当您对脚本进行点源处理时,您将看不到输出。但是,功能(而不是过程代码)将被添加到当前作用域。
答案 3 :(得分:0)
在进一步寻找该问题的解决方案时,我遇到了一个解决方案,该解决方案基本上是对Aaron答案中提示的跟踪。意图有些不同,但是可以用来达到相同的结果。
这是我发现的: https://virtualengine.co.uk/2015/testing-private-functions-with-pester/
在使用Pester进行某些测试时,我需要它,我想避免在编写逻辑测试之前避免更改文件的结构。
它工作得很好,使我有信心在重构文件结构之前先为逻辑编写一些测试,这样我就不必再对函数进行点源了。
Describe "SomeFunction" {
# Import the ‘SomeFunction’ function into the current scope
. (Get-FunctionDefinition –Path $scriptPath –Function SomeFunction)
It "executes the function without executing the script" {
SomeFunction | Should Be "fooBar"
}
}
还有Get-FunctionDefinition
#Requires -Version 3
<#
.SYNOPSIS
Retrieves a function's definition from a .ps1 file or ScriptBlock.
.DESCRIPTION
Returns a function's source definition as a Powershell ScriptBlock from an
external .ps1 file or existing ScriptBlock. This module is primarily
intended to be used to test private/nested/internal functions with Pester
by dot-sourcsing the internal function into Pester's scope.
.PARAMETER Function
The source function's name to return as a [ScriptBlock].
.PARAMETER Path
Path to a Powershell script file that contains the source function's
definition.
.PARAMETER LiteralPath
Literal path to a Powershell script file that contains the source
function's definition.
.PARAMETER ScriptBlock
A Powershell [ScriptBlock] that contains the function's definition.
.EXAMPLE
If the following functions are defined in a file named 'PrivateFunction.ps1'
function PublicFunction {
param ()
function PrivateFunction {
param ()
Write-Output 'InnerPrivate'
}
Write-Output (PrivateFunction)
}
The 'PrivateFunction' function can be tested with Pester by dot-sourcing
the required function in the either the 'Describe', 'Context' or 'It'
scopes.
Describe "PrivateFunction" {
It "tests private function" {
## Import the 'PrivateFunction' definition into the current scope.
. (Get-FunctionDefinition -Path "$here\$sut" -Function PrivateFunction)
PrivateFunction | Should BeExactly 'InnerPrivate'
}
}
.LINK
https://virtualengine.co.uk/2015/testing-private-functions-with-pester/
#>
function Get-FunctionDefinition {
[CmdletBinding(DefaultParameterSetName='Path')]
[OutputType([System.Management.Automation.ScriptBlock])]
param (
[Parameter(Position = 0,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
ParameterSetName='Path')]
[ValidateNotNullOrEmpty()]
[Alias('PSPath','FullName')]
[System.String] $Path = (Get-Location -PSProvider FileSystem),
[Parameter(Position = 0,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = 'LiteralPath')]
[ValidateNotNullOrEmpty()]
[System.String] $LiteralPath,
[Parameter(Position = 0,
ValueFromPipeline = $true,
ParameterSetName = 'ScriptBlock')]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.ScriptBlock] $ScriptBlock,
[Parameter(Mandatory = $true,
Position =1,
ValueFromPipelineByPropertyName = $true)]
[Alias('Name')]
[System.String] $Function
)
begin {
if ($PSCmdlet.ParameterSetName -eq 'Path') {
$Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path);
}
elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
## Set $Path reference to the literal path(s)
$Path = $LiteralPath;
}
} # end begin
process {
$errors = @();
$tokens = @();
if ($PSCmdlet.ParameterSetName -eq 'ScriptBlock') {
$ast = [System.Management.Automation.Language.Parser]::ParseInput($ScriptBlock.ToString(), [ref] $tokens, [ref] $errors);
}
else {
$ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref] $tokens, [ref] $errors);
}
[System.Boolean] $isFunctionFound = $false;
$functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true);
foreach ($f in $functions) {
if ($f.Name -eq $Function) {
Write-Output ([System.Management.Automation.ScriptBlock]::Create($f.Extent.Text));
$isFunctionFound = $true;
}
} # end foreach function
if (-not $isFunctionFound) {
if ($PSCmdlet.ParameterSetName -eq 'ScriptBlock') {
$errorMessage = 'Function "{0}" not defined in script block.' -f $Function;
}
else {
$errorMessage = 'Function "{0}" not defined in "{1}".' -f $Function, $Path;
}
Write-Error -Message $errorMessage;
}
} # end process
} #end function Get-Function