当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 将 PowerShell 函数导出到文件

作者:精品下载站 日期:2024-12-14 08:04:52 浏览:13 分类:玩电脑

将 PowerShell 函数导出到文件


当我编写 PowerShell 模块时,它通常包含多个导出函数。在哪里存储模块函数是一个很好的讨论话题,我认为不一定有适合每个人的最佳实践。我认为这可能取决于功能的数量和复杂性。其他人是否向该模块贡献代码?你如何使用 Pester?这些只是需要考虑的几个问题。在我的工作中,有时函数位于 .psm1 文件中。有些项目有一个具有多种功能的 public.ps1 文件。在更复杂的项目中,例如我的 PSScriptTools 模块,每个函数都有一个单独的文件。

当我决定将单个文件中定义的所有函数分开时,我的挑战就出现了。我希望每个函数都驻留在自己的文件中,使用函数名作为文件名。我厌倦了手动执行此操作,因此编写了一个 PowerShell 函数来帮我完成这项工作。

使用 AST

PowerShell 使用称为“抽象语法树”或 AST 的编码概念。 AST 使得解析 PowerShell 表达式甚至文件成为可能。使用 AST 绝对是高级 PowerShell 脚本技巧。

New-Variable astTokens -Force
New-Variable astErr -Force
$AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)

根据您要抽象的内容,您也许能够从立即解析中准确地获得您想要的内容。就我而言,我需要在 AST 结果中搜索特定类型的命令元素。

$functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)

[玩转系统] 将 PowerShell 函数导出到文件

在我的示例中,我在文件中找到了这些函数。

[玩转系统] 将 PowerShell 函数导出到文件

我会指出,不遵循动词-名词命名约定的函数名称是类构造函数。尽管它们也可能是私有辅助函数,通常使用非标准名称,因为它们从未公开暴露。更多关于我如何处理这个问题。

每个功能元素都是它自己的对象。

[玩转系统] 将 PowerShell 函数导出到文件

正如您所看到的,获取函数文本非常容易。如果我使用 ToString() 方法,那就更容易了。

 Set-Content -path "d:\temp$($functions[-1].name).ps1" -Value $functions[-1].ToString()

测试函数名称

当我构建导出工具时,我意识到我需要一种方法来跳过可能永远不会公开暴露的功能。所以我写了一个简单的函数来测试函数名。

Function Test-FunctionName {
    [CmdletBinding()]
    [OutputType("boolean")]
    Param(
    [Parameter(Position = 0,Mandatory,HelpMessage = "Specify a function name.")]
    [ValidateNotNullOrEmpty()]
    [string]$Name
    )

    Write-Verbose "Validating function name $Name"
    #Function name must first follow Verb-Noun pattern
    if ($Name -match "^\w+-\w+$") {
        #validate the standard verb
        $verb = ($Name -split "-")[0]
        Write-Verbose "Validating detected verb $verb"
        if ((Get-Verb).verb -contains $verb ) {
            $True
        }
        else {
            Write-Verbose "$($Verb.ToUpper()) is not an approved verb."
            $False
        }
    }
    else {
        Write-Verbose "$Name does not match the regex pattern ^\w+-\w+$"
        $False
    }
}

我知道这可能是一个单行命令,但我想添加一个关于函数名称失败原因的解释。该函数接受一个名称,并首先测试它是否与“Word-Word”模式匹配。如果通过,则该函数测试“动词”是否是标准动词。如果是这样,则该名称通过测试。

[玩转系统] 将 PowerShell 函数导出到文件

导出函数

是时候把这一切放在一起了。

Function Export-FunctionFromFile {
    [cmdletbinding(SupportsShouldProcess)]
    [alias("eff")]
    [OutputType("None", "System.IO.FileInfo")]
    Param(
        [Parameter(Position = 0, Mandatory, HelpMessage = "Specify the .ps1 or .psm1 file with defined functions.")]
        [ValidateScript({
                If (Test-Path $_ ) {
                    $True
                }
                Else {
                    Throw "Can't validate that $_ exists. Please verify and try again."
                    $False
                }
            })]
        [ValidateScript({
                If ($_ -match "\.ps(m)?1$") {
                    $True
                }
                Else {
                    Throw "The path must be to a .ps1 or .psm1 file."
                    $False
                }
            })]
        [string]$Path,
        [Parameter(HelpMessage = "Specify the output path. The default is the same directory as the .ps1 file.")]
        [ValidateScript({ Test-Path $_ })]
        [string]$OutputPath,
        [Parameter(HelpMessage = "Export all detected functions.")]
        [switch]$All,
        [Parameter(HelpMessage = "Pass the output file to the pipeline.")]
        [switch]$Passthru
    )
    Write-Verbose "Starting $($MyInvocation.MyCommand)"

    #always create these variables
    New-Variable astTokens -Force -WhatIf:$False
    New-Variable astErr -Force -WhatIf:$False

    if (-Not $OutputPath) {
        #use the parent path of the file unless the user specifies a different path
        $OutputPath = Split-Path -Path $Path -Parent
    }

    Write-Verbose "Processing $path for functions"
    #the file will always be parsed regardless of WhatIfPreference
    $AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)

    #parse out functions using the AST
    $functions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)

    if ($functions.count -gt 0) {
        Write-Verbose "Found $($functions.count) functions"
        Write-Verbose "Creating files in $outputpath"
        Foreach ($item in $functions) {
            Write-Verbose "Detected function $($item.name)"
            #only export functions with standard namees or if -All is detected.
            if ($All -OR (Test-FunctionName -name $item.name)) {
                $newfile = Join-Path -Path $OutputPath -ChildPath "$($item.name).ps1"
                Write-Verbose "Creating new file $newFile"
                Set-Content -Path $newFile -Value $item.ToString() -Force
                if ($Passthru -AND (-Not $WhatIfPreference)) {
                    Get-Item -Path $newfile
                }
            }
            else {
                Write-Verbose "Skipping $($item.name)"
            }
        } #foreach item
    }
    else {
        Write-Warning "No functions detected in $Path."
    }
    Write-Verbose "Ending $($MyInvocation.MyCommand)"
} #end function

默认情况下,该函数将仅导出具有标准名称的函数。

[玩转系统] 将 PowerShell 函数导出到文件

如果仔细观察详细输出,您会发现检测到的非标准函数被跳过。尽管如果需要的话我可以包含所有检测到的功能。

[玩转系统] 将 PowerShell 函数导出到文件

该函数允许我为新文件指定备用位置,尽管默认位置是源文件的父目录。

[玩转系统] 将 PowerShell 函数导出到文件

源文件不受影响,尽管我想我可以修改函数以从文件中删除代码。现在,我将手动更新源文件和任何其他模块文件以反映新的函数文件。

尝试一下

我在同一个文件中拥有这两个函数,我点了源代码。尽管我可能会在将来的某个时候将这些函数添加到 PSScriptTools 模块中。导出功能假定文件是 .ps1 或 .psm1 文件。您可以看到我在代码中使用的参数验证。尝试一下,让我知道你的想法。该代码应该可以在 Windows PowerShell 和 PowerShell 7.x 中运行。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