PowerShell參數:解鎖腳本編寫的力量

所有的 PowerShell 命令都可以有一个或多个参数,有时被称为参数。如果你在 PowerShell 函数中没有使用 PowerShell 参数,那么你就没有编写好的 PowerShell 代码!

在本文中,你将学习创建和使用 PowerShell 参数或参数的几乎所有方面!

这是我写的《PowerShell for SysAdmins》一书的示例。如果你想学习 PowerShell 或学习一些技巧,请查看一下

为什么需要参数?

当你开始创建函数时,你可以选择是否包含参数以及这些参数如何工作。

假设你有一个安装 Microsoft Office 的函数。也许它在函数内部静默调用 Office 安装程序。对于我们的目的来说,函数的具体操作并不重要。基本函数如下,包括函数的名称和脚本块。

function Install-Office {
    ## 在此运行静默安装程序
}

在这个例子中,你只是运行了没有参数的 Install-Office,它会按照它的方式执行。

无论 Install-Office 函数是否有参数,都没有关系。它显然没有任何强制性参数;否则,PowerShell 将不会允许我们在不使用参数的情况下运行它。

何时使用 PowerShell 参数

Office有很多不同的版本。也许你需要安装Office 2013和2016。目前,你无法指定这一点。每次想要更改行为时,你都可以更改函数的代码。

例如,你可以创建两个单独的函数来安装不同的版本。

function Install-Office2013 {
    Write-Host 'I installed Office 2013. Yippee!'
}

function Install-Office2016 {
    Write-Host 'I installed Office 2016. Yippee!'
}

这样做是可行的,但不具有可扩展性。它强制你为每个发布的Office版本创建一个单独的函数。当你不必这样做时,你将不得不复制很多代码。

相反,你需要一种方法在运行时传递不同的值来更改函数的行为。你如何做到这一点?

是的!参数或有些人称之为参数。

由于我们希望安装不同版本的Office而不必每次更改代码,所以你必须向这个函数添加至少一个参数。

在迅速想出要使用的PowerShell参数之前,首先必须问自己一个问题:“在这个函数中,你预计会需要哪些最小的更改或变化?”

记住,你需要重新运行这个函数,而不改变函数内部的任何代码。在这个例子中,参数对你来说可能很清楚;你需要添加一个Version参数。但是,当你有一个有几十行代码的函数时,答案可能不太明显。只要你尽可能准确地回答这个问题,它总是会有帮助的。

所以你知道你需要一个Version参数。现在怎么办?你现在可以添加一个,但像任何优秀的编程语言一样,有多种方法可以做到这一点。

在這個教程中,我將向您展示基於我在PowerShell方面近十年的經驗,創建參數的“最佳”方法。然而,請知道這不是創建參數的唯一方法。

還有一種叫做位置參數的東西。這些參數允許您在不指定參數名稱的情況下傳遞值給參數。位置參數可以工作,但不被視為“最佳實踐”。為什麼?因為當您在函數上定義了很多參數時,它們變得更難閱讀。

創建一個簡單的PowerShell參數

在函數上創建一個參數需要兩個主要組件:一個param塊和參數本身。一個param塊由param關鍵字後面跟著一組括號來定義。

An example of a param block
function Install-Office { [CmdletBinding()] param() Write-Host 'I installed Office 2016. Yippee!' }

此時,函數的實際功能並未發生任何改變。我們只是組合了一些基礎設施,為第一個參數做準備。

一旦我們放置了param塊,您現在可以創建參數了。我建議您創建參數的方法包括Parameter塊,然後是參數的類型,下面是參數的變數名稱。

An example of a param block
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host 'I installed Office 2016. Yippee!' }

現在我們已經在PowerShell中創建了一個函數參數,但究竟發生了什麼呢?

Parameter塊是每個參數的可選但推薦的一部分。就像param塊一樣,它是“函數基礎設施”,為參數添加額外功能做準備。第二行是您定義參數類型的地方。

在這個案例中,我們選擇將 Version 參數轉換為字串。明確定義類型意味著如果該參數不是字串,則任何傳遞給該參數的值都將嘗試”轉換”為字串。

類型並非強制要求,但強烈建議明確定義參數的類型,這將大大減少日後出現的許多不需要的情況。相信我。

