Všechno, co jste chtěli vědět o ShouldProcess

Funkce PowerShellu mají několik funkcí, které výrazně zlepšují způsob interakce uživatelů s nimi. Jednou z důležitých funkcí, která je často přehlédnutá, je -WhatIf a -Confirm podpora a je snadné ji přidat do svých funkcí. V tomto článku podrobně popisujeme, jak tuto funkci implementovat.

Poznámka:

Původní verze tohoto článku se objevila na blogu napsaném @KevinMarquette. Tým PowerShellu děkujeme Kevinovi za sdílení tohoto obsahu s námi. Prosím, podívejte se na jeho blog na PowerShellExplained.com.

Jedná se o jednoduchou funkci, kterou můžete ve svých funkcích povolit, abyste uživatelům, kteří ji potřebují, poskytli bezpečnostní síť. Není nic strašidelnějšího než spuštění příkazu, o kterém víte, že může být poprvé nebezpečné. Možnost spuštění s -WhatIf může velmi ovlivnit.

SpolečnéParametry

Než se podíváme na implementaci těchto běžných parametrů, chci se rychle podívat, jak se používají.

Použití -WhatIf

Když příkaz podporuje -WhatIf parametr, umožňuje zobrazit, co by příkaz udělal, místo provádění změn. Je to dobrý způsob, jak otestovat dopad příkazu, zejména před tím, než uděláte něco destruktivního.

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".

Pokud příkaz správně implementuje ShouldProcess, měl by zobrazit všechny změny, které by provedl. Tady je příklad použití zástupného znaku pro smazání více souborů.

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".

Použití funkce -Confirm

Příkazy, které podporují -WhatIf, také podporují -Confirm. Tím získáte šanci potvrdit akci před jejím provedením.

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"):

V tomto případě máte několik možností, které vám umožní pokračovat, přeskočit změnu nebo zastavit skript. Nápověda popisuje každou z těchto možností takto.

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"):

Lokalizace

Tato výzva se lokalizuje v PowerShellu, takže se jazyk změní na základě jazyka vašeho operačního systému. Toto je ještě jedna věc, kterou vám PowerShell spravuje.

[switch] parametry

Pojďme se rychle podívat na způsoby předání hodnoty parametru [switch] . Hlavním důvodem, proč tomu říkám, je, že často chcete předat hodnoty parametrů funkcím, které voláte.

Prvním přístupem je specifická syntaxe parametrů, která se dá použít pro všechny parametry, ale většinou se používá pro [switch] parametry. Zadáte dvojtečku pro připojení hodnoty k parametru.

Remove-Item -Path:* -WhatIf:$true

To samé můžete udělat s proměnnou.

$DoWhatIf = $true
Remove-Item -Path * -WhatIf:$DoWhatIf

Druhým přístupem je použití hashovatelné tabulky k vytvoření hodnoty.

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

Pokud s hashtables nebo splatting začínáte, mám další článek o tom, co jste chtěli vědět o hashtables.

SupportsShouldProcess

Prvním krokem k povolení podpory -WhatIf a -Confirm je přiřazení SupportsShouldProcess ve funkci CmdletBinding.

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()
    Remove-Item .\myfile1.txt
}

Zadáním SupportsShouldProcess tímto způsobem můžeme nyní volat naši funkci s -WhatIf, nebo -Confirm.

PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".

Všimněte si, že jsem nevytvořil parametr s názvem -WhatIf. Zadání SupportsShouldProcess automaticky způsobí jeho vytvoření pro nás. Když zadáme parametr v -WhatIf na Test-ShouldProcess, některé z těchto věcí, které nazýváme, také provádějí -WhatIf zpracování.

Poznámka:

Při použití SupportsShouldProcessPowerShell nepřidá proměnnou $WhatIf do funkce. Nemusíte kontrolovat hodnotu $WhatIf , protože se o ShouldProcess() to postará metoda za vás.

Důvěřuj, ale prověřuj

Existuje určité riziko spoléhat se na to, že vše, co voláte, dědí -WhatIf hodnoty. Pro zbývající příklady budu předpokládat, že to nefunguje, a při volání jiných příkazů budu velmi explicitní. Doporučuji, abyste to udělali stejně.

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()
    Remove-Item .\myfile1.txt -WhatIf:$WhatIfPreference
}

Budu se k nuancem vrátit mnohem později, jakmile budete mít lepší pochopení všech částí ve hře.

$PSCmdlet.ShouldProcess

Metoda, která umožňuje implementovat SupportsShouldProcess je $PSCmdlet.ShouldProcess. Zavoláte $PSCmdlet.ShouldProcess(...) , abyste zjistili, jestli byste měli zpracovat nějakou logiku a PowerShell se postará o zbytek. Začněme příkladem:

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()

    $file = Get-ChildItem './myfile1.txt'
    if($PSCmdlet.ShouldProcess($file.Name)){
        $file.Delete()
    }
}

