Allt du ville veta om ShouldProcess
PowerShell-funktioner har flera funktioner som avsevärt förbättrar hur användarna interagerar med dem.
En viktig funktion som ofta förbises är -WhatIf
och -Confirm
stöder och det är enkelt att lägga till i dina funktioner. I den här artikeln går vi igenom hur du implementerar den här funktionen.
Kommentar
Den ursprungliga versionen av den här artikeln visades på bloggen skriven av @KevinMarquette. PowerShell-teamet tackar Kevin för att ha delat det här innehållet med oss. Kolla in hans blogg på PowerShellExplained.com.
Det här är en enkel funktion som du kan aktivera i dina funktioner för att tillhandahålla ett säkerhetsnät för de användare som behöver det. Det finns inget läskigare än att köra ett kommando som du vet kan vara farligt för första gången. Alternativet att köra det med -WhatIf
kan göra stor skillnad.
CommonParameters
Innan vi tittar på implementeringen av dessa vanliga parametrar vill jag ta en snabb titt på hur de används.
Använda -WhatIf
När ett kommando stöder parametern -WhatIf
kan du se vad kommandot skulle ha gjort i stället för att göra ändringar. Det är ett bra sätt att testa effekten av ett kommando, särskilt innan du gör något destruktivt.
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".
Om kommandot implementeras ShouldProcess
korrekt bör du se alla ändringar som det skulle ha gjort. Här är ett exempel som använder ett jokertecken för att ta bort flera filer.
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".
Använda -Confirm
Kommandon som har stöd -WhatIf
för stöder -Confirm
också . Detta ger dig en chans att bekräfta en åtgärd innan du utför den.
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"):
I det här fallet har du flera alternativ som gör att du kan fortsätta, hoppa över en ändring eller stoppa skriptet. Hjälpprompten beskriver vart och ett av dessa alternativ som det här.
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"):
Lokalisering
Den här uppmaningen lokaliseras i PowerShell så att språket ändras baserat på operativsystemets språk. Det här är ytterligare en sak som PowerShell hanterar åt dig.
Växla parametrar
Nu ska vi ta en snabb titt på sätt att skicka ett värde till en växelparameter. Den främsta anledningen till att jag kallar detta är att du ofta vill skicka parametervärden till funktioner som du anropar.
Den första metoden är en specifik parametersyntax som kan användas för alla parametrar, men du ser oftast att den används för växelparametrar. Du anger ett kolon för att koppla ett värde till parametern.
Remove-Item -Path:* -WhatIf:$true
Du kan göra samma sak med en variabel.
$DoWhatIf = $true
Remove-Item -Path * -WhatIf:$DoWhatIf
Den andra metoden är att använda en hashtable för att splat värdet.
$RemoveSplat = @{
Path = '*'
WhatIf = $true
}
Remove-Item @RemoveSplat
Om du är nybörjare på hashtables eller splatting har jag en annan artikel om som täcker allt du ville veta om hashtables.
SupportsShouldProcess
Det första steget för att aktivera -WhatIf
och -Confirm
stödja är att ange SupportsShouldProcess
i CmdletBinding
funktionens.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
Remove-Item .\myfile1.txt
}
Genom att SupportsShouldProcess
ange på det här sättet kan vi nu anropa vår funktion med -WhatIf
(eller -Confirm
).
PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
Observera att jag inte skapade en parameter med namnet -WhatIf
. Om du anger SupportsShouldProcess
automatiskt skapas den åt oss. När vi anger parametern -WhatIf
på Test-ShouldProcess
utför vissa saker som vi anropar -WhatIf
även bearbetning.
Lita på men verifiera
Det finns en risk här att lita på att allt du kallar ärver -WhatIf
värden. För resten av exemplen antar jag att det inte fungerar och att det är väldigt explicit när du gör anrop till andra kommandon. Jag rekommenderar att du gör samma sak.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
Remove-Item .\myfile1.txt -WhatIf:$WhatIfPreference
}
Jag kommer att återkomma till nyanserna mycket senare när du har en bättre förståelse för alla bitar i spelet.
$PSCmdlet.ShouldProcess
Den metod som gör att du kan implementera SupportsShouldProcess
är $PSCmdlet.ShouldProcess
. Du anropar $PSCmdlet.ShouldProcess(...)
för att se om du bör bearbeta viss logik och PowerShell tar hand om resten. Låt oss börja med ett exempel:
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
$file = Get-ChildItem './myfile1.txt'
if($PSCmdlet.ShouldProcess($file.Name)){
$file.Delete()
}
}
Anropet till $PSCmdlet.ShouldProcess($file.name)
söker efter (och -Confirm
parametern -WhatIf
) och hanterar det därefter. Orsakerna -WhatIf
till ShouldProcess
att mata ut en beskrivning av ändringen och returnera $false
:
PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
Ett samtal med hjälp av -Confirm
pausar skriptet och uppmanar användaren att fortsätta. Den returnerar $true
om användaren har valt 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"):
En fantastisk funktion $PSCmdlet.ShouldProcess
i är att den fungerar som utförliga utdata. Jag är beroende av detta ofta när jag implementerar ShouldProcess
.
PS> Test-ShouldProcess -Verbose
VERBOSE: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
Överlagringar
Det finns några olika överlagringar för $PSCmdlet.ShouldProcess
med olika parametrar för att anpassa meddelanden. Vi såg redan den första i exemplet ovan. Låt oss ta en närmare titt på det.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
if($PSCmdlet.ShouldProcess('TARGET')){
# ...
}
}
Detta ger utdata som innehåller både funktionsnamnet och målet (parameterns värde).
What if: Performing the operation "Test-ShouldProcess" on target "TARGET".
Om du anger en andra parameter som åtgärden används åtgärdsvärdet i stället för funktionsnamnet i meddelandet.
## $PSCmdlet.ShouldProcess('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".
Nästa alternativ är att ange tre parametrar för att helt anpassa meddelandet. När tre parametrar används är det första hela meddelandet. De andra två parametrarna används fortfarande i -Confirm
meddelandeutdata.
## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE
Referens för snabbparameter
Om du bara kom hit för att ta reda på vilka parametrar du ska använda, här är en snabbreferens som visar hur parametrarna ändrar meddelandet i de olika -WhatIf
scenarierna.
## $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
Jag brukar använda den med två parametrar.
ShouldProcessReason
Vi har en fjärde överlagring som är mer avancerad än de andra. Det gör att du kan hämta orsaken ShouldProcess
som kördes. Jag lägger bara till detta här för fullständighet eftersom vi bara kan kontrollera om $WhatIfPreference
är $true
i stället.
$reason = ''
if($PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION',[ref]$reason)){
Write-Output "Some Action"
}
$reason
Vi måste skicka variabeln $reason
till den fjärde parametern som en referensvariabel med [ref]
. ShouldProcess
$reason
fyller med värdet None
eller WhatIf
. Jag sa inte att detta var användbart och jag har inte haft någon anledning att någonsin använda det.
Var den ska placeras
Du använder ShouldProcess
för att göra skripten säkrare. Så du använder den när skripten gör ändringar. Jag gillar att ringa så $PSCmdlet.ShouldProcess
nära ändringen som möjligt.
## general logic and variable work
if ($PSCmdlet.ShouldProcess('TARGET','OPERATION')){
# Change goes here
}
Om jag bearbetar en samling objekt anropar jag det för varje objekt. Så anropet placeras i foreach-loopen.
foreach ($node in $collection){
# general logic and variable work
if ($PSCmdlet.ShouldProcess($node,'OPERATION')){
# Change goes here
}
}
Anledningen till att jag placerar ShouldProcess
tätt runt ändringen, är att jag vill att så mycket kod som möjligt ska köras när -WhatIf
anges. Jag vill att konfigurationen och valideringen ska köras om möjligt så att användaren får se dessa fel.
Jag gillar också att använda detta i mina Pester-tester som validerar mina projekt. Om jag har en logik som är svår att håna i pester, kan jag ofta slå in ShouldProcess
den och kalla den med -WhatIf
i mina tester. Det är bättre att testa en del av koden än inget av den.
$WhatIfPreference
Den första inställningsvariabeln vi har är $WhatIfPreference
. Detta är $false
som standard. Om du ställer in den på $true
körs funktionen som om du angav -WhatIf
. Om du anger detta i sessionen utför -WhatIf
alla kommandon körning.
När du anropar en funktion med -WhatIf
anges värdet $WhatIfPreference
för till $true
inom funktionens omfång.
ConfirmImpact
De flesta av mina exempel är till för -WhatIf
men allt hittills fungerar också med -Confirm
för att fråga användaren. Du kan ställa in ConfirmImpact
funktionen på hög och den uppmanar användaren som om den anropades med -Confirm
.
function Test-ShouldProcess {
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param()
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
}
Det här anropet till Test-ShouldProcess
utför åtgärden -Confirm
på grund av High
effekten.
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
Det uppenbara problemet är att det nu är svårare att använda i andra skript utan att fråga användaren. I det här fallet kan vi skicka ett $false
till -Confirm
för att ignorera uppmaningen.
PS> Test-ShouldProcess -Confirm:$false
Some Action
Jag går igenom hur du lägger -Force
till stöd i ett senare avsnitt.
$ConfirmPreference
$ConfirmPreference
är en automatisk variabel som styr när ConfirmImpact
du uppmanas att bekräfta körningen. Här är de möjliga värdena för både $ConfirmPreference
och ConfirmImpact
.
High
Medium
Low
None
Med dessa värden kan du ange olika effektnivåer för varje funktion. Om du har $ConfirmPreference
angett ett värde som är högre än ConfirmImpact
uppmanas du inte att bekräfta körningen.
Som standard $ConfirmPreference
är inställt på High
och ConfirmImpact
är Medium
. Om du vill att funktionen automatiskt ska fråga användaren ställer du in på ConfirmImpact
High
. Annars ställer du in det på Medium
om det är destruktivt och använder Low
om kommandot alltid är säkert att köra i produktion. Om du anger det till none
uppmanas det inte även om -Confirm
det har angetts (men det ger dig -WhatIf
fortfarande stöd).
När du anropar en funktion med -Confirm
anges värdet $ConfirmPreference
för till Low
i omfånget för funktionen.
Ignorera kapslade bekräftelseprompter
$ConfirmPreference
Kan hämtas av funktioner som du anropar. Detta kan skapa scenarier där du lägger till en bekräftad fråga och funktionen du anropar uppmanar också användaren.
Vad jag brukar göra är att ange -Confirm:$false
på de kommandon som jag anropar när jag redan har hanterat frågan.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
$file = Get-ChildItem './myfile1.txt'
if($PSCmdlet.ShouldProcess($file.Name)){
Remove-Item -Path $file.FullName -Confirm:$false
}
}
Detta för oss tillbaka till en tidigare varning: Det finns nyanser om när -WhatIf
inte skickas till en funktion och när -Confirm
passerar till en funktion. Jag lovar att gå tillbaka till det här senare.
$PSCmdlet.ShouldContinue
Om du behöver mer kontroll än ShouldProcess
vad som anges kan du utlösa kommandotolken direkt med ShouldContinue
. ShouldContinue
$ConfirmPreference
ignorerar , ConfirmImpact
, -Confirm
, $WhatIfPreference
och -WhatIf
eftersom det uppmanas varje gång det körs.
Vid en snabb blick är det lätt att förvirra ShouldProcess
och ShouldContinue
. Jag brukar komma ihåg att använda ShouldProcess
eftersom parametern anropas SupportsShouldProcess
i CmdletBinding
.
Du bör använda ShouldProcess
i nästan alla scenarion. Det är därför jag täckte den metoden först.
Låt oss ta en titt på ShouldContinue
i praktiken.
function Test-ShouldContinue {
[CmdletBinding()]
param()
if($PSCmdlet.ShouldContinue('TARGET','OPERATION')){
Write-Output "Some Action"
}
}
Detta ger oss en enklare fråga med färre alternativ.
Test-ShouldContinue
Second
TARGET
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"):
Det största problemet med ShouldContinue
är att det kräver att användaren kör det interaktivt eftersom det alltid uppmanar användaren. Du bör alltid skapa verktyg som kan användas av andra skript. Det gör du genom att implementera -Force
. Jag återkommer till den här idén senare.
Ja till alla
Detta hanteras automatiskt med ShouldProcess
men vi måste göra lite mer arbete för ShouldContinue
. Det finns en andra metodöverlagring där vi måste skicka in några värden med referens för att styra logiken.
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]"
}
}
}
Jag har lagt till en foreach
loop och en samling för att visa den i praktiken. Jag drog ShouldContinue
samtalet ur instruktionen if
för att göra det lättare att läsa. Att anropa en metod med fyra parametrar börjar bli lite fult, men jag försökte få det att se så rent ut som jag kunde.
Implementera -Force
ShouldProcess
och ShouldContinue
måste implementeras -Force
på olika sätt. Tricket med dessa implementeringar är att ShouldProcess
alltid ska köras, men ShouldContinue
bör inte köras om -Force
det anges.
ShouldProcess - Force
Om du anger ConfirmImpact
till high
är det första användarna ska prova att ignorera det med -Force
. Det är det första jag gör ändå.
Test-ShouldProcess -Force
Error: Test-ShouldProcess: A parameter cannot be found that matches parameter name 'force'.
Om du kommer ihåg från ConfirmImpact
avsnittet måste de faktiskt kalla det så här:
Test-ShouldProcess -Confirm:$false
Inte alla inser att de behöver göra det och -Force
undertrycker ShouldContinue
inte .
Därför bör vi implementera -Force
för våra användares förstånd. Ta en titt på det här fullständiga exemplet här:
function Test-ShouldProcess {
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param(
[Switch]$Force
)
if ($Force -and -not $Confirm){
$ConfirmPreference = 'None'
}
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
}
Vi lägger till vår egen -Force
växel som en parameter. Parametern -Confirm
läggs automatiskt till när du använder SupportsShouldProcess
i CmdletBinding
.
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param(
[Switch]$Force
)
Fokusera på logiken -Force
här:
if ($Force -and -not $Confirm){
$ConfirmPreference = 'None'
}
Om användaren anger -Force
vill vi ignorera bekräftelseprompten om de inte också anger -Confirm
. Detta gör att en användare kan framtvinga en ändring men ändå bekräfta ändringen. Sedan anger $ConfirmPreference
vi i det lokala omfånget. Nu ställer parametern -Force
tillfälligt in $ConfirmPreference
på none och inaktiverar prompten för bekräftelse.
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
Om någon anger både -Force
och -WhatIf
måste du -WhatIf
prioritera. Den här metoden bevarar -WhatIf
bearbetningen eftersom ShouldProcess
den alltid körs.
Lägg inte till en kontroll av $Force
värdet i -instruktionen if
med ShouldProcess
. Det är ett antimönster för det här specifika scenariot även om det är vad jag visar dig i nästa avsnitt för ShouldContinue
.
ShouldContinue -Force
Det här är rätt sätt att implementera -Force
med ShouldContinue
.
function Test-ShouldContinue {
[CmdletBinding()]
param(
[Switch]$Force
)
if($Force -or $PSCmdlet.ShouldContinue('TARGET','OPERATION')){
Write-Output "Some Action"
}
}
Genom att placera $Force
till vänster om operatorn -or
utvärderas den först. När du skriver det på det här sättet kortsluts körningen av -instruktionen if
. Om $force
är $true
ShouldContinue
körs inte .
PS> Test-ShouldContinue -Force
Some Action
Vi behöver inte bekymra oss om -Confirm
eller -WhatIf
i det här scenariot eftersom de inte stöds av ShouldContinue
. Därför måste den hanteras på ett annat sätt än ShouldProcess
.
Omfångsproblem
Använda -WhatIf
och -Confirm
är tänkt att gälla för allt i dina funktioner och allt de anropar. De gör detta genom att ange $WhatIfPreference
till $true
eller ange Low
$ConfirmPreference
i funktionens lokala omfång. När du anropar en annan funktion anropar du för att ShouldProcess
använda dessa värden.
Detta fungerar faktiskt korrekt för det mesta. Varje gång du anropar den inbyggda cmdleten eller en funktion i samma omfång fungerar den. Det fungerar också när du anropar ett skript eller en funktion i en skriptmodul från konsolen.
En specifik plats där det inte fungerar är när ett skript eller en skriptmodul anropar en funktion i en annan skriptmodul. Det kanske inte låter som ett stort problem, men de flesta moduler som du skapar eller hämtar från PSGallery är skriptmoduler.
Det viktigaste problemet är att skriptmoduler inte ärver värdena för $WhatIfPreference
eller $ConfirmPreference
(och flera andra) när de anropas från funktioner i andra skriptmoduler.
Det bästa sättet att sammanfatta detta som en allmän regel är att detta fungerar korrekt för binära moduler och aldrig litar på att det fungerar för skriptmoduler. Om du inte är säker kan du antingen testa det eller bara anta att det inte fungerar korrekt.
Jag känner personligen att detta är mycket farligt eftersom det skapar scenarier där du lägger till -WhatIf
stöd för flera moduler som fungerar korrekt isolerat, men inte fungerar korrekt när de ringer varandra.
Vi har en GitHub RFC som arbetar för att få det här problemet åtgärdat. Mer information finns i Sprida körningsinställningar utanför skriptmodulomfånget .
Vid stängning
Jag måste leta upp hur man använder ShouldProcess
varje gång jag behöver använda den. Det tog mig lång tid att skilja från ShouldProcess
ShouldContinue
. Jag behöver nästan alltid leta upp vilka parametrar som ska användas. Så oroa dig inte om du fortfarande blir förvirrad då och då. Den här artikeln kommer att finnas här när du behöver den. Jag är säker på att jag kommer att referera till det ofta själv.
Om du gillade det här inlägget, dela dina tankar med mig på Twitter med hjälp av länken nedan. Jag gillar alltid att höra från människor som får värde från mitt innehåll.
Feedback
https://aka.ms/ContentUserFeedback.
Kommer snart: Under hela 2024 kommer vi att fasa ut GitHub-problem som feedbackmekanism för innehåll och ersätta det med ett nytt feedbacksystem. Mer information finns i:Skicka och visa feedback för