PowerShell – efektywnie(j), część 5.

W wersji drugiej PowerShell został dość mocno rozbudowany o nowe możliwości: zdalne uruchamianie kodu, praca w tle, moduły… Sam język też zyskał bardzo wiele, przede wszystkim – zaawansowane funkcje. Czy może raczej: zaawansowane scriptblocki. Jest bowiem prawdą, że niezależnie od tego jak nasz blok skryptu jest zapisany: w postaci funkcji w pamięci, w postaci zmiennej typu [ScriptBlock] czy też w postaci pliku z rozszerzeniem ps1 – może on w każdej z tych postaci być zaawansowany. Tą część cyklu zamierzam poświęcić w całości temu, w czym objawia się to “zaawansowanie” i jak je wykorzystać.

Spytaj PowerShella.

Parafrazując mój ulubiony zespół punk-rockowy: “Spytaj PowerShella, on ci prawdę powie, spytaj PowerShella – on ci wskaże drogę”. W tym wypadku pytanie brzmieć powinno: get-help about_functions_advanced*, pytanie pomocnicze: get-help about_functions_CmdletBindingAttribute. Postaram się opisać najważniejsze cechy funkcji zaawansowanych w tym artykule w tym, oraz następnym artykule, ale na pewno coś mi umknie. PowerShell (przynajmniej z założenia) powinien dostarczyć nam komplet informacji. Zacznijmy więc od początku. Zbudujmy od zera zaawansowaną funkcję. Nie będzie ani troszkę funkcjonalna, ale za to bardzo, bardzo zaawansowana. Puszczam oczko

CmdletBinding

Od tego wszystko się zaczyna. PowerShell jest domyślny, więc w wielu sytuacjach ten element może się okazać zbędny. Jednak warto od niego zaczynać każdą funkcję. Nic to nie kosztuje, a daje kilka korzyści. Należy jednak pamiętać, że konstrukcja ta wymaga posiadania bloku param (może on być pusty) i może być rozbudowana o kilka opcji:

  • SupportsShouldProcess – jeśli chcemy by nasza funkcja rozumiała –WhatIf i –Confirm, dopuszczalne wartości: $true, $false
  • ConfirmImpact – jeśli zależy nam na tym, by pytanie o zgodę pojawiało się częściej, lub zawsze, dopuszczalne wartości: High, Medium, Low
  • DefaultParameterSetName – jeśli definiujemy kilka zestawów parametrów i chcemy mieć pewność, że jeden z nich będzie używany domyślnie, dopuszczalne wartości: nazwy wszystkich użytych zestawów parametrów

W 9/10 przypadków [CmdletBinding()] w zupełności wystarczy, dobrze jednak wiedzieć, że to nie wszystko co ma do zaoferowania ta konstrukcja. Nasza funkcja oczywiście zalicza się do tych 10%, wszystko-w-sobie-mających:

