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 ShouldProcesskorrekt 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 -Confirmockså . 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 -WhatIfTest-ShouldProcessutfö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 -WhatIfanges 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 ConfirmImpactuppmanas 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å ConfirmImpactHigh. 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 noneuppmanas det inte även om -Confirm det har angetts (men det ger dig -WhatIf fortfarande stöd).

När du anropar en funktion med -Confirmanges 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$ConfirmPreferenceignorerar , ConfirmImpact, -Confirm, $WhatIfPreferenceoch -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 ShouldContinueinte . 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 -Forcevill 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 -WhatIfmå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 $trueShouldContinue 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 ShouldProcessShouldContinue. 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.