現在您已經定義了該參數,您可以運行 Install-Office 命令,並將 Version 參數傳遞給它,比如像 2013 這樣的版本字串。傳遞給 Version 參數的值有時被稱為參數的引數或值。

Passing a Parameter to the Function
PS> Install-Office -Version 2013 I installed Office 2016. Yippee!

到底發生了什麼?您告訴它您想安裝 2013 版本,但它仍然告訴您已經安裝了 2016 版本。當您添加一個參數後,您必須記住將函數的代碼更改為變數。當參數傳遞給函數時,該變數將被擴展為傳遞的任何值。

將靜態文本 2016 更改為 Version 參數變數,並將單引號轉換為雙引號,以便該變數能夠擴展。

Modifying function code account for a parameter
function Install-Office { [CmdletBinding()] param( [Parameter()] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" }

現在您可以看到,無論您傳遞給 Version 參數的值是什麼,它都將作為 $Version 變數傳遞到函數中。

強制參數屬性

記得我提到過[Parameter()]行只是”功能的管道”,需要準備函數進一步的工作嗎?為參數添加參數屬性就是我之前提到的額外工作。

A parameter doesn’t have to be a placeholder for a variable. PowerShell has a concept called parameter attributes and parameter validation. Parameter attributes change the behavior of the parameter in a lot of different ways.

例如,你將設置最常見的參數屬性之一是Mandatory關鍵字。默認情況下,你可以呼叫Install-Office函數而不使用Version參數,它仍然可以正常執行。版本參數將是可選的。當然,它不會展開函數內部的$Version變量,因為沒有值,但它仍然會運行。

在創建參數時,很多時候你希望用戶始終使用該參數。你將依賴於函數代碼中的參數值,如果沒有傳遞該參數,函數將失敗。在這些情況下,你希望強制用戶傳遞該參數值給你的函數。你希望該參數變得強制性

一旦你構建了基本框架,強制用戶使用參數就很簡單。你必須在參數的括號內包含關鍵字Mandatory。一旦設置完成,如果在不帶參數的情況下執行函數,它將停止執行,直到輸入了一個值。

Using a mandatory parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Write-Host "I installed Office $Version. Yippee!" } PS> Install-Office cmdlet Install-Office at command pipeline position 1 Supply values for the following parameters: Version:

函數將等待你為Version參數指定一個值。一旦你這樣做並按下Enter,PowerShell將執行函數並繼續執行。如果為該參數提供了一個值,PowerShell將不會每次提示你輸入該參數。

PowerShell 參數驗證屬性

使參數成為必填項是您可以添加的最常見的參數屬性之一,但您還可以使用參數驗證屬性。在編程中,限制用戶輸入的內容是非常重要的。限制用戶(甚至是您自己!)可以傳遞給函數或腳本的信息,可以消除函數內部不必要的代碼,因為您不需要考慮各種情況。

通過示例學習

例如,在Install-Office函數中,我演示了將值2013傳遞給它,因為我知道它會起作用。我寫了這段代碼!我假設(在代碼中絕不要這樣做!)認為對於任何了解的人來說,明確指定版本為2013或2016是很明顯的。但是,對於其他人來說,對您來說顯而易見的事情可能並非如此。

如果您想對版本進行技術性討論,那麼如果微軟仍然使用過去的版本命名方案,更準確的是將2013版本指定為15.0,將2016版本指定為16.0。但是,如果您假設他們將指定版本為2013或2016,則函數內部的代碼會查找具有這些版本的文件夾或其他操作,會發生什麼情況呢?

下面是一個示例,其中您可能在文件路徑中使用$Version字符串。如果有人傳遞了無法完成文件夾名稱Office2013Office2016的值,它將失敗,或者執行一些更糟糕的操作,例如刪除意外的文件夾或更改您未考慮的事物。

Assuming Parameter Values
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version '15.0' Get-ChildItem : Cannot find path '\SRV1\Installers\Office15.0' because it does not exist. At line:7 char:5 Get-ChildItem -Path "\\SRV1\Installers\Office$Version" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CategoryInfo : ObjectNotFound: (\SRV1\Installers\Office15.0:String) [Get-ChildItem], ItemNotFoundExcep tion FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

為了限制用戶輸入您期望的內容,您可以添加一些PowerShell參數驗證。

使用ValidateSet參數驗證屬性

有各種類型的參數驗證可供使用。要查看完整列表,執行Get-Help about_Functions_Advanced_Parameters。在這個例子中,ValidateSet屬性可能是最好的選擇。

ValidateSet驗證屬性允許您指定一個允許作為參數值的值列表。由於我們只考慮字符串20132016,我希望確保用戶只能指定這些值。否則,函數將立即失敗,通知用戶失敗的原因。

您可以在原始Parameter關鍵字下方添加參數驗證屬性。在此示例中,在參數屬性的括號內,您有一個項目數組;20132016。參數驗證屬性告訴PowerShell,Version的有效值只能是20132016。如果嘗試傳遞不在集合中的值,您將收到一個錯誤,告訴您只有特定數量的選項可用。

Using the ValidateSet parameter validation attribute
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version ) Get-ChildItem -Path "\\SRV1\Installers\Office$Version" } PS> Install-Office -Version 15.0 Install-Office : Cannot validate argument on parameter 'Version'. The argument "15.0" does not belong to the set "2013,2016" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again. At line:1 char:25 Install-Office -Version 15.0 ~~~~ CategoryInfo : InvalidData: (:) [Install-Office], ParameterBindingValidationException FullyQualifiedErrorId : ParameterArgumentValidationError,Install-Office