function Get-Foo {            
[CmdletBinding(            
    SupportsShouldProcess = $true,            
    ConfirmImpact = 'High',            
    DefaultParameterSetName = 'Domyslny'            
)]            
param (

Parameter

Wchodzimy do bloku definiowania parametrów i zaczynamy je zdobić. [Parameter()] to podstawa: pozwala nam zdefiniować parametry niezbędne (Mandatory), uzupełnić parametry o pomoc (HelpMessage), zdecydować czy będą “konsumować” rurkę i jak będą to robić (ValueFromPipeline, ValueFromPipelineByPropertyName), jaką pozycję mogą zajmować (Position), który zestaw parametrów opisujemy (ParameterSetName) i wreszcie – że parametr połknie całą resztą argumentów, do których nikt inny się nie przyznaje (ValueFromRemainingArguments). Należy pamiętać, że jeśli mamy kilka zestawów parametrów a opisywany parametr ma pojawiać się tylko w niektórych – to trzeba to wyraźnie zdefiniować (kilka bloków [Parameter()]). Jeśli parametr pojawia się wszędzie i wszędzie ma zachowywać się identycznie – wystarczy jedna deklaracja. My zdefiniujemy sobie dwa zestawy parametrów: liczba wraz z odpowiednim formatowaniem, lub po prostu słowo:

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            
            
)

Jak widać – można mieć dwa parametry o tej samej pozycji. O tym, który zostanie wykorzystany, decyduje zestaw parametrów: domyślnie nawet liczba zostanie potraktowana jako string. Jeśli jednak dodamy –Format (występujący tylko w zestawie z liczbami) PowerShell spróbuje pozycyjny parametr przerobić na [double]:

Jasio

Jest wyjątek od tej reguły: jeśli typ będzie idealnie pasował do oczekiwanego (1.123123) to PowerShell wybierze ten zestaw, który pobiera zmienną danego typu.

Walidacja na wejściu

Kolejny efekt pisania funkcji jako zaawansowanej to możliwość walidacji tego, co użytkownik próbuje do funkcji wrzucić. Zgodnie z regułą GIGO (nie wiem czy polska wersja, choć bardziej dosadna, nie oddaje bardziej powagi sytuacji) – walidacja wejścia ma sens i należy jej używać zawsze wtedy, gdy zła informacja na wejściu może spowodować nieoczekiwane (na ogół bolesne) rezultaty. Sposobów walidacji mamy kilka:

  • zestaw do wyboru: ValidateSet
  • odpowiednia ilość elementów: ValidateCount
  • odpowiednia matryca z wyrażeń regularnych: ValidatePattern
  • odpowiednia długość: ValidateLength
  • odpowiedni zakres wartośći: ValidateRange
  • nie jest ‘zerowy’: ValidateNotNull
  • nie jest ‘zerowy’ ani pusty: ValidateNotNullOrEmpty
  • i najbardziej elastyczny: ValidateScript

Wszystkie te elementy ograniczają nasz element, jednak jeśli nasz argument jest wymagany (Mandatory) czasem konieczne może się okazać przymknięcie oka na pewne niedoskonałości (zwłaszcza jeśli element wpada nam z “rurki”):

  • zezwól na wartości zerowe: AllowNull
  • zezwól na pusty string: AllowEmptyString
  • zezwól na puste kolekcje: AllowEmptyCollection

Jak widać jest w czym wybierać. Mój ulubiony to ValidateScript, który akceptuje parametr jeśli wynik skryptu będzie $true, odrzuci – jeśli wynik będzie $false, albo wyskoczy nam jakiś wyjątek. Dzięki temu i komendzie ‘throw’ możemy uczynić komunikat o błędzie bardziej zrozumiałym (zwłaszcza jeśli lokalizacja do języka narodowego nadal nie istnieje Puszczam oczko ).

Aliasy dla parametrów.

Aliasy mają dwa główne cele:

  • umożliwienie wykorzystania w naszej funkcji różnych właściwości obiektów (ComputerName, Name, Nazwa)
  • ułatwienie pracy z komendą “interaktywnie” (CN, NK)

Wydaje mi się, że druga ewentualność jest dość jasna. Co do pierwszej: jeśli zdarzy się, że jakaś komenda wyrzuca z siebie obiekty o właściwości Foo, która nam odpowiada a inna wyrzuca obiekty o właściwości Bar, która niczym się nie różni od Foo, to mamy idealnego kandydata na alias. Nazywając parametr Foo i nadając mu alias Bar zyskamy możliwość pobierania obiektów z obu komend. Wielokrotnie stosowałem tę metodę by móc “połykać” obiekty dotyczące komputerów i przypisywać wartość właściwości Name parametrowi ComputerName. Bez aliasów byłoby to trudne, lub wręcz niemożliwe…

I w ten oto sposób zamknęliśmy blok param () – przed nami wszystko to, co kryje się w automatycznie tworzonej w zaawansowanych funkcjach zmiennej $psCmdlet oraz kilka słów o Write-* w funkcji zaawansowanej. Zamierzam też wspomnieć, po co i gdzie używać konstrukcji begin, process i end. I choć wielka trójca była już dostępna w PowerShellu w wersji 1, to nie sposób pominąć tego tematu przy omawianiu zaawansowanych funkcji.

~ - autor: Bartek Bielawski w dniu Październik 25, 2011.

Jedna odpowiedź to “PowerShell – efektywnie(j), część 5.”

  1. […] polecam lekturę about_Functions_Advanced_Parameters. Można też sięgnąć do mojego starszego postu, gdzie wymieniam dostępne opcje. Tu – prosty przykład. Podajemy funkcji ścieżkę do pliku, […]

Skomentuj

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

Logo WordPress.com

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

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

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

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

 
%d blogerów lubi to: