2017-08-29 20 views
4

我遇到类似于this question的问题。我想获得给定的PowerShell脚本中的所有功能,但区别在于我不想执行脚本的内容,也不想执行这些功能。获取PowerShell脚本中的所有函数

意图是能够将所有函数加载到运行空间中,以便能够从每个函数中提取基于注释的帮助以进行文档编制。

有没有人有任何魔术技巧,只需从.ps1加载函数而不执行该文件中的所有其他代码?

我想过使用[System.Management.Automation.PSParser]::Tokenize()来解析脚本文件,但这比我想要做的更多的工作。如果有人有更容易的事情,我会很高兴。

# I want to load this to get the comment-based help 
Function Invoke-Stuff { 
    <# 
    .SYNOPSIS 
     Stuff doer 
    .DESCRIPTION 
     It does lots of stuff 
    .EXAMPLE 
     Invoke-Stuff 
    #> 
    Write-Host "Stuff was done" 
} 

# But I don't want to execute any of this 
$Items = Get-ChildItem 
$Items | ForEach-Object { 
    Invoke-Stuff 
} 
+1

相关:http://www.ravichaganti.com/blog/powershell-ise-addon-ise-function-explorer-using-the-powershell-3-0-parser/ – Matt

回答

4

AST是去静态(ISH)分析的方式。下面是我会怎么做你所描述的

$rs = [runspacefactory]::CreateRunspace() 
$rs.Open() 

# Get the AST of the file 
$tokens = $errors = $null 
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
    'MyScript.ps1', 
    [ref]$tokens, 
    [ref]$errors) 

# Get only function definition ASTs 
$functionDefinitions = $ast.FindAll({ 
    param([System.Management.Automation.Language.Ast] $Ast) 

    $Ast -is [System.Management.Automation.Language.FunctionDefinitionAst] -and 
    # Class methods have a FunctionDefinitionAst under them as well, but we don't want them. 
    ($PSVersionTable.PSVersion.Major -lt 5 -or 
    $Ast.Parent -isnot [System.Management.Automation.Language.FunctionMemberAst]) 

}, $true) 

# Add the functions into the runspace 
$functionDefinitions | ForEach-Object { 
    $rs.SessionStateProxy.InvokeProvider.Item.Set(
     'function:\{0}' -f $_.Name, 
     $_.Body.GetScriptBlock()) 
} 

# Get help within the runspace. 
$ps = [powershell]::Create().AddScript('Get-Help MyFunction') 
try { 
    $ps.Runspace = $rs 
    $ps.Invoke() 
} finally { 
    $ps.Dispose() 
} 

你可以从靠近顶部,如果你想要去一个纯粹的静态路由也使用$tokens。评论将不会在AST中,但它们将在令牌中。

编辑上面的方法实际上在进程中的某处丢失了注释帮助,这不是因为运行空间,而仅仅是因为函数是如何分配的。可能由于评论不是真的是是AST的一部分。无论如何,有一种更直接和更静态的方式来获得帮助。

而不是定义的功能,你可以使用FunctionDefinitionAst

$helpContent = $functionDefinitions | ForEach-Object { $_.GetHelpContent() } 

这个GetHelpContent方法将返回一个对象CommentHelpInfo每个功能。请注意,这是而不是Get-Help cmdlet返回的相同对象。最值得注意的是它不区分代码和示例块中的描述。但是,如果您需要正常分析CBH,则可以获取注释块文本并定义您自己的假版本。

$helpContent = $functionDefinitions | ForEach-Object { 

    # Get the plain string comment block from the AST. 
    $commentBlock = $_.GetHelpContent().GetCommentBlock() 

    # Create a scriptblock that defines a blank version of the 
    # function with the CBH. You may lose some parameter info 
    # here, if you need that replace param() with 
    # $_.Body.ParamBlock.Extent.Text 
    $scriptBlock = [scriptblock]::Create((' 
    function {0} {{ 
     {1} 
     param() 
    }}' -f $_.Name, $commentBlock)) 

    # Dot source the scriptblock in a different scope so we can 
    # get the help content but still not pollute the session. 
    & { 
     . $scriptBlock 

     Get-Help $_.Name 
    } 
} 
+1

这真棒 – Matt

+1

@Matt说,这真棒 – SomeShinyObject

+0

@帕特里克:这太棒了。 –

1

理想情况下,你有一个模块在你的函数(或自己的脚本文件),您可以加载。你的'执行'脚本将是你自己的事情,你只有当你想要执行该功能时才运行,或者你手动运行这些功能。

如果你有一个模块中的功能,在PowerShell中寻找到他们的路径之一,你就可以运行这个看功能:

Get-Command -Module Example -CommandType Function 

大部分的时间你将不包含CommandType参数,除非您不在乎的模块中有额外的东西。

此模块方法(或功能/执行的分离)将是获得基于注释的帮助正常工作的唯一方法。

如果您很高兴看到您的函数的名称,则必须加载脚本文件的内容,并检查以function关键字开头的行。有可能更聪明的方法来做到这一点,但这正是我的想法。


只是要什么我得到大约从执行代码分离的功能,它会看起来像一个有点更加清晰:

functions.ps1

# I want to load this to get the comment-based help 
Function Invoke-Stuff { 
    <# 
    .SYNOPSIS 
     Stuff doer 
    .DESCRIPTION 
     It does lots of stuff 
    .EXAMPLE 
     Invoke-Stuff 
    #> 
    Write-Host "Stuff was done" 
} 

然后,您可以随意将该功能加载到您的会话中,使基于评论的帮助可以访问。

execute.ps1

. .\path\functions.ps1 

# But I don't want to execute any of this 
$Items = Get-ChildItem 
$Items | ForEach-Object { 
    Invoke-Stuff 
} 
+0

相信我,我我更喜欢这样做,而不是这样,但不幸的是,在我正在进行的项目中,我并不孤单。他们和我的感觉不一样。所以,当我要求函数从执行中移除时,并不总是会发生。 – SomeShinyObject