PowerShell ValidateSet:Tab-Completion & 參數值

當你編寫 PowerShell 腳本或函數時,通常希望通過參數接受用戶輸入。如果不限制這些參數接受的值,就無法保證會出現不適當的值的情況。本文將介紹如何使用 PowerShell 的 ValidateSet 參數驗證屬性,將這些值限制為只能是您定義的值。

在編寫 PowerShell 腳本或函數時,可以使用多種不同的驗證屬性來檢查參數的值是否可接受,並在值不可接受時通知用戶。

本文專注於 ValidateSet 驗證屬性。您將學習到 ValidateSet 的作用,為什麼您可能希望在代碼中使用 ValidateSet,以及如何使用它。您還將了解到 ValidateSet 啟用的選項補全功能,這將有助於用戶提供有效的參數值。

PowerShell ValidateSet:簡要概述

ValidateSet是一個參數屬性,允許您定義一組元素,這些元素只能作為該參數的值接受。

例如,也許您有一個腳本被定義為與 Active Directory 域控制器一起工作。此腳本有一個參數,用於指定域控制器的名稱。在您事先知道腳本需要的值時,限制可接受的值列表為實際的域控制器名稱是有意義的。ValidateSet 允許您實現這一能力。

需求

本文將提供一個學習指南。如果您打算跟隨進行學習,您需要以下工具:

  • Visual Studio Code或任何其他的程式碼編輯器。我將使用Visual Studio Code。
  • 至少需要PowerShell 5.1來執行本文中的大多數程式碼。有一個部分需要PowerShell 6.1或更新版本,我會在需要時進行標識

本文中的所有程式碼已在以下環境中進行過測試:

Operating System PowerShell Versions
Windows 7 SP1 5.1, Core 6.2
Windows 10 1903 5.1, Core 6.2
Linux Mint 19.2 Core 6.2

為了解釋ValidateSet的概念,您將建立一個名為Get-PlanetSize.ps1的小型腳本。該腳本返回我們太陽系中行星的大小資訊。

您將從一個簡單的腳本開始,逐步提高其處理來自最終用戶的輸入能力,並使他們能夠輕鬆地發現可能的參數值。

入門指南

首先,將以下PowerShell程式碼複製並粘貼到您最喜歡的文本編輯器中,並將其保存為Get-PlanetSize.ps1

$planets = [ordered]@{
     'Mercury' = 4879
     'Venus'   = 12104
     'Earth'   = 12756
     'Mars'    = 6805
     'Jupiter' = 142984
     'Saturn'  = 120536
     'Uranus'  = 51118
     'Neptune' = 49528
     'Pluto'   = 2306
 }
 $planets.keys | Foreach-Object {
     $output = "The diameter of planet {0} is {1} km" -f $_, $planets[$_]
     Write-Output $output
 }

從PowerShell提示符處運行該腳本,您應該會得到以下結果:

PS51> .\Get-PlanetSize.ps1
 The diameter of planet Mercury is 4879 km
 The diameter of planet Venus is 12104 km
 The diameter of planet Earth is 12756 km
 The diameter of planet Mars is 6805 km
 The diameter of planet Jupiter is 142984 km
 The diameter of planet Saturn is 120536 km
 The diameter of planet Uranus is 51118 km
 The diameter of planet Neptune is 49528 km
 The diameter of planet Pluto is 2306 km

這是一個提供信息但不夠靈活的版本;即使您只想查詢火星的信息,也會返回所有行星的信息。

也许你想要指定一个单独的行星而不是返回所有行星。你可以通过引入一个参数来实现这个功能。让我们看看如何实现这个功能。

使用参数接受输入

为了让脚本接受一个参数,将一个Param()块添加到脚本的顶部。将参数命名为Planet。一个适当的Param()块如下所示。

在下面的示例中,[Parameter(Mandatory)]这一行确保了必须始终向脚本提供一个行星名称。如果缺少行星名称,脚本将提示用户输入。

Param(
     [Parameter(Mandatory)]
     $Planet
 )

将这个Planet参数简单地加入到脚本中的方法是将$planets.keys | Foreach-Object {这一行改为$Planet | Foreach-Object {。现在,你不再依赖于之前静态定义的哈希表,而是读取Planet参数的值。

现在,如果你运行脚本并使用Planet参数指定一个行星,你只会看到关于那个特定行星的信息。

PS51> .\Get-PlanetSize.ps1 -Planet Mars
 The diameter of planet Mars is 6805 km

很好。脚本完成了吗?也许还没有。

选项太开放

如果你尝试使用Get-PlanetSize.ps1来查找行星Barsoom的直径,会发生什么?

PS51> .\Get-PlanetSize.ps1 -Planet Barsoom
The diameter of planet Barsoom is  km

嗯,不对。Barsoom不在行星列表中,但脚本仍然运行。我们该如何修复这个问题?

問題在於腳本接受任何輸入並使用它,而不論它是否為有效值。腳本需要一種方式來限制接受哪些值作為Planet參數。輸入ValidateSet

確保僅使用特定值

A ValidateSet list is a comma-separated list of string values, wrapped in single or double-quotes. Adding a ValidateSet attribute to a script or function parameter consists of adding a line of text to the Param() block, as shown below. Replace the Param() block in your copy of Get-PlanetSize.ps1 with the one below and save the file.

Param(
     [Parameter(Mandatory)]
     [ValidateSet("Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto")]
     $Planet
 )

嘗試再次運行腳本,並將Barsoom作為Planet參數。現在將返回一條有用的錯誤消息。該消息具體說明了出錯的原因,甚至提供了參數的可能值列表。

PS51> .\Get-PlanetSize.ps1 -Planet Barsoom
 Get-PlanetSize.ps1 : Cannot validate argument on parameter 'Planet'. The argument "Barsoom" does not belong to the set
 "Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Pluto" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
 At line:1 char:32
 .\Get-PlanetSize.ps1 -Planet Barsoom
 ~~~
 CategoryInfo          : InvalidData: (:) [Get-PlanetSize.ps1], ParameterBindingValidationException
 FullyQualifiedErrorId : ParameterArgumentValidationError,Get-PlanetSize.ps1 

使PowerShell的ValidateSet區分大小寫

ValidateSet屬性默認情況下不區分大小寫。這意味著它將接受任何字符串,只要它在允許的列表中並且具有任何大小寫方案。例如,上面的示例將同樣容易地接受Marsmars。如果需要,您可以通過使用IgnoreCase選項強制ValidateSet區分大小寫。

ValidateSet中的IgnoreCase選項是一個驗證屬性,用於確定所提供的值是否與有效值列表完全匹配。默認情況下,IgnoreCase設置為$True(忽略大小寫)。如果將其設置為$False,則將mars作為Get-PlanetSize.ps1中的Planet參數的值將生成錯誤消息。

您可以通過在有效值列表的末尾為其分配$true值來使用IgnoreCase選項,如下所示。

[ValidateSet("Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", IgnoreCase = $false)]  

現在當您嘗試使用 Planet 的值並不完全符合清單中的值時,驗證將失敗。

使用 Tab 鍵自動完成

使用 ValidateSet 的另一個好處是它提供了自動完成功能。這意味著您可以使用 TAB 鍵在參數的可能值之間進行循環。這大大提高了腳本或函數的易用性,特別是在控制台中。

在下面的示例中,有幾點需要注意:

  • 在顯示最後一個值後,自動完成會循環回第一個值。
  • 儘管 ValidateSet 的值不是按字母順序列出的,但它們以字母順序呈現。
  • 輸入首字母並按下 TAB 鍵會將自動完成提供的值限制為以該字母開頭的值。
Cycling through parameter values using Tab Completion
Restricting values returned by Tab Completion

您還可以在 PowerShell Integrated Scripting Environment (ISE) 中充分利用 ValidateSet 的自動完成功能,如下面的示例所示。ISE 的 Intellisense 功能會以漂亮的選擇框顯示可能的值清單。

Intellisense 會返回包含您輸入的字母的所有值,而不僅僅是以該字母開頭的值。

Tab Completion and Intellisense in ISE

現在我們已經介紹了 Windows 5.1 中的 ValidateSet 驗證屬性,讓我們來看看 PowerShell Core 6.1 中新增了什麼,並看看這是否可以為我們的腳本提供更多的驗證功能。

理解 PowerShell 6.1 中 ValidateSet 的變更

隨著 PowerShell Core 6.1 的到來,ValidateSet 驗證屬性新增了兩個功能:

  • ErrorMessage 屬性
  • 透過存取 System.Management.Automation.IValidateSetValuesGenerator 中的類別使用 ValidateSet

ErrorMessage 屬性

當您向 Get-PlanetSize.ps1 提供錯誤的行星名稱時,預設的錯誤訊息是有幫助的,但有點冗長:

PS61> .\Get-PlanetSize.ps1 -Planet Barsoom
 Get-PlanetSize.ps1 : Cannot validate argument on parameter 'Planet'. The argument "Barsoom" does not belong to the set
 "Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Pluto" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
 At line:1 char:32
 .\Get-PlanetSize.ps1 -Planet Barsoom
 ~~~
 CategoryInfo          : InvalidData: (:) [Get-PlanetSize.ps1], ParameterBindingValidationException
 FullyQualifiedErrorId : ParameterArgumentValidationError,Get-PlanetSize.ps1 

使用 ValidateSet 驗證屬性的 ErrorMessage 屬性來設定不同的錯誤訊息,如下面的範例所示。{0} 會自動替換為提交的值,而 {1} 會自動替換為允許的值清單。

Param(
     [Parameter(Mandatory)]
     [ValidateSet("Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto",ErrorMessage="Value '{0}' is invalid. Try one of: '{1}'")]
     $Planet
 )

替換腳本檔案中的 Param() 區塊並儲存。然後再次嘗試 Get-PlanetSize.ps1 -Planet Barsoom。注意下面的錯誤訊息比較簡短且更具描述性。

PS61> .\Get-PlanetSize.ps1 -Planet Barsoom
 Get-PlanetSize.ps1 : Cannot validate argument on parameter 'Planet'. Value 'Barsoom' is invalid. Try one of: 'Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,Pluto'
 At line:1 char:32
 .\Get-PlanetSize.ps1 -Planet Barsoom
 ~~~
 CategoryInfo          : InvalidData: (:) [Get-PlanetSize.ps1], ParameterBindingValidationException
 FullyQualifiedErrorId : ParameterArgumentValidationError,Get-PlanetSize.ps1 

接著,看一下透過 PowerShell 類別定義 ValidateSet 中可接受的值的新方法。

PowerShell 類別

自 PowerShell 5 版本以來,已經提供了自訂類型的功能。隨著 PowerShell Core 6.1 的到來,現在可以使用類別來提供 ValidateSet 的值。

使用类可以解决 ValidateSet 的主要限制 – 它是静态的。也就是说,它作为函数或脚本的一部分嵌入其中,只能通过编辑脚本本身来更改。

与 ValidateSet 一起使用的新功能是使用 System.Management.Automation.IValidateSetValuesGenerator 类的能力。我们可以使用它作为继承的基础来创建自己的类。要使用 ValidateSet,该类必须基于 System.Management.Automation.IValidateSetValuesGenerator,并且必须实现一个名为 GetValidValues() 的方法。

GetValues() 方法返回您希望接受的值列表。将静态行星列表替换为 [Planet] 类的 Param() 块将如下所示。这个示例目前无法工作。继续阅读以了解如何实现这一点。

Param(
     [Parameter(Mandator)]
     [ValidateSet([Planet],ErrorMessage="Value '{0}' is invalid. Try one of: {1}")]
     $Planet
 )

使用类作为 ValidateSet 值列表:一个真实的示例

为了演示使用类作为 ValidateSet 值列表,您将使用一个从 CSV 文本文件加载的列表来替换之前使用的静态行星列表。您将不再需要在脚本本身中维护一个静态值列表!

为类创建数据源

首先,您需要创建一个包含每个有效值的 CSV 文件。为此,请将此一组数据复制并粘贴到一个新文本文件中,并将其另存为与 Get-PlanetSize.ps1 脚本相同文件夹中的 planets.csv。

Planet,Diameter
 "Mercury","4879"
 "Venus","12104"
 "Earth","12756"
 "Mars","6805"
 "Jupiter","142984"
 "Saturn","120536"
 "Uranus","51118"
 "Neptune","49528"
 "Pluto","2306"

將類別添加到腳本中

在ValidateSet使用之前,任何由ValidateSet使用的類別都必須已經定義。這意味著Get-PlanetSize.ps1的結構目前無法運作。

必須先定義[Planet]類別才能使用,所以它必須在腳本開頭。一個合適的基本類別定義如下:

class Planet : System.Management.Automation.IValidateSetValuesGenerator {
     [String[]] GetValidValues() {
 }
 }

在類別的GetValidValues()方法中,使用Import-CSV cmdlet匯入之前建立的文本檔planets.csv。檔案被匯入一個全域範圍的變數$planets,以便在腳本的其他地方存取。

使用return語句通過GetValidValues()返回行星名稱的清單。在這些更改後,類別應該如下所示:

class Planet : System.Management.Automation.IValidateSetValuesGenerator {
     [String[]] GetValidValues() {
             $Global:planets = Import-CSV -Path planets.csv
             return ($Global:planets).Planet
     }
 }

接下來,根據下面所示,從腳本中刪除$planets雜湊表的宣告。被[Planet]類別填充的全域變數$planets包含了行星資料。

$planets = [ordered]@{
     'Mercury' = 4879
     'Venus'   = 12104
     'Earth'   = 12756
     'Mars'    = 6805
     'Jupiter' = 142984
     'Saturn'  = 120536
     'Uranus'  = 51118
     'Neptune' = 49528
     'Pluto'   = 2306
 }

現在將剩下的原始程式碼封裝在一個名為Get-PlanetDiameter的函數中,以與腳本的名稱區分開來。將原本位於腳本開頭的Param()區塊放在函數內。將靜態的行星清單替換為對[Planet]類別的引用,如下所示。

[ValidateSet([Planet],ErrorMessage="Value '{0}' is invalid. Try one of: {1}")]`

將這行代碼 $output = "行星 {0} 的直徑為 {1} 公里" -f $_, $planets[$_] 替換為以下兩行代碼。這將使腳本能夠在由 Import-CSV 創建的對象數組中查找行星,而不是之前創建的哈希表,你已經從腳本中刪除了該哈希表:

$targetplanet = $planets | Where -Property Planet -match $_
 $output = "The diameter of planet {0} is {1} km" -f $targetplanet.Planet, $targetplanet.Diameter

在進行這些更改之後,你的最終腳本應該如下所示:

class Planet : System.Management.Automation.IValidateSetValuesGenerator {
     [String[]] GetValidValues() {
         $Global:planets = Import-CSV -Path planets.csv
         return ($Global:planets).Planet
     }
 }
 Function Get-PlanetDiameter {
     Param(
         [Parameter(Mandatory)]
         [ValidateSet([Planet],ErrorMessage="Value '{0}' is invalid. Try one of: {1}")]
         $Planet
     )
     $Planet | Foreach-Object {
         $targetplanet = $planets | Where -Property Planet -match $_
         $output = "The diameter of planet {0} is {1} km" -f $targetplanet.Planet, $targetplanet.Diameter
         Write-Output $output
     }
 }

請記住,從這一點開始,腳本只能在 PowerShell 6.1 或更高版本上運行

現在,你如何使用這個腳本?

運行腳本

你不能直接執行新版本的腳本來使用腳本。所有有用的代碼現在都包含在一個函數中。你需要使用“點源”來將文件包含到 PowerShell 會話中,以便從中訪問該函數。

PS61> . .\Get-PlanetSize.ps1

將文件點源之後,你現在可以在 PowerShell 會話中使用新的 Get-PlanetDiameter 函數,並進行選項完成。

這樣做有什麼好處?”,你問道。“腳本似乎以相同的方式工作,但使用代碼更加困難!”

試試這個:

  • 打開之前創建的 planets.csv 文件。
  • 添加一個新行,包含新的名稱和直徑。
  • 保存 CSV 文件。

在您最初dot sourced腳本的同一個會話中,嘗試使用Get-PlanetDiameter查找新行星的直徑。 它有效了!

以這種方式使用類別給我們帶來了幾個好處:

  • 有效值列表現在與代碼本身分開,但文件中的任何更改都會被腳本檔案接收。
  • 該文件可以由從不存取腳本的人維護。
  • A more complex script could look up information from a spreadsheet, database, Active Directory or a web API.

正如您所見,使用類別提供ValidateSet值時,可能性幾乎無窮。

結束

我們在構建Get-PlanetSize.ps1時涵蓋了很多內容,所以讓我們回顧一下。

在本文中您學到了:

  • 什麼是ValidateSet驗證屬性以及為什麼您可能想使用它
  • 如何將ValidateSet添加到PowerShell 函數或腳本中
  • Tab鍵自動完成如何與ValidateSet一起工作
  • 如何使用IgnoreCase屬性來控制您的ValidateSet是否區分大小寫
  • 如何在PowerShell 6.1中使用ErrorMessage屬性與您的ValidateSet
  • 如何使用類別使動態的ValidateSet與PowerShell 6.1

您還在等什麼?立即開始使用ValidateSet吧!

進一步閱讀

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