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

[玩转系统] 实用 PowerShell:函数和参数化

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

实用 PowerShell:函数和参数化


在我的上一篇文章中,我介绍了实用PowerShell系列。在处理 PowerShell 脚本时,可能会出现一组指令重复脚本中其他位置的代码的情况。您也可能希望将其他地方的代码合并到您的脚本中,以便您可以轻松调用这些代码。

这一描述可能会让您想起 cmdlet,它们具有名称以及(可选)一个或多个用于控制其操作的参数。但是,如果您希望代码具有相同的功能,例如具有高可重用性的代码,该怎么办?欢迎来到 PowerShell 函数的世界。在深入讨论之前,让我们先明确一下我们正在谈论的内容。

脚本

脚本是一个扩展名为 .ps1 的文本文件,其中包含 PowerShell 代码。该代码由 cmdlet 和(可选)函数组成。您可以通过多种方式调用脚本:

  • 使用&符号(& .\Process-Something.ps1)。该代码在具有其作用域的子会话中运行。这意味着脚本中的任何定义(例如变量或函数)都会在脚本终止时消失。当您运行交互式 PowerShell 脚本时,通常会省略 & 符号,但是当您想要运行变量中指向的代码时,您必须使用此调用方法,例如 & { Get-ChildItem }

    确保了解在命令开头和结尾使用 & 符号之间的区别。在末尾使用“&”号指示 PowerShell 在后台作业中运行代码。
  • 使用点源(例如 .\Helper-Functions.ps1),代码在当前 PowerShell 会话中运行。这意味着您在脚本中定义的任何变量或函数在会话中都可用。如果这些定义以前存在,它们就会被覆盖。

运行 PowerShell 脚本的能力取决于本地计算机当前的执行策略。这是防止恶意脚本运行的安全措施。来自 Microsoft 的脚本通常经过签名,但从互联网下载的许多社区来源的脚本通常没有签名。您可能需要先运行Set-ExecutionPolicy unrestricted,然后才能运行其他人创建的脚本,前提是公司政策不阻止您修改执行策略。

可重用的代码

函数是合并到脚本中的一组可重用代码。要识别代码,您必须为其命名,并在命令Function之后指定。使用 PowerShell 约定,函数名称应遵循动词-名词命名约定。为了避免任何冲突,您可以为名词添加前缀,例如

Function Get-MyReport {
 #reusable code
}

如果您创建重新定义现有命令的函数,则现有定义将在当前会话中被覆盖。代码的输出将返回给调用者,您可以将结果输出到屏幕或将其分配给变量以进行进一步处理。

除了函数本身之外,您还可以为函数中包含的代码定义参数。然后,函数内的代码可以使用通过参数传递的信息来执行其任务,因为参数成为函数代码上下文中可用的变量。

一个例子解释了这个概念。以下是一个虚构函数,用于在指定 MemberCount 参数时获取通讯组的详细信息以及通讯组的成员数量。 MemberCount 属性包含成员数量,或者在未指定 MemberCount 开关时被忽略。

这种函数的基本定义可能如下所示:

Function Get-DistributionGroupInfo( $Identity, $MemberCount)
{
 Get-DistributionGroup $Identity | Select-Object Identity,PrimarySmtpAddress, @{n='MemberCount';e={ If( $MemberCount) { (Get-DistributionGroupMember -Identity $_ | Measure-Object).Count } }}}
}

在起草脚本或进行概念验证时,此代码是可以接受的。然而,当稍后使用该函数或者不太熟悉该用例的其他人负责该代码时,问题可能会变得明显。

此功能的问题包括:

  • 变量 $Identity 中传递的通讯组可以是未指定的 ($null)。这可能会导致意想不到的副作用,因为当您指定 $null 作为值时,许多 Get cmdlet 会很乐意返回所有对象。采取以下代码:
Function Process-Mailbox( $Id) {
 Get-Mailbox -Identity $Id | Set-Mailbox -HiddenFromAddressListEnabled $True
}



你能猜一下如果不传递ID参数或者ID为空会发生什么吗?所有邮箱都会被返回,Set-Mailbox将很高兴地隐藏地址列表中的所有邮箱。这可能不是您想要的。

  • 在上面的示例中,Identity 和 Members 可以是任何内容;它们不需要分别是通讯组或交换机。您可以添加代码来检查 $Identity 是否为通讯组以及 Members 是否为布尔值($true 或 $false),但这需要额外的代码。如果必须处理多个参数,代码可能会变得相当复杂。

幸运的是,PowerShell 有多种机制可以帮助您定义参数要求。让我们看一下以下高级函数的示例:

