在不执行脚本的情况下对脚本进行点源

时间:2013-08-01 10:36:57

标签: powershell

是否可以在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

4 个答案:

答案 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