ValidateSet屬性是一個常見的驗證屬性。如果想要了解參數值可以如何限制,可以查看Functions_Advanced_Parameters的幫助主題,方法是運行Get-Help about_Functions_Advanced_Parameters

參數集

假設你只想讓某些PowerShell參數與其他參數一起使用。也許你已經在Install-Office函數中添加了一個Path參數。這個路徑將安裝安裝程式的任何版本。在這種情況下,你不想讓用戶使用Version參數。

你需要參數集。

參數可以分組到只能與同一組參數一起使用的集合中。使用下面的函數,你現在可以同時使用Version參數和Path參數來生成安裝程式的路徑。

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory)]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

然而,這樣會產生一個問題,因為用戶可能會使用兩個參數。而且,由於這兩個參數都是必需的,他們將被強制使用兩個參數,而這不是你想要的。為了解決這個問題,我們可以將每個參數放入一個參數集中,如下所示。

function Install-Office {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ParameterSetName = 'ByVersion')]
        [ValidateSet('2013','2016')]
        [string]$Version,
        
        [Parameter(Mandatory, ParameterSetName = 'ByPath')]
        [string]$Path
    )
    
    if ($Version) {
        Get-ChildItem -Path "\\SRV1\Installers\Office$Version"
    } elseif ($Path) {
        Get-ChildItem -Path $Path
    }
}

通過在每個參數上定義一個參數集名稱,這允許你控制一組參數。

默認參數集

如果用戶嘗試在沒有參數的情況下運行Install-Office,這是沒有考慮到的,你會看到一個友好的錯誤消息。

No parameter set

要修复这个问题,你需要在 CmdletBinding() 区域内定义一个默认参数集。这告诉函数在没有显式使用参数的情况下选择要使用的参数集,将 [CmdletBinding()] 改为 [CmdletBinding(DefaultParameterSetName = 'ByVersion')]

现在,每当运行 Install-Office,它都会提示输入 Version 参数,因为它将使用该参数集。

管道输入

到目前为止的示例中,你已经创建了一个带有 PowerShell 参数的函数,只能使用典型的 -ParameterName Value 语法进行传递。但是,正如你已经学到的,PowerShell 有一个直观的管道,允许你无缝地将对象从一个命令传递到另一个命令,而不使用“典型”的语法。

当你使用管道时,你可以使用管道符号 | 将命令“链接”在一起,允许用户将一个命令的输出发送到另一个命令,作为将 Name 参数传递给 Start-Service 的快捷方式。

“旧”方法使用循环

在你正在处理的自定义函数中,你正在安装 Office,并且有一个 Version 参数。假设你有一个包含计算机名称的 CSV 文件,在第一行是计算机名称,在第二行是需要在这些计算机上安装的 Office 版本。CSV 文件的内容如下:

ComputerName,Version
PC1,2016
PC2,2013
PC3,2016

你希望在每台计算机上安装与该计算机旁边的 Office 版本相对应的版本。

