掌握 PowerShell 函数:逐步指南

一旦你习惯了编写 PowerShell 脚本,就需要学习代码模块化。模块化只是一个用来创建代码模块的花哨词语。在 PowerShell 的世界中,PowerShell 函数是实现这一目的的最佳方式之一。

当你编写 PowerShell 脚本时,有许多选项可以编写代码。你可以编写一千行代码,完成数百个任务,全部写在一个连续的代码块中。但那将是一场灾难。相反,你应该编写函数。

函数显著提高了代码的可用性和可读性,使其更易于处理。在本教程中,你将学习如何编写函数,添加和管理函数的参数,并设置函数以接受管道输入。但首先,让我们先了解一些术语。

本文摘自我的书《PowerShell for Sysadmins: Workflow Automation Made Easy》。如果你从本教程中学到了一些东西,请务必查阅该书,了解更多关于函数和大量其他 PowerShell 神器的信息。

函数 vs. 命令(cmdlets)

概念上,函数的概念可能听起来很熟悉,因为它听起来有点像您可能已经在使用的 cmdlet。例如,像 Start-ServiceWrite-Host 这样的命令与函数类似。这些都是命名的代码片段,用于解决单一问题。函数和 cmdlet 之间的区别在于构建这些结构的方式。

A cmdlet isn’t written with PowerShell. It’s written in another language, typically something like C#. The cmdlet is then compiled and made available inside PowerShell.

另一方面,函数是用 PowerShell 编写的,而不是使用其他语言。

您可以通过使用 Get-Command cmdlet 及其 CommandType 参数来查看哪些命令是 cmdlet,哪些是函数,如下所示

Get-Command –CommandType Function

上述命令返回当前加载到您的 PowerShell 会话中或可用于 PowerShell 的模块中的所有函数。

相关: 理解并构建 PowerShell 模块

先决条件

如果您想一起运行所有示例,请确保您有一个可用的 PowerShell 版本。本教程没有特定的版本要求。另外,请确保您有一个像 Visual Studio Code 这样的好代码编辑器,可以复制、粘贴和运行一些代码片段。

构建一个简单的函数

在你使用函数之前,你需要定义它。要定义一个函数,你可以使用`function`关键字,后跟一个描述性的、用户定义的名称,再后面是一对花括号。花括号内是你想要 PowerShell 执行的脚本块。

下面你可以看到一个基本的函数以及执行该函数。这个函数叫做Install-Software,使用Write-Host来在控制台显示一条消息。一旦定义好了,你就可以使用这个函数的名称来执行其脚本块中的代码。

PS> function Install-Software { Write-Host 'I installed some software. Yippee!' }
PS> Install-Software
I installed some software, Yippee!

动词-名词命名

A function’s name is important. You can name your functions whatever you want, but the name should always describe what the function does. The function-naming convention in PowerShell is the Verb-Noun syntax.

你应该始终以动词开头,后跟一个连字符和一个名词来命名一个函数。要查找“已批准”动词的列表,请使用Get-Verb命令。

更改函数行为

如果你想要改变函数的行为,你可以简单地修改函数执行的代码,如下所示。

PS> function Install-Software { Write-Host 'You installed some software, Yay!' }
PS> Install-Software
You installed some software, Yay!

现在你已经修改了函数内的代码,它将显示一个略有不同的消息。

定义高级函数

你可以在许多不同的地方定义函数。在上面的部分中,教程假设你只是将代码直接复制粘贴到 PowerShell 控制台中。但这不是唯一的方法。你也可以在脚本中定义函数。

在前面的部分中,你正在处理一个很小的函数,所以在控制台中定义它并不是什么大问题。但大多数情况下,你会有更大的函数。最好是在脚本或模块中定义这些函数,然后调用该脚本或模块以将函数加载到内存中。

正如你可能想象的,每次想调整功能时都重新输入一个较大的函数可能会有点令人沮丧。

I suggest you now open your favorite editor and store the function in a .ps1 file as you work through the rest of the tutorial.

向函数添加参数

PowerShell函数可以具有任意数量的参数。当你创建自己的函数时,你可以选择包含参数并决定这些参数的工作方式。这些参数可以是强制性的或可选的,它们可以接受任何内容,或者被强制接受可能参数列表中的一个。

相关: 有关PowerShell参数的所有你想知道的

例如,通过Install-Software函数安装的虚构软件可能有许多版本。但是目前,Install-Software函数未提供用户指定要安装哪个版本的选项。

如果你是唯一使用该函数的人,你可以每次想要特定版本时更改其中的代码,但那将是浪费时间。这个过程还容易出现潜在错误,更不用说你希望其他人能够使用你的代码。

