PowerShell 函式有數個功能,可大幅改善使用者與其互動的方式。
容易被忽視的一個重要功能是 -WhatIf 和 -Confirm 的支援,且它很容易添加到您的函式中。 在本文中,我們會深入探討如何實作這項功能。
注意
本文的原始版本出現在@KevinMarquette撰寫的部落格上。 PowerShell 小組感謝 Kevin 與我們分享此內容。 請查看他在 PowerShellExplained.com 的部落格。
這是您可以在函式中啟用的簡單功能,可為需要此功能的使用者提供安全網。 沒有什麼比第一次執行你知道可能危險的命令更可怕了。 使用選項來運行它 -WhatIf 可能會造成很大的差異。
通用參數
在探討實作這些 常見參數之前,我想先快速查看其使用方式。
使用 -WhatIf
當命令支持 -WhatIf 參數時,它可讓您查看命令會執行的動作,而不是進行變更。 這是測試命令影響的好方法,尤其是在您執行破壞性動作之前。
PS C:\temp> Get-ChildItem
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/19/2021 8:59 AM 0 importantfile.txt
-a---- 4/19/2021 8:58 AM 0 myfile1.txt
-a---- 4/19/2021 8:59 AM 0 myfile2.txt
PS C:\temp> Remove-Item -Path .\myfile1.txt -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
如果命令正確實作 ShouldProcess,它應該會顯示出它本應進行的所有變更。 以下是使用通配符刪除多個檔案的範例。
PS C:\temp> Remove-Item -Path * -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
What if: Performing the operation "Remove File" on target "C:\Temp\myfile2.txt".
What if: Performing the operation "Remove File" on target "C:\Temp\importantfile.txt".
使用 -Confirm
支援 -WhatIf 的指令也支援 -Confirm。 這可讓您在執行動作之前先確認動作。
PS C:\temp> Remove-Item .\myfile1.txt -Confirm
Confirm
Are you sure you want to perform this action?
Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
在此情況下,您有多個選項可讓您繼續、略過變更或停止腳本。 說明提示會描述上述每個選項。
Y - Continue with only the next step of the operation.
A - Continue with all the steps of the operation.
N - Skip this operation and proceed with the next operation.
L - Skip this operation and all subsequent operations.
S - Pause the current pipeline and return to the command prompt. Type "exit" to resume the pipeline.
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
本地化
此提示會在 PowerShell 中當地語系化,因此語言會根據您的作業系統語言變更。 這又是 PowerShell 為您管理的一件事。
切換參數
讓我們花一些時間快速查看將值傳遞至 switch 參數的方法。 我之所以這樣說的主要原因是,您通常會想要將參數值傳遞到您呼叫的函式。
第一種方法是可以用於所有參數的特定語法,但您大多會看到它用於開關參數。 您可以指定冒號,將值附加至 參數。
Remove-Item -Path:* -WhatIf:$true
您可以使用變數執行相同的動作。
$DoWhatIf = $true
Remove-Item -Path * -WhatIf:$DoWhatIf
第二種方法是使用哈希表來展開值。
$RemoveSplat = @{
Path = '*'
WhatIf = $true
}
Remove-Item @RemoveSplat
如果您不熟悉哈希表或參數散播,我有另一篇文章詳細介紹關於哈希表的所有知識。
支援ShouldProcess
啟用-WhatIf和-Confirm支援的第一步驟是需要在函式的CmdletBinding中指定SupportsShouldProcess。
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
Remove-Item .\myfile1.txt
}
藉由以這種方式指定SupportsShouldProcess,我們現在可以使用-WhatIf(或-Confirm)來呼叫我們的函式。
PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
請注意,我未建立稱為 -WhatIf的參數。
SupportsShouldProcess指定會自動為我們建立它。 當我們在Test-ShouldProcess上指定參數-WhatIf時,我們呼叫的某些元件也會執行-WhatIf處理。
注意
當您使用 SupportsShouldProcess時,PowerShell 不會將 $WhatIf 變數新增至 函式。 您不需要檢查 $WhatIf 的值,因為 ShouldProcess() 方法會為您處理。
信任但驗證
這裡有一些危險,相信你呼叫的所有項目都會 -WhatIf 繼承值。 在其餘的範例中,我將假設它不會運作,而且在呼叫其他命令時保持非常明確。 我建議您這樣做。
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
Remove-Item .\myfile1.txt -WhatIf:$WhatIfPreference
}
一旦你更好地了解了比賽中的所有片段,我稍後會重新審視這些細微差別。
$PSCmdlet.ShouldProcess
可讓您執行 SupportsShouldProcess 的方法是 $PSCmdlet.ShouldProcess。 您可以呼叫 $PSCmdlet.ShouldProcess(...) 來查看是否應該處理某些邏輯,而 PowerShell 會負責其餘的作業。 讓我們從範例開始:
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
$file = Get-ChildItem './myfile1.txt'
if($PSCmdlet.ShouldProcess($file.Name)){
$file.Delete()
}
}
呼叫 $PSCmdlet.ShouldProcess($file.Name) 用來檢查 -WhatIf 和 -Confirm 參數,然後進行相應的處理。
-WhatIf 使得 ShouldProcess 輸出對變更的描述並傳回 $false:
PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
使用 -Confirm 的呼叫會暫停腳本,並提示使用者選擇繼續。 如果使用者選取 $true,則會傳Y回 。
PS> Test-ShouldProcess -Confirm
Confirm
Are you sure you want to perform this action?
Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
$PSCmdlet.ShouldProcess 的一個很棒的功能是,它同時也是詳細輸出。 我經常依賴這個來實作 ShouldProcess。
PS> Test-ShouldProcess -Verbose
VERBOSE: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
重載
有幾個不同的 $PSCmdlet.ShouldProcess 多載選項,具有不同的參數來自訂化傳訊。 我們已在上述範例中看到第一個。 讓我們仔細看看。
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
if($PSCmdlet.ShouldProcess('TARGET')){
# ...
}
}
這會產生同時包含函式名稱和目標 (參數值) 的輸出。
What if: Performing the operation "Test-ShouldProcess" on target "TARGET".
指定第二個參數做為作業會使用作業值,而不是訊息中的函式名稱。
## $PSCmdlet.ShouldProcess('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".
下一個選項是指定三個參數,以完全自定義訊息。 使用三個參數時,第一個參數是整個訊息。 訊息輸出中 -Confirm 仍會使用第二個參數。
## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE
快速參數參考
如果您來到這裡是希望知道您應該使用什麼參數,以下是快速參考,說明參數設定在不同 -WhatIf 場景中如何影響訊息。
## $PSCmdlet.ShouldProcess('TARGET')
What if: Performing the operation "FUNCTION_NAME" on target "TARGET".
## $PSCmdlet.ShouldProcess('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".
## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE
我傾向於使用具有兩個參數的 。
ShouldProcessReason
我們有一個比其他更先進的第四個方法重載。 它允許您取得ShouldProcess被執行的原因。 我在這裡添加這個只是為了完整性考量,因為我們可以只檢查 $WhatIfPreference 是否為 $true 就行。
$reason = ''
if($PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION',[ref]$reason)){
Write-Output "Some Action"
}
$reason
我們必須將 $reason 變數作為參考變數,傳遞至第四個參數 [ref]。
ShouldProcess 會 $reason 填入 值 None 或 WhatIf。 我沒有說這很有用,我沒有理由使用它。
放置位置
您使用 ShouldProcess 讓您的腳本更安全。 因此,當您的腳本進行變更時,請使用它。 我喜歡將 $PSCmdlet.ShouldProcess 通話安排得盡可能接近變更。
## general logic and variable work
if ($PSCmdlet.ShouldProcess('TARGET','OPERATION')){
# Change goes here
}
如果我正在處理項目集合,則會針對每個項目呼叫它。 因此,呼叫會放在 foreach 迴圈內。
foreach ($node in $collection){
# general logic and variable work
if ($PSCmdlet.ShouldProcess($node,'OPERATION')){
# Change goes here
}
}
我緊緊圍繞變更放置 ShouldProcess 的原因,是因為我希望在指定 -WhatIf 時,能夠執行盡可能多的代碼。 如果可能,我希望安裝程式和驗證能夠執行,讓用戶能夠看到這些錯誤。
我也想要在 Pester 測試中使用此項目來驗證我的專案。 如果我有一個在 pester 中很難模擬的邏輯,我通常可以將它用ShouldProcess包裝起來,然後在我的測試中用-WhatIf呼叫它。 測試部分程式碼總比不測試好。
$WhatIfPreference
我們有的第一個喜好設定變數是 $WhatIfPreference。 根據預設,這是 $false 。 如果您將它設定為 $true ,則您的函式會如同您指定 -WhatIf一樣執行。 如果您在會話中進行這項設定,則所有命令都會執行 -WhatIf。
當您呼叫函式 -WhatIf 時,$WhatIfPreference 的值會在函式的範圍內被設定為 $true。
ConfirmImpact
我大部分的範例都適用 -WhatIf ,但到目前為止,所有專案也會與 提示使用者搭配使用 -Confirm 。 您可以將函式的ConfirmImpact設定為高,系統會提示使用者,就像以-Confirm進行呼叫一樣。
function Test-ShouldProcess {
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param()
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
}
此次對Test-ShouldProcess的呼叫正在執行-Confirm動作,是因為High的影響。
PS> Test-ShouldProcess
Confirm
Are you sure you want to perform this action?
Performing the operation "Test-ShouldProcess" on target "TARGET".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): y
Some Action
明顯的問題是,現在在其他程式中使用時,沒有提示使用者就更難了。 在此情況下,我們可以傳遞 $false 給 -Confirm 來抑制提示。
PS> Test-ShouldProcess -Confirm:$false
Some Action
我將說明如何在稍後的章節中新增 -Force 支援。
$ConfirmPreference
$ConfirmPreference 是一個自動變數,可控制 ConfirmImpact 何時要求您確認執行。 以下是 $ConfirmPreference 和 ConfirmImpact 的可能值。
HighMediumLowNone
透過這些值,您可以為每個函式指定不同的影響層級。 如果您已 $ConfirmPreference 設定為高於 ConfirmImpact的值,則系統不會提示您確認執行。
根據預設, $ConfirmPreference 會設定為 High ,且 ConfirmImpact 為 Medium。 如果您要讓函式自動提示使用者,請將 設定 ConfirmImpact 為 High。 否則,若命令具有破壞性,則將其設定為 Medium,若命令在生產環境中一律安全執行,則使用 Low。 如果您將它設定為 none,則即使 -Confirm 已指定 ,也不會提示它 (但它仍然提供您 -WhatIf 支援)。
在呼叫函數 -Confirm 時,$ConfirmPreference 的值會在你的函數範圍內被設為 Low。
隱藏巢狀確認提示
$ConfirmPreference可由您呼叫的函式所挑選。 這可以建立您新增確認提示的案例,而您呼叫的函式也會提示使用者。
我傾向於在我已經處理提示時所呼叫的命令上指定 -Confirm:$false 。
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
$file = Get-ChildItem './myfile1.txt'
if($PSCmdlet.ShouldProcess($file.Name)){
Remove-Item -Path $file.FullName -Confirm:$false
}
}
這讓我們回到先前的警告:在-WhatIf不被傳遞至函式和-Confirm傳遞至函式的情況下,會有細微差異。 我保證我稍後會處理這件事。
$PSCmdlet.ShouldContinue
如果您需要比 ShouldProcess 提供的更多控制,您可以使用 直接 ShouldContinue觸發提示。
ShouldContinue忽略$ConfirmPreference、ConfirmImpact、-Confirm、$WhatIfPreference和-WhatIf,因為它會在每次執行時提示。
乍看之下,很容易混淆 ShouldProcess 和 ShouldContinue。 我通常會記得使用 ShouldProcess,因為在 CmdletBinding 中這個參數被稱作 SupportsShouldProcess。
您幾乎應該在每個案例中使用 ShouldProcess 。 這就是為什麼我先涵蓋該方法。
讓我們看看 ShouldContinue 運作情形。
function Test-ShouldContinue {
[CmdletBinding()]
param()
if($PSCmdlet.ShouldContinue('TARGET','OPERATION')){
Write-Output "Some Action"
}
}
這提供我們更簡單的提示,且選項較少。
Test-ShouldContinue
Second
TARGET
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"):
最大的問題是 ShouldContinue ,它要求使用者以互動方式執行,因為它一律會提示使用者。 您應該隨時建立其他腳本可以使用的工具。 執行此動作的方式是實作 -Force。 我稍後會重新審視這個想法。
是,全部
這會由 ShouldProcess 自動處理,但對於 ShouldContinue 我們必須額外做一些工作。 我們有第二種方法重載,必須藉由引用傳入幾個值來控制邏輯。
function Test-ShouldContinue {
[CmdletBinding()]
param()
$collection = 1..5
$yesToAll = $false
$noToAll = $false
foreach($target in $collection) {
$continue = $PSCmdlet.ShouldContinue(
"TARGET_$target",
'OPERATION',
[ref]$yesToAll,
[ref]$noToAll
)
if ($continue){
Write-Output "Some Action [$target]"
}
}
}
我新增了 foreach 迴圈和集合,以顯示其運作情形。 我把 if 陳述中的 ShouldContinue 呼叫拉出來,讓它更容易閱讀。 呼叫帶有四個參數的方法開始顯得有些繁瑣,但我盡量讓它看起來簡潔。
實施 -Force
ShouldProcess 和 ShouldContinue 需要以不同的方式實 -Force 作。 這些實作的訣竅是 ShouldProcess 要一律執行,而如果有指定 -Force,則 ShouldContinue 不應該執行。
ShouldProcess -Force
如果您將 設定 ConfirmImpact 為 high,則用戶會嘗試的第一件事是使用 -Force來隱藏它。 不管怎樣,這是我做的第一件事。
Test-ShouldProcess -Force
Error: Test-ShouldProcess: A parameter cannot be found that matches parameter name 'force'.
如果您回想一下ConfirmImpact段落,他們實際上需要像這樣稱呼它:
Test-ShouldProcess -Confirm:$false
並不是每個人都意識到他們需要這樣做, -Force 而且不會抑制 ShouldContinue。
因此,為了使用者的良好體驗,我們應該實施-Force。 請在這裡檢視此完整範例:
function Test-ShouldProcess {
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param(
[switch]$Force
)
if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
$ConfirmPreference = 'None'
}
if ($PSCmdlet.ShouldProcess('TARGET')) {
Write-Output "Some Action"
}
}
我們新增了一個自己的 -Force 開關作為參數。 使用 SupportsShouldProcess 在 CmdletBinding 時,會自動新增 -Confirm 參數。 不過,當您使用 SupportsShouldProcess時,PowerShell 不會將變數新增 $Confirm 至 函式。 如果您在 Strict 模式中執行,並嘗試在定義變數之前使用 $Confirm 變數,您會收到錯誤。 若要避免錯誤,您可以使用 $PSBoundParameters 來測試參數是否由用戶傳遞。
if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
$ConfirmPreference = 'None'
}
如果使用者指定 -Force 我們在本機範圍中設定 $ConfirmPreference 為 None 。 如果使用者也指定 -Confirm,則 ShoudProcess() 遵循 -Confirm 參數的值。
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
如果某人同時指定了 -Force 和 -WhatIf,那麼 -WhatIf 需要被優先考慮。 此方法會維持-WhatIf的處理,因為ShouldProcess始終會被執行。
請勿在if陳述內使用ShouldProcess來測試$Force值。 這是此特定案例的反模式,雖然這就是我在下一節 ShouldContinue中向您展示的內容。
ShouldContinue -Force
正確的方式是使用 -Force 來實現 ShouldContinue。
function Test-ShouldContinue {
[CmdletBinding()]
param(
[switch]$Force
)
if($Force -or $PSCmdlet.ShouldContinue('TARGET','OPERATION')){
Write-Output "Some Action"
}
}
藉由將$Force放在運算子-or左邊,它會先被評估。 以這種方式撰寫,會中斷語句的執行進程if。 如果 $Force 為 $true,則 ShouldContinue 不會執行 。
PS> Test-ShouldContinue -Force
Some Action
我們在此案例中不需要擔心 -Confirm 或 -WhatIf,因為它們不被 ShouldContinue 支援。 這就是為什麼它需要以不同於 ShouldProcess的方式處理。
範圍問題
使用 -WhatIf 和 -Confirm 應該套用至函式內的所有內容,以及它們所呼叫的所有內容。 其方式是在函式的本機範圍中將 設定 $WhatIfPreference 為 $true 或 設定 $ConfirmPreference 為 Low 。 當您呼叫另一個函式時,對 ShouldProcess 的呼叫會使用這些值。
這實際上大部分時間都正常運作。 每當您在相同範圍中呼叫內建 cmdlet 或函式時,它就能正常運作。 當您從主控台呼叫腳本模組中的腳本或函式時,它也會運作。
當腳本或腳本模組呼叫另一個腳本模組中的函式時,特定情況下會出現問題。 這聽起來可能不像是個大問題,但您從 PSGallery 建立或提取的大部分模組都是腳本模組。
核心問題是,當從其他腳本模組中的函式呼叫 時,腳本模組不會繼承 $WhatIfPreference 或 $ConfirmPreference 的值(以及其他幾個值)。
總結此為一般規則的最佳方式是,這適用於二進位模組,且絕不信任它適用於腳本模組。 如果您不確定,請測試它,或只是假設它無法正常運作。
我個人覺得這是非常危險的,因為它會創造情境,讓您將支援新增 -WhatIf 至多個模組,在單獨運作時運行正常,但在相互呼叫時運行不正常。
我們確實有 GitHub RFC 可修正此問題。 如需詳細資訊,請參閱 將執行喜好設定傳播到腳本模組範圍 之外。
在結尾
我每次需要使用 ShouldProcess 時,都必須查閱如何使用它。 我花了很長的時間才區分ShouldProcessShouldContinue。 我幾乎總是需要查閱要使用的參數。 所以別擔心, 如果你仍然不時感到困惑。 當您需要本文時,本文將會在這裡。 我相信我自己會經常參考它。
如果您喜歡這篇文章,請使用下列連結,在 Twitter 上與我分享您的想法。 我總是樂於聽到那些從我內容中受益的人。