首先,您需要在函数中添加一个ComputerName参数,以便在每次函数迭代中传递不同的计算机名称。下面我创建了一些伪代码,表示可能在虚构函数中的一些代码,并添加了一个Write-Host实例,以查看函数内部变量的展开情况。

Adding the ComputerName parameter
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('2013','2016')] [string]$Version, [Parameter()] [string]$ComputerName ) <# ## 使用一些代码连接到远程计算机 Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## 对此计算机安装 Office 版本的操作 Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

一旦您在函数中添加了ComputerName参数,您可以通过读取CSV文件并将计算机名称和版本的值传递给Install-Office函数来实现这一点。

$computers = Import-Csv -Path 'C:\ComputerOfficeVersions.csv'
foreach ($pc in $computers) {
    Install-Office -Version $_.Version -ComputerName $_.ComputerName
}

为参数构建管道输入

通过读取CSV行并使用循环将每行的属性传递给函数的方法是“旧”方法。在本节中,您希望完全放弃foreach循环,改为使用管道。

目前,该函数完全不支持管道。直观上可能认为您可以使用管道将每个计算机名称和版本传递给函数。下面,我们正在读取CSV并直接将其传递给Install-Office,但这行不通。

No function pipeline input defined
PS> Import-Csv -Path 'C:\ComputerOfficeVersions.csv' | Install-Office

你可以假設任何你想的,但那並不能讓它生效。當你知道Import-Csv正在將它作為物件屬性發送時,我們會被提示輸入Version參數。為什麼它不起作用呢?因為你尚未添加任何管道支援。

在PowerShell函數中有兩種類型的管道輸入;按值(整個物件)和按屬性名(單個物件屬性)。你認為將Import-Csv的輸出與Install-Office的輸入串接起來的最佳方式是哪一種?

在完全不重構的情況下,你可以使用按屬性名的方法,因為畢竟Import-Csv已經返回了CSV中的VersionComputerName這兩個屬性。

將管道支援添加到自定義函數比你想像的要簡單得多。它只是一個使用兩個關鍵字表示的參數屬性;ValueFromPipelineValueFromPipelineByPropertyName

在這個例子中,你希望將Import-Csv返回的ComputerNameVersion屬性綁定到Install-OfficeVersionComputerName參數上,因此你將使用ValueFromPipelineByPropertyName

由於我們希望綁定這兩個參數,你將在兩個參數中都添加這個關鍵字,如下所示,然後使用管道重新運行函數。

Adding Pipeline Support
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) <# ## 使用一些代碼連接到遠程主機 Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## 做一些安裝辦公室版本的工作 Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" }

這很奇怪。它只運行了CSV的最後一行。怎麼回事?它只執行了最後一行,因為在構建不支持管道的函數時,你跳過了一個不需要的概念。

不要忘記 Process 塊!

當你需要創建涉及管道支持的函數時,你必須在函數內部包含(至少)一個“嵌入”的塊,稱為 process。這個 process 塊告訴 PowerShell 在接收到管道輸入時,運行每一次迭代的函數。默認情況下,它只會執行最後一次。

你還可以添加其他塊,如 begin 和 end,但腳本編寫者很少使用它們。

為了告訴 PowerShell 對每個輸入的對象執行此函數,我將添加一個包含其中代碼的 process 塊。

Adding the process block
function Install-Office { [CmdletBinding()] param( [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [ValidateSet('2013','2016')] [string]$Version, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) process { <# ## 使用一些代碼連接到遠程主機 Invoke-Command -ComputerName $ComputerName -ScriptBlock { ## 做一些安裝辦公室版本的工作 Start-Process -FilePath 'msiexec.exe' -ArgumentList 'C:\Setup\Office{0}.msi' -f $using:Version } #> Write-Host "I am installing Office version [$Version] on computer [$ComputerName]" } }

現在你可以看到從Import-Csv返回的每個物件的VersionComputerName屬性已傳遞給Install-Office綁定VersionComputerName參數。

資源

若要深入了解函數參數的工作原理,請查看我關於PowerShell函數的部落格文章

I also encourage you to check my Pluralsight course entitled Building Advanced PowerShell Functions and Modules for an in-depth breakdown of everything there is to know about PowerShell functions, function parameters, and PowerShell modules.

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