在函数中引入参数允许它具有可变性。就像变量允许你编写能够处理同一情况的多个版本的脚本一样,参数允许你编写一个执行同一任务的单个函数的多种方式。

在这种情况下,你希望它安装同一软件的不同版本,并在许多计算机上执行此操作。

让我们首先向函数添加一个参数,使您或用户能够指定要安装的版本。

创建一个简单的参数

在函数上创建参数需要一个param块。 param块保存函数的所有参数。如下所示,使用param关键字后跟括号来定义param块。

function Install-Software {
	[CmdletBinding()]
	param()

	Write-Host 'I installed software version 2. Yippee!'
}

到目前为止,您的函数的实际功能没有改变。您只是安装了管道,为函数准备了一个参数。

目前,该函数实际上并未安装任何软件。它只是使用Write-Host cmdlet来模拟软件安装,以便您专注于编写函数。

添加了param块后,您可以将参数放在param块的括号内,如下所示。

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter()]
		[string] $Version
	)
	
	Write-Host "I installed software version $Version. Yippee!"

}

在上面的param块内,您首先会定义参数块。像这样使用Parameter()块将其转换为“高级参数”。 这里的空参数块什么都不做,但是是必需的;我将在下一节中解释如何使用它。

让我们将注意力转移到参数名称前面的 `[string]` 类型上。通过在参数变量名称之前将参数的类型放在方括号中,您将参数的值转换为特定类型。PowerShell 将始终尝试将传递给此参数的任何值转换为字符串——如果它还不是字符串的话。在上面,任何传入的 `$Version` 都将始终被视为字符串。

将参数转换为类型并非强制性要求,但我强烈建议这样做。这样明确地定义了类型,并将大大减少后续错误。

现在,您还需要将 `$Version` 添加到您的 `Write-Host` 语句中。这意味着当您运行带有 `Version` 参数的 `Install-Software` 函数并传递版本号时,您应该会得到如下所示的语句。

PS> Install-Software -Version 2
I installed software version 2. Yippee!

现在让我们看看您能用这个参数做些什么。

强制参数属性

您可以使用参数块来控制各种参数属性,这将允许您更改参数的行为。例如,如果您希望确保调用函数的任何人都必须传递给定的参数,您可以将该参数定义为 Mandatory。

默认情况下,参数是可选的。让我们使用参数块内的 Mandatory 关键字来强制用户传递一个版本,如下所示。

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter(Mandatory)]
		[string]$Version
	)

	Write-Host "I installed software version $Version. Yippee!"

}

如果您运行上面的函数,您应该会得到以下提示:

Prompting for a mandatory parameter

一旦设置了强制属性,执行没有参数的函数将会暂停执行,直到用户输入一个值。函数现在将等待用户为Version参数指定一个值。一旦输入,PowerShell将执行该函数。

为了避免强制参数提示,只需在调用函数时将一个值传递给参数,如下所示。

Install-Software -Version 2

默认参数值

例如,如果你发现自己一遍又一遍地传递同一个参数的相同值,你可以定义一个默认参数值。默认参数在大多数情况下期望参数有一个特定值时非常有用。

例如,如果你想90%的时间安装软件的第2版,而不想每次运行此函数时都设置该值,你可以将$Version参数的默认值设置为2。你可以在下面的示例中看到这个例子。

function Install-Software {
	[CmdletBinding()]
	param(
		[Parameter()]
		[string]$Version = 2
	)

	Write-Host "I installed software version $Version. Yippee!"

}

拥有默认参数不会阻止你传递一个值。你传入的值将覆盖默认值。

添加参数验证属性

限制通过参数传递给函数的值总是个好主意,最好的方法是使用参数验证属性。

通过参数验证属性限制用户(甚至是你自己!)可以传递给函数或脚本的信息,将消除函数内部不必要的代码。例如,假设你将值3传递给Install-Software函数,知道版本3是一个现有的版本。

你的函数假设每个用户都知道存在哪些版本,因此它没有考虑当你尝试指定第 4 版本时会发生什么情况。在这种情况下,函数将无法找到适当的文件夹,因为它不存在。

没有参数验证的生活

查看下面的示例,当你在文件路径中使用$Version字符串时会发生什么。如果有人传递一个不完整的现有文件夹名称(例如,SoftwareV3或SoftwareV4),代码将失败。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[string]$Version
	)

	Get-ChildItem -Path \\SRV1\Installers\SoftwareV$Version

}
Failing on unexpected parameter values

您可以编写错误处理代码来解决此问题,或者您可以通过要求用户仅传递现有软件版本来从根本上解决问题。为了限制用户的输入,请添加参数验证。

添加参数验证

存在各种参数验证,但就您的Install-Software函数而言,[ValidateSet属性](https://adamtheautomator.com/powershell-validateset/)效果最佳。ValidateSet验证属性使您能够指定参数允许的值列表。如果您只考虑字符串12,您将确保用户只能指定这些值;否则,函数将立即失败并通知用户原因。

param块内部添加参数验证属性,就像下面显示的原始Parameter块下方一样。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version
	)

	Get-ChildItem -Path \\SRV1\Installers\SoftwareV$Version

}

通过在`ValidateSet`属性的尾括号中添加一组项目(12),这告诉 PowerShell Version 的唯一有效值是12。如果用户尝试传递除集合中内容以外的值,他们将收到下面显示的错误消息,通知他们只有少数选项可用。

Parameter validation stopping PowerShell function execution

`ValidateSet`属性是常见的验证属性,但还有其他选项可用。要详细了解如何限制参数值的所有方法,请查阅微软文档

接受管道输入

到目前为止,您创建了一个只能通过典型的`-ParameterName `语法传递参数的函数。这样做是有效的,但您还有另一种选择,即使用PowerShell 管道传递参数值。让我们为我们的 Install-Software 函数添加管道功能。

相关:ATA PowerShell 参数文章中的接受管道输入

首先,向您的代码添加另一个参数,该参数指定要安装软件的计算机。另外,将该参数添加到您的Write-Host引用中以模拟安装。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[string]$Version
		[ValidateSet('1','2')],
		
		[Parameter(Mandatory)]
		[string]$ComputerName
	)

	Write-Host "I installed software version $Version on $ComputerName. Yippee!"

}

一旦您向函数添加了ComputerName参数,您就可以像下面这样迭代计算机名称列表,并将计算机名称和版本的值传递给Install-Software函数。

$computers = @("SRV1", "SRV2", "SRV3")
foreach ($pc in $computers) {
	Install-Software -Version 2 -ComputerName $pc
}

而不是进行所有这些操作,您应该学会使用管道。

使函数与管道兼容

不幸的是,您无法仅使用之前构建的简单函数就利用PowerShell管道。您必须决定您希望函数接受哪种类型的管道输入,并加以实现。

A PowerShell function uses two kinds of pipeline input: ByValue (entire object) and ByPropertyName (a single object property). Here, because the $computers array contains only strings, you’ll pass those strings via ByValue.

要添加管道支持,请使用以下两个关键字之一向您想要的参数添加参数属性:ValueFromPipelineValueFromPipelineByPropertyName,如下所示。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version,

		[Parameter(Mandatory, ValueFromPipeline)]
		[string]$ComputerName
	)

	Write-Host "I installed software version $Version on $ComputerName. Yippee!"

}

一旦您加载了更新的Instal-Software函数,然后像这样调用它:

$computers = @("SRV1", "SRV2", "SRV3")
$computers | Install-Software -Version 2

再次运行脚本,您应该会得到类似以下的结果:

I installed software version 2 on SRV3. Yippee!

请注意,Install-Software仅对数组中的最后一个字符串执行。在下一节中,您将看到如何修复这个问题。

添加一个process块

要告诉 PowerShell 对每个传入的对象执行此函数,您必须包含一个处理块。在处理块内部,放入您希望每次函数接收管道输入时执行的代码。按照下面所示的方式向您的脚本添加处理块。

function Install-Software {
	param(
		[Parameter(Mandatory)]
		[ValidateSet('1','2')]
		[string]$Version,

		[Parameter(Mandatory, ValueFromPipeline)]
		[string]$ComputerName
	)

	process {
		Write-Host "I installed software version $Version on $ComputerName. Yippee!"
	}
}

现在像之前一样再次调用该函数。Install-Software 函数现在会返回三行(每个对象一行)。

I installed software version 2 on SRV1. Yippee!
I installed software version 2 on SRV2. Yippee!
I installed software version 2 on SRV3. Yippee!

处理块包含您希望执行的主要代码。您还可以使用 beginend 块来执行在函数调用的开始和结束时执行的代码。您可以通过 Microsoft 文档了解更多关于 beginprocessend 块的信息。

下一步

函数允许您将代码分成离散的构建块。它们不仅帮助您将工作分解为更小、更易管理的块,还迫使您编写可读性强且易于测试的代码。

I now challenge you to look through some old scripts and see where you can add a PowerShell function. Look for patterns in code. Build a function from those patterns. Notice where you’re copying/pasting code snippets and turn those into PowerShell functions!

Source:
https://adamtheautomator.com/powershell-functions/