Function Get-DistributionGroupInfo {
 [CmdletBinding()]
 Param(

 [Parameter(Position= 0, Mandatory= $true, ValueFromPipeline= $true, ValueFromPipelineByPropertyName=$true, HelpMessage= 'Please provide a Distribution Group')]
 [String]$Identity,

 [Parameter(HelpMessage= 'Output member count')]
 [Switch]$MemberCount
 )
 Process {
 Write-Verbose ('Fetching Distribution Group {0}' -f $Identity)
 Get-DistributionGroup $Identity | Select-Object Identity,PrimarySmtpAddress, @{n='MemberCount';e={ If( $MemberCount) { (Get-DistributionGroupMember -Identity $_ | Measure-Object).Count } }}
 }
}

此高级功能包含以下增强功能:

  • 第一个参数定义之前的[CmdletBinding()]告诉PowerShell该函数支持通用参数。常见参数的示例包括 Verbose 和Confirm。然后,您可以在函数中包含代码来支持这一点。例如,如果您传递 -Verbose 并且您的函数包含 Write-Verbose 命令,则将显示详细输出。当您省略 -Verbose 时,不会显示输出。
  • 第一个参数规范中的 Position=0 指示 PowerShell 将传递给 Get-DistributionGroupInfo 的第一个未命名参数视为身份。因此,以下两个命令是相同的:
Get-DistributionGroupInfo -Identity 'DG-X'
Get-DistributionGroupInfo 'DG-X'

其他未命名参数可以指定为 Position=1 等

  • Mandatory=$true 告诉 PowerShell 该参数是强制性的。当用户在运行脚本时省略身份时,PowerShell 将要求提供身份。通过将 Mandatory 设置为 $false 或省略条件,参数可以是可选的。
  • 我们希望在管道中调用函数时能够传递 Identity。您可以通过指定 ValueFromPipeline 来启用此参数的管道使用。您可以通过指定ValueFromPipelineByPropertyName来使用传递对象的属性。在管道中调用该函数可以如下所示:
Get-DistributionGroup -Identity 'DG-X' | Get-DistributionGroupInfo
  • 当您省略强制参数时,HelpMessage 定义帮助信息。当 PowerShell 要求您输入时输入 !? 时会显示此帮助信息,并且当您使用以下命令时也会显示此帮助信息:
Get-Help Get-DistributionGroupInfo -Full


我不知道有谁使用!?,但如果你愿意的话你可以。

  • 指定约束后,您可以定义参数本身。您可以通过为其指定名称(在本例中为身份)来完成此操作。该名称包含调用函数时作为参数传递的值,使其在其范围内可用。您可以选择定义参数将接受的对象类型。在本例中,我们指定 [String],它等于 [System.String],但 PowerShell 有一些用于内置类型的类型加速器(短别名)。

严格输入参数的好处是,当您传递不同类型的对象(例如整数)时,PowerShell 将抛出错误,指出传递的内容和预期的内容。例如,下面的基本函数接受单个参数A,该参数需要是整数(int)类型。传递数字与传递字符串将产生以下输出:

Function Test {
param(
[int]$A
)
Write-Output ($A)
}
❯ test -A 123
123
❯ test -A 'string'
Test: Cannot process argument transformation on parameter 'A'. Cannot convert value "string" to type "System.Int32". Error: "The input string 'string' was not in a correct format."

我建议尽可能使用严格的参数类型。键入有助于解决使用问题,还有助于记录代码,如下所述。需要考虑的一件事是,值可能会通过解释进行转换。例如,如果您传递参数值 123,并且需要一个字符串,PowerShell 会很乐意将其转换为字符串表示形式“123”。

  • 第二个参数(注意 Identity 后面的逗号)是一个名为 MemberCount 的 Switch。由于该参数不是强制性的并且不需要使用管道,因此定义中没有指定这些。开关的好处是您只需提及即可使用它们,例如-会员计数;会将 MemberCount 变量设置为 $true。如果不这样做,它将是 $false。您不能使用 -MemberCount $true,因为 PowerShell 会将 $true 解释为下一个参数,因为 MemberCount 是一个开关。如果需要,例如当 $true 或 $false 存储在变量中时,您可以通过指定 : 使用变量来设置它,例如-会员计数:$false。当避免必须确认某些命令时,例如,您可能已经在使用此语法。设置邮箱…-确认:$False
  • 通过将执行实际任务的代码放入 Process 脚本块 {} 中,我们使该函数适用于通过管道传递的对象。如果我们省略这一点并保持代码不变,它将不支持管道操作,并且代码只会针对通过管道接收的最后一个对象执行一次。请注意,如果需要,管道中的当前对象可通过 Process 脚本块中的自动变量 $_ 获得。

当我们将此函数的代码放入脚本文件(例如 MyDemo.ps1)中时,它就可以在我们的 PowerShell 会话中使用。为了实现这一点,我们需要点源它以在我们的会话中定义它。然后,我们可以调用它(前提是已加载并连接 Exchange Online Management Shell),并通过调用 Get-Help(其中还包括文档)检查其定义。

PS❯ . .\MyDemo.ps1

PS❯ Get-DistributionGroupInfo -Identity MyDG -MemberCount -Verbose
VERBOSE: Fetching Distribution Group MyDG
Identity PrimarySmtpAddress MemberCount
-------- ------------------ -----------
MyDG     [email protected]             2

PS❯ Get-DistributionGroup | Get-DistributionGroupInfo -MemberCount

Identity              PrimarySmtpAddress          MemberCount
--------              ------------------          -----------
MyDG                  [email protected]                      2
OtherDG               [email protected]                   8

PS❯ Get-Help Get-DistributionGroupInfo -Full

NAME
    Get-DistributionGroupInfo

SYNTAX

    Get-DistributionGroupInfo [-Identity] <string> [-MemberCount] [<CommonParameters>]

PARAMETERS

    -Identity <string>
        Please provide a Distribution Group.

        Required?                    True
        Position?                    0
        Accept pipeline input?       true (ByValue)
        Parameter set name           (All)
        Aliases                      None
        Dynamic?                     False
        Accept wildcard characters?  False

    -MemberCount
        Output member count

        Required?                    False
        Position?                    Named
        Accept pipeline input?       False
        Parameter set name           (All)
        Aliases                      None
        Dynamic?                     False
        Accept wildcard characters?  False

    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see
        about_CommonParameters (https://go.microsoft.com/fwlink/?LinkID=113216).

开始、过程、结束

示例函数支持通过管道传递对象。 Process 脚本块适用于通过管道传递的每个项目。但是,如果我们想在处理这些对象之前和之后执行一些内务处理怎么办?例如,我们想在处理所有对象之前初始化一些变量。

为此,我们可以在 Process 块之前和之后添加 Begin 和 End 脚本块。

Begin {
 # Initialize
 $Items=0
 }
 Process {
 # Do Something
 $Items++
 }
 End {
 # Cleanup
 Write-Host ('We processed {0} object(s)' -f $Items)
 }

一个示例是,当您想要计算已处理的对象数量时,因为通过管道传递的对象数量预先未知。另一个例子是 Sort-Object 命令,它只能在所有对象都传递给它时才对对象进行排序。

该脚本可能有一个管道函数,其中包含一个 Begin 脚本块来初始化数据集,Process 将所有项目添加到其中,最后End > 执行排序。请注意,Begin 和 End 块是可选的。另外,如果没有传递任何对象,则跳过 Process 块,但 Begin 和 End 将在定义时执行。

脚本参数

我们讨论了如何为函数定义参数,但是如何为脚本定义参数呢?答案是,为脚本定义参数的方式与为函数定义参数的方式类似,但发生在脚本级别。实际上,这意味着将定义放在脚本的开头。例如,以下是脚本的前几行:

[CmdletBinding()]
Param(
 [parameter( Mandatory= $true)
 [ValidateScript({ Test-Path -Path $_ -PathType Leaf})]
 [String]$CSVFile,
 [ValidateSet(',', ';')]
 [string]$Delimiter=',',
 [System.Security.SecureString]$Password
)

Function X {
 # …
}
#etc.
  • 当未给出值时,要求为 $CSVFile 提供一个值(强制= $true)。您会注意到 ValidateScript 行作为 CSVFile 参数定义的一部分。指定参数时,您可以让 PowerShell 针对提供的值执行某些验证。一些可能的测试是:

    • ValidateScript 用于执行需要返回 $true 才能接受参数值的脚本块。在示例中,我们使用指定自动变量 $_ (实际文件名)的 Test-Path 检查文件名是否有效。
  • ValidateSet 根据一组预定义值测试该值。在示例中,我们使用 ValidateSet 仅允许 $Delimiter 参数使用逗号或分号。
  • 其他一些选项包括 ValidateRange、ValidateCount、ValidatePattern 和 ValidateLength a.o。有关参数验证的更多信息可以在此处找到。
  • 可以指定分隔符参数。如果没有指定(非强制),我们将其设置为默认值“,.”
  • 可以提供 $Password 参数。指定后,其类型必须为 [System.Security.SecureString]。您不仅限于 PowerShell 的内置类型;您还可以使用其他 (.NET) 类型,例如 SecureString 或 [PSCredential] 类型的凭据。
  • 让 PowerShell 为您服务

    在一篇文章中讨论 PowerShell 中的函数和参数是雄心勃勃的。我没有涉及其他主题,例如命令集和动态参数。这些可能是另一篇文章的主题。

    我希望我鼓励您编写可重用的代码,不仅可以利用脚本和函数,还可以正确定义代码。尽可能让 PowerShell 为您服务,让它处理参数验证并检查其他约束(例如类型)。这使您能够专注于任务,同时降低代码的复杂性并提高可读性。

    如果您有疑问或意见,请随时在评论中联系。如果没有,请等到下一篇文章,我将讨论流量控制。

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

    取消回复欢迎 发表评论:

    关灯