PowerShell–efektywniej, część 6.

W poprzedniej części opisałem składnię, dzięki której funkcja zmienia się w funkcję zaawansowaną. Dziś skupię się na tym, co z tego wynika przy używaniu takiej funkcji.

Debug i Verbose

W wielu językach – zarówno programowania jak i skryptowych – pierwszą metodą sprawdzania co poszło nie tak jest wyświetlanie na ekran informacji, które mogą pomóc ustalić, gdzie popełniliśmy błąd. To generuje dwa problemy:

  • nie usunięte komunikaty pojawiają się w najmniej odpowiednim momencie
  • ponowne “włączenie” komunikatów po ich usunięciu jest niemożliwe

PowerShell ma bardzo przydatną właściwość: generuje nieco więcej strumieni niż domyślne wyjście standardowe i wyjście błędów: mamy dodatkowo ostrzeżenia, informacje rozszerzone (verbose) i komunikaty pomagające debugować (debug). Komunikaty verbose i debug można z powodzeniem pozostawić (jeśli są cenzuralne Puszczam oczko ) a w razie potrzeby – włączyć na żądanie. W funkcjach zwykłych wymagałoby to zmiany $DebugPreference i $VerbosePreference na poziomie sesji. Funkcje zaawansowane dają nam możliwość użycia po prostu jednego z parametrów: –Debug lub –Verbose. Dzięki temu automagicznie zmienia się wartość odpowiedniej zmiennej i wszystkie komunikaty pojawiają się w odpowiednich strumieniach. Dodajmy więc komunikaty dotyczące zawartości naszych parametrów:

    Write-Verbose "Format: $Format"            
    Write-Verbose "Liczba: $Liczba"            
    Write-Verbose "Slowo: $Slowo"            
    if ($Format) {            
        Write-Debug "Wygląda na to, że używamy liczby."            
    }            

Begin, Process, End

Funkcja w PowerShellu może mieć trzy odseparowane części, co ułatwia zdecydowanie pracę w potokach i dzięki czemu każda funkcja może działać podobnie jak prawdziwy cmdlet (jeśli chodzi o funkcjonalność, nie o wydajność). Mamy więc:

  • begin gdzie można zainicjować połączenia, zdefiniować funkcje pomocnicze, przetworzyć zmienne podawane “w linii”
  • process stanowiący serce funkcji działającej w rurce: tu możemy sięgnąć do parametrów pobieranych z poprzedniej komendy i przekazać informację dalej
  • end który sprząta na koniec: zabija połączenia, czyści pamięć ze zbędnych elementów

Jeśli zamierzamy korzystać tylko z parametrów podawanych w linii – nasza funkcja może mieć jedno, zbiorcze “ciało”. Jeśli jednak chcemy skorzystać z pipe’a – absolutnym minimum jest dodanie części process, pozostałe dwie pozostają wówczas opcjonalne. Begin ma sens wtedy, gdy potrzebujemy pewną operację przeprowadzić dokładnie raz na początku, a wykonywanie jej dla każdego elementu wpadającego w rurkę jest stratą czasu, czy wręcz może doprowadzić do nieoczekiwanych rezultatów. End działa podobnie – tylko operacja musi mieć miejsce dokładnie raz na końcu. Cała reszta – powinna trafić do bloku process. Zaawansowane funkcje tracą wiele jeśli pozbawimy ich tej troistości.

PSCmdlet

Zaawansowane funkcja ma jeszcze jedną wielką zaletę: w jej wnętrzu dostępna jest automatyczna zmienna ($PSCmdlet), która w zasadzie daje nam dostęp do tych samych metod i właściwości, jakie ma programista piszący cmdlet w C# lub VB.NET. Na blogu zespołu PowerShell jakiś czas temu opublikowana była prosta funkcja, która pozwala zajrzeć do wnętrza tej zmiennej, sprowadza się to właściwie do:

function Test-PSCmdlet {            
[CmdletBinding()]            
param()            
    $p = $PSCmdlet            
    function prompt {            
        "Test-PSCmdlet> "            
    }            
    $host.EnterNestedPrompt()            
}

Dzięki temu możemy poeksperymentować ze zmienną $p (która jest kopią $PSCdmlet). W praktyce na ogół wykorzystuje się dwa jej elementy:

  • właściwość ParameterSetName – pozwala ustalić, który zestaw parametrów jest wykorzystywany
  • metody ShouldProcess i ShouldContinue – pozwalają zastosować parametry WhatIf oraz Confirm.

Ponieważ budowana przez nas zaawansowana funkcja wspiera ShouldProcess (a więc metody Shoud* będą w niej dostępne), oraz założyliśmy kilka różnych zestawów parametrów, więc wykorzystamy i jedno, i drugie:

 process {            
    if ($psCmdlet.ShouldProcess(            
        "Cel: $Slowo $Liczba",            
        "Operacja: Wyświetl"            
    )) {            
            
        switch ($psCmdlet.ParameterSetName) {            
            Domyslny {             
                "{0} to moje slowo" -f $Slowo            
            }            
            Liczby {             
                "Moja liczba: {0:$Format}" -f $Liczba             
            }            
        }            
    }            
}

Ponieważ część parametrów zamierzamy opcjonalnie pobierać z rurki – możemy do nich sięgać dopiero w bloku process. Jeśli mamy dwa zestawy parametrów na ogół wystarczy zwykły if – jeśli zestawów jest więcej, opcjonalnym rozwiązaniem może okazać się switch. Jak widać operacja przeprowadzana nie jest specjalnie niebezpieczna, ale czasem jednak funkcja napisana przez nas może nieść z sobą pewne ryzyko – warto wtedy wiedzieć jak zaimplementować parametry –WhatIf i –Confirm.

I tym optymistycznym akcentem kończę temat zaawansowanych funkcji. Jeszcze tylko nasza zaawansowana funkcja w pełnej krasie:

function Get-Foo {            
[CmdletBinding(            
    SupportsShouldProcess = $true,            
    ConfirmImpact = 'High',            
    DefaultParameterSetName = 'Domyslny'            
)]            
param (            
    [Parameter(            
        Mandatory = $true,            
        HelpMessage = 'Pomocy!',            
        ValueFromPipeline = $true,            
        Position = 0,            
        ParameterSetName = 'Liczby'            
    )]            
    [double]$Liczba,            
    [Parameter(            
        Mandatory = $true,            
        HelpMessage = 'Pomocy!',            
        ValueFromPipelineByPropertyName = $true,            
        Position = 0,            
        ParameterSetName = 'Domyslny'            
    )]            
    [ValidatePattern('^\w+$')]            
    [string]$Slowo,            
    [Parameter(            
        Position = 1,            
        ParameterSetName = 'Liczby',            
        Mandatory = $true,            
        HelpMessage = 'Pomozcie mi!'            
    )]            
    [ValidateSet('P4','N4','C4')]            
    [string]$Format            
            
)            
            
begin {            
    Write-Verbose "Format: $Format"            
    Write-Verbose "Liczba: $Liczba"            
    Write-Verbose "Slowo: $Slowo"            
    if ($Format) {            
        Write-Debug "Wygląda na to, że używamy liczby."            
    }            
}            
            
process {            
    if ($psCmdlet.ShouldProcess(            
        "Cel: $Slowo $Liczba",            
        "Operacja: Wyświetl"            
    )) {            
            
        switch ($psCmdlet.ParameterSetName) {            
            Domyslny {             
                "{0} to moje slowo" -f $Slowo            
            }            
            Liczby {             
                "Moja liczba: {0:$Format}" -f $Liczba             
            }            
        }            
    }            
}            
}

Następną część zamierzam poświęcić kolejnej funkcjonalności, przydatnej zarówno w funkcjach (również zaawansowanych) jak i w skryptach: pomocy generowanej w oparciu o komentarze.

~ - autor: Bartek Bielawski w dniu 6 listopada, 2011.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s

 
%d blogerów lubi to: