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 är enkel att lägga till i dina funktioner. I den här artikeln går vi in närmare på hur du implementerar den här funktionen.

Anteckning

Den ursprungliga versionen av den här artikeln visades på bloggen som skrivits av @KevinMarquette. PowerShell-teamet tackar Kevin för att han delade 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 skyddsnä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å att implementera 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 implementerar ShouldProcesskorrekt bör du se alla ändringar som skulle ha gjorts. Här är ett exempel som använder 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 även stöder -WhatIf-Confirm. 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. I hjälpprompten beskrivs vart och ett av dessa alternativ så 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 prompten 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

Vi tar en snabb stund och tittar på hur du kan 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 hash-tabell för att formatera värdet.

$RemoveSplat = @{
    Path = '*'
    WhatIf = $true
}
Remove-Item @RemoveSplat

Om du är ny på hashtables eller splatting, jag har 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 för CmdletBinding din funktion.

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 skapas den automatiskt åt oss. När vi anger parametern -WhatIfTest-ShouldProcessutför -WhatIf vissa saker som vi anropar även bearbetning.

Lita på men verifiera

Det finns en risk här att allt du anropar -WhatIf ärver värden. I resten av exemplen antar jag att det inte fungerar och är väldigt tydligt när du anropar 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 gå tillbaka 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. Vi börjar 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 ) 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 anrop med pausar -Confirm 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 genererar utdata som innehåller både funktionsnamnet och målet (värdet för parametern).

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

Snabbparameterreferens

Om du bara kom hit för att ta reda på vilka parametrar du bör 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 överbelastning 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 istä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 det när dina skript gör ändringar. Jag gillar att ringa så $PSCmdlet.ShouldProcess nära förändringen som möjligt.

## general logic and variable work
if ($PSCmdlet.ShouldProcess('TARGET','OPERATION')){
    # Change goes here
}

Om jag bearbetar en samling objekt kallar 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 verifieringen 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 din kod än ingen 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örningen.

När du anropar en funktion med -WhatIfanges värdet $WhatIfPreference för till $true inuti funktionens omfång.

ConfirmImpact

De flesta av mina exempel är till för -WhatIf men allt hittills fungerar också med -Confirm för att uppmana användaren. Du kan ange ConfirmImpact för funktionen till 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 -Confirm till Test-ShouldProcess utför åtgärden på grund av High påverkan.

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 en $false till för -Confirm att undertrycka prompten.

PS> Test-ShouldProcess -Confirm:$false
Some Action

Jag ska gå 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 högre värde ä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 anger du till ConfirmImpactHigh. Annars ställer du in den på Medium om den är destruktiv och använder Low om kommandot alltid körs säkert i produktion. Om du ställer in den på noneuppmanas den inte även om -Confirm den har angetts (men den 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äftande fråga och funktionen du anropar också uppmanar 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 leder 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 den frågar varje gång den körs.

Snabbt ä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 tog upp 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 ut samtalet från if uttalandet 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 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 inte köras om -Force det anges.

ShouldProcess - Force

Om du ställer in ConfirmImpacthighä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

Alla inser inte 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){
        $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){
    $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 till none och inaktiverar prompten för bekräftelse.

if ($Force -or $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 ifShouldProcessmed . 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. Det är därför som det måste hanteras annorlunda ä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 $ConfirmPreference i Low 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. När du anropar inbyggd cmdlet eller en funktion i samma omfång fungerar det. Det fungerar också när du anropar ett skript eller en funktion i en skriptmodul från -konsolen.

Den enda specifika plats där det inte fungerar är när ett skript eller en skriptmodul anropar en funktion i en annan skriptmodul. Detta kanske inte låter som ett stort problem, men de flesta moduler som du skapar eller hämtar från PSGallery är skriptmoduler.

Huvudproblemet ä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 den eller bara anta att den inte fungerar korrekt.

Personligen tycker jag 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 kallar varandra.

Vi har en GitHub RFC som arbetar för att åtgärda problemet. Mer information finns i Sprida körningsinställningar utanför skriptmodulomfånget .

Vid stängning

Jag måste slå 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, vänligen 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.