WGUiSW Idol i IT Professional.

•30 stycznia, 2012 • Dodaj komentarz

Dziś postanowiłem wreszcie napisać parę słów o tym, co działo się w ostatnim czasie. Grudzień – to oczywiście czas świąt. Dla mnie święta to czas podróży: w zasadzie każdego dnia przemieszczaliśmy się z miejsca na miejsce. Potem wróciliśmy z Ładniejszą Połową do Warszawy, a maluchy zostały u dziadków. Cel? Prosty – przygotowania. Emi szykowała się do spotkania z promotorem. Ja – do występu w ramach WGUiSW Idola. Dzięki pomocy Darka Porowskiego miałem sporo materiału do przemyśleń, starałem się jak najwięcej “wyciągnąć” z książek, które mi pożyczył i informacji, którymi się ze mną podzielił. Efekt? Żonka była zachwycona, a to przecież najważniejsze. Puszczam oczko Sam bawiłem się doskonale w trakcie prezentacji. Tego, że dotyczyła PowerShella mówić chyba nie muszę. Nadmienię tylko, że skupiłem się na dodawaniu GUI do skryptów, ze szczególnym uwzględnieniem mojego ulubionego ostatnio narzędzia – ShowUI. Zamieszczam prezentację, ale ponieważ odrobiłem lekcję dotyczącą tego, co w prezentacji znaleźć się nie powinno – to sam plik pptx wiele Wam nie da… Więc jeśli w spotkaniu nie uczestniczyliście? Cóż, pozostaje Wam trzymać kciuki bym konkurs wygrał (wątpię, ale w końcu wszystko się może zdarzyć) i przybyć na  Windows Community Launch Puszczam oczko Główna nagroda to właśnie prelekcja w trakcie tego wydarzenia. BTW: ciekaw jestem – ile osób załapało o co chodzi w tym slajdzie (obrazkowi temu na slajdzie w zasadzie nie towarzyszyło nic, poza logo samej akcji i logo grupy):

SPQR

Druga rzecz, która w ostatnim czasie pochłania sporo mojego czasu to rzecz nowa w moim życiu – i nie ukrywam, bardzo ekscytująca. Zostałem bowiem zaproszony do współpracy z miesięcznikiem “IT Professional”. Jest to dość ciekawa pozycja na rynku wydawniczym w Polsce – jej grupą docelową są bowiem czynni zawodowo specjaliści IT. Moja działka to (czy to nie nudne?) Windows PowerShell. A ponieważ artykuły z założenia mają być praktyczne – to opisuję dla tego czasopisma skrypty, które w ostatnim czasie zdarzyło mi się sklecić. Oczywiście, przed publikacją staram się je najpierw dopieścić, następnie odpowiednio przedstawić i wyjaśnić: co, czemu, gdzie i dlaczego. Właśnie w tym tygodniu, tuż po powrocie z ferii, znalazłem w skrzynce pierwszy numer pisma, w którym można znaleźć moje nazwisko:

IMG_0301

Myślę, że będzie co pokazywać wnukom… Uśmiech Mam tylko nadzieję, że pomysły na skrypty nie wyczerpią się przedwcześnie… i że druga strona jest równie zadowolona ze współpracy ze mną, co ja ze współpracy z tym czasopismem. A że samo pismo jest ciekawe, to dla samego choćby egzemplarza autorskiego – warto się było pochylić nad swoimi pisanymi na kolanie skryptami i nieco je podrasować. Choć oczywiście czasu wolnego mi z tego tytułu nie przybyło… Smutek Obiecuję jednak w najbliższym czasie wrócić do modułów w PowerShellu. Mam nadzieję, że ktoś dotrwa… Puszczam oczko

Powershell – efektywniej, część 8.

•17 grudnia, 2011 • Dodaj komentarz

Zgodnie z obietnicą – w tej części zajmę się modułami. Czym są moduły w PowerShellu? Moduł to rozszerzenie. Może to być zarówno rozszerzenie binarne – w formie skompilowanej biblioteki dll, jak i skryptowe – w najbardziej podstawowej formie plik zawierający odpowiedni kod PowerShella z rozszerzeniem .psm1. Nas oczywiście interesować będzie moduł skryptowy.

Po co?

Czemu moglibyśmy chcieć stworzyć moduł? Moduły nadają się świetnie do zmieniania środowiska, pozwalają zebrać funkcje/ zmienne/ aliasy związane z jakimś zagadnieniem (np. obsługa lokalnych kont na stacjach roboczych; obsługa AD w danym, lokalnym OU). Moduł sprawdza się lepiej niż wrzucanie wszystkiego do profilu – dzięki temu kod staje się bardziej przenośny, a jeśli chcemy zmienić coś w zestawie funkcji dotyczących konkretnej technologii/ zagadnienia, to nie musimy ich szukać na piechotkę. Moduł pozwala też oddzielić elementy “prywatne” od “publicznych”. Ta izolacja pozwala uniknąć sytuacji, gdy dwa narzędzia będą ze sobą walczyć o jakąś zmienną. W ilu skryptach używamy gdzieś zmiennych typu $i, albo $sum(a)? No cóż, moduły mogą pracować w swoim wewnętrznym zakresie z dowolnymi, kolidującymi zmiennymi. Jeśli moduły nie “wypluwają” ich na zewnątrz (dojdziemy do tego jak to zrobić, domyślnie żadna zmienna nie wychodzi poza moduł) – do kolizji zmiennych nigdy nie dojdzie. Nie da się tego powiedzieć o dotsourcingu – tam zawartość skryptu wpada do naszego głównego zakresu, przez co możemy czasem wylać dziecko z kąpielą.

To wszystko?

Moduł to jednak dużo, dużo więcej. Kompletny moduł, oprócz tworzenia zestawu funkcji potrafi również:

  • zmieniać sposób wyświetlania obiektów
  • dodawać do istniejących/ definiowanych typów właściwości i metody skryptowe
  • tworzyć wielojęzyczną pomoc do naszego modułu
  • odpowiednio opisać metadane modułu (autora, wersję) – do tego służy manifest

Pierwsze dwa punkty realizowane są przy pomocy plików XML o specjalnej strukturze. Wszystkie pliki tego typu muszą mieć rozszerzenie .ps1xml. Ich stosowanie to jednak całkiem osobny temat. Poza tym takie modyfikacje przydają się zwykle w bardziej zaawansowanych modułach.

Pomoc dla modułu też raczej nie ma większego sensu jeśli tworzymy moduł w miarę prosty. Prosty moduł to zestaw funkcji, a funkcje można łatwo opisać posługując się pomocą w komentarzach. Oczywiście taka pomoc nie będzie wielojęzyczna, ale w przypadku, gdy z modułu nie będzie korzystało wielu ludzi – język nie jest istotną kwestią.

Ostatni punkt, czyli manifest, można w zasadzie zastosować do każdego modułu. Pomaga on śledzić wersje modułu, co z czasem może okazać się przydatne (gdy np. pracujemy na kilku komputerach i zechcemy moduł uaktualnić), informację o autorze, opis modułu… A ponieważ jego tworzenie nie jest bardzo skomplikowane (służy do tego cmdlet New-ModuleManifest), nic nie stoi na przeszkodzie, by moduł (nawet najprostszy) w taki manifest wyposażyć.

Tworzymy moduł: krok 1

Pierwszy krok to zebranie wszystkich funkcji, które nasz moduł ma udostępniać i zapisanie ich w pliku z odpowiednim rozszerzeniem. Taki moduł zwykle można od razu zacząć używać (wystarczy go zaimportować: Import-Module). Jeśli jednak chcemy by nasz moduł zawierał również funkcje pomocnicze (które nie powinny być widoczne na zewnątrz) bądź dodawał do sesji zmienne i aliasy (domyślnie wszystkie aliasy i zmienne definiowane w module nie są eksportowane poza moduł), musimy pójść krok dalej. Podstawowa metoda to wyeksportowanie “ręcznie” funkcji, zmiennych i aliasów. Trzeba jednak pamiętać, że eksportując dowolny element w ten sposób wyłączamy domyślny mechanizm eksportujący wszystkie funkcje z modułu. Popatrzmy na konkretny przykład, trzy proste moduły, bardzo podobne do siebie:

function Get-Foo { Write-Host foo }            
function pomoc { Write-Host Pomagam }            
New-Alias -Name foo -Value Get-Foo            
$Foo = 'Bar'            

Taki moduł udostępnia zarówno funkcję Get-Foo (która ma być widoczna) jak i funkcję pomoc – która w naszym przykładzie jest pomocnicza. Co jeśli dodamy Export-ModuleMember i zapomnimy o funkcjach…:

function Get-Foo { Write-Host foo }            
function pomoc { Write-Host Pomagam }            
New-Alias -Name foo -Value Get-Foo            
$Foo = 'Bar'            
            
Export-ModuleMember -Alias foo -Variable Foo

Alias ‘foo’ zadziała, ale skutkiem będzie błąd (funkcja Get-Foo nie będzie zdefiniowana, więc użyć się jej bezpośrednio nie da). Działać poprawnie będzie w zasadzie tylko zmienna $Foo. Prawidłowe użycie cmdletu Export-ModuleMember:

function Get-Foo { Write-Host foo }            
function pomoc { Write-Host Pomagam }            
New-Alias -Name foo -Value Get-Foo            
$Foo = 'Bar'            
            
Export-ModuleMember -Function Get-Foo -Alias foo -Variable Foo

Po zaimportowaniu tego modułu widoczna będzie tylko potrzebna nam funkcja (Get-Foo), alias, który do niej prowadzi (foo) oraz zmienna, którą chcemy zdefiniować ($Foo).

Tworzymy moduł: krok 2.

Zainicjowaliśmy więc moduł. Jak go testować? Import-Module działa jednorazowo, jeśli moduł w sesji już funkcjonuje – ponowny import nie da żadnego rezultatu. Na etapie tworzenia bardzo przydatny może się okazać parametr –Force – pozwala on zaimportować moduł wielokrotnie, dzięki czemu wprowadzone zmiany będą widoczne. Na etapie “produkcji” warto jest po każdej poważniejszej zmianie dokonać importu by przekonać się, czy nasz moduł nie przestał działać.

Warto też na tym etapie zastanowić się nad skutecznym mechanizmem, który polecenia i zmienne eksportowane z naszego modułu pozwoli wyróżnić. Prefiks ułatwi zarówno używanie modułu, jak i eksport poleceń/ zmiennych. Dlaczego? Otóż polecenie Export-ModuleMember obsługuje symbole wieloznaczne. Zamiast więc pojedynczo eksportować polecenia i zmienne – możemy na końcu modułu użyć:

Export-ModuleMember -Function *-Prefiks* -Variable Prefiks* -Alias *

Naturalnie, prefiks to raczej 2-3 literki. Puszczam oczko Gdzie zaś ułatwienie użytkowania? O ile prościej jest (posługując się TABem np.) dopełniać polecenie Get-Prefiks[TAB] zamiast przypominania sobie za każdym razem, jakich rzeczowników użyliśmy w naszych funkcjach? Get-Command –Module NaszModuł nieco sprawę upraszcza, ale nie wydaje mi się to najefektywniejszą metodą.

Co dalej?

W kolejnej części zamierzam opisać dokładniej manifest modułu i to, co on nam daje. Oprócz tego zamierzam napisać jak moduły przygotować do dystrybucji i jak “instalować” je, by zamiast składni: Import-Module .\Nazwa.psm1 móc skorzystać ze składni: Import-Module Nazwa. To w zasadzie drobiazg, ale myślę że warto o nim wspomnieć, na wypadek gdyby ktoś tego jednak nie wiedział… Puszczam oczko

PowerShell – efektywniej, część 7.

•29 listopada, 2011 • Dodaj komentarz

Minął prawie miesiąc od ostatniego wpisu… dla mnie był to miesiąc wyjątkowo długi. Kończymy powoli wdrażanie Windows 7 w naszej firmie, a wiadomo jak to jest – końcówki zwykle są najgorsze, szczególnie gdy innych spraw również nie można odłożyć na później. Skutek jest taki jak widać – blog musiał nieco poczekać… Dziś zamknąć zamierzam kwestie związane z zaawansowanymi funkcjami/ skryptami. Nasza zaawansowana (choć niezbyt funkcjonalna) ma już niemal wszystko. Brakuje tylko jednego – pomocy. W przypadku cmdletów pomoc jest tworzona przy pomocy plików maml. Pisanie ich na piechotę nie należy do wdzięcznych czynności, można to sobie znacznie uprościć korzystając z narzędzia Cmdlet Help Editor. Pisząc zaawansowane funkcje mamy zadanie znacznie uproszczone. W zasadzie całość sprowadza się do umieszczenia odpowiednio sformatowanego komentarza w odpowiednim miejscu. Zasady przypominają nieco regulamin dotyczący spalonego w piłce nożnej: niby wszystko jest proste, ale jak się zacznie wyjaśniać komuś, robi się coraz mniej “prosto”. Puszczam oczko W zasadzie budowanie pomocy sprowadza się do dodania komentarza w formacie:

<#
.Nagłówek
    Treść
.Dwuczłonowy Nagłówek
    Treść
#>

Dokładny opis wszystkich elementów znajduje się w pomocy (about_comment_based_help), ja wspomnę o najistotniejszych (moim zdaniem):

  1. Synopsis – krótki opis naszej funkcji
  2. Description – opis obszerniejszy, bardziej szczegółowy
  3. Example – przykłady, każdy przykład umieszczamy w osobnej sekcji Example a sekcji takich możemy definiować ile chcemy (nawet jeśli istnieje jakiś limit, to trudno będzie do niego “dobić”
  4. Notes – dodatkowe informacje, które nie pojawią się w widoku domyślnym
  5. Parameter MojParametr – opis konkretnego parametru

Ale to dopiero początek, dalej zaczyna się cała litania innych warunków, po kolei więc:

  • można użyć zarówno komentarzy blokowych (<# #>), “tradycyjnych” (# jako pierwszy znak w linii) oraz mieszanki obu
  • komentarz do funkcji można umieścić “w ciele”, lub tuż nad słowem kluczowym “function”
  • umieszczając pomoc nad funkcją, możemy zostawić maksymalnie jedną linię pustą pomiędzy końcem komentarza a początkiem funkcji.
  • umieszczając pomoc w ciele funkcji, musimy pamiętać by umieścić go przed blokiem param () i [CmdletBinding()]
  • w przypadku pomocy do skryptu – musimy umieścić komentarz na jego początku lub końcu
  • jeśli chcemy dodać linie #requires na początku skryptu – musimy odseparować ją pustą linią od bloku zawierającego pomoc
  • umieszczenie pomocy na końcu skryptu i podpis elektroniczny wzajemnie się wykluczają (niedopatrzenie Puszczam oczko ) – podpis jest komentarzem, więc automatycznie blok pomocy nie jest ostatni i przestaje być obsługiwany
  • jeśli chcemy pomoc skryptu umieścić na jego początku musimy zadbać o to, by pomoc nie “przykleiła” nam się do pierwszej funkcji znajdującej się w skrypcie
  • jakakolwiek literówka w nagłówkach pomocy (n.p.: .Synosis) automatycznie sprawi, że pomoc się nie pojawi w ogóle
  • każdy nagłówek musi być w osobnej linii, sekcja musi zaczynać się w linii następnej i kończyć w linii poprzedzającej następny nagłówek
  • komentarz musi stanowić jedną całość – jeśli będą przerwy między częściami, brana pod uwagę będzie tylko ta część, której położenie zgadza się z zasadami wymienionymi powyżej
  • opis parametrów można umieszczać zarówno tuż nad nimi, jak i w treści “głównej” pomocy

Jak widać – miejsc na potencjalne pomyłki nie brakuje. Próbowałem sobie wyłapywanie błędów (szczególnie literówek) ułatwić jakiś czas temu (dokładnie w czasie Scripting Games 2010) – wtedy też napisałem prostą funkcję, która miała mi w wyłapywanie błędów pomóc. Bazuje ona na wyrażeniach regularnych i pewnie wymagałaby poprawek – pisałem ją w czasach gdy o tych ostatnich wiedziałem zdecydowanie mniej niż teraz. Ale na pewno może się ona przydać od czasu do czasu, by wyłapać najbardziej bagatelne błędy…

Tyle zasady. A jak najlepiej budować taką pomoc? Synopsis i Description to moim zdaniem absolutne minimum. Dobrze jest też umieścić choć kilka przykładów użycia naszej funkcji. Odnośnie parametrów – preferuję umieszczania pomocy do nich bezpośrednio na nimi, w bloku param. Dzięki temu, moim zdaniem, łatwo tę pomoc “wyłapać” w czasie modyfikowania skryptu/ funkcji. A i dodając nowy parametr możemy od razu wzbogacić go o odpowiednią pomoc. Wyjątkiem będą tu parametry dodawane w zaawansowanych funkcjach automatycznie, jak WhatIf, Confirm, Debug i Verbose.

Pozostałe elementy pomocy używam niezmiernie rzadko. Możliwości jest sporo, więc najlepiej korzystać z nich z głową, by nie przesadzić… Jak pomoc będzie zbyt przeładowana – nikomu nie będzie się chciało jej czytać. Puszczam oczko

Na koniec nasza funkcja, tym razem już odpowiednio uzbrojona w system pomocy:

function Get-Foo {            
            
<#

.Synopsis
    Wyświetla literki i cyferki.

.Description
    Funkcja wyświetla literki i cyferki, w zależnosci od potrzeb.
    Liczby są formatowane jako waluta (C4), procenty (P4) 
    i zwykłe liczby (N4).
    W przypadku słów nie jest używane żadne specjalne formatowanie.

.Example
    Get-Foo Slowo
    Slowo to moje slowo

    Wyświetla wybrany string.

.Example
    Get-Foo -Liczba .24 -Format P4
    Moja liczba: 24.0000 %

    Wyświetla liczbę sformatowaną w wybrany sposób.

.Parameter WhatIf
    Dodawany automatycznie.
    Pomoc do niego możemy umieścić jedynie w taki sposób.

.Parameter Confirm
    I tu podobnie...
#>            
            
[CmdletBinding(            
    SupportsShouldProcess = $true,            
    ConfirmImpact = 'Medium',            
    DefaultParameterSetName = 'Domyslny'            
)]            
param (            
    # Liczba, którą chcemy wyświetlić.            
    [Parameter(            
        Mandatory = $true,            
        HelpMessage = 'Pomocy!',            
        ValueFromPipeline = $true,            
        Position = 0,            
        ParameterSetName = 'Liczby'            
    )]            
    [double]$Liczba,            
            
    # Pojedyncze słowo, które chcemy wyświetlić.            
    [Parameter(            
        Mandatory = $true,            
        HelpMessage = 'Pomocy!',            
        ValueFromPipelineByPropertyName = $true,            
        Position = 0,            
        ParameterSetName = 'Domyslny'            
    )]            
    [ValidatePattern('^\w+$')]            
    [string]$Slowo,            
            
    # Format, który zostanie użyty do wyświetlenia podanej liczby.            
    [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             
            }            
        }            
    }            
}            
}

W następnej części zamierzam zająć się modułami. Ponieważ temat ten jest dość obszerny, nie wykluczone że rozbiję go na kilka wpisów. Byleby tylko czasu i zapału na pisanie nie zabrakło… Puszczam oczko

PowerShell–efektywniej, część 6.

•6 listopada, 2011 • Dodaj komentarz

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.

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

•25 października, 2011 • 1 komentarz

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.

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

•23 października, 2011 • Dodaj komentarz

Troszkę to trwało nim udało mi się przysiąść do tego artykułu. Przyczyna jest prozaiczna: temat jest dość obszerny i nie wiem jak go ugryźć. Pisanie skryptów w PowerShellu nie wymaga wielkiego wysiłku, ale jeśli chcemy by nasz skrypt był przydatny dla innych i dla nas za pół roku – trzeba się nieco przyłożyć w czasie jego tworzenia. Uznałem jednak, że rzecz sama się nie zrobi. W najgorszym wypadku po prostu rozpiszę się nieco bardziej niż zwykle (ha, ha, ha… Puszczam oczko)

Od czego zacząć?

W zasadzie pierwsze pytanie jakie powinniśmy sobie zadać powinno brzmieć: czy chcemy napisać coś, co wykona serię operacji i da nam na koniec jakiś wynik (skrypt) czy bardziej interesuje nas biblioteka narzędzi (moduł). Dalej jest już prościej: dzielimy zadanie na proste czynności (funkcje), które następnie połączymy w całość. I o ile w przypadku modułów sprawa wydaje się “czysta” i dość naturalna, o tyle w wypadku skryptów musimy zwalczyć pokusę umieszczenia operacji w jednym, wielki worze.

Kod wielokrotnego użycia

Skrypty lubią rosnąć niby hydra: dodajemy do nich kolejne funkcjonalności, jednocześnie nie specjalnie dbając o to, by kod można było później wykorzystać. To moim zdaniem spory błąd: utrudnia ponowne wykorzystanie napisanego kodu w innym narzędziu, czyni narzędzie dość statycznym i dość silnie uwiązanym do wykorzystanej technologii. Może prosty przykład, by pokazać o co mi chodzi…

Proces

Naturalna kolej rzeczy: nasz skrypt może przyjąć jako parametr samą listę (przekazaną przez rurkę), OU w AD, plik txt/ csv z listą komputerów itd. Na wyjściu – podamy ścieżkę do pliku .csv w którym dane zostaną zapisane. To co się dzieje w środku zależy już od nas. Możemy więc albo zrobić to w formie funkcji, które następnie ustawimy w jednej “rurce” i uzyskamy pożądanych efekt. Albo wszystko zrobić za jednym zamachem, bez dziabania kodu na poszczególne fragmenty. Co tracimy?

  • przejrzystość kodu (logika się “zlewa”)
  • utrudnienia przy wymianie komponentu
  • rozwiązując podobny problem musimy praktycznie kopiować cały skrypt, albo zacząć pisanie od początku

Problem, jaki może się pojawić, to konieczność zdefiniowania funkcji zanim ich użyjemy. Jak to w prosty sposób rozwiązać? Mój ulubiony trik to definiowanie na początku funkcji, która ma za zadanie tylko opisać zakładaną logikę – czy inaczej – szkic tego co chciałbym osiągnąć. Na ogół wygląda to tak:

#requires -version 2.0            
            
<#
    Pomoc do skryptu
#>            
            
            
function Invoke-Main {            
param (            
    $Parametr = $Script:Parametr,            
    $DrugiParametr = $Script:DrugiParametr,            
    $Path = $Script:Path            
)            
            
    Get-Foo -Parametr $Parametr | Test-Foo |             
        ConvertTo-Bar -Drugi $DrugiParametr | Export-Csv -Path $Path            
}            
            
<#
    Tu definiujemy: 
        * Get-Foo
        * Test-Foo
        * ConvertTo-Bar
#>            
            
Invoke-Main

Jak widać – definicje funkcji następują przed jej wywołaniem (które ma miejsce na samym końcu skryptu) ale sposób ich wywołania znany jest od początku (zdefiniowany w pierwszej funkcji w skrypcie). Jeśli zechcę kiedyś zmienić finalny obiekt na ‘Boo’ zamiast funkcji ConvertTo-Bar zdefiniuję ConvertTo-Boo, zmienię nieco Invoke-Main i już – skrypt gotowy do użycia.

Funkcja w rurce – awansujemy.

Patrząc na składnię moich hipotetycznych funkcji od razu widać jedną rzecz: wszystkie “klocki” radzą sobie w rurce. To podstawa: pisząc funkcję niezdolną do pracy w środku pipe’a strzelamy sobie w stopę. By jednak współpraca przebiegała bezboleśnie – należy z całą pewnością przyzwyczaić się do pisania zaawansowanych funkcji. Wymagają one nieco dekoracji na wstępie, ale wartość dodaną trudno moim zdaniem przecenić. Druga rzecz, która być może w skryptach nie jest tak cenna, ale w modułach moim zdaniem absolutnie niezbędna: pomoc do funkcji. Wymaga to maciupeńkich nakładów pracy (odpowiednio skomponowany komentarz) a oszczędza konieczności czytania definicji funkcji za każdym razem. Myślę, że sam temat zaawansowanych funkcji jest zbyt obszerny, by go streścić tutaj, zostawię więc to na piątą część cyklu. Na rozbudzenie apetytu: malutka funkcja, która wykorzystuje część możliwości, które dają zaawansowane funkcje:

function Test-IsAlive {            
[CmdletBinding()]            
param (            
    [Parameter(            
        ValueFromPipelineByPropertyName = $true,            
        Mandatory = $true,            
        HelpMessage = 'Nazwa komputera, ktory testujemy'            
    )]            
    [ValidatePattern('(?# Bez spacji w nazwach komputerow!)^\S+$')]            
    [Alias('Name','CN','Nazwa')]            
    [string]$ComputerName,            
    [Parameter(ValueFromPipeline = $true)]            
    [Alias('IO')]            
    [PSObject]$InputObject            
            
)            
process {            
    Write-Verbose "Testuje komputer: $ComputerName"            
    if (Test-Connection -ComputerName $ComputerName -Quiet -Count 1) {            
        if (!$InputObject) {            
            Write-Verbose 'Nic na wejsciu, tworzymy sami.'            
            $InputObject = New-Object PSObject -Property @{            
                ComputerName = $ComputerName            
            }            
        }            
        $InputObject | Add-Member NoteProperty IsAlive $true -PassThru            
    }            
}            
}

Skutek? Mogę przez tą funkcję przepuścić dowolny obiekt, który posiada właściwość ComputerName, Name, Nazwa lub CN i jeśli właściwość ta nie zawiera spacji (ValidatePattern, z komentarzem, by nie straszyć ludzi niezrozumiałym regexpem) i da się ją “pingnąć” (preferowane więc będą obiekty pobrane z AD, lub innego zawierającego nazwę komputera) to na wyjściu dostanę ten sam obiekt “wzbogacony” o właściwość IsAlive. Jeśli spróbuję uruchomić funkcję bez parametru – poprosi mnie ona o wartość ComputerName. I rzecz nie bez znaczenia: dodanie wtrąceń typu Write-Verbose/ Debug działa bez potrzeby implementacji. [CmdletBinding()] daje mi te opcje za darmo. Mogę więc uruchomić funkcję z parametrem –Debug lub –Verbose i dowiedzieć się więcej o tym, co dzieje się w środku.

Wejście – wyjście.

Skrypty mają to do siebie, że czasem wolelibyśmy nie musieć pamiętać co do nich trzeba włożyć, oraz co byśmy chcieli z nich wyjąć. Z drugiej strony – czasem jednak chcemy mieć wpływ na to, co się stanie… PowerShell pozwala pogodzić te dwie sprzeczności. Rozwiązanie to wyciągnąć jak najwięcej elementów na zewnątrz skryptu (w postaci parametrów) i jednocześnie przypisać im wartość domyślną (by nie być zmuszonym podawać ich za każdym razem). Z wyjściem jest gorzej – optymalne rozwiązanie to parametr typu [switch], który na wyjściu da nam “żywe” obiekty. To co z nimi zrobimy dalej będzie zależeć już tylko od nas. I wilk syty, i owca cała.

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

•8 października, 2011 • Dodaj komentarz

W trzeciej części jeszcze troszkę popastwię się nad konsolową pracą z PowerShellem. Opiszę kilka trików, które mogą nam pomóc oszczędzić nieco czasu i w miarę szybko dojść do pożądanych rezultatów.

Komentarze

