精通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,以便複製、粘貼和運行一些代碼片段。

構建一個簡單的函數

在使用函数之前,您需要定义它。要定义一个函数,您使用函数关键字,后面是一个描述性的用户定义名称,再后面是一对大括号。大括号内是要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 關鍵字來強制用戶傳遞一個版本,如下所示。

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

默認參數值

例如,如果你發現自己一遍又一遍地傳遞同一個參數值,你可以定義一個默認參數值。當你大部分時間都期望某個參數有一個特定值時,默認參數非常有用。

例如,如果你想大部分時間安裝該軟件的第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屬性是一個常見的驗證屬性,但也有其他可用的屬性。要了解如何限制參數值的所有方式的完整說明,請參閱Microsoft文檔

接受管道輸入

到目前為止,您已經創建了一個只能使用典型的-ParameterName <Value>語法傳遞的參數的函數。這是有效的,但您還有另一個選項,即使用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/