一旦你习惯了编写 PowerShell 脚本,就需要学习代码模块化。模块化只是一个用来创建代码模块的花哨词语。在 PowerShell 的世界中,PowerShell 函数是实现这一目的的最佳方式之一。
当你编写 PowerShell 脚本时,有许多选项可以编写代码。你可以编写一千行代码,完成数百个任务,全部写在一个连续的代码块中。但那将是一场灾难。相反,你应该编写函数。
函数显著提高了代码的可用性和可读性,使其更易于处理。在本教程中,你将学习如何编写函数,添加和管理函数的参数,并设置函数以接受管道输入。但首先,让我们先了解一些术语。
本文摘自我的书《PowerShell for Sysadmins: Workflow Automation Made Easy》。如果你从本教程中学到了一些东西,请务必查阅该书,了解更多关于函数和大量其他 PowerShell 神器的信息。
函数 vs. 命令(cmdlets)
概念上,函数的概念可能听起来很熟悉,因为它听起来有点像您可能已经在使用的 cmdlet。例如,像 Start-Service
和 Write-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,哪些是函数,如下所示
上述命令返回当前加载到您的 PowerShell 会话中或可用于 PowerShell 的模块中的所有函数。
先决条件
如果您想一起运行所有示例,请确保您有一个可用的 PowerShell 版本。本教程没有特定的版本要求。另外,请确保您有一个像 Visual Studio Code 这样的好代码编辑器,可以复制、粘贴和运行一些代码片段。
构建一个简单的函数
在你使用函数之前,你需要定义它。要定义一个函数,你可以使用`function`关键字,后跟一个描述性的、用户定义的名称,再后面是一对花括号。花括号内是你想要 PowerShell 执行的脚本块。
下面你可以看到一个基本的函数以及执行该函数。这个函数叫做Install-Software
,使用Write-Host
来在控制台显示一条消息。一旦定义好了,你就可以使用这个函数的名称来执行其脚本块中的代码。
动词-名词命名
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
命令。
更改函数行为
如果你想要改变函数的行为,你可以简单地修改函数执行的代码,如下所示。
现在你已经修改了函数内的代码,它将显示一个略有不同的消息。
定义高级函数
你可以在许多不同的地方定义函数。在上面的部分中,教程假设你只是将代码直接复制粘贴到 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函数可以具有任意数量的参数。当你创建自己的函数时,你可以选择包含参数并决定这些参数的工作方式。这些参数可以是强制性的或可选的,它们可以接受任何内容,或者被强制接受可能参数列表中的一个。
例如,通过Install-Software
函数安装的虚构软件可能有许多版本。但是目前,Install-Software
函数未提供用户指定要安装哪个版本的选项。
如果你是唯一使用该函数的人,你可以每次想要特定版本时更改其中的代码,但那将是浪费时间。这个过程还容易出现潜在错误,更不用说你希望其他人能够使用你的代码。
在函数中引入参数允许它具有可变性。就像变量允许你编写能够处理同一情况的多个版本的脚本一样,参数允许你编写一个执行同一任务的单个函数的多种方式。
在这种情况下,你希望它安装同一软件的不同版本,并在许多计算机上执行此操作。
让我们首先向函数添加一个参数,使您或用户能够指定要安装的版本。
创建一个简单的参数
在函数上创建参数需要一个param
块。 param
块保存函数的所有参数。如下所示,使用param
关键字后跟括号来定义param
块。
到目前为止,您的函数的实际功能没有改变。您只是安装了管道,为函数准备了一个参数。
目前,该函数实际上并未安装任何软件。它只是使用
Write-Host
cmdlet来模拟软件安装,以便您专注于编写函数。
添加了param
块后,您可以将参数放在param
块的括号内,如下所示。
在上面的param
块内,您首先会定义参数块。像这样使用Parameter()
块将其转换为“高级参数”。 这里的空参数块什么都不做,但是是必需的;我将在下一节中解释如何使用它。
让我们将注意力转移到参数名称前面的 `[string]
` 类型上。通过在参数变量名称之前将参数的类型放在方括号中,您将参数的值转换为特定类型。PowerShell 将始终尝试将传递给此参数的任何值转换为字符串——如果它还不是字符串的话。在上面,任何传入的 `$Version` 都将始终被视为字符串。
将参数转换为类型并非强制性要求,但我强烈建议这样做。这样明确地定义了类型,并将大大减少后续错误。
现在,您还需要将 `$Version` 添加到您的 `Write-Host` 语句中。这意味着当您运行带有 `Version` 参数的 `Install-Software` 函数并传递版本号时,您应该会得到如下所示的语句。
现在让我们看看您能用这个参数做些什么。
强制参数属性
您可以使用参数块来控制各种参数属性,这将允许您更改参数的行为。例如,如果您希望确保调用函数的任何人都必须传递给定的参数,您可以将该参数定义为 Mandatory。
默认情况下,参数是可选的。让我们使用参数块内的 Mandatory 关键字来强制用户传递一个版本,如下所示。
如果您运行上面的函数,您应该会得到以下提示:

一旦设置了强制属性,执行没有参数的函数将会暂停执行,直到用户输入一个值。函数现在将等待用户为Version
参数指定一个值。一旦输入,PowerShell将执行该函数。
为了避免强制参数提示,只需在调用函数时将一个值传递给参数,如下所示。
默认参数值
例如,如果你发现自己一遍又一遍地传递同一个参数的相同值,你可以定义一个默认参数值。默认参数在大多数情况下期望参数有一个特定值时非常有用。
例如,如果你想90%的时间安装软件的第2版,而不想每次运行此函数时都设置该值,你可以将$Version
参数的默认值设置为2。你可以在下面的示例中看到这个例子。
拥有默认参数不会阻止你传递一个值。你传入的值将覆盖默认值。
添加参数验证属性
限制通过参数传递给函数的值总是个好主意,最好的方法是使用参数验证属性。
通过参数验证属性限制用户(甚至是你自己!)可以传递给函数或脚本的信息,将消除函数内部不必要的代码。例如,假设你将值3传递给Install-Software
函数,知道版本3是一个现有的版本。
你的函数假设每个用户都知道存在哪些版本,因此它没有考虑当你尝试指定第 4 版本时会发生什么情况。在这种情况下,函数将无法找到适当的文件夹,因为它不存在。
没有参数验证的生活
查看下面的示例,当你在文件路径中使用$Version
字符串时会发生什么。如果有人传递一个不完整的现有文件夹名称(例如,SoftwareV3或SoftwareV4),代码将失败。

您可以编写错误处理代码来解决此问题,或者您可以通过要求用户仅传递现有软件版本来从根本上解决问题。为了限制用户的输入,请添加参数验证。
添加参数验证
存在各种参数验证,但就您的Install-Software
函数而言,[ValidateSet
属性](https://adamtheautomator.com/powershell-validateset/)效果最佳。ValidateSet
验证属性使您能够指定参数允许的值列表。如果您只考虑字符串1
或2
,您将确保用户只能指定这些值;否则,函数将立即失败并通知用户原因。
在param
块内部添加参数验证属性,就像下面显示的原始Parameter
块下方一样。
通过在`ValidateSet`属性的尾括号中添加一组项目(1
和2
),这告诉 PowerShell Version
的唯一有效值是1
或2
。如果用户尝试传递除集合中内容以外的值,他们将收到下面显示的错误消息,通知他们只有少数选项可用。

`ValidateSet`属性是常见的验证属性,但还有其他选项可用。要详细了解如何限制参数值的所有方法,请查阅微软文档。
接受管道输入
到目前为止,您创建了一个只能通过典型的`-ParameterName
相关:ATA PowerShell 参数文章中的接受管道输入
首先,向您的代码添加另一个参数,该参数指定要安装软件的计算机。另外,将该参数添加到您的Write-Host
引用中以模拟安装。
一旦您向函数添加了ComputerName
参数,您就可以像下面这样迭代计算机名称列表,并将计算机名称和版本的值传递给Install-Software
函数。
而不是进行所有这些操作,您应该学会使用管道。
使函数与管道兼容
不幸的是,您无法仅使用之前构建的简单函数就利用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
.
要添加管道支持,请使用以下两个关键字之一向您想要的参数添加参数属性:ValueFromPipeline
或ValueFromPipelineByPropertyName
,如下所示。
一旦您加载了更新的Instal-Software
函数,然后像这样调用它:
再次运行脚本,您应该会得到类似以下的结果:
请注意,Install-Software
仅对数组中的最后一个字符串执行。在下一节中,您将看到如何修复这个问题。
添加一个process块
要告诉 PowerShell 对每个传入的对象执行此函数,您必须包含一个处理块。在处理块内部,放入您希望每次函数接收管道输入时执行的代码。按照下面所示的方式向您的脚本添加处理块。
现在像之前一样再次调用该函数。Install-Software
函数现在会返回三行(每个对象一行)。
处理块包含您希望执行的主要代码。您还可以使用 begin
和 end
块来执行在函数调用的开始和结束时执行的代码。您可以通过 Microsoft 文档了解更多关于 begin
、process
和 end
块的信息。
下一步
函数允许您将代码分成离散的构建块。它们不仅帮助您将工作分解为更小、更易管理的块,还迫使您编写可读性强且易于测试的代码。
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!