Wydawać się może, że w interaktywnej pracy komentarze są zbędne. I faktycznie, raczej nie ma sensu korzystać z nich w celach dla których zostały stworzone. Czasem jednak zdarzy się, że zaczniemy pisać jakąś komendę (np. przenosimy komputer w AD do odpowiedniego kontenera). Mamy już niemal gotową rurkę, gdy zdajemy sobie sprawę, że nie pamiętamy całej ścieżki. Do wyboru mamy:

  • [Esc], sprawdzamy, piszemy od początku
  • [Home], wstawiamy #, [Enter], sprawdzamy, strzała w górę, usuwamy #, kończymy.

I choć drugie zdanie jest dłuższe – to jednak zwykle to jednak pierwsza opcja jest bardziej czasochłonna. Zwłaszcza, że w międzyczasie może zadzwonić telefon, szef wpadnie z niezapowiedzianą wizytą, pamięć nam się zresetuje… a Get-History zwykle nie zapomina. A przynajmniej nie od razu. 😉

Drugi sposób twórczego wykorzystania komentarzy: w PowerShellu komentarz blokowy może znaleźć się dosłownie wszędzie, przetestujcie sami:

ls <# czyli dir 
#> -r <# czyli -Recurse 
#> -fi <# znaczy filtr 
#> *.ps1 -EA 0

Oczywiście, to samo mogłoby się znaleźć w jednej linii i nadal by działało. Jakie to może mieć praktyczne zastosowanie? Powiedzmy, że napisaliśmy jakiś zagmatwany filtr do Where-Object, który nie daje żadnych rezultatów… Wystarczy czasem “wyłączyć” na moment jego fragment (odpowiednio zamykając go w <# #>) i sprawdzić, która część nie daje oczekiwanego efektu. W ostateczności można wyłączyć Where-Object a całość przepuścić przez Get-Member by upewnić się, czy nie pozajączkowała nam się nazwa właściwości.

Historia

Wspominałem już o jednym z trików związanych z historią. Jest ich więcej, ale sama historia domyślnie jest dość krótka: $MaximumHistoryCount, która za długość historii odpowiada, domyślnie ma wartość 64. Zwykle zwiększam ją do wartości maksymalnej. 😉

Gdy już mamy jakąś historię istnieje kilka opcji, by z niej skorzystać. Przede wszystkim możemy użyć kombinacji # z tabulatorem. #Numer przywoła komendę o danym ID. #Ciąg Znaków przywoła wszystkie komendy, które pasują do wzorca (ciąg znaków nie musi znajdować się na początku).

Można też uruchomić polecenie bezpośrednio z historii. Invoke-History pobiera polecenie o podany –Id i wykonuje je ponownie. By jednak efektywnie z niego korzystać dobrze jest wiedzieć jakie id miało każde z wykonanych wcześniej poleceń. Bardzo w tym pomaga odpowiednio przygotowany prompt (mój typ to dzieło Jaykula, o którym wspominałem w cyklu o profilach).

ScriptBlock jako argument

Gdy to pierwszy raz zobaczyłem, najzwyczajnie w świecie powaliło mnie z nóg. Otóż jeśli jakieś polecenie potrafi pobierać informacje z rurki, to możemy “nakarmić” je scriptblokiem. Oczywiście – jeśli scriptblock to nasze możliwości właśnie stały się niemal nieograniczone. Zwykle jednak nie jest nam potrzebny 20-linijkowy skrypt, starcza lekki retusz obiektu wpadającego z innego polecenia. Jest jeden warunek: musi być to pobieranie z rurki imienne, nie na podstawie typu (oczywiście nie wadzi jeśli możliwe są oba rozwiązania). Prosty przykład:

ls *.ps1 | Rename-Item -NewName { $_.BaseName + '.psm1' } -WhatIf

I nagle wszystkie skrypty w bieżącym katalogu zmieniły(by) się w moduły. 😉 Inne zastosowania to już kwestia naszej kreatywności. Akurat ten przykład (ze zmianą nazw plików) jest moim ulubionym.

Podsumowanie

Praca w konsoli jest tym przyjemniejsza, im swobodniej się w niej poruszamy. Myślę, że warto poświęcić nieco czasu na jej wybór, dostosowanie, zapoznać się z jej możliwościami (i ograniczeniami). Później odpowiednio ją dopasować “pod siebie” przy pomocy profilu i/ lub modułów.

W kolejnych kilku częściach tego cyklu zajmę się funkcjami, skryptami, modułami… Pewnie rozwlecze się to na kilka łaaadnych artykułów, bo to temat-rzeka. Do konsoli wrócę jeszcze na koniec, gdy opiszę wędrówkę z konsoli do skryptu i z powrotem.

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

•4 października, 2011 • Dodaj komentarz

W części pierwszej tego cyklu opisałem ogólnie to, jak moim zdaniem praca w PowerShellu różni się w zależności od naszej aktualnej aktywności. Skupiłem się na różnicach między trybem pracy interaktywnej i trybem tworzenia skryptów. Dziś, zgodnie z obietnicą, poznęcam się nieco nad pracą w konsoli.

Badacz

PowerShell daje nam możliwości, których próżno szukać w VBS. Trudno je też znaleźć w cmd, choć temu ostatniemu nieco bliżej do interaktywnej natury PowerShella. W VBS byliśmy skazani na pisanie od zera, sklejanie skryptów z klocków, żmudne testy bez prostej możliwości wglądu w działanie skryptu. PowerShell pozwala każdy obiekt zbadać, obejrzeć, obrócić, przeskanować, rozłożyć na czynniki pierwsze. Nie korzystając z tych możliwości ograniczamy sobie pole działania. Wielu rzeczy o PowerShellu dowiadywałem się po prostu klepiąc TABem na różnych obiektach. Get-Member też zwykle służy pomocą. To wszystko sprawia, że PowerShell jest wręcz stworzony do zgłębiania świata .NET. Swoją drogą to dumam, czy programistom zdarza się uruchamiać PowerShella tylko po to, by sobie szybciutko pogmerać w jakimś skompilowany kodzie…

Oprócz tego możemy pracując obserwować cały czas rezultaty i nasze zachowanie dostosowywać do wyników. Jeśli jakaś metoda wyrzuca błąd – możemy sprawdzić jej definicję, zmodyfikować, uruchomić ponownie… aż do skutku.

Z pewną taką nieśmiałością…

Pisałem już o tym chyba kiedyś, ale ta cecha PowerShella niezmiennie mnie zachwyca. Pięknie o tym mówi w czasie swoich prezentacji o PowerShellu Jeffrey Snover. Spróbuję przetłumaczyć 😉 Odwieczny dylemat administratora:

Czy ta komenda jest poprawna? Czy jeśli ją wykonam, to stracę pracę? Czy przez moją pomyłkę Buffy i Puffy nie pójdą na studia? Może lepiej sprawdzą Co Jeśli…

-WhatIf i –Confirm – dwa parametry, które pewnie nie raz uratują adminowi jego cztery litery, lub przynajmniej pozwolą uniknąć 16-godzinnej dniówki. Korzystać z nich można, należy. Co więcej – korzystanie z –Confirm można w prosty (stosunkowo) sposób wymusić: wystarczy ustawić $ConfirmPreference na odpowiednio niską wartość. Domyślnie jest High (czyli tylko komendy z ConfirmImpact równym High będą wymuszać potwierdzenie). Ustawienie tego parametru na ‘Low’ może sprawić, że PowerShell będzie nas pytał o niemal wszystko. 😉 WhatIf też można wymusić ($WhatIfPrerence = 1) ale to chyba już przesada… Zamiast coś robić, będziemy tylko dostawać informację co by było gdyby… Za to raczej nam nikt nie zapłaci. 😉 A odwrócić to można tylko używając składni:

Komenda –WhatIf:$false

Niezbyt przyjazne, jeśli mam być szczery…

Klocki lego

Pisanie pełnej, “wypasionej” komendy od zera to troszkę jak malowanie obrazu bez szkiców… PowerShell to idealny szkicownik: możemy wykonać jakąś “rurkę”, sprawdzić czy rezultat nas zadowala, dodać kolejną komendę do potoku, sprawdzić. Zapisać w zmiennej, sprawdzić zawartość, przepuścić ją przez foreach ()… I tak, krok po kroku, dojść od koncepcji do pełnego rozwiązania. Czasem oczywiście nie jest to konieczne: łatwiej nam będzie zmodyfikować coś, co już kiedyś w pocie czoła stworzyliśmy. Ale na początku lepiej właśnie posłużyć się techniką “małych kroczków”. PowerShell, przez swoją interaktywność oraz fakt, że składnia w skrypcie niczym się nie różni od tej pisanej w konsoli, daje nam taką możliwość. Warto z niej skorzystać by nie napracować się nad jakimś skryptem tylko po to, by na koniec odkryć, że obiekt który zapowiadał się obiecująco kompletnie nie nadaje się do rozwiązania postawionego przed nami problemu. Budując rozwiązanie stopniowo unikniemy takich frustrujących i zniechęcających przygód.

Krótkie podsumowanie

Na dziś kończę. W następnej części skupię się na “trikach”, które często w konsoli wykorzystuję. Większość z nich opisałem w moim gościnnym artykule na blogu Hey, Scripting Guy! Część jednak dość lakonicznie, tu postaram się nieco bardziej rozpisać… 😉

PowerShell MVP!

•1 października, 2011 • 3 Komentarze

Dziś w mojej skrzynce mailowej znalazłem maila od support@mvpaward.com. O tym, że będę nominowany dowiedziałem się już w czasie TechEd. Kiedy Aleksandar o tym wspomniał, sądziłem, że po prostu stara się być miły… Potem przyszła prośba o wypełnienie kwestionariusza z informacjami o mojej aktywności w ostatnim roku. I czekanie…

Wiedziałem, że decyzja zostanie ogłoszona dziś. Mail nie był więc dla mnie zakoczeniem. Jego treść – zdecydowanie tak.

MVP_FullColor_ForScreen

Nadal nie mogę w to uwierzyć, ale to nie żart. Bartek Bielawski, PowerShell MVP. Szok. I dodatkowa motywacja, by wiedzą na temat tej technologii dzielić się z innymi. Dziękuję wszystkim, którzy się do tego przyczynili. 🙂 Tym bardziej nie mogę doczekać się PowerShell Deep Dive, na który pojadę jako “najmłodszy w rodzinie”. Jak wrócę na ziemię obięcuję kontynuować cykl o efektynym PowerShellu. Dziś już się raczej nie uda…

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

•24 września, 2011 • 1 komentarz

Wszystkie narzędzia używane przez człowieka mają swoje ograniczenia. Wkręt można wbić młotkiem, ale śrubokręt zwykle daje lepszy rezultat. Tak samo naturalnie jest z PowerShellem: można walić nim jak młotem, można subtelnie trafiać idealnie w problem. W tej serii postaram się opisać to, co moim zdaniem może pracę w PowerShellu usprawnić, uprościć i dostosować do poziomu, na którym obecnie pracujemy.

Wyróżniłbym dwa skrajne poziomy, wraz z pewnym obszarem pośrednim:

Poziomy

Innymi zasadami kierowałbym się w każdym z tych obszarów. Dlaczego? Bo życie należy sobie ułatwiać. Na początek postaram się w skrócie przedstawić różnice pomiędzy oboma trybami (tryb pośredni pozostawię sobie na deser) a następnie zagłębie się w to, co moim zdaniem może uczynić pracę na obu poziomach bardziej efektywną, czasem również efektówną. 🙂

Aliasy

Pracując interaktywnie korzystamy z aliasów: po to zostały stworzone. W skrypcie – każdy alias to potencjalne źródło problemów, błędów i dodatkowo – element pogarszający czytelność skryptu. Przykład z życia wzięty: doskonały moduł ShowUI w jednym z przykładowych skryptów korzysta z aliasu “ps”. Długo musiałem dochodzić do tego, czemu ten konkretny przykład u mnie nie działał. Przyczyna? Usuwam w profilu w/w alias, mam dysk PS:, do niego odpowiednią funkcję, która działa podobnie do a:, c:, d: – alias mi uniemożliwiał szybkie dopełnianie nazwy tabem, więc go usunąłem. Skrypt, mimo że napisany poprawnie, stracił swą funkcjonalność.

Pisałem o czytelności… Dla początkującego użytkownika PowerShella taki kod będzie mało czytelny…:

h|?{$_.Id-gt5}|%{            
$_.CommandLine-replace'^.(\w+)','$1'}

Przykładów dowodzących zasadności używania aliasów w shellu jest pewnie jeszcze więcej. Wystarczy porównać:

# Z aliasami...            
ls -r -fo -ea 0 -fi *.ps1            
            
# I bez. 😦            
Get-ChildItem -Recurse -Force `
-ErrorAction SilentlyContinue -Filter *.ps1

Aliasy są też bardzo przydatne, jeśli używacie czasem narzędzi nie przesiadujących w Waszej ścieżce: można albo dodać kolejny folder do $env:PATH, albo stworzyć alias, który będzie uruchamiał potrzebne narzędzie.

Profile

Pracując interaktywnie bez profilu tracimy czas – wiele funkcji i narzędzi definiujemy za każdym razem, zamiast zapisać je raz i później po prostu używać. Profil może za nas załadować wtyczki i moduły, może dodać wykorzystywane przez nas często zmienne, stworzyć dyski – możliwości jest bardzo wiele.

Skrypty dla odmiany powinny być kompletnie niezależne od profilu. Skrypt, jeśli jest napisany z głową, powinien sprawdzić czy wszystkie niezbędne do jego działania elementy są już w środowisku. Do testów najlepiej użyć możliwości, jakie daje powershell.exe – ma on parametr –noprofile, który świetnie nadaje się do uruchamiania “środowiska testowego” dla skryptów.

Host i UAC

Pracując interaktywnie korzystajmy raczej z hosta, który daje nam największy komfort. Dobrze poświęcić nieco czasu, “sprawdzić” dostępne opcje – w tym również opcje komercyjne (na ogół dostępne są wersje trial). Nie ma rozwiązania idealnego dla wszystkich, dlatego najlepiej “posmakować” dostępnych opcji i wybrać taką, w której będziemy się czuć najlepiej. Dodatkowo warto rozważyć ustawienie wybranego hosta tak, by uruchamiał się z “odpowiednimi” uprawnieniami. Osobiście uważam UAC za świetny wynalazek, nawet na serwerach go nie wyłączam. Ale póki PowerShell nie ma możliwości w prosty sposób “przeskoczyć” na wyższy poziom w trakcie pracy – wolę go od razu na tym wyższym poziomie uruchamiać.

Skrypty dla odmiany powinny “umieć się zachować”. Naturalnie, są takie, których użycie w innym hoście niż ten, w którym go tworzono, po prostu nie ma sensu. Ale jeśli skrypt nie jest z hostem bezpośrednio “powiązany” – dajmy użytkownikowi wybór. To oznacza, że powinniśmy przetestować nasz skrypt w kilku hostach, absolutne moim zdaniem minimum to PowerShell.exe i PowerShell ISE. Jeśli zaś chodzi o UAC – skrypt powinien wiedzieć, co będzie mu potrzebne. Powinien uprawnienia sprawdzić. I jeśli mu czegoś brakuje – uprzedzić użytkownika zanim spróbuje coś zmodyfikować.

Formalizm

Pracując interaktywnie tworzymy czasem krótkie funkcje, bloki skryptu, tymczasowe narzędzia. Ponieważ stworzyliśmy je przed chwilką – nie ma sensu ich dokumentować. Get-Help raczej na nich nie użyjemy. Nie ma też raczej powodu nadmiernie rozbudowywać blok przyjmujący parametry – wszelkie walidacje nie mają większego sensu, wiemy jak wygląda nasza funkcja, więc znamy jej ograniczenia, wiemy z jakimi założeniami została stworzona.

Skrypt dla odmiany powinien być dobrze udokumentowany. Korzystajmy z tego wszystkiego, co daje nam środowisko: pomoc w komentarzach, walidacja parametrów, zmienne z usztywnionymi typami, informacje typu warning, debug, verbose – to wszystko ułatwi nam modyfikowanie skryptu w przyszłości i używanie go bez konieczności otwierania go w edytorze za każdym razem. Koszt takiej operacji w wypadku skryptów używanych od czasu do czasu szybko się zwróci. A ewentualna rozbudowa o dodatkowe funkcje nie przyprawi nas o ból głowy.

Co dalej?

Ten wpis zaczyna pewien cykl. Dalej zagłębię się nieco bardziej w obu skrajnościach, na koniec zaś postaram się napisać, jak w prosty sposób można “przeskoczyć” z poziomu interaktywnego do skryptowego. Druga część będzie w całości poświęcona pracy interaktywnej w PowerShellu.