Volání na $PSCmdlet.ShouldProcess($file.Name) kontroluje -WhatIf (a -Confirm parametr) a poté je zpracovává odpovídajícím způsobem. Způsobí, že -WhatIf dá ShouldProcess pokyn k výstupu popisu změny a vrácení $false:

PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".

Volání pomocí -Confirm pozastaví skript a vyzve uživateli možnost pokračování. Vrátí, $true pokud uživatel vybral 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"):

Úžasná funkce $PSCmdlet.ShouldProcess spočívá v tom, že může také sloužit jako podrobný výstup. Závisím na tom často při implementaci ShouldProcess.

PS> Test-ShouldProcess -Verbose
VERBOSE: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".

Přetížení

Existuje několik různých přetížení pro $PSCmdlet.ShouldProcess s různými parametry pro přizpůsobení zprávování. V předchozím příkladu jsme už viděli první. Pojďme se na to podívat podrobněji.

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()

    if($PSCmdlet.ShouldProcess('TARGET')){
        # ...
    }
}

Výsledkem je výstup, který zahrnuje název funkce i cíl (hodnotu parametru).

What if: Performing the operation "Test-ShouldProcess" on target "TARGET".

Zadání druhého parametru jako operace používá hodnotu operace místo názvu funkce ve zprávě.

## $PSCmdlet.ShouldProcess('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".

Další možností je zadat tři parametry pro úplné přizpůsobení zprávy. Když se použijí tři parametry, první z nich je celá zpráva. Ve výstupu -Confirm zprávy se stále používají druhé dva parametry.

## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE

Stručná referenční dokumentace k parametrům

V případě, že jste sem přišli, jen abyste zjistili, jaké parametry byste měli použít, tady je stručný přehled o tom, jak se parametry mění ve zprávách v různých -WhatIf scénářích.

## $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

Mám tendenci používat ten s dvěma parametry.

DůvodZpracování

Máme čtvrté přetížení, které je pokročilejší než ostatní. Umožňuje získat důvod, proč ShouldProcess byl proveden. Přidávám to jen kvůli úplnosti, protože můžeme jen zkontrolovat, jestli $WhatIfPreference je $true místo toho.

$reason = ''
if($PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION',[ref]$reason)){
    Write-Output "Some Action"
}
$reason

Musíme $reason předat proměnnou do čtvrtého parametru jako referenční proměnnou s [ref]. ShouldProcess naplní $reason hodnotou None nebo WhatIf. Neřekl jsem, že to bylo užitečné a neměl jsem důvod ho nikdy používat.

Kam ho umístit

Pomocí ShouldProcess můžete své skripty lépe zabezpečit. Proto ho použijete, když skripty provádějí změny. Rád bych $PSCmdlet.ShouldProcess hovor umístil co nejblíže ke změně.

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

Pokud zpracovávám kolekci položek, vyvolávám to pro každou položku. Takže volání se umístí do smyčky foreach .

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

Důvodem, proč umisťuji ShouldProcess těsně kolem změny, je to, že chci, aby bylo provedeno co nejvíce kódu, když je -WhatIf zadán. Chci, aby se instalace a ověření spustily, pokud je to možné, aby se uživateli zobrazily tyto chyby.

Rád bych to použil i v testech Pesteru, které ověřují moje projekty. Pokud mám kus logiky, kterou je těžké napodobit v Pesteru, mohu ji často zabalit do ShouldProcess a volat ji s -WhatIf v mých testech. Je lepší otestovat některý z vašich kódů než žádný z nich.

$WhatIfPreference

První proměnná předvoleb, které máme, je $WhatIfPreference. To je $false ve výchozím nastavení. Pokud ji nastavíte na $true, funkce se spustí, jako kdybyste zadali -WhatIf. Pokud tuto hodnotu nastavíte ve své relaci, všechny příkazy provádějí -WhatIf.

Při volání funkce s -WhatIf se hodnota $WhatIfPreference nastaví na $true uvnitř rozsahu vaší funkce.

ConfirmImpact

Většina mých příkladů je určená pro -WhatIf, ale zatím všechno funguje i s -Confirm, aby vyzvalo uživatele. Funkci můžete nastavit ConfirmImpact na vysokou a zobrazí se výzva uživateli, jako by byla volána s -Confirm.

function Test-ShouldProcess {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    param()

    if ($PSCmdlet.ShouldProcess('TARGET')){
        Write-Output "Some Action"
    }
}

Toto volání Test-ShouldProcess provádí -Confirm akci kvůli High dopadu.

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

Zjevným problémem je, že teď je obtížnější použít v jiných skriptech bez výzvy uživatele. V této situaci můžeme předat $false k -Confirm potlačení výzvy.

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

Probereme, jak přidat -Force podporu v další části.

$ConfirmPreference

$ConfirmPreference je automatická proměnná, která řídí, když ConfirmImpact vás požádá o potvrzení provádění. Zde jsou možné hodnoty pro obě $ConfirmPreference a ConfirmImpact.

  • High
  • Medium
  • Low
  • None

Pomocí těchto hodnot můžete pro každou funkci zadat různé úrovně dopadu. Pokud jste $ConfirmPreference nastavili hodnotu vyšší než ConfirmImpact, pak se nezobrazí výzva k potvrzení provádění.

Ve výchozím nastavení je $ConfirmPreference nastaveno na High a ConfirmImpact je Medium. Pokud chcete, aby funkce automaticky zobrazovala výzvu uživateli, nastavte na hodnotu ConfirmImpactHigh. V opačném případě ho nastavte na Medium pokud je destruktivní a použijte Low, pokud je příkaz vždy bezpečný v produkčním prostředí. Pokud ho nastavíte na none, nezobrazí se výzva, i kdyby byla zadána hodnota -Confirm (ale přesto vám -WhatIf poskytuje podporu).

Při volání funkce s -Confirm se hodnota $ConfirmPreference nastaví na Low v rámci vaší funkce.

Potlačení vnořených potvrzovacích výzev

$ConfirmPreference můžou být zachyceny funkcemi, které voláte. To může vytvořit scénáře, kdy přidáte potvrzovací výzvu a volaná funkce také vyzve uživatele.

Co obvykle dělám, je zadat -Confirm:$false u příkazů, které volám, když jsem už se postaral o zpracování výzev.

function Test-ShouldProcess {
    [CmdletBinding(SupportsShouldProcess)]
    param()

    $file = Get-ChildItem './myfile1.txt'
    if($PSCmdlet.ShouldProcess($file.Name)){
        Remove-Item -Path $file.FullName -Confirm:$false
    }
}

Tím se vrátíme k dřívějšímu upozornění: Existují nuance v tom, kdy -WhatIf není předán do funkce a kdy -Confirm je předán do funkce. Slibuju, že se k tomu později vrátím.

$PSCmdlet.ShouldContinue

Pokud potřebujete větší kontrolu, než ShouldProcess poskytujete, můžete výzvu aktivovat přímo pomocí ShouldContinue. ShouldContinue ignoruje $ConfirmPreference, ConfirmImpact, -Confirm, $WhatIfPreference a -WhatIf , protože se pokaždé při spuštění zobrazí výzva.

Na rychlý pohled je snadné zmást ShouldProcess a ShouldContinue. Mám tendenci pamatovat na použití ShouldProcess, protože parametr je nazýván SupportsShouldProcess v CmdletBinding. Měli byste použít ShouldProcess téměř ve všech scénářích. Proto jsem tuto metodu probrala jako první.

Pojďme se podívat na ShouldContinue v akci.

function Test-ShouldContinue {
    [CmdletBinding()]
    param()

    if($PSCmdlet.ShouldContinue('TARGET','OPERATION')){
        Write-Output "Some Action"
    }
}

To nám poskytuje jednodušší výzvu s menším počtem možností.

Test-ShouldContinue

OPERATION
TARGET
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"):

Největší problém spočívá v ShouldContinue tom, že vyžaduje, aby ho uživatel spustil interaktivně, protože vždy vyzve uživatele. Vždy byste měli vytvářet nástroje, které můžou používat jiné skripty. Způsob, jakým to uděláte, je implementace -Force. Později se k této myšlence vrátím.

Ano všem

To se automaticky zpracovává pomocí ShouldProcess, ale musíme udělat trochu více práce pro ShouldContinue. Existuje druhé přetížení metody, kde musíme předat několik hodnot odkazem pro řízení logiky.

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]"
        }
    }
}

Přidal jsem smyčku foreach a kolekci, aby se zobrazila v akci. Aby bylo snazší číst, vytáhl jsem volání ShouldContinue z prohlášení if. Volání metody se čtyřmi parametry začíná být trochu nehezké, ale snažil jsem se, aby to vypadalo co nejčistěji, jak jsem mohl.

Implementace příkazu -Force

ShouldProcess a ShouldContinue musí být implementovány -Force různými způsoby. Trikem pro tyto implementace je, že ShouldProcess by se měl vždy provést, ale ShouldContinue neměl by být proveden, pokud -Force je zadán.

-Force ShouldProcess

Pokud nastavíte ConfirmImpact na high, první věc, kterou se vaši uživatelé pokusí udělat, je potlačit to pomocí -Force. To je první věc, kterou stejně dělám.

Test-ShouldProcess -Force
Error: Test-ShouldProcess: A parameter cannot be found that matches parameter name 'force'.

Pokud si z oddílu ConfirmImpact vzpomenete, musí ho ve skutečnosti volat takto:

Test-ShouldProcess -Confirm:$false

Ne všichni si uvědomí, že to musí udělat a -Force nepotlačí ShouldContinue. Proto bychom měli implementovat -Force pro sanitu našich uživatelů. Podívejte se na tento úplný příklad tady:

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"
    }
}

Jako parametr přidáme vlastní -Force přepínač. Parametr -Confirm se automaticky přidá při použití SupportsShouldProcess v souboru CmdletBinding. Při použití SupportsShouldProcessale PowerShell nepřidá proměnnou $Confirm do funkce. Pokud používáte striktní režim a pokusíte se použít $Confirm proměnnou dříve, než byla definována, zobrazí se chyba. Abyste se vyhnuli chybě, můžete použít $PSBoundParameters k otestování, jestli byl parametr předán uživatelem.

if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
    $ConfirmPreference = 'None'
}

Pokud uživatel určí -Force, nastavíme $ConfirmPreference na None v místním oboru. Pokud uživatel také určí -Confirm, pak ShoudProcess() respektuje hodnoty parametru -Confirm.

if ($PSCmdlet.ShouldProcess('TARGET')){
    Write-Output "Some Action"
}

Pokud někdo určí obojí -Force a -WhatIfpak -WhatIf musí mít prioritu. Tento přístup zachovává -WhatIf zpracování, protože ShouldProcess se vždy spustí.

Nepřidávejte test na hodnotu $Force uvnitř příkazu if pomocí ShouldProcess. To je antivzorec pro tento konkrétní scénář, i když to předvedu v další části pro ShouldContinue.

ShouldContinue -Force

Toto je správný způsob implementace -Force s ShouldContinue.

function Test-ShouldContinue {
    [CmdletBinding()]
    param(
        [switch]$Force
    )

    if($Force -or $PSCmdlet.ShouldContinue('TARGET','OPERATION')){
        Write-Output "Some Action"
    }
}

Umístěním $Force nalevo od operátoru -or se vyhodnotí jako první. Napište to tímto způsobem, abyste přerušili spuštění příkazu if. Pokud $Force je $true, pak ShouldContinue se nespustí.

PS> Test-ShouldContinue -Force
Some Action

Nemusíme si dělat starosti -Confirm ani -WhatIf v tomto scénáři, protože je nepodporuje ShouldContinue. To je důvod, proč musí být zpracována jinak než ShouldProcess.

Problémy s oborem

Použití -WhatIf a -Confirm má platit pro všechno uvnitř vašich funkcí a pro vše, co tyto funkce volají. Dělají to tak, že nastaví $WhatIfPreference na $true nebo $ConfirmPreference na Low v místním rozsahu funkce. Když zavoláte jinou funkci, volání ShouldProcess používají tyto hodnoty.

Ve skutečnosti funguje většinu času správně. Kdykoli voláte vestavěný cmdlet nebo funkci ve stejném rozsahu, funguje to. Funguje také při volání skriptu nebo funkce v modulu skriptu z konzoly.

Jedno konkrétní místo, kde nefunguje, je, když skript nebo modul skriptu volá funkci v jiném modulu skriptu. Nemusí to znít jako velký problém, ale většina modulů, které vytvoříte nebo stáhnete z PSGallery, jsou skriptovací moduly.

Základním problémem je, že moduly skriptu nezdědí hodnoty pro $WhatIfPreference nebo $ConfirmPreference (a několik dalších) při zavolání z funkcí v jiných modulech skriptů.

Nejlepší způsob, jak to shrnout jako obecné pravidlo, je, že to funguje správně pro binární moduly a nikdy mu nedůvěřujete, aby fungoval pro skriptovací moduly. Pokud si nejste jistí, buď ho otestujte, nebo jen předpokládejme, že nefunguje správně.

Osobně cítím, že je to velmi nebezpečné, protože vytváří scénáře, kdy přidáváte -WhatIf podporu více modulů, které fungují správně izolovaně, ale nefungují správně, když se navzájem volají.

Pracujeme na GitHub RFC, abychom tento problém vyřešili. Další podrobnosti najdete v tématu Šíření předvoleb spouštění nad rámec modulu skriptu.

Na závěr

Musím si pokaždé vyhledat, jak ShouldProcess používat, když ho potřebuji. Trvalo mi to dlouho, než jsem se odlišil ShouldProcess od ShouldContinue. Skoro vždy potřebuji vyhledat, jaké parametry použít. Takže si nedělejte starosti, pokud jste stále čas od času zmatení. Tento článek bude tady, až ho budete potřebovat. Jsem si jistá, že na to budu často odkazovat sám.

Pokud se vám tento příspěvek líbil, podělte se se mnou o své nápady na Twitteru pomocí odkazu níže. Vždycky se mi líbí, když slyším od lidí, kteří z mého obsahu získávají hodnotu.